import 'package:freezer/api/definitions.dart' as d; import 'package:freezer/api/definitions.dart' show AlbumType; import 'package:freezer/api/paths.dart'; import 'package:isar/isar.dart'; part 'database.g.dart'; class Database { static Future open() async => Isar.open( [ // collections TrackSchema, AlbumSchema, ArtistSchema, PlaylistSchema, ], directory: await Paths.dataDirectory(), name: 'offline', ); static String sanitize(String input) => input.replaceAll(RegExp(r'[\/\\\?\%\*\:\|\"\<\>]'), ''); } @collection class Track { Id get isarId => int.parse(id); late final String id; // index title for search @Index(type: IndexType.value) late final String title; late final String albumId; late final List artistIds; late final DeezerImageDetails albumArt; late final int? trackNumber; late final bool offline; late final Lyrics? lyrics; late final bool favorite; late final int? diskNumber; late final bool explicit; late final String localPath; Track(); factory Track.from(d.Track t) { return Track() ..id = t.id ..title = t.title! ..albumId = t.album!.id! ..artistIds = t.artists!.map((e) => e.id).toList(growable: false) ..albumArt = DeezerImageDetails.from(t.albumArt!) ..trackNumber = t.trackNumber ..offline = t.offline ?? false ..lyrics = t.lyrics == null ? null : Lyrics.from(t.lyrics!) ..favorite = t.favorite ?? false ..diskNumber = t.diskNumber ..explicit = t.explicit ?? false; } d.Track to({d.Album? album, List? artists}) { return d.Track( id: id, title: title, album: album ?? d.Album(id: albumId), artists: artists ?? artistIds.map((id) => d.Artist(id: id)).toList(growable: false), albumArt: albumArt.to(), trackNumber: trackNumber, offline: offline, lyrics: lyrics?.to(), favorite: favorite, diskNumber: diskNumber, explicit: explicit, ); } } @collection class Album { Id get isarId => int.parse(id); late final String id; @Index(type: IndexType.value) late final String title; late final List artistIds; late final List trackIds; late final DeezerImageDetails art; late final int fansCount; late final bool offline; late final bool library; @enumerated late final AlbumType type; late final String releaseDate; Album(); factory Album.from(d.Album album) { return Album() ..id = album.id! ..title = album.title! ..artistIds = album.artists!.map((e) => e.id).toList(growable: false) ..trackIds = album.tracks!.map((e) => e.id).toList(growable: false) ..art = DeezerImageDetails.from(album.art!) ..fansCount = album.fans! ..offline = album.offline ?? false ..library = album.library ?? false ..type = album.type! ..releaseDate = album.releaseDate!; } d.Album to({List? artists, List? tracks}) { return d.Album( id: id, title: title, art: art.to(), artists: artists ?? artistIds.map((id) => d.Artist(id: id)).toList(growable: false), tracks: tracks ?? trackIds.map((id) => d.Track(id: id)).toList(growable: false), fans: fansCount, offline: offline, library: library, type: type, releaseDate: releaseDate, ); } } @collection class Artist { Id get isarId => int.parse(id); late final String id; @Index(type: IndexType.value) late final String name; late final List albumIds; late final List topTracksIds; late final DeezerImageDetails picture; late final int fansCount; late final int albumCount; late final bool offline; late final bool library; late final bool radio; Artist(); factory Artist.from(d.Artist artist) { return Artist() ..id = artist.id ..name = artist.name! ..albumIds = artist.albums! .map((d.Album a) => a.id!) .toList(growable: false) ..topTracksIds = artist.topTracks! .map((d.Track t) => t.id) .toList(growable: false) ..picture = DeezerImageDetails.from(artist.picture!) ..fansCount = artist.fans ?? 0 ..albumCount = artist.albumCount ?? 0 ..offline = artist.offline ?? false ..library = artist.library ?? false ..radio = artist.radio ?? false; } } @collection class Playlist { late final String id; Id get isarId => int.parse(id); @Index(type: IndexType.value) late final String title; late final List trackIds; late final DeezerImageDetails image; late final int durationSec; late final String userId; late final String userName; late final int fansCount; late final String description; late final bool library; Playlist(); factory Playlist.from(d.Playlist playlist) { return Playlist() ..id = playlist.id ..title = playlist.title! ..trackIds = playlist.tracks! .map((d.Track t) => t.id) .toList(growable: false) ..image = DeezerImageDetails.from(playlist.image! as d.DeezerImageDetails) ..durationSec = playlist.duration!.inSeconds ..userId = playlist.user!.id! ..userName = playlist.user!.name! ..fansCount = playlist.fans ?? 0 ..description = playlist.description ?? '' ..library = playlist.library ?? false; } d.Playlist to({List? tracks}) { return d.Playlist( id: id, title: title, tracks: tracks ?? trackIds.map((id) => d.Track(id: id)).toList(growable: false), image: image.to(), trackCount: trackIds.length, duration: Duration(seconds: durationSec), user: d.User(id: userId, name: userName), fans: fansCount, library: library, description: description, ); } } @embedded class DeezerImageDetails { late String type; late String md5; DeezerImageDetails(); factory DeezerImageDetails.from(d.DeezerImageDetails details) { return DeezerImageDetails() ..type = details.type ..md5 = details.md5; } d.DeezerImageDetails to() { return d.DeezerImageDetails(md5, type: type); } } @embedded class Lyrics { late final String lyricsId; late final String writers; late final List lyrics; late final bool sync; Lyrics(); factory Lyrics.from(d.Lyrics lyrics) { return Lyrics() ..lyricsId = lyrics.id ?? '' ..writers = lyrics.writers ?? '' ..sync = lyrics.sync ..lyrics = lyrics.lyrics!.map(Lyric.from).toList(growable: false); } d.Lyrics to() { return d.Lyrics( id: lyricsId, writers: writers, sync: sync, lyrics: lyrics.map((e) => e.to()).toList(growable: false)); } } @embedded class Lyric { late final String text; late final int? offsetMs; late final String? lrcTimestamp; Lyric(); factory Lyric.from(d.Lyric l) { return Lyric() ..text = l.text! ..offsetMs = l.offset?.inMilliseconds ..lrcTimestamp = l.lrcTimestamp; } d.Lyric to() { return d.Lyric( offset: offsetMs == null ? null : Duration(milliseconds: offsetMs!), text: text, lrcTimestamp: lrcTimestamp); } }