-
Notifications
You must be signed in to change notification settings - Fork 2
/
Main.scala
139 lines (118 loc) · 4.42 KB
/
Main.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package tgtg
import cats.data.{Ior, NonEmptyList}
import cats.effect.{ExitCode, IO}
import cats.syntax.all.*
import com.monovore.decline.Opts
import com.monovore.decline.effect.CommandIOApp
import cron4s.CronExpr
import eu.timepit.fs2cron.cron4s.Cron4sScheduler
import fs2.Stream
import org.legogroup.woof.{Logger, given}
import tgtg.cache.CacheKey
import tgtg.notification.{Message, Title}
import scala.concurrent.duration.*
val version = new String(
classOf[Main]
.getClassLoader()
.getResourceAsStream("version.txt")
.readAllBytes()
).trim()
object Main extends CommandIOApp("tgtg", "TooGoodToGo notifier for your favourite stores", version = version):
override def main: Opts[IO[ExitCode]] = (Config.opts orElse Config.auth).map { config =>
Deps
.mkLogger(config.log)
.flatMap: log =>
given Logger[IO] = log
config match
case config: AuthConfig =>
Deps
.mkHttpBackend(none)
.use: http =>
val tgtg = TooGoodToGo(http)
val auth = new Auth(tgtg, config)
auth.run
.as(ExitCode.Success)
.handleErrorWithLog
case config: Config =>
val main = new Main(config)
main.logDeprecations
*> main.runOrServer
.as(ExitCode.Success)
.handleErrorWithLog
end match
}
end Main
final class Main(config: Config)(using log: Logger[IO]):
def runOrServer =
config.server match
// isServer is deprecated, but used: run with default interval
case ServerConfig(None, None, true) =>
loop(NonEmptyList.of(5.minutes.asLeft))
case ServerConfig(intervals, crons, _) =>
// If intervals or crons is defined, run with them. Otherwise, run once
Ior
.fromOptions(intervals, crons)
.map: ior =>
val nel = ior.bimap(_.map(_.asLeft), _.map(_.asRight)).merge
loop(nel)
.getOrElse(run)
/** Run the main loop (log errors, never exit)
*/
def loop(intervals: NonEmptyList[Either[FiniteDuration, CronExpr]]): IO[Unit] =
val scheduler = Cron4sScheduler.systemDefault[IO]
// Run, and find the minimum next interval and log it
val runAndLog: IO[Unit] = run.handleErrorWithLog.void >> (
intervals
.parTraverse(_.fold(_.pure[IO], scheduler.fromNowUntilNext))
.map(_.minimum)
.flatMap(nextInterval => log.info(show"Sleeping for $nextInterval"))
)
val streams: List[Stream[IO, Unit]] = intervals
.map(
_.fold(
Stream.awakeEvery[IO](_),
scheduler.awakeEvery(_)
) >> Stream.eval(runAndLog)
)
.toList
// Run for the first time, then run every interval
(log.info("Starting tgtg notifier") *>
log.info(show"Intervals: ${intervals.map(_.fold(_.show, _.show)).mkString_(", ")}") *>
runAndLog *> Stream.emits(streams).parJoinUnbounded.compile.drain)
.guarantee(log.info("Shutting down") *> log.info("Bye!"))
end loop
/** Run once
*/
def run =
(Deps.mkHttpBackend(config.cronitor), Deps.mkCache(config.redis)).parTupled.use: (http, cache) =>
val tgtg = TooGoodToGo(http)
val notify = Deps.mkNotifyService(http, config.notification)
tgtg
.getItems(cache, config.tgtg)
.flatMap: stores =>
val items = stores.filter(_.items_available > 0)
if items.isEmpty then log.info(show"No boxes to notify for (from ${stores.length} stores).")
else
items.parTraverse_ : item =>
val key = CacheKey(show"gotify-${item.store.store_name}")
val notificationTimeout = 60.minutes
cache
.get[Boolean](key)
.attempt
.map(_.toOption.flatten)
.map(!_.contains(true))
.ifM(
cache
.set(true, key, notificationTimeout)
.guarantee(
notify.sendNotification(Title(item.display_name), Message(item.show))
),
log.info(
show"Found boxes, but notification for '$key' was already sent in the last $notificationTimeout."
)
)
end if
def logDeprecations = IO.whenA(config.server.isServer)(
log.warn("The --server flag is deprecated. Use --interval or --cron options instead.")
)
end Main