2023-02-20 15:21:35 +01:00
|
|
|
part of 'database.dart';
|
2022-07-05 22:20:24 +02:00
|
|
|
|
2022-08-06 19:00:38 +02:00
|
|
|
const maxByFileIdsSize = 30000;
|
|
|
|
|
2022-07-05 22:20:24 +02:00
|
|
|
class CompleteFile {
|
2022-08-27 17:31:58 +02:00
|
|
|
const CompleteFile(
|
|
|
|
this.file, this.accountFile, this.image, this.imageLocation, this.trash);
|
2022-07-05 22:20:24 +02:00
|
|
|
|
|
|
|
final File file;
|
|
|
|
final AccountFile accountFile;
|
|
|
|
final Image? image;
|
2022-08-27 17:31:58 +02:00
|
|
|
final ImageLocation? imageLocation;
|
2022-07-05 22:20:24 +02:00
|
|
|
final Trash? trash;
|
|
|
|
}
|
|
|
|
|
|
|
|
class CompleteFileCompanion {
|
|
|
|
const CompleteFileCompanion(
|
2022-08-27 17:31:58 +02:00
|
|
|
this.file, this.accountFile, this.image, this.imageLocation, this.trash);
|
2022-07-05 22:20:24 +02:00
|
|
|
|
|
|
|
final FilesCompanion file;
|
|
|
|
final AccountFilesCompanion accountFile;
|
|
|
|
final ImagesCompanion? image;
|
2022-08-27 17:31:58 +02:00
|
|
|
final ImageLocationsCompanion? imageLocation;
|
2022-07-05 22:20:24 +02:00
|
|
|
final TrashesCompanion? trash;
|
|
|
|
}
|
|
|
|
|
|
|
|
extension CompleteFileListExtension on List<CompleteFile> {
|
|
|
|
Future<List<app.File>> convertToAppFile(app.Account account) {
|
|
|
|
return map((f) => {
|
2022-07-11 20:14:42 +02:00
|
|
|
"userId": account.userId.toString(),
|
2022-07-05 22:20:24 +02:00
|
|
|
"completeFile": f,
|
|
|
|
}).computeAll(_covertSqliteDbFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension FileListExtension on List<app.File> {
|
|
|
|
Future<List<CompleteFileCompanion>> convertToFileCompanion(Account? account) {
|
|
|
|
return map((f) => {
|
|
|
|
"account": account,
|
|
|
|
"file": f,
|
|
|
|
}).computeAll(_convertAppFile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-13 17:32:31 +02:00
|
|
|
class FileDescriptor {
|
|
|
|
const FileDescriptor({
|
|
|
|
required this.relativePath,
|
|
|
|
required this.fileId,
|
|
|
|
required this.contentType,
|
|
|
|
required this.isArchived,
|
|
|
|
required this.isFavorite,
|
|
|
|
required this.bestDateTime,
|
|
|
|
});
|
|
|
|
|
|
|
|
final String relativePath;
|
|
|
|
final int fileId;
|
|
|
|
final String? contentType;
|
|
|
|
final bool? isArchived;
|
|
|
|
final bool? isFavorite;
|
|
|
|
final DateTime bestDateTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
extension FileDescriptorListExtension on List<FileDescriptor> {
|
|
|
|
List<app.FileDescriptor> convertToAppFileDescriptor(app.Account account) {
|
|
|
|
return map((f) =>
|
|
|
|
SqliteFileDescriptorConverter.fromSql(account.userId.toString(), f))
|
|
|
|
.toList();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-05 22:20:24 +02:00
|
|
|
class AlbumWithShare {
|
|
|
|
const AlbumWithShare(this.album, this.share);
|
|
|
|
|
|
|
|
final Album album;
|
|
|
|
final AlbumShare? share;
|
|
|
|
}
|
|
|
|
|
|
|
|
class CompleteAlbumCompanion {
|
|
|
|
const CompleteAlbumCompanion(this.album, this.albumShares);
|
|
|
|
|
|
|
|
final AlbumsCompanion album;
|
|
|
|
final List<AlbumSharesCompanion> albumShares;
|
|
|
|
}
|
|
|
|
|
|
|
|
class AccountFileRowIds {
|
|
|
|
const AccountFileRowIds(
|
|
|
|
this.accountFileRowId, this.accountRowId, this.fileRowId);
|
|
|
|
|
|
|
|
final int accountFileRowId;
|
|
|
|
final int accountRowId;
|
|
|
|
final int fileRowId;
|
|
|
|
}
|
|
|
|
|
|
|
|
class AccountFileRowIdsWithFileId {
|
|
|
|
const AccountFileRowIdsWithFileId(
|
|
|
|
this.accountFileRowId, this.accountRowId, this.fileRowId, this.fileId);
|
|
|
|
|
|
|
|
final int accountFileRowId;
|
|
|
|
final int accountRowId;
|
|
|
|
final int fileRowId;
|
|
|
|
final int fileId;
|
|
|
|
}
|
|
|
|
|
2023-04-13 17:32:31 +02:00
|
|
|
class ByAccount {
|
|
|
|
const ByAccount.sql(Account account) : this._(sqlAccount: account);
|
|
|
|
|
|
|
|
const ByAccount.app(app.Account account) : this._(appAccount: account);
|
|
|
|
|
|
|
|
const ByAccount._({
|
|
|
|
this.sqlAccount,
|
|
|
|
this.appAccount,
|
|
|
|
}) : assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
|
|
|
|
final Account? sqlAccount;
|
|
|
|
final app.Account? appAccount;
|
|
|
|
}
|
|
|
|
|
2022-07-05 22:20:24 +02:00
|
|
|
extension SqliteDbExtension on SqliteDb {
|
|
|
|
/// Start a transaction and run [block]
|
|
|
|
///
|
|
|
|
/// The [db] argument passed to [block] is identical to this
|
|
|
|
///
|
|
|
|
/// Do NOT call this when using [isolate], call [useInIsolate] instead
|
|
|
|
Future<T> use<T>(Future<T> Function(SqliteDb db) block) async {
|
|
|
|
return await platform.Lock.synchronized(k.appDbLockId, () async {
|
|
|
|
return await transaction(() async {
|
|
|
|
return await block(this);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-08-12 21:09:42 +02:00
|
|
|
/// Run [block] after acquiring the database
|
|
|
|
///
|
|
|
|
/// The [db] argument passed to [block] is identical to this
|
|
|
|
///
|
|
|
|
/// This function does not start a transaction, see [use] instead
|
|
|
|
Future<T> useNoTransaction<T>(Future<T> Function(SqliteDb db) block) async {
|
|
|
|
return await platform.Lock.synchronized(k.appDbLockId, () async {
|
|
|
|
return await block(this);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-07-05 22:20:24 +02:00
|
|
|
/// Start an isolate and run [callback] there, with access to the
|
|
|
|
/// SQLite database
|
|
|
|
Future<U> isolate<T, U>(T args, ComputeWithDbCallback<T, U> callback) async {
|
|
|
|
// we need to acquire the lock here as method channel is not supported in
|
|
|
|
// background isolates
|
|
|
|
return await platform.Lock.synchronized(k.appDbLockId, () async {
|
|
|
|
// in unit tests we use an in-memory db, which mean there's no way to
|
|
|
|
// access it in other isolates
|
|
|
|
if (platform_k.isUnitTest) {
|
|
|
|
return await callback(this, args);
|
|
|
|
} else {
|
|
|
|
return await computeWithDb(callback, args);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Start a transaction and run [block], this version is suitable to be called
|
|
|
|
/// in [isolate]
|
|
|
|
///
|
|
|
|
/// See: [use]
|
|
|
|
Future<T> useInIsolate<T>(Future<T> Function(SqliteDb db) block) async {
|
|
|
|
return await transaction(() async {
|
|
|
|
return await block(this);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> insertAccountOf(app.Account account) async {
|
|
|
|
Server dbServer;
|
|
|
|
try {
|
|
|
|
dbServer = await into(servers).insertReturning(
|
|
|
|
ServersCompanion.insert(
|
|
|
|
address: account.url,
|
|
|
|
),
|
|
|
|
mode: InsertMode.insertOrIgnore,
|
|
|
|
);
|
|
|
|
} on StateError catch (_) {
|
|
|
|
// already exists
|
|
|
|
final query = select(servers)
|
|
|
|
..where((t) => t.address.equals(account.url));
|
|
|
|
dbServer = await query.getSingle();
|
|
|
|
}
|
|
|
|
await into(accounts).insert(
|
|
|
|
AccountsCompanion.insert(
|
|
|
|
server: dbServer.rowId,
|
2022-07-11 20:14:42 +02:00
|
|
|
userId: account.userId.toCaseInsensitiveString(),
|
2022-07-05 22:20:24 +02:00
|
|
|
),
|
|
|
|
mode: InsertMode.insertOrIgnore,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Account> accountOf(app.Account account) {
|
|
|
|
final query = select(accounts).join([
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
|
|
useColumns: false)
|
|
|
|
])
|
|
|
|
..where(servers.address.equals(account.url))
|
2022-07-11 20:14:42 +02:00
|
|
|
..where(accounts.userId.equals(account.userId.toCaseInsensitiveString()))
|
2022-07-05 22:20:24 +02:00
|
|
|
..limit(1);
|
|
|
|
return query.map((r) => r.readTable(accounts)).getSingle();
|
|
|
|
}
|
|
|
|
|
2022-07-15 20:10:08 +02:00
|
|
|
/// Delete Account by app Account
|
|
|
|
///
|
|
|
|
/// If the deleted Account is the last one associated with a Server, then the
|
|
|
|
/// Server will also be deleted
|
|
|
|
Future<void> deleteAccountOf(app.Account account) async {
|
|
|
|
final dbAccount = await accountOf(account);
|
|
|
|
_log.info("[deleteAccountOf] Remove account: ${dbAccount.rowId}");
|
|
|
|
await (delete(accounts)..where((t) => t.rowId.equals(dbAccount.rowId)))
|
|
|
|
.go();
|
|
|
|
final accountCountExp =
|
|
|
|
accounts.rowId.count(filter: accounts.server.equals(dbAccount.server));
|
|
|
|
final accountCountQuery = selectOnly(accounts)
|
|
|
|
..addColumns([accountCountExp]);
|
|
|
|
final accountCount =
|
|
|
|
await accountCountQuery.map((r) => r.read(accountCountExp)).getSingle();
|
|
|
|
_log.info("[deleteAccountOf] Remaining accounts in server: $accountCount");
|
|
|
|
if (accountCount == 0) {
|
|
|
|
_log.info("[deleteAccountOf] Remove server: ${dbAccount.server}");
|
|
|
|
await (delete(servers)..where((t) => t.rowId.equals(dbAccount.server)))
|
|
|
|
.go();
|
|
|
|
}
|
|
|
|
await cleanUpDanglingFiles();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Delete Files without a corresponding entry in AccountFiles
|
|
|
|
Future<void> cleanUpDanglingFiles() async {
|
|
|
|
final query = selectOnly(files).join([
|
|
|
|
leftOuterJoin(accountFiles, accountFiles.file.equalsExp(files.rowId),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
|
|
|
..addColumns([files.rowId])
|
|
|
|
..where(accountFiles.relativePath.isNull());
|
|
|
|
final fileRowIds = await query.map((r) => r.read(files.rowId)!).get();
|
|
|
|
if (fileRowIds.isNotEmpty) {
|
|
|
|
_log.info("[cleanUpDanglingFiles] Delete ${fileRowIds.length} files");
|
2022-08-06 19:00:38 +02:00
|
|
|
await fileRowIds.withPartitionNoReturn((sublist) async {
|
|
|
|
await (delete(files)..where((t) => t.rowId.isIn(sublist))).go();
|
|
|
|
}, maxByFileIdsSize);
|
2022-07-15 20:10:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-05 22:20:24 +02:00
|
|
|
FilesQueryBuilder queryFiles() => FilesQueryBuilder(this);
|
|
|
|
|
|
|
|
/// Query File by app File
|
|
|
|
///
|
|
|
|
/// Only one of [sqlAccount] and [appAccount] must be passed
|
|
|
|
Future<File> fileOf(
|
|
|
|
app.File file, {
|
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
final query = queryFiles().run((q) {
|
|
|
|
q.setQueryMode(FilesQueryMode.file);
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
q.setSqlAccount(sqlAccount);
|
|
|
|
} else {
|
|
|
|
q.setAppAccount(appAccount!);
|
|
|
|
}
|
|
|
|
if (file.fileId != null) {
|
|
|
|
q.byFileId(file.fileId!);
|
|
|
|
} else {
|
|
|
|
q.byRelativePath(file.strippedPathWithEmpty);
|
|
|
|
}
|
|
|
|
return q.build()..limit(1);
|
|
|
|
});
|
|
|
|
return query.map((r) => r.readTable(files)).getSingle();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Query AccountFiles, Accounts and Files row ID by app File
|
|
|
|
///
|
|
|
|
/// Only one of [sqlAccount] and [appAccount] must be passed
|
|
|
|
Future<AccountFileRowIds?> accountFileRowIdsOfOrNull(
|
2023-04-13 17:32:31 +02:00
|
|
|
app.FileDescriptor file, {
|
2022-07-05 22:20:24 +02:00
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
final query = queryFiles().run((q) {
|
|
|
|
q.setQueryMode(FilesQueryMode.expression, expressions: [
|
|
|
|
accountFiles.rowId,
|
|
|
|
accountFiles.account,
|
|
|
|
accountFiles.file,
|
|
|
|
]);
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
q.setSqlAccount(sqlAccount);
|
|
|
|
} else {
|
|
|
|
q.setAppAccount(appAccount!);
|
|
|
|
}
|
2023-04-13 17:32:31 +02:00
|
|
|
try {
|
|
|
|
q.byFileId(file.fdId);
|
|
|
|
} catch (_) {
|
2022-07-05 22:20:24 +02:00
|
|
|
q.byRelativePath(file.strippedPathWithEmpty);
|
|
|
|
}
|
|
|
|
return q.build()..limit(1);
|
|
|
|
});
|
|
|
|
return query
|
|
|
|
.map((r) => AccountFileRowIds(
|
|
|
|
r.read(accountFiles.rowId)!,
|
|
|
|
r.read(accountFiles.account)!,
|
|
|
|
r.read(accountFiles.file)!,
|
|
|
|
))
|
|
|
|
.getSingleOrNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [accountFileRowIdsOfOrNull]
|
|
|
|
Future<AccountFileRowIds> accountFileRowIdsOf(
|
2023-04-13 17:32:31 +02:00
|
|
|
app.FileDescriptor file, {
|
2022-07-05 22:20:24 +02:00
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
}) =>
|
|
|
|
accountFileRowIdsOfOrNull(file,
|
|
|
|
sqlAccount: sqlAccount, appAccount: appAccount)
|
|
|
|
.notNull();
|
|
|
|
|
|
|
|
/// Query AccountFiles, Accounts and Files row ID by fileIds
|
|
|
|
///
|
|
|
|
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
|
|
|
Future<List<AccountFileRowIdsWithFileId>> accountFileRowIdsByFileIds(
|
2023-05-16 16:58:53 +02:00
|
|
|
ByAccount account, Iterable<int> fileIds) {
|
2022-08-06 19:00:38 +02:00
|
|
|
return fileIds.withPartition((sublist) {
|
|
|
|
final query = queryFiles().run((q) {
|
|
|
|
q.setQueryMode(FilesQueryMode.expression, expressions: [
|
|
|
|
accountFiles.rowId,
|
|
|
|
accountFiles.account,
|
|
|
|
accountFiles.file,
|
|
|
|
files.fileId,
|
|
|
|
]);
|
2023-05-16 16:58:53 +02:00
|
|
|
if (account.sqlAccount != null) {
|
|
|
|
q.setSqlAccount(account.sqlAccount!);
|
2022-08-06 19:00:38 +02:00
|
|
|
} else {
|
2023-05-16 16:58:53 +02:00
|
|
|
q.setAppAccount(account.appAccount!);
|
2022-08-06 19:00:38 +02:00
|
|
|
}
|
|
|
|
q.byFileIds(sublist);
|
|
|
|
return q.build();
|
|
|
|
});
|
|
|
|
return query
|
|
|
|
.map((r) => AccountFileRowIdsWithFileId(
|
|
|
|
r.read(accountFiles.rowId)!,
|
|
|
|
r.read(accountFiles.account)!,
|
|
|
|
r.read(accountFiles.file)!,
|
|
|
|
r.read(files.fileId)!,
|
|
|
|
))
|
|
|
|
.get();
|
|
|
|
}, maxByFileIdsSize);
|
2022-07-05 22:20:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Query CompleteFile by fileId
|
|
|
|
///
|
|
|
|
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
|
|
|
Future<List<CompleteFile>> completeFilesByFileIds(
|
|
|
|
Iterable<int> fileIds, {
|
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
2022-08-06 19:00:38 +02:00
|
|
|
return fileIds.withPartition((sublist) {
|
|
|
|
final query = queryFiles().run((q) {
|
|
|
|
q.setQueryMode(FilesQueryMode.completeFile);
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
q.setSqlAccount(sqlAccount);
|
|
|
|
} else {
|
|
|
|
q.setAppAccount(appAccount!);
|
|
|
|
}
|
|
|
|
q.byFileIds(sublist);
|
|
|
|
return q.build();
|
|
|
|
});
|
|
|
|
return query
|
|
|
|
.map((r) => CompleteFile(
|
|
|
|
r.readTable(files),
|
|
|
|
r.readTable(accountFiles),
|
|
|
|
r.readTableOrNull(images),
|
2022-08-27 17:31:58 +02:00
|
|
|
r.readTableOrNull(imageLocations),
|
2022-08-06 19:00:38 +02:00
|
|
|
r.readTableOrNull(trashes),
|
|
|
|
))
|
|
|
|
.get();
|
|
|
|
}, maxByFileIdsSize);
|
2022-07-05 22:20:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<CompleteFile>> completeFilesByDirRowId(
|
|
|
|
int dirRowId, {
|
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
final query = queryFiles().run((q) {
|
|
|
|
q.setQueryMode(FilesQueryMode.completeFile);
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
q.setSqlAccount(sqlAccount);
|
|
|
|
} else {
|
|
|
|
q.setAppAccount(appAccount!);
|
|
|
|
}
|
|
|
|
q.byDirRowId(dirRowId);
|
|
|
|
return q.build();
|
|
|
|
});
|
|
|
|
return query
|
|
|
|
.map((r) => CompleteFile(
|
|
|
|
r.readTable(files),
|
|
|
|
r.readTable(accountFiles),
|
|
|
|
r.readTableOrNull(images),
|
2022-08-27 17:31:58 +02:00
|
|
|
r.readTableOrNull(imageLocations),
|
2022-07-05 22:20:24 +02:00
|
|
|
r.readTableOrNull(trashes),
|
|
|
|
))
|
|
|
|
.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Query CompleteFile by favorite
|
|
|
|
Future<List<CompleteFile>> completeFilesByFavorite({
|
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
final query = queryFiles().run((q) {
|
|
|
|
q.setQueryMode(FilesQueryMode.completeFile);
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
q.setSqlAccount(sqlAccount);
|
|
|
|
} else {
|
|
|
|
q.setAppAccount(appAccount!);
|
|
|
|
}
|
|
|
|
q.byFavorite(true);
|
|
|
|
return q.build();
|
|
|
|
});
|
|
|
|
return query
|
|
|
|
.map((r) => CompleteFile(
|
|
|
|
r.readTable(files),
|
|
|
|
r.readTable(accountFiles),
|
|
|
|
r.readTableOrNull(images),
|
2022-08-27 17:31:58 +02:00
|
|
|
r.readTableOrNull(imageLocations),
|
2022-07-05 22:20:24 +02:00
|
|
|
r.readTableOrNull(trashes),
|
|
|
|
))
|
|
|
|
.get();
|
|
|
|
}
|
2022-07-15 20:10:08 +02:00
|
|
|
|
2023-04-13 17:32:31 +02:00
|
|
|
/// Query [FileDescriptor]s by fileId
|
|
|
|
///
|
|
|
|
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
|
|
|
Future<List<FileDescriptor>> fileDescriptorsByFileIds(
|
|
|
|
ByAccount account, Iterable<int> fileIds) {
|
|
|
|
return fileIds.withPartition((sublist) {
|
|
|
|
final query = queryFiles().run((q) {
|
|
|
|
q.setQueryMode(
|
|
|
|
FilesQueryMode.expression,
|
|
|
|
expressions: [
|
|
|
|
accountFiles.relativePath,
|
|
|
|
files.fileId,
|
|
|
|
files.contentType,
|
|
|
|
accountFiles.isArchived,
|
|
|
|
accountFiles.isFavorite,
|
|
|
|
accountFiles.bestDateTime,
|
|
|
|
],
|
|
|
|
);
|
|
|
|
if (account.sqlAccount != null) {
|
|
|
|
q.setSqlAccount(account.sqlAccount!);
|
|
|
|
} else {
|
|
|
|
q.setAppAccount(account.appAccount!);
|
|
|
|
}
|
|
|
|
q.byFileIds(sublist);
|
|
|
|
return q.build();
|
|
|
|
});
|
|
|
|
return query
|
|
|
|
.map((r) => FileDescriptor(
|
|
|
|
relativePath: r.read(accountFiles.relativePath)!,
|
|
|
|
fileId: r.read(files.fileId)!,
|
|
|
|
contentType: r.read(files.contentType),
|
|
|
|
isArchived: r.read(accountFiles.isArchived),
|
|
|
|
isFavorite: r.read(accountFiles.isFavorite),
|
|
|
|
bestDateTime: r.read(accountFiles.bestDateTime)!,
|
|
|
|
))
|
|
|
|
.get();
|
|
|
|
}, maxByFileIdsSize);
|
|
|
|
}
|
|
|
|
|
2023-05-16 16:58:53 +02:00
|
|
|
Future<void> moveFileByFileId(
|
|
|
|
ByAccount account, int fileId, String destinationRelativePath) async {
|
|
|
|
final rowId = (await accountFileRowIdsByFileIds(account, [fileId])).first;
|
|
|
|
final q = update(accountFiles)
|
|
|
|
..where((t) => t.rowId.equals(rowId.accountFileRowId));
|
|
|
|
await q.write(AccountFilesCompanion(
|
|
|
|
relativePath: Value(destinationRelativePath),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:28:28 +02:00
|
|
|
Future<List<Tag>> allTags({
|
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
final query = select(tags)
|
|
|
|
..where((t) => t.server.equals(sqlAccount.server));
|
|
|
|
return query.get();
|
|
|
|
} else {
|
|
|
|
final query = select(tags).join([
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(tags.server),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
|
|
|
..where(servers.address.equals(appAccount!.url));
|
|
|
|
return query.map((r) => r.readTable(tags)).get();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-07 19:01:50 +02:00
|
|
|
Future<Tag?> tagByDisplayName({
|
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
required String displayName,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
final query = select(tags)
|
|
|
|
..where((t) => t.server.equals(sqlAccount.server))
|
|
|
|
..where((t) => t.displayName.like(displayName))
|
|
|
|
..limit(1);
|
|
|
|
return query.getSingleOrNull();
|
|
|
|
} else {
|
|
|
|
final query = select(tags).join([
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(tags.server),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
|
|
|
..where(servers.address.equals(appAccount!.url))
|
|
|
|
..where(tags.displayName.like(displayName))
|
|
|
|
..limit(1);
|
|
|
|
return query.map((r) => r.readTable(tags)).getSingleOrNull();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-04 19:34:39 +02:00
|
|
|
Future<List<FaceRecognitionPerson>> allFaceRecognitionPersons({
|
2023-07-03 19:23:42 +02:00
|
|
|
required ByAccount account,
|
2022-08-05 11:39:49 +02:00
|
|
|
}) {
|
2023-07-03 19:23:42 +02:00
|
|
|
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
|
|
if (account.sqlAccount != null) {
|
2023-07-04 19:34:39 +02:00
|
|
|
final query = select(faceRecognitionPersons)
|
2023-07-03 19:23:42 +02:00
|
|
|
..where((t) => t.account.equals(account.sqlAccount!.rowId));
|
2022-08-05 11:39:49 +02:00
|
|
|
return query.get();
|
|
|
|
} else {
|
2023-07-04 19:34:39 +02:00
|
|
|
final query = select(faceRecognitionPersons).join([
|
|
|
|
innerJoin(
|
|
|
|
accounts, accounts.rowId.equalsExp(faceRecognitionPersons.account),
|
2022-08-05 11:39:49 +02:00
|
|
|
useColumns: false),
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
2023-07-03 19:23:42 +02:00
|
|
|
..where(servers.address.equals(account.appAccount!.url))
|
2022-08-05 11:39:49 +02:00
|
|
|
..where(accounts.userId
|
2023-07-03 19:23:42 +02:00
|
|
|
.equals(account.appAccount!.userId.toCaseInsensitiveString()));
|
2023-07-04 19:34:39 +02:00
|
|
|
return query.map((r) => r.readTable(faceRecognitionPersons)).get();
|
2022-08-05 11:39:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-04 19:34:39 +02:00
|
|
|
Future<List<FaceRecognitionPerson>> faceRecognitionPersonsByName({
|
2022-09-07 19:01:50 +02:00
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
required String name,
|
|
|
|
}) {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
|
|
|
if (sqlAccount != null) {
|
2023-07-04 19:34:39 +02:00
|
|
|
final query = select(faceRecognitionPersons)
|
2022-09-07 19:01:50 +02:00
|
|
|
..where((t) => t.account.equals(sqlAccount.rowId))
|
|
|
|
..where((t) =>
|
|
|
|
t.name.like(name) |
|
|
|
|
t.name.like("% $name") |
|
|
|
|
t.name.like("$name %"));
|
|
|
|
return query.get();
|
|
|
|
} else {
|
2023-07-04 19:34:39 +02:00
|
|
|
final query = select(faceRecognitionPersons).join([
|
|
|
|
innerJoin(
|
|
|
|
accounts, accounts.rowId.equalsExp(faceRecognitionPersons.account),
|
2022-09-07 19:01:50 +02:00
|
|
|
useColumns: false),
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
|
|
|
..where(servers.address.equals(appAccount!.url))
|
2023-07-16 19:08:06 +02:00
|
|
|
..where(
|
|
|
|
accounts.userId.equals(appAccount.userId.toCaseInsensitiveString()))
|
2023-07-04 19:34:39 +02:00
|
|
|
..where(faceRecognitionPersons.name.like(name) |
|
|
|
|
faceRecognitionPersons.name.like("% $name") |
|
|
|
|
faceRecognitionPersons.name.like("$name %"));
|
|
|
|
return query.map((r) => r.readTable(faceRecognitionPersons)).get();
|
2022-09-07 19:01:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-16 13:30:23 +02:00
|
|
|
Future<List<RecognizeFace>> allRecognizeFaces({
|
|
|
|
required ByAccount account,
|
|
|
|
}) {
|
|
|
|
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
|
|
if (account.sqlAccount != null) {
|
|
|
|
final query = select(recognizeFaces)
|
|
|
|
..where((t) => t.account.equals(account.sqlAccount!.rowId));
|
|
|
|
return query.get();
|
|
|
|
} else {
|
|
|
|
final query = select(recognizeFaces).join([
|
|
|
|
innerJoin(accounts, accounts.rowId.equalsExp(recognizeFaces.account),
|
|
|
|
useColumns: false),
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
|
|
|
..where(servers.address.equals(account.appAccount!.url))
|
|
|
|
..where(accounts.userId
|
|
|
|
.equals(account.appAccount!.userId.toCaseInsensitiveString()));
|
|
|
|
return query.map((r) => r.readTable(recognizeFaces)).get();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<RecognizeFace> recognizeFaceByLabel({
|
|
|
|
required ByAccount account,
|
|
|
|
required String label,
|
|
|
|
}) {
|
|
|
|
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
|
|
if (account.sqlAccount != null) {
|
|
|
|
final query = select(recognizeFaces)
|
|
|
|
..where((t) => t.account.equals(account.sqlAccount!.rowId))
|
|
|
|
..where((t) => t.label.equals(label));
|
|
|
|
return query.getSingle();
|
|
|
|
} else {
|
|
|
|
final query = select(recognizeFaces).join([
|
|
|
|
innerJoin(accounts, accounts.rowId.equalsExp(recognizeFaces.account),
|
|
|
|
useColumns: false),
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
|
|
|
..where(servers.address.equals(account.appAccount!.url))
|
|
|
|
..where(accounts.userId
|
|
|
|
.equals(account.appAccount!.userId.toCaseInsensitiveString()))
|
|
|
|
..where(recognizeFaces.label.equals(label));
|
|
|
|
return query.map((r) => r.readTable(recognizeFaces)).getSingle();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<RecognizeFaceItem>> recognizeFaceItemsByParentLabel({
|
|
|
|
required ByAccount account,
|
|
|
|
required String label,
|
|
|
|
List<OrderingTerm>? orderBy,
|
|
|
|
int? limit,
|
|
|
|
int? offset,
|
|
|
|
}) {
|
|
|
|
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
|
|
final query = select(recognizeFaceItems).join([
|
|
|
|
innerJoin(recognizeFaces,
|
|
|
|
recognizeFaces.rowId.equalsExp(recognizeFaceItems.parent),
|
|
|
|
useColumns: false),
|
|
|
|
]);
|
|
|
|
if (account.sqlAccount != null) {
|
|
|
|
query
|
|
|
|
..where(recognizeFaces.account.equals(account.sqlAccount!.rowId))
|
|
|
|
..where(recognizeFaces.label.equals(label));
|
|
|
|
} else {
|
|
|
|
query
|
|
|
|
..join([
|
|
|
|
innerJoin(accounts, accounts.rowId.equalsExp(recognizeFaces.account),
|
|
|
|
useColumns: false),
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
|
|
useColumns: false),
|
|
|
|
])
|
|
|
|
..where(servers.address.equals(account.appAccount!.url))
|
|
|
|
..where(accounts.userId
|
|
|
|
.equals(account.appAccount!.userId.toCaseInsensitiveString()))
|
|
|
|
..where(recognizeFaces.label.equals(label));
|
|
|
|
}
|
|
|
|
if (orderBy != null) {
|
|
|
|
query.orderBy(orderBy);
|
|
|
|
if (limit != null) {
|
|
|
|
query.limit(limit, offset: offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return query.map((r) => r.readTable(recognizeFaceItems)).get();
|
|
|
|
}
|
|
|
|
|
2022-10-15 16:29:18 +02:00
|
|
|
Future<int> countMissingMetadataByFileIds({
|
|
|
|
Account? sqlAccount,
|
|
|
|
app.Account? appAccount,
|
|
|
|
required List<int> fileIds,
|
|
|
|
}) async {
|
|
|
|
assert((sqlAccount != null) != (appAccount != null));
|
2022-12-03 03:44:55 +01:00
|
|
|
if (fileIds.isEmpty) {
|
|
|
|
return 0;
|
|
|
|
}
|
2022-10-15 16:29:18 +02:00
|
|
|
final counts = await fileIds.withPartition((sublist) async {
|
|
|
|
final count = countAll(
|
|
|
|
filter:
|
|
|
|
images.lastUpdated.isNull() | imageLocations.version.isNull());
|
|
|
|
final query = selectOnly(files).join([
|
|
|
|
innerJoin(accountFiles, accountFiles.file.equalsExp(files.rowId),
|
|
|
|
useColumns: false),
|
|
|
|
if (appAccount != null) ...[
|
|
|
|
innerJoin(accounts, accounts.rowId.equalsExp(accountFiles.account),
|
|
|
|
useColumns: false),
|
|
|
|
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
|
|
useColumns: false),
|
|
|
|
],
|
|
|
|
leftOuterJoin(images, images.accountFile.equalsExp(accountFiles.rowId),
|
|
|
|
useColumns: false),
|
|
|
|
leftOuterJoin(imageLocations,
|
|
|
|
imageLocations.accountFile.equalsExp(accountFiles.rowId),
|
|
|
|
useColumns: false),
|
|
|
|
]);
|
|
|
|
query.addColumns([count]);
|
|
|
|
if (sqlAccount != null) {
|
|
|
|
query.where(accountFiles.account.equals(sqlAccount.rowId));
|
|
|
|
} else if (appAccount != null) {
|
|
|
|
query
|
|
|
|
..where(servers.address.equals(appAccount.url))
|
|
|
|
..where(accounts.userId
|
|
|
|
.equals(appAccount.userId.toCaseInsensitiveString()));
|
|
|
|
}
|
|
|
|
query
|
|
|
|
..where(files.fileId.isIn(sublist))
|
|
|
|
..where(whereFileIsSupportedImageMime());
|
2023-07-10 19:30:56 +02:00
|
|
|
return [await query.map((r) => r.read(count)!).getSingle()];
|
2022-10-15 16:29:18 +02:00
|
|
|
}, maxByFileIdsSize);
|
|
|
|
return counts.reduce((value, element) => value + element);
|
|
|
|
}
|
|
|
|
|
2022-07-21 12:28:44 +02:00
|
|
|
Future<void> truncate() async {
|
|
|
|
await delete(servers).go();
|
|
|
|
// technically deleting Servers table is enough to clear the followings, but
|
|
|
|
// just in case
|
|
|
|
await delete(accounts).go();
|
|
|
|
await delete(files).go();
|
|
|
|
await delete(images).go();
|
2022-08-27 17:31:58 +02:00
|
|
|
await delete(imageLocations).go();
|
2022-07-21 12:28:44 +02:00
|
|
|
await delete(trashes).go();
|
|
|
|
await delete(accountFiles).go();
|
|
|
|
await delete(dirFiles).go();
|
|
|
|
await delete(albums).go();
|
|
|
|
await delete(albumShares).go();
|
2022-08-05 11:28:28 +02:00
|
|
|
await delete(tags).go();
|
2023-07-04 19:34:39 +02:00
|
|
|
await delete(faceRecognitionPersons).go();
|
2023-04-13 17:32:31 +02:00
|
|
|
await delete(ncAlbums).go();
|
|
|
|
await delete(ncAlbumItems).go();
|
2022-07-21 12:28:44 +02:00
|
|
|
|
|
|
|
// reset the auto increment counter
|
|
|
|
await customStatement("UPDATE sqlite_sequence SET seq=0;");
|
|
|
|
}
|
|
|
|
|
2023-07-10 19:30:56 +02:00
|
|
|
Expression<bool> whereFileIsSupportedMime() {
|
2022-10-15 16:29:18 +02:00
|
|
|
return file_util.supportedFormatMimes
|
2023-07-10 19:30:56 +02:00
|
|
|
.map<Expression<bool>>((m) => files.contentType.equals(m))
|
2022-10-15 16:29:18 +02:00
|
|
|
.reduce((value, element) => value | element);
|
|
|
|
}
|
|
|
|
|
2023-07-10 19:30:56 +02:00
|
|
|
Expression<bool> whereFileIsSupportedImageMime() {
|
2022-10-15 16:29:18 +02:00
|
|
|
return file_util.supportedImageFormatMimes
|
2023-07-10 19:30:56 +02:00
|
|
|
.map<Expression<bool>>((m) => files.contentType.equals(m))
|
2022-10-15 16:29:18 +02:00
|
|
|
.reduce((value, element) => value | element);
|
|
|
|
}
|
2022-07-05 22:20:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
app.File _covertSqliteDbFile(Map map) {
|
2022-07-11 20:14:42 +02:00
|
|
|
final userId = map["userId"] as String;
|
2022-07-05 22:20:24 +02:00
|
|
|
final file = map["completeFile"] as CompleteFile;
|
2022-07-11 20:14:42 +02:00
|
|
|
return SqliteFileConverter.fromSql(userId, file);
|
2022-07-05 22:20:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
CompleteFileCompanion _convertAppFile(Map map) {
|
|
|
|
final account = map["account"] as Account?;
|
|
|
|
final file = map["file"] as app.File;
|
|
|
|
return SqliteFileConverter.toSql(account, file);
|
|
|
|
}
|