import 'package:collection/collection.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/entity_converter.dart'; import 'package:nc_photos/db/entity_converter.dart'; import 'package:nc_photos/entity/nc_album.dart'; import 'package:nc_photos/entity/nc_album/repo.dart'; import 'package:nc_photos/entity/nc_album_item.dart'; import 'package:nc_photos/exception.dart'; import 'package:nc_photos/np_api_util.dart'; import 'package:np_api/np_api.dart' as api; import 'package:np_codegen/np_codegen.dart'; import 'package:np_db/np_db.dart'; part 'data_source.g.dart'; @npLog class NcAlbumRemoteDataSource implements NcAlbumDataSource { const NcAlbumRemoteDataSource(); @override Future> getAlbums(Account account) async { _log.info("[getAlbums] account: ${account.userId}"); final results = await Future.wait([ _getAlbums(account), _getSharedAlbums(account), ]); return results.flattened.toList(); } @override Future create(Account account, NcAlbum album) async { _log.info("[create] account: ${account.userId}, album: ${album.path}"); final response = await ApiUtil.fromAccount(account) .photos(account.userId.toString()) .album(album.strippedPath) .mkcol(); if (!response.isGood) { _log.severe("[create] Failed requesting server: $response"); throw ApiException( response: response, message: "Server responed with an error: HTTP ${response.statusCode}", ); } } @override Future remove(Account account, NcAlbum album) async { _log.info("[remove] account: ${account.userId}, album: ${album.path}"); final response = await ApiUtil.fromAccount(account) .photos(account.userId.toString()) .album(album.strippedPath) .delete(); if (!response.isGood) { _log.severe("[remove] Failed requesting server: $response"); throw ApiException( response: response, message: "Server responed with an error: HTTP ${response.statusCode}", ); } } @override Future> getItems(Account account, NcAlbum album) async { _log.info( "[getItems] account: ${account.userId}, album: ${album.strippedPath}"); final response = await ApiUtil.fromAccount(account).files().propfind( path: album.path, getlastmodified: 1, getetag: 1, getcontenttype: 1, getcontentlength: 1, hasPreview: 1, fileid: 1, favorite: 1, customProperties: [ "nc:file-metadata-size", "nc:face-detections", "nc:realpath", "oc:permissions", ], ); if (!response.isGood) { _log.severe("[getItems] Failed requesting server: $response"); throw ApiException( response: response, message: "Server responed with an error: HTTP ${response.statusCode}", ); } final apiFiles = await api.NcAlbumItemParser().parse(response.body); return apiFiles .where((f) => f.fileId != null) .map(ApiNcAlbumItemConverter.fromApi) .toList(); } Future> _getAlbums(Account account) async { final response = await ApiUtil.fromAccount(account) .photos(account.userId.toString()) .albums() .propfind( lastPhoto: 1, nbItems: 1, location: 1, dateRange: 1, collaborators: 1, ); if (!response.isGood) { _log.severe("[_getAlbums] Failed requesting server: $response"); throw ApiException( response: response, message: "Server responed with an error: HTTP ${response.statusCode}", ); } final apiNcAlbums = await api.NcAlbumParser().parse(response.body); return apiNcAlbums .map(ApiNcAlbumConverter.fromApi) .where((a) => a.strippedPath != ".") .toList(); } Future> _getSharedAlbums(Account account) async { final response = await ApiUtil.fromAccount(account) .photos(account.userId.toString()) .sharedalbums() .propfind( lastPhoto: 1, nbItems: 1, location: 1, dateRange: 1, collaborators: 1, ); if (!response.isGood) { _log.severe("[_getSharedAlbums] Failed requesting server: $response"); throw ApiException( response: response, message: "Server responed with an error: HTTP ${response.statusCode}", ); } final apiNcAlbums = await api.NcAlbumParser().parse(response.body); return apiNcAlbums .map(ApiNcAlbumConverter.fromApi) .where((a) => a.strippedPath != ".") .toList(); } } @npLog class NcAlbumSqliteDbDataSource implements NcAlbumCacheDataSource { const NcAlbumSqliteDbDataSource(this.npDb); @override Future> getAlbums(Account account) async { _log.info("[getAlbums] account: ${account.userId}"); final results = await npDb.getNcAlbums(account: account.toDb()); return results .map((e) { try { return DbNcAlbumConverter.fromDb(account.userId.toString(), e); } catch (e, stackTrace) { _log.severe( "[getAlbums] Failed while converting DB entry", e, stackTrace); return null; } }) .whereNotNull() .toList(); } @override Future create(Account account, NcAlbum album) async { _log.info("[create] account: ${account.userId}, album: ${album.path}"); await npDb.addNcAlbum(account: account.toDb(), album: album.toDb()); } @override Future remove(Account account, NcAlbum album) async { _log.info("[remove] account: ${account.userId}, album: ${album.path}"); await npDb.deleteNcAlbum(account: account.toDb(), album: album.toDb()); } @override Future> getItems(Account account, NcAlbum album) async { _log.info( "[getItems] account: ${account.userId}, album: ${album.strippedPath}"); final results = await npDb.getNcAlbumItemsByParent( account: account.toDb(), parent: album.toDb(), ); return results .map((e) { try { return DbNcAlbumItemConverter.fromDb(account.userId.toString(), album.strippedPath, album.isOwned, e); } catch (e, stackTrace) { _log.severe( "[getItems] Failed while converting DB entry", e, stackTrace); return null; } }) .whereNotNull() .toList(); } @override Future updateAlbumsCache(Account account, List remote) async { _log.info( "[updateAlbumsCache] account: ${account.userId}, remote: ${remote.map((e) => e.strippedPath)}"); await npDb.syncNcAlbums( account: account.toDb(), albums: remote.map(DbNcAlbumConverter.toDb).toList(), ); } @override Future updateItemsCache( Account account, NcAlbum album, List remote) async { _log.info( "[updateItemsCache] account: ${account.userId}, album: ${album.name}, remote: ${remote.map((e) => e.strippedPath)}"); await npDb.syncNcAlbumItems( account: account.toDb(), album: album.toDb(), items: remote.map(DbNcAlbumItemConverter.toDb).toList(), ); } final NpDb npDb; }