import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:idb_shim/idb.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/app_db.dart'; import 'package:nc_photos/ci_string.dart'; import 'package:nc_photos/entity/album.dart'; import 'package:nc_photos/entity/album/cover_provider.dart'; import 'package:nc_photos/entity/album/item.dart'; import 'package:nc_photos/entity/album/provider.dart'; import 'package:nc_photos/entity/album/sort_provider.dart'; import 'package:nc_photos/entity/file.dart'; import 'package:nc_photos/entity/share.dart'; import 'package:nc_photos/entity/sharee.dart'; import 'package:nc_photos/iterable_extension.dart'; import 'package:nc_photos/type.dart'; class FilesBuilder { FilesBuilder({ int initialFileId = 0, }) : fileId = initialFileId; List build() { return files.map((f) => f.copyWith()).toList(); } void add( String relativePath, { int? contentLength, String? contentType, DateTime? lastModified, bool isCollection = false, bool hasPreview = true, String ownerId = "admin", }) { files.add(File( path: "remote.php/dav/files/$relativePath", contentLength: contentLength, contentType: contentType, lastModified: lastModified ?? DateTime.utc(2020, 1, 2, 3, 4, 5 + files.length), isCollection: isCollection, hasPreview: hasPreview, fileId: fileId++, ownerId: ownerId.toCi(), )); } void addGenericFile( String relativePath, String contentType, { int contentLength = 1024, DateTime? lastModified, bool hasPreview = true, String ownerId = "admin", }) => add( relativePath, contentLength: contentLength, contentType: contentType, lastModified: lastModified, hasPreview: hasPreview, ownerId: ownerId, ); void addJpeg( String relativePath, { int contentLength = 1024, DateTime? lastModified, bool hasPreview = true, String ownerId = "admin", }) => add( relativePath, contentLength: contentLength, contentType: "image/jpeg", lastModified: lastModified, hasPreview: hasPreview, ownerId: ownerId, ); void addDir( String relativePath, { int contentLength = 1024, DateTime? lastModified, String ownerId = "admin", }) => add( relativePath, lastModified: lastModified, isCollection: true, hasPreview: false, ownerId: ownerId, ); final files = []; int fileId; } /// Create an album for testing class AlbumBuilder { AlbumBuilder({ DateTime? lastUpdated, String? name, this.albumFilename = "test0.nc_album.json", this.fileId = 0, String? ownerId, }) : lastUpdated = lastUpdated ?? DateTime.utc(2020, 1, 2, 3, 4, 5), name = name ?? "test", ownerId = ownerId ?? "admin"; factory AlbumBuilder.ofId({ required int albumId, DateTime? lastUpdated, String? name, String? ownerId, }) => AlbumBuilder( lastUpdated: lastUpdated, name: name, albumFilename: "test$albumId.nc_album.json", fileId: albumId, ownerId: ownerId, ); Album build() { final latestFileItem = items .whereType() .stableSorted( (a, b) => a.file.lastModified!.compareTo(b.file.lastModified!)) .reversed .firstOrNull; return Album( lastUpdated: lastUpdated, name: name, provider: AlbumStaticProvider( items: items, latestItemTime: latestFileItem?.file.lastModified, ), coverProvider: cover == null ? AlbumAutoCoverProvider(coverFile: latestFileItem?.file) : AlbumManualCoverProvider(coverFile: cover!), sortProvider: const AlbumNullSortProvider(), shares: shares.isEmpty ? null : shares, albumFile: buildAlbumFile( path: buildAlbumFilePath(albumFilename, user: ownerId), fileId: fileId, ownerId: ownerId, ), ); } /// Add a file item /// /// By default, the item will be added by admin and added at the same time as /// the file's lastModified. /// /// If [isCover] is true, the coverProvider of the album will become /// [AlbumManualCoverProvider] void addFileItem( File file, { String addedBy = "admin", DateTime? addedAt, bool isCover = false, }) { final fileItem = AlbumFileItem( file: file, addedBy: addedBy.toCi(), addedAt: addedAt ?? file.lastModified!, ); items.add(fileItem); if (isCover) { cover = file; } } /// Add an album share /// /// By default, the album will be shared at 2020-01-02 03:04:05 void addShare( String userId, { DateTime? sharedAt, }) { shares.add(buildAlbumShare( userId: userId, sharedAt: sharedAt, )); } static List fileItemsOf(Album album) => AlbumStaticProvider.of(album).items.whereType().toList(); final DateTime lastUpdated; final String name; final String albumFilename; final int fileId; final String ownerId; final items = []; File? cover; final shares = []; } void initLog() { Logger.root.level = Level.ALL; Logger.root.onRecord.listen((record) { String msg = "[${record.loggerName}] ${record.level.name}: ${record.message}"; if (record.error != null) { msg += " (throw: ${record.error.runtimeType} { ${record.error} })"; } if (record.stackTrace != null) { msg += "\nStack Trace:\n${record.stackTrace}"; } int color; if (record.level >= Level.SEVERE) { color = 91; } else if (record.level >= Level.WARNING) { color = 33; } else if (record.level >= Level.INFO) { color = 34; } else if (record.level >= Level.FINER) { color = 32; } else { color = 90; } msg = "\x1B[${color}m$msg\x1B[0m"; debugPrint(msg); }); } Account buildAccount({ String id = "123456-000000", String scheme = "http", String address = "example.com", String username = "admin", String password = "pass", List roots = const [""], }) => Account(id, scheme, address, username.toCi(), password, null, roots); /// Build a mock [File] pointing to a album JSON file /// /// Warning: not all fields are filled, but the most essential ones are File buildAlbumFile({ required String path, int contentLength = 1024, DateTime? lastModified, required int fileId, String ownerId = "admin", }) => File( path: path, contentLength: contentLength, contentType: "application/json", lastModified: lastModified ?? DateTime.utc(2020, 1, 2, 3, 4, 5), isCollection: false, hasPreview: false, fileId: fileId, ownerId: ownerId.toCi(), ); String buildAlbumFilePath( String filename, { String user = "admin", }) => "remote.php/dav/files/$user/.com.nkming.nc_photos/albums/$filename"; AlbumShare buildAlbumShare({ required String userId, String? displayName, DateTime? sharedAt, }) => AlbumShare( userId: userId.toCi(), displayName: displayName ?? userId, sharedAt: sharedAt ?? DateTime.utc(2020, 1, 2, 3, 4, 5), ); /// Build a mock [File] pointing to a JPEG image file /// /// Warning: not all fields are filled, but the most essential ones are File buildJpegFile({ required String path, int contentLength = 1024, DateTime? lastModified, bool hasPreview = true, required int fileId, String ownerId = "admin", }) => File( path: path, contentLength: contentLength, contentType: "image/jpeg", lastModified: lastModified ?? DateTime.utc(2020, 1, 2, 3, 4, 5), isCollection: false, hasPreview: hasPreview, fileId: fileId, ownerId: ownerId.toCi(), ); Share buildShare({ required String id, DateTime? stime, String uidOwner = "admin", String? displaynameOwner, required File file, required String shareWith, }) => Share( id: id, shareType: ShareType.user, stime: stime ?? DateTime.utc(2020, 1, 2, 3, 4, 5), uidOwner: uidOwner.toCi(), displaynameOwner: displaynameOwner ?? uidOwner, uidFileOwner: file.ownerId!, path: file.strippedPath, itemType: ShareItemType.file, mimeType: file.contentType ?? "", itemSource: file.fileId!, shareWith: shareWith.toCi(), shareWithDisplayName: shareWith, ); Sharee buildSharee({ ShareeType type = ShareeType.user, String? label, int shareType = 0, required CiString shareWith, String? shareWithDisplayNameUnique, }) => Sharee( type: type, label: label ?? shareWith.toString(), shareType: shareType, shareWith: shareWith, ); Future fillAppDb( AppDb appDb, Account account, Iterable files) async { await appDb.use( (db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite), (transaction) async { final file2Store = transaction.objectStore(AppDb.file2StoreName); for (final f in files) { await file2Store.put(AppDbFile2Entry.fromFile(account, f).toJson(), AppDbFile2Entry.toPrimaryKeyForFile(account, f)); } }, ); } Future fillAppDbDir( AppDb appDb, Account account, File dir, List children) async { await appDb.use( (db) => db.transaction(AppDb.dirStoreName, idbModeReadWrite), (transaction) async { final dirStore = transaction.objectStore(AppDb.dirStoreName); await dirStore.put( AppDbDirEntry.fromFiles(account, dir, children).toJson(), AppDbDirEntry.toPrimaryKeyForDir(account, dir)); }, ); } Future> listAppDb( AppDb appDb, String storeName, T Function(JsonObj) transform) { return appDb.use( (db) => db.transaction(storeName, idbModeReadOnly), (transaction) async { final store = transaction.objectStore(storeName); return await store .openCursor(autoAdvance: true) .map((c) => c.value) .cast() .map((e) => transform(e.cast())) .toList(); }, ); }