nc-photos/app/test/test_compat_util.dart

496 lines
16 KiB
Dart

part of 'test_util.dart';
extension DiContainerExtension on DiContainer {
// ignore: deprecated_member_use
compat.SqliteDb get sqliteDb => (npDb as NpDbSqlite).compatDb;
}
class _ByAccount {
const _ByAccount.sql(compat.Account account) : this._(sqlAccount: account);
// const _ByAccount.app(Account account) : this._(appAccount: account);
const _ByAccount._({
this.sqlAccount,
this.appAccount,
}) : assert((sqlAccount != null) != (appAccount != null));
final compat.Account? sqlAccount;
final Account? appAccount;
}
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;
}
extension on compat.SqliteDb {
/// Query AccountFiles, Accounts and Files row ID by app File
///
/// Only one of [sqlAccount] and [appAccount] must be passed
Future<_AccountFileRowIds?> accountFileRowIdsOfOrNull(
FileDescriptor file, {
compat.Account? sqlAccount,
Account? appAccount,
}) {
assert((sqlAccount != null) != (appAccount != null));
final query = queryFiles().let((q) {
q.setQueryMode(_FilesQueryMode.expression, expressions: [
accountFiles.rowId,
accountFiles.account,
accountFiles.file,
]);
if (sqlAccount != null) {
q.setSqlAccount(sqlAccount);
} else {
q.setAppAccount(appAccount!);
}
try {
q.byFileId(file.fdId);
} catch (_) {
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(
FileDescriptor file, {
compat.Account? sqlAccount,
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(
_ByAccount account, Iterable<int> fileIds) {
final query = queryFiles().let((q) {
q.setQueryMode(_FilesQueryMode.expression, expressions: [
accountFiles.rowId,
accountFiles.account,
accountFiles.file,
files.fileId,
]);
if (account.sqlAccount != null) {
q.setSqlAccount(account.sqlAccount!);
} else {
q.setAppAccount(account.appAccount!);
}
q.byFileIds(fileIds);
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();
}
_FilesQueryBuilder queryFiles() => _FilesQueryBuilder(this);
}
class _SqliteAlbumConverter {
static Album fromSql(
compat.Album album, File albumFile, List<compat.AlbumShare> shares) {
return Album(
lastUpdated: album.lastUpdated,
name: album.name,
provider: AlbumProvider.fromJson({
"type": album.providerType,
"content": jsonDecode(album.providerContent),
}),
coverProvider: AlbumCoverProvider.fromJson({
"type": album.coverProviderType,
"content": jsonDecode(album.coverProviderContent),
}),
sortProvider: AlbumSortProvider.fromJson({
"type": album.sortProviderType,
"content": jsonDecode(album.sortProviderContent),
}),
shares: shares.isEmpty
? null
: shares
.map((e) => AlbumShare(
userId: e.userId.toCi(),
displayName: e.displayName,
sharedAt: e.sharedAt.toUtc(),
))
.toList(),
// replace with the original etag when this album was cached
albumFile: albumFile.copyWith(etag: OrNull(album.fileEtag)),
savedVersion: album.version,
);
}
static compat.CompleteAlbumCompanion toSql(
Album album, int albumFileRowId, String albumFileEtag) {
final providerJson = album.provider.toJson();
final coverProviderJson = album.coverProvider.toJson();
final sortProviderJson = album.sortProvider.toJson();
final dbAlbum = compat.AlbumsCompanion.insert(
file: albumFileRowId,
fileEtag: sql.Value(albumFileEtag),
version: Album.version,
lastUpdated: album.lastUpdated,
name: album.name,
providerType: providerJson["type"],
providerContent: jsonEncode(providerJson["content"]),
coverProviderType: coverProviderJson["type"],
coverProviderContent: jsonEncode(coverProviderJson["content"]),
sortProviderType: sortProviderJson["type"],
sortProviderContent: jsonEncode(sortProviderJson["content"]),
);
final dbAlbumShares = album.shares
?.map((s) => compat.AlbumSharesCompanion(
userId: sql.Value(s.userId.toCaseInsensitiveString()),
displayName: sql.Value(s.displayName),
sharedAt: sql.Value(s.sharedAt),
))
.toList();
return compat.CompleteAlbumCompanion(dbAlbum, 1, dbAlbumShares ?? []);
}
}
class _SqliteFileConverter {
static File fromSql(String userId, compat.CompleteFile f) {
final metadata = f.image?.let((obj) => Metadata(
lastUpdated: obj.lastUpdated,
fileEtag: obj.fileEtag,
imageWidth: obj.width,
imageHeight: obj.height,
exif: obj.exifRaw?.let((e) => Exif.fromJson(jsonDecode(e))),
));
final location = f.imageLocation?.let((obj) => ImageLocation(
version: obj.version,
name: obj.name,
latitude: obj.latitude,
longitude: obj.longitude,
countryCode: obj.countryCode,
admin1: obj.admin1,
admin2: obj.admin2,
));
return File(
path: "remote.php/dav/files/$userId/${f.accountFile.relativePath}",
contentLength: f.file.contentLength,
contentType: f.file.contentType,
etag: f.file.etag,
lastModified: f.file.lastModified,
isCollection: f.file.isCollection,
usedBytes: f.file.usedBytes,
hasPreview: f.file.hasPreview,
fileId: f.file.fileId,
isFavorite: f.accountFile.isFavorite,
ownerId: f.file.ownerId?.toCi(),
ownerDisplayName: f.file.ownerDisplayName,
trashbinFilename: f.trash?.filename,
trashbinOriginalLocation: f.trash?.originalLocation,
trashbinDeletionTime: f.trash?.deletionTime,
metadata: metadata,
isArchived: f.accountFile.isArchived,
overrideDateTime: f.accountFile.overrideDateTime,
location: location,
);
}
static compat.CompleteFileCompanion toSql(
compat.Account? account, File file) {
final dbFile = compat.FilesCompanion(
server: account == null
? const sql.Value.absent()
: sql.Value(account.server),
fileId: sql.Value(file.fileId!),
contentLength: sql.Value(file.contentLength),
contentType: sql.Value(file.contentType),
etag: sql.Value(file.etag),
lastModified: sql.Value(file.lastModified),
isCollection: sql.Value(file.isCollection),
usedBytes: sql.Value(file.usedBytes),
hasPreview: sql.Value(file.hasPreview),
ownerId: sql.Value(file.ownerId!.toCaseInsensitiveString()),
ownerDisplayName: sql.Value(file.ownerDisplayName),
);
final dbAccountFile = compat.AccountFilesCompanion(
account:
account == null ? const sql.Value.absent() : sql.Value(account.rowId),
relativePath: sql.Value(file.strippedPathWithEmpty),
isFavorite: sql.Value(file.isFavorite),
isArchived: sql.Value(file.isArchived),
overrideDateTime: sql.Value(file.overrideDateTime),
bestDateTime: sql.Value(file.bestDateTime),
);
final dbImage = file.metadata?.let((m) => compat.ImagesCompanion.insert(
lastUpdated: m.lastUpdated,
fileEtag: sql.Value(m.fileEtag),
width: sql.Value(m.imageWidth),
height: sql.Value(m.imageHeight),
exifRaw: sql.Value(m.exif?.toJson().let((j) => jsonEncode(j))),
dateTimeOriginal: sql.Value(m.exif?.dateTimeOriginal),
));
final dbImageLocation =
file.location?.let((l) => compat.ImageLocationsCompanion.insert(
version: l.version,
name: sql.Value(l.name),
latitude: sql.Value(l.latitude),
longitude: sql.Value(l.longitude),
countryCode: sql.Value(l.countryCode),
admin1: sql.Value(l.admin1),
admin2: sql.Value(l.admin2),
));
final dbTrash = file.trashbinDeletionTime == null
? null
: compat.TrashesCompanion.insert(
filename: file.trashbinFilename!,
originalLocation: file.trashbinOriginalLocation!,
deletionTime: file.trashbinDeletionTime!,
);
return compat.CompleteFileCompanion(
dbFile, dbAccountFile, dbImage, dbImageLocation, dbTrash);
}
}
enum _FilesQueryMode {
file,
completeFile,
expression,
}
typedef _FilesQueryRelativePathBuilder = sql.Expression<bool> Function(
sql.GeneratedColumn<String> relativePath);
/// Build a Files table query
///
/// If you call more than one by* methods, the condition will be added up
/// instead of replaced. No validations will be made to make sure the resulting
/// conditions make sense
class _FilesQueryBuilder {
_FilesQueryBuilder(this.db);
/// Set the query mode
///
/// If [mode] == FilesQueryMode.expression, [expressions] must be defined and
/// not empty
void setQueryMode(
_FilesQueryMode mode, {
Iterable<sql.Expression>? expressions,
}) {
assert((mode == _FilesQueryMode.expression) !=
(expressions?.isEmpty != false));
_queryMode = mode;
_selectExpressions = expressions;
}
void setSqlAccount(compat.Account account) {
assert(_appAccount == null);
_sqlAccount = account;
}
void setAppAccount(Account account) {
assert(_sqlAccount == null);
_appAccount = account;
}
void setAccountless() {
assert(_sqlAccount == null && _appAccount == null);
_isAccountless = true;
}
void byRowId(int rowId) {
_byRowId = rowId;
}
void byFileId(int fileId) {
_byFileId = fileId;
}
void byFileIds(Iterable<int> fileIds) {
_byFileIds = fileIds;
}
void byRelativePath(String path) {
_byRelativePath = path;
}
void byOrRelativePath(String path) {
_byOrRelativePathBuilder((relativePath) => relativePath.equals(path));
}
void byOrRelativePathPattern(String pattern) {
_byOrRelativePathBuilder((relativePath) => relativePath.like(pattern));
}
void byMimePattern(String pattern) {
(_byMimePatterns ??= []).add(pattern);
}
void byFavorite(bool favorite) {
_byFavorite = favorite;
}
void byDirRowId(int dirRowId) {
_byDirRowId = dirRowId;
}
void byServerRowId(int serverRowId) {
_byServerRowId = serverRowId;
}
void byLocation(String location) {
_byLocation = location;
}
sql.JoinedSelectStatement build() {
if (_sqlAccount == null && _appAccount == null && !_isAccountless) {
throw StateError("Invalid query: missing account");
}
final dynamic select = _queryMode == _FilesQueryMode.expression
? db.selectOnly(db.files)
: db.select(db.files);
final query = select.join([
sql.innerJoin(
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId),
useColumns: _queryMode == _FilesQueryMode.completeFile),
if (_appAccount != null) ...[
sql.innerJoin(
db.accounts, db.accounts.rowId.equalsExp(db.accountFiles.account),
useColumns: false),
sql.innerJoin(
db.servers, db.servers.rowId.equalsExp(db.accounts.server),
useColumns: false),
],
if (_byDirRowId != null)
sql.innerJoin(db.dirFiles, db.dirFiles.child.equalsExp(db.files.rowId),
useColumns: false),
if (_queryMode == _FilesQueryMode.completeFile) ...[
sql.leftOuterJoin(
db.images, db.images.accountFile.equalsExp(db.accountFiles.rowId)),
sql.leftOuterJoin(db.imageLocations,
db.imageLocations.accountFile.equalsExp(db.accountFiles.rowId)),
sql.leftOuterJoin(
db.trashes, db.trashes.file.equalsExp(db.files.rowId)),
],
]) as sql.JoinedSelectStatement;
if (_queryMode == _FilesQueryMode.expression) {
query.addColumns(_selectExpressions!);
}
if (_sqlAccount != null) {
query.where(db.accountFiles.account.equals(_sqlAccount!.rowId));
} else if (_appAccount != null) {
query
..where(db.servers.address.equals(_appAccount!.url))
..where(db.accounts.userId
.equals(_appAccount!.userId.toCaseInsensitiveString()));
}
if (_byRowId != null) {
query.where(db.files.rowId.equals(_byRowId!));
}
if (_byFileId != null) {
query.where(db.files.fileId.equals(_byFileId!));
}
if (_byFileIds != null) {
query.where(db.files.fileId.isIn(_byFileIds!));
}
if (_byRelativePath != null) {
query.where(db.accountFiles.relativePath.equals(_byRelativePath!));
}
if (_byOrRelativePathBuilders?.isNotEmpty == true) {
final expression = _byOrRelativePathBuilders!
.sublist(1)
.fold<sql.Expression<bool>>(
_byOrRelativePathBuilders![0](db.accountFiles.relativePath),
(previousValue, builder) =>
previousValue | builder(db.accountFiles.relativePath));
query.where(expression);
}
if (_byMimePatterns?.isNotEmpty == true) {
final expression = _byMimePatterns!.sublist(1).fold<sql.Expression<bool>>(
db.files.contentType.like(_byMimePatterns![0]),
(previousValue, element) =>
previousValue | db.files.contentType.like(element));
query.where(expression);
}
if (_byFavorite != null) {
if (_byFavorite!) {
query.where(db.accountFiles.isFavorite.equals(true));
} else {
// null are treated as false
query.where(db.accountFiles.isFavorite.equals(true).not());
}
}
if (_byDirRowId != null) {
query.where(db.dirFiles.dir.equals(_byDirRowId!));
}
if (_byServerRowId != null) {
query.where(db.files.server.equals(_byServerRowId!));
}
if (_byLocation != null) {
var clause = db.imageLocations.name.like(_byLocation!) |
db.imageLocations.admin1.like(_byLocation!) |
db.imageLocations.admin2.like(_byLocation!);
final countryCode = nameToAlpha2Code(_byLocation!.toCi());
if (countryCode != null) {
clause = clause | db.imageLocations.countryCode.equals(countryCode);
} else if (_byLocation!.length == 2 &&
alpha2CodeToName(_byLocation!.toUpperCase()) != null) {
clause = clause |
db.imageLocations.countryCode.equals(_byLocation!.toUpperCase());
}
query.where(clause);
}
return query;
}
void _byOrRelativePathBuilder(_FilesQueryRelativePathBuilder builder) {
(_byOrRelativePathBuilders ??= []).add(builder);
}
final compat.SqliteDb db;
_FilesQueryMode _queryMode = _FilesQueryMode.file;
Iterable<sql.Expression>? _selectExpressions;
compat.Account? _sqlAccount;
Account? _appAccount;
bool _isAccountless = false;
int? _byRowId;
int? _byFileId;
Iterable<int>? _byFileIds;
String? _byRelativePath;
List<_FilesQueryRelativePathBuilder>? _byOrRelativePathBuilders;
List<String>? _byMimePatterns;
bool? _byFavorite;
int? _byDirRowId;
int? _byServerRowId;
String? _byLocation;
}