Add DB implementation, models, logging.

This commit is contained in:
Jonathan Bernard 2025-02-23 08:23:24 -06:00
parent bad288a24b
commit 3f3a6b286b
8 changed files with 145 additions and 8 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
*.sw?
/wdiwtlt
nimble.develop
nimble.paths

4
config.nims Normal file
View File

@ -0,0 +1,4 @@
# begin Nimble config (version 2)
when withDir(thisDir(), system.fileExists("nimble.paths")):
include "nimble.paths"
# end Nimble config

View File

@ -1,6 +1,8 @@
import std/[os, uri]
import mpv
import wdiwtlt/[models, db]
when isMainModule:
var ctx: ptr handle
try:

View File

@ -1,9 +1,7 @@
import std/[json, jsonutils, options, sequtils, strutils, times]
import std/[json, options, sequtils, strutils, times]
import db_connector/db_sqlite
import waterpark/sqlite
import fiber_orm, namespaced_logging, timeutils
import ./models
import fiber_orm, timeutils, uuids
export fiber_orm.NotFoundError
export fiber_orm.PaginationParams
@ -11,6 +9,8 @@ export fiber_orm.PagedRecords
export fiber_orm.enableDbLogging
export sqlite.close
import ./[logging, models]
type
WdiwtltDb* = SqlitePool
@ -25,3 +25,53 @@ proc initDB*(dbPath: string): SqlitePool =
generateProcsForModels(WdiwtltDb,
[Artist, Album, MediaFile, Tag, Playlist, Bookmark, Image])
generateJoinTableLookups(WdiwtltDb, Artist, Album, "artists_albums")
generateJoinTableLookups(WdiwtltDb, Artist, MediaFile, "artists_media_files")
generateJoinTableLookups(WdiwtltDb, Album, MediaFile, "albums_media_files")
generateJoinTableLookups(WdiwtltDb, Playlist, MediaFile, "playlists_media_files")
generateJoinTableLookups(WdiwtltDb, MediaFile, Tag, "media_files_tags")
generateJoinTableLookups(WdiwtltDb, Artist, Image, "artists_images")
generateJoinTableLookups(WdiwtltDb, Album, Image, "albums_images")
proc removeEmptyAlbums*(sql: WdiwtltDb) =
## Remove albums that have no media files.
let query = """
DELETE FROM albums WHERE id IN (
SELECT DISTINCT al.id
FROM albums al LEFT OUTER JOIN albums_media_files almf ON
al.id = almf.album_id
WHERE almf.album_id IS NULL)"""
getLogger("wdiwtlt/db").debug(%*{
"msg": "Deleting empty albums.",
"query": query})
proc removeEmptyArtists*(sql: WdiwtltDb) =
## Remove artists that have no albums.
let query = """
DELETE FROM artists WHERE id IN (
SELECT DISTINCT ar.id
FROM artists ar LEFT OUTER JOIN artists_albums aral ON
ar.id = aral.artist_id
WHERE aral.artist_id IS NULL)"""
getLogger("wdiwtlt/db").debug(%*{
"msg": "Deleting empty artists.",
"query": query})
proc removeEmptyPlaylists*(sql: WdiwtltDb) =
## Remove playlists that have no media files.
let query = """
DELETE FROM playlists WHERE id IN (
SELECT DISTINCT pl.id
FROM playlists pl LEFT OUTER JOIN playlists_media_files plmf ON
pl.id = plmf.playlist_id
WHERE plmf.playlist_id IS NULL)"""
getLogger("wdiwtlt/db").debug(%*{
"msg": "Deleting empty playlists.",
"query": query})

View File

@ -0,0 +1,45 @@
import std/[options, os, strutils, unicode]
import namespaced_logging, zero_functional
import ./db
export namespaced_logging
var logService* {.threadvar.}: Option[LogService]
proc enableLogging*(svc: LogService = initLogService(), debug = false): LogService =
if not (svc.cfg.appenders --> exists(it of ConsoleLogAppender)):
svc.addAppender(initConsoleLogAppender(threshold = lvlAll))
enableDbLogging(svc)
logService = some(svc)
result = svc
proc configureLoggingThresholds*(debug = false) =
if not logService.isSome: return
let logSvc = logService.get
if debug:
logSvc.cfg.rootLevel = Level.lvlDebug
logSvc.cfg.loggers.add([
LoggerConfig(name: "wdiwtlt", threshold: some(Level.lvlDebug)),
LoggerConfig(name: "fiber_orm", threshold: some(Level.lvlDebug)),
])
else: logSvc.cfg.rootLevel = Level.lvlInfo
logSvc.reloadThreadState()
proc enableLoggingByEnvVar*(envVar = "DEBUG"): void =
if not logService.isSome: discard enableLogging()
let val = getEnv(envVar, "false").toLower()
configureLoggingThresholds(
"true".startsWith(val) or
"yes".startsWith(val) or
"on".startsWith(val) or
val == "1")
proc getLogger*(name: string): Option[Logger] =
if logService.isSome: return some(logService.get.getLogger(name))
else: return none[Logger]()

View File

@ -1,4 +1,4 @@
import std/[paths]
import std/[paths, times]
import namespaced_logging
import ./[db, models]
@ -7,3 +7,34 @@ type
MediaLibrary* = ref object
db: WdiwtltDb
libraryRoot: Path
proc clean*(lib: MediaLibrary) =
removeEmptyAlbums(lib.db)
removeEmptyArtists(lib.db)
removeEmptyPlaylists(lib.db)
let expirationDate = now() - weeks(1)
let expiredPlaylists = lib.db.findPlaylstsWhere(
"user_created = false AND last_used_at < ?",
[expirationDate])
let expiredBookmarks = lib.db.findBookmarksWhere(
"user_created = false AND last_used_at < ?",
[expirationDate])
expiredPlaylists.applyIt(lib.db.deletePlaylist(it.id))
expiredBookmarks.applyIt(lib.db.deleteBookmark(it.id))
proc rescanLibrary*(lib: MediaLibrary) =
# TODO: Implement this. Below is AI-generated code.
let mediaFiles = lib.db.getAllMediaFiles()
for mf in mediaFiles:
let filePath = lib.libraryRoot / mf.filePath
if not filePath.existsFile:
lib.db.deleteMediaFile(mf.id)
continue
let fileHash = filePath.hashFile
if fileHash != mf.fileHash:
lib.db.updateMediaFile(mf.id, fileHash: fileHash)

View File

@ -1,4 +1,4 @@
import std/[options, paths, times]
import std/[options, times]
import uuids
type
@ -18,7 +18,7 @@ type
discNumber*: int
trackNumber*: Option[int]
playCount*: int
filePath*: Path
filePath*: string
fileHash*: string
metaInfoSource*: string
dateAdded*: DateTime

View File

@ -11,4 +11,7 @@ bin = @["wdiwtlt"]
# Dependencies
requires "nim >= 2.2.0"
requires @["mpv", "nimterop"]
requires @["mpv", "nimterop", "uuids", "waterpark"]
# Dependencies from https://git.jdb-software.com/jdb/nim-packages
requires @["db_migrate", "fiber_orm"]