mirror of
https://github.com/austinried/subtracks.git
synced 2025-12-27 09:09:29 +01:00
sync the rest of the source models
refactor music download/storage to avoid re-download during reset add palylist to test server setup
This commit is contained in:
parent
0e6acbed0f
commit
0c80dbdba5
3
.gitignore
vendored
3
.gitignore
vendored
@ -46,3 +46,6 @@ app.*.map.json
|
|||||||
|
|
||||||
# VSCode
|
# VSCode
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
|
||||||
|
# subtracks
|
||||||
|
/music
|
||||||
|
|||||||
@ -3,7 +3,7 @@ services:
|
|||||||
build: ./docker/library-manager
|
build: ./docker/library-manager
|
||||||
volumes:
|
volumes:
|
||||||
- deno-dir:/deno-dir
|
- deno-dir:/deno-dir
|
||||||
- music:/music
|
- ./music:/music
|
||||||
|
|
||||||
navidrome:
|
navidrome:
|
||||||
image: deluan/navidrome:latest
|
image: deluan/navidrome:latest
|
||||||
@ -14,7 +14,7 @@ services:
|
|||||||
ND_LOGLEVEL: debug
|
ND_LOGLEVEL: debug
|
||||||
volumes:
|
volumes:
|
||||||
- navidrome-data:/data
|
- navidrome-data:/data
|
||||||
- music:/music:ro
|
- ./music:/music:ro
|
||||||
|
|
||||||
gonic:
|
gonic:
|
||||||
image: sentriz/gonic:latest
|
image: sentriz/gonic:latest
|
||||||
@ -26,7 +26,7 @@ services:
|
|||||||
- 4747:80
|
- 4747:80
|
||||||
volumes:
|
volumes:
|
||||||
- gonic-data:/data
|
- gonic-data:/data
|
||||||
- music:/music:ro
|
- ./music:/music:ro
|
||||||
- gonic-podcasts:/podcasts
|
- gonic-podcasts:/podcasts
|
||||||
- gonic-playlists:/playlists
|
- gonic-playlists:/playlists
|
||||||
- gonic-cache:/cache
|
- gonic-cache:/cache
|
||||||
|
|||||||
@ -3,9 +3,6 @@ import * as path from "jsr:@std/path@1.1.2";
|
|||||||
import { MUSIC_DIR } from "./util/env.ts";
|
import { MUSIC_DIR } from "./util/env.ts";
|
||||||
import { SubsonicClient } from "./util/subsonic.ts";
|
import { SubsonicClient } from "./util/subsonic.ts";
|
||||||
|
|
||||||
await new Deno.Command("rm", { args: ["-rf", path.join(MUSIC_DIR, "*")] })
|
|
||||||
.output();
|
|
||||||
|
|
||||||
const client = new SubsonicClient(
|
const client = new SubsonicClient(
|
||||||
"http://demo.subsonic.org",
|
"http://demo.subsonic.org",
|
||||||
"guest1",
|
"guest1",
|
||||||
@ -13,7 +10,7 @@ const client = new SubsonicClient(
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const id of ["197", "199", "321"]) {
|
for (const id of ["197", "199", "321"]) {
|
||||||
const { res } = await client.get("download", { id });
|
const { res } = await client.get("download", [["id", id]]);
|
||||||
|
|
||||||
let filename = res.headers.get("Content-Disposition")
|
let filename = res.headers.get("Content-Disposition")
|
||||||
?.split(";")[1];
|
?.split(";")[1];
|
||||||
|
|||||||
@ -2,31 +2,69 @@
|
|||||||
import { SubsonicClient } from "./util/subsonic.ts";
|
import { SubsonicClient } from "./util/subsonic.ts";
|
||||||
import { sleep } from "./util/util.ts";
|
import { sleep } from "./util/util.ts";
|
||||||
|
|
||||||
async function scrobbleTrack(
|
async function getSongId(
|
||||||
client: SubsonicClient,
|
client: SubsonicClient,
|
||||||
album: string,
|
album: string,
|
||||||
track: number,
|
track: number,
|
||||||
|
): Promise<string> {
|
||||||
|
const { xml: albumsXml } = await client.get("getAlbumList2", [
|
||||||
|
["type", "newest"],
|
||||||
|
]);
|
||||||
|
const albumId = albumsXml.querySelector(
|
||||||
|
`album[name='${album.replaceAll("'", "\\'")}']`,
|
||||||
|
)?.id;
|
||||||
|
|
||||||
|
const { xml: songsXml } = await client.get("getAlbum", [["id", albumId!]]);
|
||||||
|
return songsXml.querySelector(`song[track='${track}']`)?.id!;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrobbleTrack(
|
||||||
|
client: SubsonicClient,
|
||||||
|
songId: string,
|
||||||
) {
|
) {
|
||||||
const { xml: albumsXml } = await client.get("getAlbumList2", {
|
await client.get("scrobble", [
|
||||||
type: "newest",
|
["id", songId!],
|
||||||
});
|
["submission", "true"],
|
||||||
const albumId = albumsXml.querySelector(`album[name='${album}']`)?.id;
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
const { xml: songsXml } = await client.get("getAlbum", { id: albumId! });
|
async function createPlaylist(
|
||||||
const songId = songsXml.querySelector(`song[track='${track}']`)?.id;
|
client: SubsonicClient,
|
||||||
|
name: string,
|
||||||
|
songs: { album: string; track: number }[],
|
||||||
|
) {
|
||||||
|
const songIds = await Promise.all(songs.map(({ album, track }) => {
|
||||||
|
return getSongId(client, album, track);
|
||||||
|
}));
|
||||||
|
|
||||||
await client.get("scrobble", {
|
await client.get("createPlaylist", [
|
||||||
id: songId!,
|
["name", name],
|
||||||
submission: "true",
|
...songIds.map((songId) => ["songId", songId] as [string, string]),
|
||||||
});
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupTestData(client: SubsonicClient) {
|
async function setupTestData(client: SubsonicClient) {
|
||||||
await scrobbleTrack(client, "Retroconnaissance EP", 1);
|
await scrobbleTrack(
|
||||||
|
client,
|
||||||
|
await getSongId(client, "Retroconnaissance EP", 1),
|
||||||
|
);
|
||||||
await sleep(1_000);
|
await sleep(1_000);
|
||||||
await scrobbleTrack(client, "Retroconnaissance EP", 2);
|
await scrobbleTrack(
|
||||||
|
client,
|
||||||
|
await getSongId(client, "Retroconnaissance EP", 2),
|
||||||
|
);
|
||||||
await sleep(1_000);
|
await sleep(1_000);
|
||||||
await scrobbleTrack(client, "Kosmonaut", 1);
|
await scrobbleTrack(client, await getSongId(client, "Kosmonaut", 1));
|
||||||
|
|
||||||
|
await createPlaylist(client, "Playlist 1", [
|
||||||
|
{ album: "Retroconnaissance EP", track: 2 },
|
||||||
|
{ album: "Retroconnaissance EP", track: 1 },
|
||||||
|
{ album: "Kosmonaut", track: 2 },
|
||||||
|
{ album: "Kosmonaut", track: 4 },
|
||||||
|
{ album: "I Don't Know What I'm Doing", track: 9 },
|
||||||
|
{ album: "I Don't Know What I'm Doing", track: 10 },
|
||||||
|
{ album: "I Don't Know What I'm Doing", track: 11 },
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupNavidrome() {
|
async function setupNavidrome() {
|
||||||
|
|||||||
@ -10,15 +10,15 @@ export class SubsonicClient {
|
|||||||
|
|
||||||
async get(
|
async get(
|
||||||
method: "download",
|
method: "download",
|
||||||
params?: Record<string, string>,
|
params?: [string, string][],
|
||||||
): Promise<{ res: Response; xml: undefined }>;
|
): Promise<{ res: Response; xml: undefined }>;
|
||||||
async get(
|
async get(
|
||||||
method: string,
|
method: string,
|
||||||
params?: Record<string, string>,
|
params?: [string, string][],
|
||||||
): Promise<{ res: Response; xml: Document }>;
|
): Promise<{ res: Response; xml: Document }>;
|
||||||
async get(
|
async get(
|
||||||
method: string,
|
method: string,
|
||||||
params?: Record<string, string>,
|
params?: [string, string][],
|
||||||
): Promise<{ res: Response; xml: Document | undefined }> {
|
): Promise<{ res: Response; xml: Document | undefined }> {
|
||||||
const url = new URL(`rest/${method}.view`, this.baseUrl);
|
const url = new URL(`rest/${method}.view`, this.baseUrl);
|
||||||
|
|
||||||
@ -28,9 +28,9 @@ export class SubsonicClient {
|
|||||||
url.searchParams.set("c", "subtracks-test-fixture");
|
url.searchParams.set("c", "subtracks-test-fixture");
|
||||||
|
|
||||||
if (params) {
|
if (params) {
|
||||||
Object.entries(params).forEach(([key, value]) =>
|
for (const [key, value] of params) {
|
||||||
url.searchParams.append(key, value)
|
url.searchParams.append(key, value);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
|
|||||||
@ -440,6 +440,61 @@ extension ArtistToDb on models.Artist {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AlbumToDb on models.Album {
|
||||||
|
AlbumsCompanion toDb(int sourceId) => AlbumsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
id: id,
|
||||||
|
artistId: Value(artistId),
|
||||||
|
name: name,
|
||||||
|
albumArtist: Value(albumArtist),
|
||||||
|
created: created,
|
||||||
|
coverArt: Value(coverArt),
|
||||||
|
genre: Value(genre),
|
||||||
|
year: Value(year),
|
||||||
|
starred: Value(starred),
|
||||||
|
frequentRank: Value(frequentRank),
|
||||||
|
recentRank: Value(recentRank),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SongToDb on models.Song {
|
||||||
|
SongsCompanion toDb(int sourceId) => SongsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
id: id,
|
||||||
|
albumId: Value(albumId),
|
||||||
|
artistId: Value(artistId),
|
||||||
|
title: title,
|
||||||
|
album: Value(album),
|
||||||
|
artist: Value(artist),
|
||||||
|
duration: Value(duration),
|
||||||
|
track: Value(track),
|
||||||
|
disc: Value(disc),
|
||||||
|
starred: Value(starred),
|
||||||
|
genre: Value(genre),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlaylistToDb on models.Playlist {
|
||||||
|
PlaylistsCompanion toDb(int sourceId) => PlaylistsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
comment: Value(comment),
|
||||||
|
coverArt: Value(coverArt),
|
||||||
|
created: created,
|
||||||
|
changed: changed,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlaylistSongToDb on models.PlaylistSong {
|
||||||
|
PlaylistSongsCompanion toDb(int sourceId) => PlaylistSongsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
playlistId: playlistId,
|
||||||
|
songId: songId,
|
||||||
|
position: position,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// LazyDatabase _openConnection() {
|
// LazyDatabase _openConnection() {
|
||||||
// return LazyDatabase(() async {
|
// return LazyDatabase(() async {
|
||||||
// final dbFolder = await getApplicationDocumentsDirectory();
|
// final dbFolder = await getApplicationDocumentsDirectory();
|
||||||
|
|||||||
@ -1419,17 +1419,6 @@ class Playlists extends Table with TableInfo<Playlists, models.Playlist> {
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
$customConstraints: '',
|
$customConstraints: '',
|
||||||
);
|
);
|
||||||
static const VerificationMeta _songCountMeta = const VerificationMeta(
|
|
||||||
'songCount',
|
|
||||||
);
|
|
||||||
late final GeneratedColumn<int> songCount = GeneratedColumn<int>(
|
|
||||||
'song_count',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: DriftSqlType.int,
|
|
||||||
requiredDuringInsert: true,
|
|
||||||
$customConstraints: 'NOT NULL',
|
|
||||||
);
|
|
||||||
static const VerificationMeta _createdMeta = const VerificationMeta(
|
static const VerificationMeta _createdMeta = const VerificationMeta(
|
||||||
'created',
|
'created',
|
||||||
);
|
);
|
||||||
@ -1449,10 +1438,8 @@ class Playlists extends Table with TableInfo<Playlists, models.Playlist> {
|
|||||||
aliasedName,
|
aliasedName,
|
||||||
false,
|
false,
|
||||||
type: DriftSqlType.dateTime,
|
type: DriftSqlType.dateTime,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: true,
|
||||||
$customConstraints:
|
$customConstraints: 'NOT NULL',
|
||||||
'NOT NULL DEFAULT (strftime(\'%s\', CURRENT_TIMESTAMP))',
|
|
||||||
defaultValue: const CustomExpression('strftime(\'%s\', CURRENT_TIMESTAMP)'),
|
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [
|
List<GeneratedColumn> get $columns => [
|
||||||
@ -1461,7 +1448,6 @@ class Playlists extends Table with TableInfo<Playlists, models.Playlist> {
|
|||||||
name,
|
name,
|
||||||
comment,
|
comment,
|
||||||
coverArt,
|
coverArt,
|
||||||
songCount,
|
|
||||||
created,
|
created,
|
||||||
changed,
|
changed,
|
||||||
];
|
];
|
||||||
@ -1510,14 +1496,6 @@ class Playlists extends Table with TableInfo<Playlists, models.Playlist> {
|
|||||||
coverArt.isAcceptableOrUnknown(data['cover_art']!, _coverArtMeta),
|
coverArt.isAcceptableOrUnknown(data['cover_art']!, _coverArtMeta),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data.containsKey('song_count')) {
|
|
||||||
context.handle(
|
|
||||||
_songCountMeta,
|
|
||||||
songCount.isAcceptableOrUnknown(data['song_count']!, _songCountMeta),
|
|
||||||
);
|
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_songCountMeta);
|
|
||||||
}
|
|
||||||
if (data.containsKey('created')) {
|
if (data.containsKey('created')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_createdMeta,
|
_createdMeta,
|
||||||
@ -1531,6 +1509,8 @@ class Playlists extends Table with TableInfo<Playlists, models.Playlist> {
|
|||||||
_changedMeta,
|
_changedMeta,
|
||||||
changed.isAcceptableOrUnknown(data['changed']!, _changedMeta),
|
changed.isAcceptableOrUnknown(data['changed']!, _changedMeta),
|
||||||
);
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_changedMeta);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
@ -1588,7 +1568,6 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
final Value<String> name;
|
final Value<String> name;
|
||||||
final Value<String?> comment;
|
final Value<String?> comment;
|
||||||
final Value<String?> coverArt;
|
final Value<String?> coverArt;
|
||||||
final Value<int> songCount;
|
|
||||||
final Value<DateTime> created;
|
final Value<DateTime> created;
|
||||||
final Value<DateTime> changed;
|
final Value<DateTime> changed;
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
@ -1598,7 +1577,6 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
this.name = const Value.absent(),
|
this.name = const Value.absent(),
|
||||||
this.comment = const Value.absent(),
|
this.comment = const Value.absent(),
|
||||||
this.coverArt = const Value.absent(),
|
this.coverArt = const Value.absent(),
|
||||||
this.songCount = const Value.absent(),
|
|
||||||
this.created = const Value.absent(),
|
this.created = const Value.absent(),
|
||||||
this.changed = const Value.absent(),
|
this.changed = const Value.absent(),
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
@ -1609,22 +1587,20 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
required String name,
|
required String name,
|
||||||
this.comment = const Value.absent(),
|
this.comment = const Value.absent(),
|
||||||
this.coverArt = const Value.absent(),
|
this.coverArt = const Value.absent(),
|
||||||
required int songCount,
|
|
||||||
required DateTime created,
|
required DateTime created,
|
||||||
this.changed = const Value.absent(),
|
required DateTime changed,
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : sourceId = Value(sourceId),
|
}) : sourceId = Value(sourceId),
|
||||||
id = Value(id),
|
id = Value(id),
|
||||||
name = Value(name),
|
name = Value(name),
|
||||||
songCount = Value(songCount),
|
created = Value(created),
|
||||||
created = Value(created);
|
changed = Value(changed);
|
||||||
static Insertable<models.Playlist> custom({
|
static Insertable<models.Playlist> custom({
|
||||||
Expression<int>? sourceId,
|
Expression<int>? sourceId,
|
||||||
Expression<String>? id,
|
Expression<String>? id,
|
||||||
Expression<String>? name,
|
Expression<String>? name,
|
||||||
Expression<String>? comment,
|
Expression<String>? comment,
|
||||||
Expression<String>? coverArt,
|
Expression<String>? coverArt,
|
||||||
Expression<int>? songCount,
|
|
||||||
Expression<DateTime>? created,
|
Expression<DateTime>? created,
|
||||||
Expression<DateTime>? changed,
|
Expression<DateTime>? changed,
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
@ -1635,7 +1611,6 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
if (comment != null) 'comment': comment,
|
if (comment != null) 'comment': comment,
|
||||||
if (coverArt != null) 'cover_art': coverArt,
|
if (coverArt != null) 'cover_art': coverArt,
|
||||||
if (songCount != null) 'song_count': songCount,
|
|
||||||
if (created != null) 'created': created,
|
if (created != null) 'created': created,
|
||||||
if (changed != null) 'changed': changed,
|
if (changed != null) 'changed': changed,
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
@ -1648,7 +1623,6 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
Value<String>? name,
|
Value<String>? name,
|
||||||
Value<String?>? comment,
|
Value<String?>? comment,
|
||||||
Value<String?>? coverArt,
|
Value<String?>? coverArt,
|
||||||
Value<int>? songCount,
|
|
||||||
Value<DateTime>? created,
|
Value<DateTime>? created,
|
||||||
Value<DateTime>? changed,
|
Value<DateTime>? changed,
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
@ -1659,7 +1633,6 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
comment: comment ?? this.comment,
|
comment: comment ?? this.comment,
|
||||||
coverArt: coverArt ?? this.coverArt,
|
coverArt: coverArt ?? this.coverArt,
|
||||||
songCount: songCount ?? this.songCount,
|
|
||||||
created: created ?? this.created,
|
created: created ?? this.created,
|
||||||
changed: changed ?? this.changed,
|
changed: changed ?? this.changed,
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
@ -1684,9 +1657,6 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
if (coverArt.present) {
|
if (coverArt.present) {
|
||||||
map['cover_art'] = Variable<String>(coverArt.value);
|
map['cover_art'] = Variable<String>(coverArt.value);
|
||||||
}
|
}
|
||||||
if (songCount.present) {
|
|
||||||
map['song_count'] = Variable<int>(songCount.value);
|
|
||||||
}
|
|
||||||
if (created.present) {
|
if (created.present) {
|
||||||
map['created'] = Variable<DateTime>(created.value);
|
map['created'] = Variable<DateTime>(created.value);
|
||||||
}
|
}
|
||||||
@ -1707,7 +1677,6 @@ class PlaylistsCompanion extends UpdateCompanion<models.Playlist> {
|
|||||||
..write('name: $name, ')
|
..write('name: $name, ')
|
||||||
..write('comment: $comment, ')
|
..write('comment: $comment, ')
|
||||||
..write('coverArt: $coverArt, ')
|
..write('coverArt: $coverArt, ')
|
||||||
..write('songCount: $songCount, ')
|
|
||||||
..write('created: $created, ')
|
..write('created: $created, ')
|
||||||
..write('changed: $changed, ')
|
..write('changed: $changed, ')
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
@ -2063,19 +2032,6 @@ class Songs extends Table with TableInfo<Songs, models.Song> {
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
$customConstraints: '',
|
$customConstraints: '',
|
||||||
);
|
);
|
||||||
static const VerificationMeta _updatedMeta = const VerificationMeta(
|
|
||||||
'updated',
|
|
||||||
);
|
|
||||||
late final GeneratedColumn<DateTime> updated = GeneratedColumn<DateTime>(
|
|
||||||
'updated',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: DriftSqlType.dateTime,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
$customConstraints:
|
|
||||||
'NOT NULL DEFAULT (strftime(\'%s\', CURRENT_TIMESTAMP))',
|
|
||||||
defaultValue: const CustomExpression('strftime(\'%s\', CURRENT_TIMESTAMP)'),
|
|
||||||
);
|
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [
|
List<GeneratedColumn> get $columns => [
|
||||||
sourceId,
|
sourceId,
|
||||||
@ -2090,7 +2046,6 @@ class Songs extends Table with TableInfo<Songs, models.Song> {
|
|||||||
disc,
|
disc,
|
||||||
starred,
|
starred,
|
||||||
genre,
|
genre,
|
||||||
updated,
|
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@ -2173,12 +2128,6 @@ class Songs extends Table with TableInfo<Songs, models.Song> {
|
|||||||
genre.isAcceptableOrUnknown(data['genre']!, _genreMeta),
|
genre.isAcceptableOrUnknown(data['genre']!, _genreMeta),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (data.containsKey('updated')) {
|
|
||||||
context.handle(
|
|
||||||
_updatedMeta,
|
|
||||||
updated.isAcceptableOrUnknown(data['updated']!, _updatedMeta),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2268,7 +2217,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
final Value<int?> disc;
|
final Value<int?> disc;
|
||||||
final Value<DateTime?> starred;
|
final Value<DateTime?> starred;
|
||||||
final Value<String?> genre;
|
final Value<String?> genre;
|
||||||
final Value<DateTime> updated;
|
|
||||||
final Value<int> rowid;
|
final Value<int> rowid;
|
||||||
const SongsCompanion({
|
const SongsCompanion({
|
||||||
this.sourceId = const Value.absent(),
|
this.sourceId = const Value.absent(),
|
||||||
@ -2283,7 +2231,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
this.disc = const Value.absent(),
|
this.disc = const Value.absent(),
|
||||||
this.starred = const Value.absent(),
|
this.starred = const Value.absent(),
|
||||||
this.genre = const Value.absent(),
|
this.genre = const Value.absent(),
|
||||||
this.updated = const Value.absent(),
|
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
});
|
});
|
||||||
SongsCompanion.insert({
|
SongsCompanion.insert({
|
||||||
@ -2299,7 +2246,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
this.disc = const Value.absent(),
|
this.disc = const Value.absent(),
|
||||||
this.starred = const Value.absent(),
|
this.starred = const Value.absent(),
|
||||||
this.genre = const Value.absent(),
|
this.genre = const Value.absent(),
|
||||||
this.updated = const Value.absent(),
|
|
||||||
this.rowid = const Value.absent(),
|
this.rowid = const Value.absent(),
|
||||||
}) : sourceId = Value(sourceId),
|
}) : sourceId = Value(sourceId),
|
||||||
id = Value(id),
|
id = Value(id),
|
||||||
@ -2317,7 +2263,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
Expression<int>? disc,
|
Expression<int>? disc,
|
||||||
Expression<DateTime>? starred,
|
Expression<DateTime>? starred,
|
||||||
Expression<String>? genre,
|
Expression<String>? genre,
|
||||||
Expression<DateTime>? updated,
|
|
||||||
Expression<int>? rowid,
|
Expression<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
@ -2333,7 +2278,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
if (disc != null) 'disc': disc,
|
if (disc != null) 'disc': disc,
|
||||||
if (starred != null) 'starred': starred,
|
if (starred != null) 'starred': starred,
|
||||||
if (genre != null) 'genre': genre,
|
if (genre != null) 'genre': genre,
|
||||||
if (updated != null) 'updated': updated,
|
|
||||||
if (rowid != null) 'rowid': rowid,
|
if (rowid != null) 'rowid': rowid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -2351,7 +2295,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
Value<int?>? disc,
|
Value<int?>? disc,
|
||||||
Value<DateTime?>? starred,
|
Value<DateTime?>? starred,
|
||||||
Value<String?>? genre,
|
Value<String?>? genre,
|
||||||
Value<DateTime>? updated,
|
|
||||||
Value<int>? rowid,
|
Value<int>? rowid,
|
||||||
}) {
|
}) {
|
||||||
return SongsCompanion(
|
return SongsCompanion(
|
||||||
@ -2367,7 +2310,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
disc: disc ?? this.disc,
|
disc: disc ?? this.disc,
|
||||||
starred: starred ?? this.starred,
|
starred: starred ?? this.starred,
|
||||||
genre: genre ?? this.genre,
|
genre: genre ?? this.genre,
|
||||||
updated: updated ?? this.updated,
|
|
||||||
rowid: rowid ?? this.rowid,
|
rowid: rowid ?? this.rowid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2413,9 +2355,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
if (genre.present) {
|
if (genre.present) {
|
||||||
map['genre'] = Variable<String>(genre.value);
|
map['genre'] = Variable<String>(genre.value);
|
||||||
}
|
}
|
||||||
if (updated.present) {
|
|
||||||
map['updated'] = Variable<DateTime>(updated.value);
|
|
||||||
}
|
|
||||||
if (rowid.present) {
|
if (rowid.present) {
|
||||||
map['rowid'] = Variable<int>(rowid.value);
|
map['rowid'] = Variable<int>(rowid.value);
|
||||||
}
|
}
|
||||||
@ -2437,7 +2376,6 @@ class SongsCompanion extends UpdateCompanion<models.Song> {
|
|||||||
..write('disc: $disc, ')
|
..write('disc: $disc, ')
|
||||||
..write('starred: $starred, ')
|
..write('starred: $starred, ')
|
||||||
..write('genre: $genre, ')
|
..write('genre: $genre, ')
|
||||||
..write('updated: $updated, ')
|
|
||||||
..write('rowid: $rowid')
|
..write('rowid: $rowid')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
@ -3448,9 +3386,8 @@ typedef $PlaylistsCreateCompanionBuilder =
|
|||||||
required String name,
|
required String name,
|
||||||
Value<String?> comment,
|
Value<String?> comment,
|
||||||
Value<String?> coverArt,
|
Value<String?> coverArt,
|
||||||
required int songCount,
|
|
||||||
required DateTime created,
|
required DateTime created,
|
||||||
Value<DateTime> changed,
|
required DateTime changed,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $PlaylistsUpdateCompanionBuilder =
|
typedef $PlaylistsUpdateCompanionBuilder =
|
||||||
@ -3460,7 +3397,6 @@ typedef $PlaylistsUpdateCompanionBuilder =
|
|||||||
Value<String> name,
|
Value<String> name,
|
||||||
Value<String?> comment,
|
Value<String?> comment,
|
||||||
Value<String?> coverArt,
|
Value<String?> coverArt,
|
||||||
Value<int> songCount,
|
|
||||||
Value<DateTime> created,
|
Value<DateTime> created,
|
||||||
Value<DateTime> changed,
|
Value<DateTime> changed,
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
@ -3500,11 +3436,6 @@ class $PlaylistsFilterComposer
|
|||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnFilters<int> get songCount => $composableBuilder(
|
|
||||||
column: $table.songCount,
|
|
||||||
builder: (column) => ColumnFilters(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
ColumnFilters<DateTime> get created => $composableBuilder(
|
ColumnFilters<DateTime> get created => $composableBuilder(
|
||||||
column: $table.created,
|
column: $table.created,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
@ -3550,11 +3481,6 @@ class $PlaylistsOrderingComposer
|
|||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnOrderings<int> get songCount => $composableBuilder(
|
|
||||||
column: $table.songCount,
|
|
||||||
builder: (column) => ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get created => $composableBuilder(
|
ColumnOrderings<DateTime> get created => $composableBuilder(
|
||||||
column: $table.created,
|
column: $table.created,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
@ -3590,9 +3516,6 @@ class $PlaylistsAnnotationComposer
|
|||||||
GeneratedColumn<String> get coverArt =>
|
GeneratedColumn<String> get coverArt =>
|
||||||
$composableBuilder(column: $table.coverArt, builder: (column) => column);
|
$composableBuilder(column: $table.coverArt, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<int> get songCount =>
|
|
||||||
$composableBuilder(column: $table.songCount, builder: (column) => column);
|
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get created =>
|
GeneratedColumn<DateTime> get created =>
|
||||||
$composableBuilder(column: $table.created, builder: (column) => column);
|
$composableBuilder(column: $table.created, builder: (column) => column);
|
||||||
|
|
||||||
@ -3636,7 +3559,6 @@ class $PlaylistsTableManager
|
|||||||
Value<String> name = const Value.absent(),
|
Value<String> name = const Value.absent(),
|
||||||
Value<String?> comment = const Value.absent(),
|
Value<String?> comment = const Value.absent(),
|
||||||
Value<String?> coverArt = const Value.absent(),
|
Value<String?> coverArt = const Value.absent(),
|
||||||
Value<int> songCount = const Value.absent(),
|
|
||||||
Value<DateTime> created = const Value.absent(),
|
Value<DateTime> created = const Value.absent(),
|
||||||
Value<DateTime> changed = const Value.absent(),
|
Value<DateTime> changed = const Value.absent(),
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
@ -3646,7 +3568,6 @@ class $PlaylistsTableManager
|
|||||||
name: name,
|
name: name,
|
||||||
comment: comment,
|
comment: comment,
|
||||||
coverArt: coverArt,
|
coverArt: coverArt,
|
||||||
songCount: songCount,
|
|
||||||
created: created,
|
created: created,
|
||||||
changed: changed,
|
changed: changed,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
@ -3658,9 +3579,8 @@ class $PlaylistsTableManager
|
|||||||
required String name,
|
required String name,
|
||||||
Value<String?> comment = const Value.absent(),
|
Value<String?> comment = const Value.absent(),
|
||||||
Value<String?> coverArt = const Value.absent(),
|
Value<String?> coverArt = const Value.absent(),
|
||||||
required int songCount,
|
|
||||||
required DateTime created,
|
required DateTime created,
|
||||||
Value<DateTime> changed = const Value.absent(),
|
required DateTime changed,
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => PlaylistsCompanion.insert(
|
}) => PlaylistsCompanion.insert(
|
||||||
sourceId: sourceId,
|
sourceId: sourceId,
|
||||||
@ -3668,7 +3588,6 @@ class $PlaylistsTableManager
|
|||||||
name: name,
|
name: name,
|
||||||
comment: comment,
|
comment: comment,
|
||||||
coverArt: coverArt,
|
coverArt: coverArt,
|
||||||
songCount: songCount,
|
|
||||||
created: created,
|
created: created,
|
||||||
changed: changed,
|
changed: changed,
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
@ -3899,7 +3818,6 @@ typedef $SongsCreateCompanionBuilder =
|
|||||||
Value<int?> disc,
|
Value<int?> disc,
|
||||||
Value<DateTime?> starred,
|
Value<DateTime?> starred,
|
||||||
Value<String?> genre,
|
Value<String?> genre,
|
||||||
Value<DateTime> updated,
|
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
typedef $SongsUpdateCompanionBuilder =
|
typedef $SongsUpdateCompanionBuilder =
|
||||||
@ -3916,7 +3834,6 @@ typedef $SongsUpdateCompanionBuilder =
|
|||||||
Value<int?> disc,
|
Value<int?> disc,
|
||||||
Value<DateTime?> starred,
|
Value<DateTime?> starred,
|
||||||
Value<String?> genre,
|
Value<String?> genre,
|
||||||
Value<DateTime> updated,
|
|
||||||
Value<int> rowid,
|
Value<int> rowid,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -3988,11 +3905,6 @@ class $SongsFilterComposer extends Composer<_$SubtracksDatabase, Songs> {
|
|||||||
column: $table.genre,
|
column: $table.genre,
|
||||||
builder: (column) => ColumnFilters(column),
|
builder: (column) => ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnFilters<DateTime> get updated => $composableBuilder(
|
|
||||||
column: $table.updated,
|
|
||||||
builder: (column) => ColumnFilters(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $SongsOrderingComposer extends Composer<_$SubtracksDatabase, Songs> {
|
class $SongsOrderingComposer extends Composer<_$SubtracksDatabase, Songs> {
|
||||||
@ -4062,11 +3974,6 @@ class $SongsOrderingComposer extends Composer<_$SubtracksDatabase, Songs> {
|
|||||||
column: $table.genre,
|
column: $table.genre,
|
||||||
builder: (column) => ColumnOrderings(column),
|
builder: (column) => ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
ColumnOrderings<DateTime> get updated => $composableBuilder(
|
|
||||||
column: $table.updated,
|
|
||||||
builder: (column) => ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $SongsAnnotationComposer extends Composer<_$SubtracksDatabase, Songs> {
|
class $SongsAnnotationComposer extends Composer<_$SubtracksDatabase, Songs> {
|
||||||
@ -4112,9 +4019,6 @@ class $SongsAnnotationComposer extends Composer<_$SubtracksDatabase, Songs> {
|
|||||||
|
|
||||||
GeneratedColumn<String> get genre =>
|
GeneratedColumn<String> get genre =>
|
||||||
$composableBuilder(column: $table.genre, builder: (column) => column);
|
$composableBuilder(column: $table.genre, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumn<DateTime> get updated =>
|
|
||||||
$composableBuilder(column: $table.updated, builder: (column) => column);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $SongsTableManager
|
class $SongsTableManager
|
||||||
@ -4160,7 +4064,6 @@ class $SongsTableManager
|
|||||||
Value<int?> disc = const Value.absent(),
|
Value<int?> disc = const Value.absent(),
|
||||||
Value<DateTime?> starred = const Value.absent(),
|
Value<DateTime?> starred = const Value.absent(),
|
||||||
Value<String?> genre = const Value.absent(),
|
Value<String?> genre = const Value.absent(),
|
||||||
Value<DateTime> updated = const Value.absent(),
|
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => SongsCompanion(
|
}) => SongsCompanion(
|
||||||
sourceId: sourceId,
|
sourceId: sourceId,
|
||||||
@ -4175,7 +4078,6 @@ class $SongsTableManager
|
|||||||
disc: disc,
|
disc: disc,
|
||||||
starred: starred,
|
starred: starred,
|
||||||
genre: genre,
|
genre: genre,
|
||||||
updated: updated,
|
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
@ -4192,7 +4094,6 @@ class $SongsTableManager
|
|||||||
Value<int?> disc = const Value.absent(),
|
Value<int?> disc = const Value.absent(),
|
||||||
Value<DateTime?> starred = const Value.absent(),
|
Value<DateTime?> starred = const Value.absent(),
|
||||||
Value<String?> genre = const Value.absent(),
|
Value<String?> genre = const Value.absent(),
|
||||||
Value<DateTime> updated = const Value.absent(),
|
|
||||||
Value<int> rowid = const Value.absent(),
|
Value<int> rowid = const Value.absent(),
|
||||||
}) => SongsCompanion.insert(
|
}) => SongsCompanion.insert(
|
||||||
sourceId: sourceId,
|
sourceId: sourceId,
|
||||||
@ -4207,7 +4108,6 @@ class $SongsTableManager
|
|||||||
disc: disc,
|
disc: disc,
|
||||||
starred: starred,
|
starred: starred,
|
||||||
genre: genre,
|
genre: genre,
|
||||||
updated: updated,
|
|
||||||
rowid: rowid,
|
rowid: rowid,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
|
|||||||
@ -133,9 +133,8 @@ CREATE TABLE playlists(
|
|||||||
name TEXT NOT NULL COLLATE NOCASE,
|
name TEXT NOT NULL COLLATE NOCASE,
|
||||||
comment TEXT COLLATE NOCASE,
|
comment TEXT COLLATE NOCASE,
|
||||||
cover_art TEXT,
|
cover_art TEXT,
|
||||||
song_count INT NOT NULL,
|
|
||||||
created DATETIME NOT NULL,
|
created DATETIME NOT NULL,
|
||||||
changed DATETIME NOT NULL DEFAULT (strftime('%s', CURRENT_TIMESTAMP)),
|
changed DATETIME NOT NULL,
|
||||||
PRIMARY KEY (source_id, id),
|
PRIMARY KEY (source_id, id),
|
||||||
FOREIGN KEY (source_id) REFERENCES sources (id) ON DELETE CASCADE
|
FOREIGN KEY (source_id) REFERENCES sources (id) ON DELETE CASCADE
|
||||||
) WITH Playlist;
|
) WITH Playlist;
|
||||||
@ -184,7 +183,6 @@ CREATE TABLE songs(
|
|||||||
disc INT,
|
disc INT,
|
||||||
starred DATETIME,
|
starred DATETIME,
|
||||||
genre TEXT,
|
genre TEXT,
|
||||||
updated DATETIME NOT NULL DEFAULT (strftime('%s', CURRENT_TIMESTAMP)),
|
|
||||||
PRIMARY KEY (source_id, id),
|
PRIMARY KEY (source_id, id),
|
||||||
FOREIGN KEY (source_id) REFERENCES sources (id) ON DELETE CASCADE
|
FOREIGN KEY (source_id) REFERENCES sources (id) ON DELETE CASCADE
|
||||||
) WITH Song;
|
) WITH Song;
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import 'package:drift/drift.dart';
|
|||||||
import '../database/database.dart';
|
import '../database/database.dart';
|
||||||
import '../sources/music_source.dart';
|
import '../sources/music_source.dart';
|
||||||
|
|
||||||
|
const kSliceSize = 200;
|
||||||
|
|
||||||
class SyncService {
|
class SyncService {
|
||||||
SyncService({
|
SyncService({
|
||||||
required this.source,
|
required this.source,
|
||||||
@ -18,28 +20,121 @@ class SyncService {
|
|||||||
|
|
||||||
Future<void> sync() async {
|
Future<void> sync() async {
|
||||||
await db.transaction(() async {
|
await db.transaction(() async {
|
||||||
await syncArtists();
|
await Future.wait([
|
||||||
|
syncArtists(),
|
||||||
|
syncAlbums(),
|
||||||
|
syncSongs(),
|
||||||
|
syncPlaylists(),
|
||||||
|
syncPlaylistSongs(),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncArtists() async {
|
Future<void> syncArtists() async {
|
||||||
final sourceArtistIds = <String>{};
|
final sourceIds = <String>{};
|
||||||
|
|
||||||
await for (final artists in source.allArtists().slices(200)) {
|
await for (final slice in source.allArtists().slices(kSliceSize)) {
|
||||||
sourceArtistIds.addAll(artists.map((e) => e.id));
|
sourceIds.addAll(slice.map((e) => e.id));
|
||||||
|
|
||||||
await db.batch((batch) async {
|
await db.batch((batch) async {
|
||||||
batch.insertAllOnConflictUpdate(
|
batch.insertAllOnConflictUpdate(
|
||||||
db.artists,
|
db.artists,
|
||||||
artists.map((artist) => artist.toDb(sourceId)),
|
slice.map((artist) => artist.toDb(sourceId)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var slice in sourceArtistIds.slices(kSqliteMaxVariableNumber - 1)) {
|
for (var slice in sourceIds.slices(kSqliteMaxVariableNumber - 1)) {
|
||||||
await db.artists.deleteWhere(
|
await db.artists.deleteWhere(
|
||||||
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(slice),
|
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(slice),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> syncAlbums() async {
|
||||||
|
final sourceIds = <String>{};
|
||||||
|
|
||||||
|
await for (final slice in source.allAlbums().slices(kSliceSize)) {
|
||||||
|
sourceIds.addAll(slice.map((e) => e.id));
|
||||||
|
|
||||||
|
await db.batch((batch) async {
|
||||||
|
batch.insertAllOnConflictUpdate(
|
||||||
|
db.albums,
|
||||||
|
slice.map((e) => e.toDb(sourceId)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var slice in sourceIds.slices(kSqliteMaxVariableNumber - 1)) {
|
||||||
|
await db.albums.deleteWhere(
|
||||||
|
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(slice),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncSongs() async {
|
||||||
|
final sourceIds = <String>{};
|
||||||
|
|
||||||
|
await for (final slice in source.allSongs().slices(kSliceSize)) {
|
||||||
|
sourceIds.addAll(slice.map((e) => e.id));
|
||||||
|
|
||||||
|
await db.batch((batch) async {
|
||||||
|
batch.insertAllOnConflictUpdate(
|
||||||
|
db.songs,
|
||||||
|
slice.map((e) => e.toDb(sourceId)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var slice in sourceIds.slices(kSqliteMaxVariableNumber - 1)) {
|
||||||
|
await db.songs.deleteWhere(
|
||||||
|
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(slice),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncPlaylists() async {
|
||||||
|
final sourceIds = <String>{};
|
||||||
|
|
||||||
|
await for (final slice in source.allPlaylists().slices(kSliceSize)) {
|
||||||
|
sourceIds.addAll(slice.map((e) => e.id));
|
||||||
|
|
||||||
|
await db.batch((batch) async {
|
||||||
|
batch.insertAllOnConflictUpdate(
|
||||||
|
db.playlists,
|
||||||
|
slice.map((e) => e.toDb(sourceId)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var slice in sourceIds.slices(kSqliteMaxVariableNumber - 1)) {
|
||||||
|
await db.playlists.deleteWhere(
|
||||||
|
(tbl) => tbl.sourceId.equals(sourceId) & tbl.id.isNotIn(slice),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncPlaylistSongs() async {
|
||||||
|
final sourceIds = <(String, String)>{};
|
||||||
|
|
||||||
|
await for (final slice in source.allPlaylistSongs().slices(kSliceSize)) {
|
||||||
|
sourceIds.addAll(slice.map((e) => (e.playlistId, e.songId)));
|
||||||
|
|
||||||
|
await db.batch((batch) async {
|
||||||
|
batch.insertAllOnConflictUpdate(
|
||||||
|
db.playlistSongs,
|
||||||
|
slice.map((e) => e.toDb(sourceId)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var slice in sourceIds.slices((kSqliteMaxVariableNumber ~/ 2) - 1)) {
|
||||||
|
await db.playlistSongs.deleteWhere(
|
||||||
|
(tbl) =>
|
||||||
|
tbl.sourceId.equals(sourceId) &
|
||||||
|
tbl.playlistId.isNotIn(slice.map((e) => e.$1)) &
|
||||||
|
tbl.songId.isNotIn(slice.map((e) => e.$2)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
7
mise-tasks/servers-download-music.sh
Executable file
7
mise-tasks/servers-download-music.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
rm -rf ./music
|
||||||
|
|
||||||
|
docker compose build
|
||||||
|
docker compose run --rm library-manager music-download.ts
|
||||||
@ -5,8 +5,6 @@ docker compose build
|
|||||||
docker compose down
|
docker compose down
|
||||||
docker volume rm $(docker compose volumes -q) || true
|
docker volume rm $(docker compose volumes -q) || true
|
||||||
|
|
||||||
docker compose run --rm library-manager music-download.ts
|
|
||||||
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
echo "waiting for library scans..."
|
echo "waiting for library scans..."
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|||||||
@ -76,4 +76,215 @@ void main() {
|
|||||||
isNotNull,
|
isNotNull,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('syncAlbums', () async {
|
||||||
|
await db
|
||||||
|
.into(db.albums)
|
||||||
|
.insert(
|
||||||
|
AlbumsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
id: 'shouldBeDeleted',
|
||||||
|
name: 'shouldBeDeleted',
|
||||||
|
created: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await db
|
||||||
|
.into(db.albums)
|
||||||
|
.insert(
|
||||||
|
AlbumsCompanion.insert(
|
||||||
|
sourceId: sourceIdOther,
|
||||||
|
id: 'shouldBeKept',
|
||||||
|
name: 'shouldBeKept',
|
||||||
|
created: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await sync.syncAlbums();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await db.managers.albums
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(3),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.albums
|
||||||
|
.filter((f) => f.id.equals('shouldBeDeleted'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNull,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.albums
|
||||||
|
.filter((f) => f.id.equals('shouldBeKept'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('syncSongs', () async {
|
||||||
|
await db
|
||||||
|
.into(db.songs)
|
||||||
|
.insert(
|
||||||
|
SongsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
id: 'shouldBeDeleted',
|
||||||
|
title: 'shouldBeDeleted',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await db
|
||||||
|
.into(db.songs)
|
||||||
|
.insert(
|
||||||
|
SongsCompanion.insert(
|
||||||
|
sourceId: sourceIdOther,
|
||||||
|
id: 'shouldBeKept',
|
||||||
|
title: 'shouldBeKept',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await sync.syncSongs();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await db.managers.songs
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(20),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.songs
|
||||||
|
.filter((f) => f.id.equals('shouldBeDeleted'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNull,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.songs
|
||||||
|
.filter((f) => f.id.equals('shouldBeKept'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('syncPlaylists', () async {
|
||||||
|
await db
|
||||||
|
.into(db.playlists)
|
||||||
|
.insert(
|
||||||
|
PlaylistsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
id: 'shouldBeDeleted',
|
||||||
|
name: 'shouldBeDeleted',
|
||||||
|
created: DateTime.now(),
|
||||||
|
changed: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await db
|
||||||
|
.into(db.playlists)
|
||||||
|
.insert(
|
||||||
|
PlaylistsCompanion.insert(
|
||||||
|
sourceId: sourceIdOther,
|
||||||
|
id: 'shouldBeKept',
|
||||||
|
name: 'shouldBeKept',
|
||||||
|
created: DateTime.now(),
|
||||||
|
changed: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await sync.syncPlaylists();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await db.managers.playlists
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(1),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.playlists
|
||||||
|
.filter((f) => f.id.equals('shouldBeDeleted'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNull,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.playlists
|
||||||
|
.filter((f) => f.id.equals('shouldBeKept'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('syncPlaylistSongs', () async {
|
||||||
|
await db
|
||||||
|
.into(db.playlistSongs)
|
||||||
|
.insert(
|
||||||
|
PlaylistSongsCompanion.insert(
|
||||||
|
sourceId: sourceId,
|
||||||
|
playlistId: 'shouldBeDeleted',
|
||||||
|
songId: 'shouldBeDeleted',
|
||||||
|
position: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await db
|
||||||
|
.into(db.playlistSongs)
|
||||||
|
.insert(
|
||||||
|
PlaylistSongsCompanion.insert(
|
||||||
|
sourceId: sourceIdOther,
|
||||||
|
playlistId: 'shouldBeKept',
|
||||||
|
songId: 'shouldBeKept',
|
||||||
|
position: 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await sync.syncPlaylistSongs();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await db.managers.playlistSongs
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(7),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.playlistSongs
|
||||||
|
.filter((f) => f.playlistId.equals('shouldBeDeleted'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNull,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.playlistSongs
|
||||||
|
.filter((f) => f.playlistId.equals('shouldBeKept'))
|
||||||
|
.getSingleOrNull(),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('syncPlaylistSongs', () async {
|
||||||
|
await sync.sync();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await db.managers.artists
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(2),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.albums
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(3),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.songs
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(20),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.playlists
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(1),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await db.managers.playlistSongs
|
||||||
|
.filter((f) => f.sourceId.equals(sourceId))
|
||||||
|
.count(),
|
||||||
|
equals(7),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,13 +64,13 @@ void main() {
|
|||||||
test('allPlaylists', () async {
|
test('allPlaylists', () async {
|
||||||
final items = await source.allPlaylists().toList();
|
final items = await source.allPlaylists().toList();
|
||||||
|
|
||||||
expect(items.length, equals(0));
|
expect(items.length, equals(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('allPlaylistSongs', () async {
|
test('allPlaylistSongs', () async {
|
||||||
final items = await source.allPlaylistSongs().toList();
|
final items = await source.allPlaylistSongs().toList();
|
||||||
|
|
||||||
expect(items.length, equals(0));
|
expect(items.length, equals(7));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('album-artist relation', () async {
|
test('album-artist relation', () async {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user