WIP: tempoaray DB implementation.
This commit is contained in:
parent
87202437a8
commit
8788015c4f
@ -1,4 +1,4 @@
|
||||
import std/json, std/jsonutils, std/os, std/strtabs, std/tables, std/times
|
||||
import std/[logging, json, jsonutils, options, os, tables, times]
|
||||
import timeutils, uuids, zero_functional
|
||||
|
||||
import ./models
|
||||
@ -10,7 +10,7 @@ type
|
||||
bookmarks: TableRef[UUID, Bookmark]
|
||||
mediaFiles: TableRef[UUID, MediaFile]
|
||||
playlists: TableRef[UUID, Playlist]
|
||||
tags: StringTableRef
|
||||
tags: TableRef[string, Option[string]]
|
||||
|
||||
albumsToMediaFiles: TableRef[UUID, seq[UUID]]
|
||||
artistsToMediaFiles: TableRef[UUID, seq[UUID]]
|
||||
@ -18,128 +18,390 @@ type
|
||||
playlistsToMediaFiles: TableRef[UUID, seq[UUID]]
|
||||
tagsAndMediaFiles: seq[tuple[tagName: string, mediaFileId: UUID]]
|
||||
|
||||
mediaFileHashToId: TableRef[string, UUID]
|
||||
|
||||
WdiwtltDb* = ref object
|
||||
jsonFilePath: string
|
||||
root: DbRoot
|
||||
|
||||
## API Contract
|
||||
proc debug*(db: WdiwtltDb): string =
|
||||
"\p\talbums: " & $db.root.albums &
|
||||
"\p\tartists: " & $db.root.artists &
|
||||
"\p\tbookmarks: " & $db.root.bookmarks &
|
||||
"\p\tmediaFiles: " & $db.root.mediaFiles &
|
||||
"\p\tplaylists: " & $db.root.playlists &
|
||||
"\p\ttags: " & $db.root.tags &
|
||||
"\p\talbumsToMediaFiles: " & $db.root.albumsToMediaFiles &
|
||||
"\p\tartistsToMediaFiles: " & $db.root.artistsToMediaFiles &
|
||||
"\p\tartistsAndAlbums: " & $db.root.artistsAndAlbums &
|
||||
"\p\tplaylistsToMediaFiles: " & $db.root.playlistsToMediaFiles &
|
||||
"\p\ttagsAndMediaFiles: " & $db.root.tagsAndMediaFiles &
|
||||
"\p\tmediaFileHashToId: " & $db.root.mediaFileHashToId & "\p"
|
||||
|
||||
## ========================================================================= ##
|
||||
## API Contract ##
|
||||
## ========================================================================= ##
|
||||
proc initDb*(path: string): WdiwtltDb;
|
||||
proc loadDb*(path: string): WdiwtltDb;
|
||||
proc persist*(db: WdiwtltDb): void;
|
||||
|
||||
proc delete*(db: WdiwtltDb, a: Album);
|
||||
proc delete*(db: WdiwtltDb, a: Artist);
|
||||
proc delete*(db: WdiwtltDb, b: Bookmark);
|
||||
proc delete*(db: WdiwtltDb, mf: MediaFile);
|
||||
proc delete*(db: WdiwtltDb, p: Playlist);
|
||||
proc delete*(db: WdiwtltDb, t: Tag);
|
||||
## Albums
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, a: Album);
|
||||
proc remove*(db: WdiwtltDb, a: Album);
|
||||
proc update*(db: WdiwtltDb, a: Album);
|
||||
|
||||
proc findAlbumsByArtist*(db: WdiwtltDb, a: Artist): seq[Album];
|
||||
proc findAlbumsByName*(db: WdiwtltDb, name: string): seq[Album];
|
||||
|
||||
proc add*(db: WdiwtltDb, a: Album, mf: MediaFile);
|
||||
proc remove*(db: WdiwtltDb, a: Album, mf: MediaFile);
|
||||
proc remove*(db: WdiwtltDb, a: Artist, mf: MediaFile);
|
||||
proc remove*(db: WdiwtltDb, artist: Artist, album: Album);
|
||||
|
||||
## Artists
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, a: Artist);
|
||||
proc remove*(db: WdiwtltDb, a: Artist);
|
||||
proc update*(db: WdiwtltDb, a: Artist);
|
||||
|
||||
proc findArtistsByAlbum*(db: WdiwtltDb, a: Album): seq[Artist];
|
||||
proc findArtistsByName*(db: WdiwtltDb, name: string): seq[Artist];
|
||||
|
||||
proc add*(db: WdiwtltDb, a: Artist, mf: MediaFile);
|
||||
proc remove*(db: WdiwtltDb, a: Artist, mf: MediaFile);
|
||||
|
||||
proc isAssociated*(db: WdiwtltDb, artist: Artist, album: Album): bool;
|
||||
proc associate*(db: WdiwtltDb, artist: Artist, album: Album);
|
||||
proc disassociate*(db: WdiwtltDb, artist: Artist, album: Album);
|
||||
|
||||
## Bookmarks
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, b: Bookmark);
|
||||
proc update*(db: WdiwtltDb, b: Bookmark);
|
||||
proc remove*(db: WdiwtltDb, b: Bookmark);
|
||||
|
||||
## Media Files
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, mf: MediaFile);
|
||||
proc remove*(db: WdiwtltDb, mf: MediaFile);
|
||||
proc update*(db: WdiwtltDb, mf: MediaFile);
|
||||
proc findMediaFilesByAlbum*(db: WdiwtltDb, a: Album): seq[MediaFile];
|
||||
proc findMediaFilesByArtist*(db: WdiwtltDb, a: Artist): seq[MediaFile];
|
||||
proc findMediaFileByHash*(db: WdiwtltDb, hash: string): Option[MediaFile];
|
||||
|
||||
## Playlists
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, p: Playlist);
|
||||
proc remove*(db: WdiwtltDb, p: Playlist);
|
||||
proc update*(db: WdiwtltDb, p: Playlist);
|
||||
|
||||
## Tags
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, t: Tag);
|
||||
proc remove*(db: WdiwtltDb, t: Tag);
|
||||
proc update*(db: WdiwtltDb, t: Tag);
|
||||
|
||||
## Housekeeping
|
||||
## --------------------
|
||||
proc pruneStalePlaylists*(db: WdiwtltDb, ts: DateTime);
|
||||
proc pruneStaleBookmarks*(db: WdiwtltDb, ts: DateTime);
|
||||
proc removeEmptyAlbums*(db: WdiwtltDb): void;
|
||||
proc removeEmptyArtists*(db: WdiwtltDb): void;
|
||||
proc removeEmptyPlaylists*(db: WdiwtltDb): void;
|
||||
|
||||
proc toJsonHook(t: TableRef[UUID, Album]): JsonNode;
|
||||
proc fromJsonHook(t: var TableRef[UUID, Album], n: JsonNode);
|
||||
proc toJsonHook(t: TableRef[UUID, Artist]): JsonNode;
|
||||
proc fromJsonHook(t: var TableRef[UUID, Artist], n: JsonNode);
|
||||
proc toJsonHook(t: TableRef[UUID, Bookmark]): JsonNode;
|
||||
proc fromJsonHook(t: var TableRef[UUID, Bookmark], n: JsonNode);
|
||||
proc toJsonHook(t: TableRef[UUID, MediaFile]): JsonNode;
|
||||
proc fromJsonHook(t: var TableRef[UUID, MediaFile], n: JsonNode);
|
||||
proc toJsonHook(t: TableRef[UUID, Playlist]): JsonNode;
|
||||
proc fromJsonHook(t: var TableRef[UUID, Playlist], n: JsonNode);
|
||||
proc toJsonHook(t: TableRef[UUID, seq[UUID]]): JsonNode;
|
||||
proc fromJsonHook(t: var TableRef[UUID, seq[UUID]], n: JsonNode);
|
||||
proc toJsonHook(uuid: UUID): JsonNode;
|
||||
proc fromJsonHook(u: var UUID, node: JsonNode);
|
||||
proc toJsonHook(dt: DateTime): JsonNode;
|
||||
proc fromJsonHook(dt: var DateTime, node: JsonNode);
|
||||
|
||||
|
||||
## ========================================================================= ##
|
||||
## API Implementation ##
|
||||
## ========================================================================= ##
|
||||
## Internals
|
||||
## --------------------
|
||||
proc toJsonHook(t: TableRef[UUID, Album]): JsonNode =
|
||||
result = newJObject()
|
||||
for k, v in t.pairs: result[$k] = toJson(v)
|
||||
|
||||
proc fromJsonHook(t: var TableRef[UUID, Album], n: JsonNode) =
|
||||
for k in n.keys:
|
||||
var a: Album
|
||||
fromJson(a, n[k])
|
||||
t[parseUUID(k)] = a
|
||||
|
||||
proc toJsonHook(t: TableRef[UUID, Artist]): JsonNode =
|
||||
result = newJObject()
|
||||
for k, v in t.pairs: result[$k] = toJson(v)
|
||||
|
||||
proc fromJsonHook(t: var TableRef[UUID, Artist], n: JsonNode) =
|
||||
for k in n.keys:
|
||||
var a: Artist
|
||||
fromJson(a, n[k])
|
||||
t[parseUUID(k)] = a
|
||||
|
||||
proc toJsonHook(t: TableRef[UUID, Bookmark]): JsonNode =
|
||||
result = newJObject()
|
||||
for k, v in t.pairs: result[$k] = toJson(v)
|
||||
|
||||
proc fromJsonHook(t: var TableRef[UUID, Bookmark], n: JsonNode) =
|
||||
for k in n.keys:
|
||||
var b: Bookmark
|
||||
fromJson(b, n[k])
|
||||
t[parseUUID(k)] = b
|
||||
|
||||
proc toJsonHook(t: TableRef[UUID, MediaFile]): JsonNode =
|
||||
result = newJObject()
|
||||
for k, v in t.pairs: result[$k] = toJson(v)
|
||||
|
||||
proc fromJsonHook(t: var TableRef[UUID, MediaFile], n: JsonNode) =
|
||||
for k in n.keys:
|
||||
var mf: MediaFile
|
||||
fromJson(mf, n[k])
|
||||
t[parseUUID(k)] = mf
|
||||
|
||||
proc toJsonHook(t: TableRef[UUID, Playlist]): JsonNode =
|
||||
result = newJObject()
|
||||
for k, v in t.pairs: result[$k] = toJson(v)
|
||||
|
||||
proc fromJsonHook(t: var TableRef[UUID, Playlist], n: JsonNode) =
|
||||
for k in n.keys:
|
||||
var p: Playlist
|
||||
fromJson(p, n[k])
|
||||
t[parseUUID(k)] = p
|
||||
|
||||
proc toJsonHook(t: TableRef[UUID, seq[UUID]]): JsonNode =
|
||||
result = newJObject()
|
||||
for k, v in t.pairs: result[$k] = toJson(v)
|
||||
|
||||
proc fromJsonHook(t: var TableRef[UUID, seq[UUID]], n: JsonNode) =
|
||||
for k in n.keys:
|
||||
var s: seq[UUID]
|
||||
fromJson(s, n[k])
|
||||
t[parseUUID(k)] = s
|
||||
|
||||
proc toJsonHook(uuid: UUID): JsonNode = %($uuid)
|
||||
proc fromJsonHook(u: var UUID,node: JsonNode) =
|
||||
proc fromJsonHook(u: var UUID, node: JsonNode) =
|
||||
u = parseUUID(node.getStr)
|
||||
|
||||
proc toJsonHook(dt: DateTime): JsonNode = %(dt.formatIso8601)
|
||||
proc fromJsonHook(dt: var DateTime, node: JsonNode) =
|
||||
dt = parseIso8601(node.getStr)
|
||||
|
||||
## API Implementation
|
||||
|
||||
proc initDbRoot(): DbRoot =
|
||||
DbRoot(
|
||||
albums: newTable[UUID, Album](),
|
||||
artists: newTable[UUID, Artist](),
|
||||
bookmarks: newTable[UUID, Bookmark](),
|
||||
mediaFiles: newTable[UUID, MediaFile](),
|
||||
playlists: newTable[UUID, Playlist](),
|
||||
tags: newTable[string, Option[string]](),
|
||||
albumsToMediaFiles: newTable[UUID, seq[UUID]](),
|
||||
artistsToMediaFiles: newTable[UUID, seq[UUID]](),
|
||||
artistsAndAlbums: @[],
|
||||
playlistsToMediaFiles: newTable[UUID, seq[UUID]](),
|
||||
tagsAndMediaFiles: @[],
|
||||
mediaFileHashToId: newTable[string, UUID]())
|
||||
|
||||
proc initDb*(path: string): WdiwtltDb =
|
||||
WdiwtltDb(
|
||||
jsonFilePath: path,
|
||||
root: DbRoot(
|
||||
albums: newTable[UUID, Album](),
|
||||
artists: newTable[UUID, Artist](),
|
||||
bookmarks: newTable[UUID, Bookmark](),
|
||||
mediaFiles: newTable[UUID, MediaFile](),
|
||||
playlists: newTable[UUID, Playlist](),
|
||||
tags: newStringTable(),
|
||||
albumsToMediaFiles: newTable[UUID, seq[UUID]](),
|
||||
artistsToMediaFiles: newTable[UUID, seq[UUID]](),
|
||||
artistsAndAlbums: @[],
|
||||
playlistsToMediaFiles: newTable[UUID, seq[UUID]](),
|
||||
tagsAndMediaFiles: @[]
|
||||
))
|
||||
root: initDbRoot())
|
||||
|
||||
proc loadDb*(path: string): WdiwtltDb =
|
||||
if not fileExists(path):
|
||||
raise newException(Exception, "Unable to open database file '" & path & "'")
|
||||
|
||||
let jsonRoot = parseJson(path)
|
||||
var root: DbRoot
|
||||
let jsonRoot = parseJson(path.readFile)
|
||||
var root: DbRoot = initDbRoot()
|
||||
root.fromJson(jsonRoot)
|
||||
|
||||
debug "loaded DB"
|
||||
result = WdiwtltDb(
|
||||
jsonFilePath: path,
|
||||
root: root)
|
||||
debug result.debug
|
||||
|
||||
proc persist*(db: WdiwtltDb): void =
|
||||
let jsonRoot = db.root.toJson
|
||||
db.jsonFilePath.writeFile($jsonRoot)
|
||||
|
||||
proc delete*(db: WdiwtltDb, a: Album) =
|
||||
## Albums
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, a: Album) = db.root.albums[a.id] = a
|
||||
|
||||
proc remove*(db: WdiwtltDb, a: Album) =
|
||||
db.root.albums.del(a.id)
|
||||
db.root.albumsToMediaFiles.del(a.id)
|
||||
db.root.artistsAndAlbums =
|
||||
db.root.artistsAndAlbums --> filter(it.albumId != a.id)
|
||||
|
||||
proc delete*(db: WdiwtltDb, a: Artist) =
|
||||
proc update*(db: WdiwtltDb, a: Album) = db.add(a)
|
||||
|
||||
proc findAlbumsByArtist*(db: WdiwtltDb, a: Artist): seq[Album] =
|
||||
db.root.artistsAndAlbums -->
|
||||
filter(it.artistId == a.id and db.root.artists.contains(a.id)).
|
||||
map(db.root.albums[it.albumId])
|
||||
|
||||
proc findAlbumsByName*(db: WdiwtltDb, name: string): seq[Album] =
|
||||
result = @[]
|
||||
for a in db.root.albums.values:
|
||||
if a.name == name: result.add(a)
|
||||
|
||||
proc add*(db: WdiwtltDb, a: Album, mf: MediaFile) =
|
||||
if not db.root.albumsToMediaFiles.contains(a.id):
|
||||
db.root.albumsToMediaFiles[a.id] = @[mf.id]
|
||||
else:
|
||||
let idsInAlbum = db.root.albumsToMediaFiles[a.id]
|
||||
if not idsInAlbum.contains(mf.id):
|
||||
db.root.albumsToMediaFiles[a.id].add(mf.id)
|
||||
|
||||
proc remove*(db: WdiwtltDb, a: Album, mf: MediaFile) =
|
||||
if db.root.albumsToMediaFiles.contains(a.id):
|
||||
db.root.albumsToMediaFiles[a.id] =
|
||||
db.root.albumsToMediaFiles[a.id] --> filter(it != mf.id)
|
||||
|
||||
## Artists
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, a: Artist) = db.root.artists[a.id] = a
|
||||
|
||||
proc remove*(db: WdiwtltDb, a: Artist) =
|
||||
db.root.artists.del(a.id)
|
||||
db.root.artistsToMediaFiles.del(a.id)
|
||||
db.root.artistsAndAlbums =
|
||||
db.root.artistsAndAlbums --> filter(it.artistId != a.id)
|
||||
|
||||
proc delete*(db: WdiwtltDb, b: Bookmark) = db.root.bookmarks.del(b.id)
|
||||
proc update*(db: WdiwtltDb, a: Artist) = db.add(a)
|
||||
|
||||
proc delete*(db: WdiwtltDb, mf: MediaFile) =
|
||||
proc findArtistsByAlbum*(db: WdiwtltDb, a: Album): seq[Artist] =
|
||||
db.root.artistsAndAlbums -->
|
||||
filter(it.albumId == a.id and db.root.albums.contains(a.id)).
|
||||
map(db.root.artists[it.artistId])
|
||||
|
||||
proc findArtistsByName*(db: WdiwtltDb, name: string): seq[Artist] =
|
||||
result = @[]
|
||||
for a in db.root.artists.values:
|
||||
if a.name == name: result.add(a)
|
||||
|
||||
proc add*(db: WdiwtltDb, a: Artist, mf: MediaFile) =
|
||||
if not db.root.artistsToMediaFiles.contains(a.id):
|
||||
db.root.artistsToMediaFiles[a.id] = @[mf.id]
|
||||
else:
|
||||
let idsInArtist = db.root.artistsToMediaFiles[a.id]
|
||||
if not idsInArtist.contains(mf.id):
|
||||
db.root.artistsToMediaFiles[a.id].add(mf.id)
|
||||
|
||||
proc remove*(db: WdiwtltDb, a: Artist, mf: MediaFile) =
|
||||
if db.root.artistsToMediaFiles.contains(a.id):
|
||||
db.root.artistsToMediaFiles[a.id] =
|
||||
db.root.artistsToMediaFiles[a.id] --> filter(it != mf.id)
|
||||
|
||||
proc isAssociated*(
|
||||
db: WdiwtltDb,
|
||||
artist: Artist,
|
||||
album: Album
|
||||
): bool =
|
||||
let matching = db.root.artistsAndAlbums -->
|
||||
filter(it.artistId == artist.id and it.albumId == album.id)
|
||||
return matching.len > 0
|
||||
|
||||
proc associate*(db: WdiwtltDb, artist: Artist, album: Album) =
|
||||
if not db.isAssociated(artist, album):
|
||||
db.root.artistsAndAlbums.add((artist.id, album.id))
|
||||
|
||||
proc disassociate*(db: WdiwtltDb, artist: Artist, album: Album) =
|
||||
db.root.artistsAndAlbums = db.root.artistsAndAlbums -->
|
||||
filter(it.albumId != album.id or it.artistId != artist.id)
|
||||
|
||||
## Bookmarks
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, b: Bookmark) = db.root.bookmarks[b.id] = b
|
||||
proc remove*(db: WdiwtltDb, b: Bookmark) = db.root.bookmarks.del(b.id)
|
||||
proc update*(db: WdiwtltDb, b: Bookmark) = db.add(b)
|
||||
|
||||
## Media Files
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, mf: MediaFile) =
|
||||
db.root.mediaFiles[mf.id] = mf
|
||||
db.root.mediaFileHashToId[mf.fileHash] = mf.id
|
||||
|
||||
proc remove*(db: WdiwtltDb, mf: MediaFile) =
|
||||
db.root.mediaFiles.del(mf.id)
|
||||
|
||||
for albumId, mfIds in db.root.albumsToMediaFiles.pairs:
|
||||
if mfIds.contains(mf.id):
|
||||
db.remove(db.root.albums[albumId], mf)
|
||||
|
||||
proc delete*(db: WdiwtltDb, p: Playlist) =
|
||||
db.root.mediaFileHashToId.del(mf.fileHash)
|
||||
|
||||
proc update*(db: WdiwtltDb, mf: MediaFile) = db.add(mf)
|
||||
|
||||
proc findMediaFilesByAlbum*(db: WdiwtltDb, a: Album): seq[MediaFile] =
|
||||
result = @[]
|
||||
if db.root.albumsToMediaFiles.contains(a.id):
|
||||
return db.root.albumsToMediaFiles[a.id] -->
|
||||
filter(db.root.mediaFiles.contains(it)).
|
||||
map(db.root.mediaFiles[it])
|
||||
|
||||
proc findMediaFilesByArtist*(db: WdiwtltDb, a: Artist): seq[MediaFile] =
|
||||
result = @[]
|
||||
if db.root.artistsToMediaFiles.contains(a.id):
|
||||
return db.root.artistsToMediaFiles[a.id] -->
|
||||
filter(db.root.mediaFiles.contains(it)).
|
||||
map(db.root.mediaFiles[it])
|
||||
|
||||
proc findMediaFileByHash*(db: WdiwtltDb, hash: string): Option[MediaFile] =
|
||||
if db.root.mediaFileHashToId.contains(hash):
|
||||
let mfId = db.root.mediaFileHashToId[hash]
|
||||
if db.root.mediaFiles.contains(mfId):
|
||||
return some(db.root.mediaFiles[mfId])
|
||||
|
||||
return none[MediaFile]()
|
||||
|
||||
## Playlists
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, p: Playlist) = db.root.playlists[p.id] = p
|
||||
|
||||
proc remove*(db: WdiwtltDb, p: Playlist) =
|
||||
db.root.playlists.del(p.id)
|
||||
db.root.playlistsToMediaFiles.del(p.id)
|
||||
for b in db.root.bookmarks.values:
|
||||
if b.playlistId == p.id: db.delete(b)
|
||||
if b.playlistId == p.id: db.remove(b)
|
||||
|
||||
proc delete*(db: WdiwtltDb, t: Tag) =
|
||||
proc update*(db: WdiwtltDb, p: Playlist) = db.add(p)
|
||||
|
||||
## Tags
|
||||
## --------------------
|
||||
proc add*(db: WdiwtltDb, t: Tag) = db.root.tags[t.name] = t.description
|
||||
|
||||
proc remove*(db: WdiwtltDb, t: Tag) =
|
||||
db.root.tags.del(t.name)
|
||||
db.root.tagsAndMediaFiles = db.root.tagsAndMediaFiles -->
|
||||
filter(it.tagName != t.name)
|
||||
|
||||
proc remove*(db: WdiwtltDb, a: Album, mf: MediaFile) =
|
||||
db.root.albumsToMediaFiles[a.id] =
|
||||
db.root.albumsToMediaFiles[a.id] -->
|
||||
filter(it != mf.id)
|
||||
|
||||
proc remove*(db: WdiwtltDb, a: Artist, mf: MediaFile) =
|
||||
db.root.artistsToMediaFiles[a.id] =
|
||||
db.root.artistsToMediaFiles[a.id] -->
|
||||
filter(it != mf.id)
|
||||
|
||||
proc remove*(db: WdiwtltDb, artist: Artist, album: Album) =
|
||||
db.root.artistsAndAlbums = db.root.artistsAndAlbums -->
|
||||
filter(it.albumId != album.id or it.artistId != artist.id)
|
||||
proc update*(db: WdiwtltDb, t: Tag) = db.add(t)
|
||||
|
||||
## Housekeeping
|
||||
## --------------------
|
||||
proc pruneStalePlaylists*(db: WdiwtltDb, ts: DateTime) =
|
||||
db.root.playlists.values -->
|
||||
filter(it.lastUsed > ts).foreach(db.delete(it))
|
||||
filter(it.lastUsed > ts).foreach(db.remove(it))
|
||||
|
||||
proc pruneStaleBookmarks*(db: WdiwtltDb, ts: DateTime) =
|
||||
db.root.bookmarks.values -->
|
||||
filter(it.lastUsed > ts).foreach(db.delete(it))
|
||||
filter(it.lastUsed > ts).foreach(db.remove(it))
|
||||
|
||||
proc removeEmptyAlbums*(db: WdiwtltDb): void =
|
||||
let emptyAlbumIds = db.root.albumsToMediaFiles.pairs -->
|
||||
|
Loading…
Reference in New Issue
Block a user