174 lines
5.0 KiB
Nim
Raw Normal View History

2025-02-05 16:06:04 +01:00
import std/os
2025-02-06 15:32:39 +01:00
import std/sequtils
2025-02-05 16:06:04 +01:00
import pkg/chronicles
import pkg/chronos
import pkg/questionable
import pkg/questionable/results
2025-02-05 16:35:02 +01:00
import pkg/datastore
import pkg/datastore/typedds
import pkg/metrics
2025-02-05 16:06:04 +01:00
import ./config
import ./logging
import ./metrics
2025-02-05 16:35:02 +01:00
import ./list
2025-02-06 10:16:47 +01:00
import ./dht
import ./keyutils
2025-02-07 13:57:57 +01:00
import ./crawler
2025-02-07 16:48:47 +01:00
import ./timetracker
2025-02-05 16:06:04 +01:00
2025-02-05 16:35:02 +01:00
declareGauge(todoNodesGauge, "DHT nodes to be visited")
declareGauge(okNodesGauge, "DHT nodes successfully contacted")
declareGauge(nokNodesGauge, "DHT nodes failed to contact")
2025-02-05 16:06:04 +01:00
type
ApplicationStatus* {.pure.} = enum
Stopped
Stopping
Running
Application* = ref object
status: ApplicationStatus
2025-02-05 16:35:02 +01:00
config*: CrawlerConfig
2025-02-07 13:57:57 +01:00
todoNodes*: List
2025-02-05 16:35:02 +01:00
okNodes*: List
nokNodes*: List
2025-02-06 10:16:47 +01:00
dht*: Dht
2025-02-07 13:57:57 +01:00
crawler*: Crawler
2025-02-07 16:48:47 +01:00
timeTracker*: TimeTracker
2025-02-05 16:35:02 +01:00
2025-02-06 10:16:47 +01:00
proc createDatastore(app: Application, path: string): ?!Datastore =
without store =? LevelDbDatastore.new(path), err:
2025-02-05 16:35:02 +01:00
error "Failed to create datastore"
return failure(err)
2025-02-06 10:16:47 +01:00
return success(Datastore(store))
proc createTypedDatastore(app: Application, path: string): ?!TypedDatastore =
without store =? app.createDatastore(path), err:
return failure(err)
2025-02-05 16:35:02 +01:00
return success(TypedDatastore.init(store))
proc initializeLists(app: Application): Future[?!void] {.async.} =
2025-02-06 15:32:39 +01:00
without store =? app.createTypedDatastore(app.config.dataDir / "lists"), err:
2025-02-05 16:35:02 +01:00
return failure(err)
# We can't extract this into a function because gauges cannot be passed as argument.
# The use of global state in nim-metrics is not pleasant.
proc onTodoMetric(value: int64) =
todoNodesGauge.set(value)
2025-02-05 16:43:36 +01:00
2025-02-05 16:35:02 +01:00
proc onOkMetric(value: int64) =
okNodesGauge.set(value)
2025-02-05 16:43:36 +01:00
2025-02-05 16:35:02 +01:00
proc onNokMetric(value: int64) =
nokNodesGauge.set(value)
2025-02-07 13:57:57 +01:00
app.todoNodes = List.new("todo", store, onTodoMetric)
2025-02-05 16:35:02 +01:00
app.okNodes = List.new("ok", store, onOkMetric)
app.nokNodes = List.new("nok", store, onNokMetric)
2025-02-07 13:57:57 +01:00
if err =? (await app.todoNodes.load()).errorOption:
2025-02-05 16:35:02 +01:00
return failure(err)
if err =? (await app.okNodes.load()).errorOption:
return failure(err)
if err =? (await app.nokNodes.load()).errorOption:
return failure(err)
return success()
2025-02-06 10:33:11 +01:00
proc initializeDht(app: Application): Future[?!void] {.async.} =
2025-02-06 15:32:39 +01:00
without dhtStore =? app.createDatastore(app.config.dataDir / "dht"), err:
2025-02-06 10:16:47 +01:00
return failure(err)
let keyPath = app.config.dataDir / "privatekey"
without privateKey =? setupKey(keyPath), err:
return failure(err)
2025-02-07 13:44:35 +01:00
var listenAddresses = newSeq[MultiAddress]()
# TODO: when p2p connections are supported:
# let aaa = MultiAddress.init("/ip4/" & app.config.publicIp & "/tcp/53678").expect("Should init multiaddress")
# listenAddresses.add(aaa)
var discAddresses = newSeq[MultiAddress]()
2025-02-07 13:57:57 +01:00
let bbb = MultiAddress
.init("/ip4/" & app.config.publicIp & "/udp/" & $app.config.discPort)
.expect("Should init multiaddress")
2025-02-07 13:44:35 +01:00
discAddresses.add(bbb)
2025-02-06 10:16:47 +01:00
app.dht = Dht.new(
privateKey,
bindPort = app.config.discPort,
2025-02-07 13:44:35 +01:00
announceAddrs = listenAddresses,
2025-02-06 10:16:47 +01:00
bootstrapNodes = app.config.bootNodes,
2025-02-06 10:33:11 +01:00
store = dhtStore,
2025-02-06 10:16:47 +01:00
)
2025-02-06 10:33:11 +01:00
2025-02-07 13:44:35 +01:00
app.dht.updateAnnounceRecord(listenAddresses)
app.dht.updateDhtRecord(discAddresses)
2025-02-06 10:33:11 +01:00
await app.dht.start()
2025-02-07 13:44:35 +01:00
2025-02-06 10:16:47 +01:00
return success()
2025-02-07 14:51:03 +01:00
proc initializeCrawler(app: Application): Future[?!void] {.async.} =
2025-02-07 16:19:26 +01:00
app.crawler =
Crawler.new(app.dht, app.todoNodes, app.okNodes, app.nokNodes, app.config)
2025-02-07 14:51:03 +01:00
return await app.crawler.start()
2025-02-07 13:57:57 +01:00
2025-02-07 16:48:47 +01:00
proc initializeTimeTracker(app: Application): Future[?!void] {.async.} =
app.timeTracker =
TimeTracker.new(app.todoNodes, app.okNodes, app.nokNodes, app.config)
return await app.timeTracker.start()
2025-02-05 16:35:02 +01:00
proc initializeApp(app: Application): Future[?!void] {.async.} =
if err =? (await app.initializeLists()).errorOption:
error "Failed to initialize lists", err = err.msg
return failure(err)
2025-02-06 10:16:47 +01:00
2025-02-06 10:33:11 +01:00
if err =? (await app.initializeDht()).errorOption:
2025-02-06 10:16:47 +01:00
error "Failed to initialize DHT", err = err.msg
return failure(err)
2025-02-07 14:51:03 +01:00
if err =? (await app.initializeCrawler()).errorOption:
error "Failed to initialize crawler", err = err.msg
return failure(err)
2025-02-07 13:57:57 +01:00
2025-02-07 16:48:47 +01:00
if err =? (await app.initializeTimeTracker()).errorOption:
error "Failed to initialize timetracker", err = err.msg
return failure(err)
2025-02-05 16:35:02 +01:00
return success()
proc stop*(app: Application) =
app.status = ApplicationStatus.Stopping
2025-02-06 10:33:11 +01:00
waitFor app.dht.stop()
2025-02-05 16:06:04 +01:00
proc run*(app: Application) =
2025-02-05 16:35:02 +01:00
app.config = parseConfig()
info "Loaded configuration", config = app.config
2025-02-05 16:06:04 +01:00
# Configure loglevel
2025-02-05 16:35:02 +01:00
updateLogLevel(app.config.logLevel)
2025-02-05 16:06:04 +01:00
# Ensure datadir path exists:
2025-02-05 16:35:02 +01:00
if not existsDir(app.config.dataDir):
createDir(app.config.dataDir)
2025-02-05 16:06:04 +01:00
2025-02-05 16:35:02 +01:00
setupMetrics(app.config.metricsAddress, app.config.metricsPort)
2025-02-05 16:06:04 +01:00
info "Metrics endpoint initialized"
info "Starting application"
app.status = ApplicationStatus.Running
2025-02-05 16:35:02 +01:00
if err =? (waitFor app.initializeApp()).errorOption:
2025-02-05 16:06:04 +01:00
app.status = ApplicationStatus.Stopping
error "Failed to start application", err = err.msg
2025-02-05 16:35:02 +01:00
return
2025-02-05 16:06:04 +01:00
while app.status == ApplicationStatus.Running:
try:
chronos.poll()
except Exception as exc:
error "Unhandled exception", msg = exc.msg
quit QuitFailure
notice "Application closed"