@@ -62,6 +62,7 @@ final class AppDatabaseBootstrap {
|
||||
ApiCache,
|
||||
OfflineWatchProgress,
|
||||
SyncRules,
|
||||
SyncRuleDownloads,
|
||||
Connections,
|
||||
Profiles,
|
||||
ProfileConnections,
|
||||
@@ -495,7 +496,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 19;
|
||||
int get schemaVersion => 20;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@@ -911,6 +912,18 @@ class AppDatabase extends _$AppDatabase {
|
||||
WHERE backend IS NULL
|
||||
''');
|
||||
}
|
||||
if (from < 20) {
|
||||
appLogger.i('Adding sync rule download associations (v20 migration)');
|
||||
await _ignoreAlreadyExists(
|
||||
'SyncRules.downloadLinksInitialized column',
|
||||
() => m.addColumn(syncRules, syncRules.downloadLinksInitialized),
|
||||
);
|
||||
await _ignoreAlreadyExists('SyncRuleDownloads table', () => m.createTable(syncRuleDownloads));
|
||||
await _ignoreAlreadyExists(
|
||||
'Index idx_sync_rule_downloads_profile_key',
|
||||
() => m.create(idxSyncRuleDownloadsProfileKey),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1230,6 +1243,81 @@ class AppDatabase extends _$AppDatabase {
|
||||
return (select(syncRules)..where((t) => t.globalKey.equals(globalKey))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> associateSyncRuleDownload(SyncRuleItem rule, String downloadGlobalKey) {
|
||||
return into(syncRuleDownloads).insertOnConflictUpdate(
|
||||
SyncRuleDownloadsCompanion.insert(
|
||||
syncRuleId: rule.id,
|
||||
profileId: rule.profileId,
|
||||
downloadGlobalKey: downloadGlobalKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> markSyncRuleDownloadLinksInitialized(String globalKey) {
|
||||
return (update(syncRules)..where((t) => t.globalKey.equals(globalKey))).write(
|
||||
const SyncRulesCompanion(downloadLinksInitialized: Value(true)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<SyncRuleItem>> getUninitializedSyncRulesForServer({
|
||||
required String profileId,
|
||||
required ServerId serverId,
|
||||
}) {
|
||||
return (select(syncRules)..where(
|
||||
(t) => t.profileId.equals(profileId) & t.serverId.equals(serverId) & t.downloadLinksInitialized.equals(false),
|
||||
))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<List<SyncRuleDownloadItem>> getSyncRuleDownloadLinks(int syncRuleId) {
|
||||
return (select(syncRuleDownloads)..where((t) => t.syncRuleId.equals(syncRuleId))).get();
|
||||
}
|
||||
|
||||
Future<List<String>> getOwnedDownloadKeysForAncestorRule({
|
||||
required String profileId,
|
||||
required ServerId serverId,
|
||||
required String ratingKey,
|
||||
required bool matchGrandparent,
|
||||
}) async {
|
||||
final query = select(
|
||||
downloadedMedia,
|
||||
).join([innerJoin(downloadOwners, downloadOwners.globalKey.equalsExp(downloadedMedia.globalKey))]);
|
||||
final ancestorMatches = matchGrandparent
|
||||
? downloadedMedia.grandparentRatingKey.equals(ratingKey) | downloadedMedia.parentRatingKey.equals(ratingKey)
|
||||
: downloadedMedia.parentRatingKey.equals(ratingKey);
|
||||
query.where(
|
||||
downloadOwners.profileId.equals(profileId) &
|
||||
downloadedMedia.serverId.equals(serverId) &
|
||||
downloadedMedia.status.isIn([
|
||||
DownloadStatus.queued.index,
|
||||
DownloadStatus.downloading.index,
|
||||
DownloadStatus.completed.index,
|
||||
DownloadStatus.paused.index,
|
||||
]) &
|
||||
ancestorMatches,
|
||||
);
|
||||
final rows = await query.get();
|
||||
return rows.map((row) => row.readTable(downloadedMedia).globalKey).toList(growable: false);
|
||||
}
|
||||
|
||||
Future<List<String>> getExclusiveSyncRuleDownloadKeys(SyncRuleItem rule) async {
|
||||
final links = await getSyncRuleDownloadLinks(rule.id);
|
||||
if (links.isEmpty) return const [];
|
||||
|
||||
final keys = links.map((link) => link.downloadGlobalKey).toSet();
|
||||
final allLinks = await (select(
|
||||
syncRuleDownloads,
|
||||
)..where((t) => t.profileId.equals(rule.profileId) & t.downloadGlobalKey.isIn(keys))).get();
|
||||
final linkedRuleCounts = <String, int>{};
|
||||
for (final link in allLinks) {
|
||||
linkedRuleCounts.update(link.downloadGlobalKey, (count) => count + 1, ifAbsent: () => 1);
|
||||
}
|
||||
return [
|
||||
for (final key in keys)
|
||||
if (linkedRuleCounts[key] == 1) key,
|
||||
];
|
||||
}
|
||||
|
||||
Future<void> insertSyncRule({
|
||||
String profileId = '',
|
||||
required ServerId serverId,
|
||||
@@ -1290,6 +1378,9 @@ class AppDatabase extends _$AppDatabase {
|
||||
await (update(syncRules)..where((t) => t.id.equals(rule.id))).write(
|
||||
SyncRulesCompanion(profileId: Value(profileId), globalKey: Value(scopedKey)),
|
||||
);
|
||||
await (update(
|
||||
syncRuleDownloads,
|
||||
)..where((t) => t.syncRuleId.equals(rule.id))).write(SyncRuleDownloadsCompanion(profileId: Value(profileId)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1317,6 +1408,15 @@ class AppDatabase extends _$AppDatabase {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> completeSyncRuleExecution(String globalKey) {
|
||||
return (update(syncRules)..where((t) => t.globalKey.equals(globalKey))).write(
|
||||
SyncRulesCompanion(
|
||||
lastExecutedAt: Value(DateTime.now().millisecondsSinceEpoch),
|
||||
downloadLinksInitialized: const Value(true),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteSyncRule(String globalKey) async {
|
||||
await (delete(syncRules)..where((t) => t.globalKey.equals(globalKey))).go();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -51,8 +51,13 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> removeDownloadOwner({required String profileId, required String globalKey}) async {
|
||||
await (delete(downloadOwners)..where((t) => t.profileId.equals(profileId) & t.globalKey.equals(globalKey))).go();
|
||||
Future<void> removeDownloadOwner({required String profileId, required String globalKey}) {
|
||||
return transaction(() async {
|
||||
await (delete(
|
||||
syncRuleDownloads,
|
||||
)..where((t) => t.profileId.equals(profileId) & t.downloadGlobalKey.equals(globalKey))).go();
|
||||
await (delete(downloadOwners)..where((t) => t.profileId.equals(profileId) & t.globalKey.equals(globalKey))).go();
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes one owner from a shared download while keeping an incomplete
|
||||
@@ -102,8 +107,12 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
)..where((t) => t.profileId.equals(profileId) & t.globalKey.equals(globalKey))).getSingleOrNull();
|
||||
}
|
||||
|
||||
Future<void> clearAllDownloadOwners() async {
|
||||
await delete(downloadOwners).go();
|
||||
Future<void> clearAllDownloadOwners() {
|
||||
return transaction(() async {
|
||||
await delete(syncRuleDownloads).go();
|
||||
await update(syncRules).write(const SyncRulesCompanion(downloadLinksInitialized: Value(false)));
|
||||
await delete(downloadOwners).go();
|
||||
});
|
||||
}
|
||||
|
||||
Future<Set<String>> getDownloadOwnerKeysForProfile(String profileId) async {
|
||||
@@ -620,6 +629,7 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
late String? safRootUri;
|
||||
await transaction(() async {
|
||||
safRootUri = (await getDownloadedMedia(globalKey))?.safRootUri;
|
||||
await (delete(syncRuleDownloads)..where((t) => t.downloadGlobalKey.equals(globalKey))).go();
|
||||
await (delete(downloadOwners)..where((t) => t.globalKey.equals(globalKey))).go();
|
||||
await (delete(downloadedMedia)..where((t) => t.globalKey.equals(globalKey))).go();
|
||||
await (delete(downloadQueue)..where((t) => t.mediaGlobalKey.equals(globalKey))).go();
|
||||
|
||||
@@ -105,6 +105,27 @@ class SyncRules extends Table {
|
||||
IntColumn get mediaIndex => integer().withDefault(const Constant(0))();
|
||||
TextColumn get downloadFilter => text().withDefault(const Constant('unwatched'))();
|
||||
BoolColumn get includeSpecials => boolean().withDefault(const Constant(true))();
|
||||
|
||||
/// Whether every currently-owned candidate has been associated in
|
||||
/// [SyncRuleDownloads]. Existing rules start false and are backfilled before
|
||||
/// destructive cleanup.
|
||||
BoolColumn get downloadLinksInitialized => boolean().withDefault(const Constant(false))();
|
||||
}
|
||||
|
||||
/// Downloads covered by a sync rule for one profile.
|
||||
///
|
||||
/// Links are retained when list membership changes so removing a rule can
|
||||
/// clean up items it previously synced without re-fetching the list. A
|
||||
/// download may be linked to multiple rules.
|
||||
@DataClassName('SyncRuleDownloadItem')
|
||||
@TableIndex(name: 'idx_sync_rule_downloads_profile_key', columns: {#profileId, #downloadGlobalKey})
|
||||
class SyncRuleDownloads extends Table {
|
||||
IntColumn get syncRuleId => integer().references(SyncRules, #id, onDelete: KeyAction.cascade)();
|
||||
TextColumn get profileId => text()();
|
||||
TextColumn get downloadGlobalKey => text()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {syncRuleId, downloadGlobalKey};
|
||||
}
|
||||
|
||||
/// Persisted media-server connections.
|
||||
|
||||
Reference in New Issue
Block a user