import 'dart:math'; import 'package:computer/computer.dart'; import 'package:logging/logging.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:photos/models/file.dart'; final _logger = Logger("FileSyncUtil"); const ignoreSizeConstraint = SizeConstraint(ignoreSize: true); const assetFetchPageSize = 2000; Future> getDeviceFiles( int fromTime, int toTime, Computer computer, ) async { final pathEntities = await _getGalleryList(fromTime, toTime); List files = []; AssetPathEntity recents; for (AssetPathEntity pathEntity in pathEntities) { if (pathEntity.name == "Recent" || pathEntity.name == "Recents") { recents = pathEntity; } else { files = await _computeFiles(pathEntity, fromTime, files, computer); } } if (recents != null) { files = await _computeFiles(recents, fromTime, files, computer); } files.sort( (first, second) => first.creationTime.compareTo(second.creationTime), ); return files; } Future> getAllLocalAssets() async { final filterOptionGroup = FilterOptionGroup(); filterOptionGroup.setOption( AssetType.image, const FilterOption(sizeConstraint: ignoreSizeConstraint), ); filterOptionGroup.setOption( AssetType.video, const FilterOption(sizeConstraint: ignoreSizeConstraint), ); filterOptionGroup.createTimeCond = DateTimeCond.def().copyWith(ignore: true); final assetPaths = await PhotoManager.getAssetPathList( hasAll: true, type: RequestType.common, filterOption: filterOptionGroup, ); final List assets = []; for (final assetPath in assetPaths) { for (final asset in await _getAllAssetLists(assetPath)) { assets.add(LocalAsset(asset.id, assetPath.name)); } } return assets; } Future> getUnsyncedFiles( List assets, Set existingIDs, Set invalidIDs, Computer computer, ) async { final Map args = {}; args['assets'] = assets; args['existingIDs'] = existingIDs; args['invalidIDs'] = invalidIDs; final unsyncedAssets = await computer.compute(_getUnsyncedAssets, param: args); if (unsyncedAssets.isEmpty) { return []; } return _convertToFiles(unsyncedAssets, computer); } List _getUnsyncedAssets(Map args) { final List assets = args['assets']; final Set existingIDs = args['existingIDs']; final Set invalidIDs = args['invalidIDs']; final List unsyncedAssets = []; for (final asset in assets) { if (!existingIDs.contains(asset.id) && !invalidIDs.contains(asset.id)) { unsyncedAssets.add(asset); } } return unsyncedAssets; } Future> _convertToFiles( List assets, Computer computer, ) async { final List recents = []; final List entities = []; for (final asset in assets) { if (asset.path == "Recent" || asset.path == "Recents") { recents.add(asset); } else { entities.add( LocalAssetEntity(await AssetEntity.fromId(asset.id), asset.path), ); } } // Ignore duplicate items in recents for (final recent in recents) { bool presentInOthers = false; for (final entity in entities) { if (recent.id == entity.entity.id) { presentInOthers = true; break; } } if (!presentInOthers) { entities.add( LocalAssetEntity(await AssetEntity.fromId(recent.id), recent.path), ); } } return await computer.compute(_getFilesFromAssets, param: entities); } Future> _getGalleryList( final int fromTime, final int toTime, ) async { final filterOptionGroup = FilterOptionGroup(); filterOptionGroup.setOption( AssetType.image, const FilterOption(needTitle: true, sizeConstraint: ignoreSizeConstraint), ); filterOptionGroup.setOption( AssetType.video, const FilterOption(needTitle: true, sizeConstraint: ignoreSizeConstraint), ); filterOptionGroup.updateTimeCond = DateTimeCond( min: DateTime.fromMicrosecondsSinceEpoch(fromTime), max: DateTime.fromMicrosecondsSinceEpoch(toTime), ); final galleryList = await PhotoManager.getAssetPathList( hasAll: true, type: RequestType.common, filterOption: filterOptionGroup, ); galleryList.sort((s1, s2) { return s2.assetCount.compareTo(s1.assetCount); }); return galleryList; } Future> _computeFiles( AssetPathEntity pathEntity, int fromTime, List files, Computer computer, ) async { final Map args = {}; args["pathEntity"] = pathEntity; args["assetList"] = await _getAllAssetLists(pathEntity); args["fromTime"] = fromTime; args["files"] = files; return await computer.compute(_getFiles, param: args); } Future> _getAllAssetLists(AssetPathEntity pathEntity) async { List result = []; int currentPage = 0; List currentPageResult = []; do { currentPageResult = await pathEntity.getAssetListPaged( page: currentPage, size: assetFetchPageSize, ); result.addAll(currentPageResult); currentPage = currentPage + 1; } while (currentPageResult.length >= assetFetchPageSize); return result; } Future> _getFiles(Map args) async { final pathEntity = args["pathEntity"]; final assetList = args["assetList"]; final fromTime = args["fromTime"]; final files = args["files"]; for (AssetEntity entity in assetList) { if (max( entity.createDateTime.microsecondsSinceEpoch, entity.modifiedDateTime.microsecondsSinceEpoch, ) > fromTime) { try { final file = await File.fromAsset(pathEntity.name, entity); if (!files.contains(file)) { files.add(file); } } catch (e) { _logger.severe(e); } } } return files; } Future> _getFilesFromAssets(List assets) async { final List files = []; for (final asset in assets) { files.add( await File.fromAsset( asset.path, asset.entity, ), ); } return files; } class LocalAsset { final String id; final String path; LocalAsset( this.id, this.path, ); } class LocalAssetEntity { final AssetEntity entity; final String path; LocalAssetEntity(this.entity, this.path); }