Merge remote-tracking branch 'origin/master' into share_to_ente

This commit is contained in:
Neeraj Gupta 2021-07-22 13:16:22 +05:30
commit 77c2489714
28 changed files with 301 additions and 203 deletions

View file

@ -10,7 +10,7 @@ class ThumbnailLruCache {
static Uint8List get(File photo, [int size]) { static Uint8List get(File photo, [int size]) {
return _map.get(photo.generatedID.toString() + return _map.get(photo.generatedID.toString() +
"_" + "_" +
(size != null ? size.toString() : THUMBNAIL_LARGE_SIZE.toString())); (size != null ? size.toString() : kThumbnailLargeSize.toString()));
} }
static void put( static void put(
@ -21,7 +21,7 @@ class ThumbnailLruCache {
_map.put( _map.put(
photo.generatedID.toString() + photo.generatedID.toString() +
"_" + "_" +
(size != null ? size.toString() : THUMBNAIL_LARGE_SIZE.toString()), (size != null ? size.toString() : kThumbnailLargeSize.toString()),
imageData); imageData);
} }
} }

View file

@ -46,6 +46,8 @@ class Configuration {
static const tokenKey = "token"; static const tokenKey = "token";
static const encryptedTokenKey = "encrypted_token"; static const encryptedTokenKey = "encrypted_token";
static const userIDKey = "user_id"; static const userIDKey = "user_id";
static const hasMigratedSecureStorageToFirstUnlockKey =
"has_migrated_secure_storage_to_first_unlock";
final kTempFolderDeletionTimeBuffer = Duration(days: 1).inMicroseconds; final kTempFolderDeletionTimeBuffer = Duration(days: 1).inMicroseconds;
@ -61,12 +63,15 @@ class Configuration {
String _thumbnailCacheDirectory; String _thumbnailCacheDirectory;
String _volatilePassword; String _volatilePassword;
final _secureStorageOptionsIOS =
IOSOptions(accessibility: IOSAccessibility.first_unlock_this_device);
Future<void> init() async { Future<void> init() async {
_preferences = await SharedPreferences.getInstance(); _preferences = await SharedPreferences.getInstance();
_secureStorage = FlutterSecureStorage(); _secureStorage = FlutterSecureStorage();
_documentsDirectory = (await getApplicationDocumentsDirectory()).path; _documentsDirectory = (await getApplicationDocumentsDirectory()).path;
_tempDirectory = _documentsDirectory + "/temp/"; _tempDirectory = _documentsDirectory + "/temp/";
final tempDirectory = new io.Directory(_tempDirectory); final tempDirectory = io.Directory(_tempDirectory);
try { try {
final currentTime = DateTime.now().microsecondsSinceEpoch; final currentTime = DateTime.now().microsecondsSinceEpoch;
if (tempDirectory.existsSync() && if (tempDirectory.existsSync() &&
@ -86,10 +91,17 @@ class Configuration {
(await getTemporaryDirectory()).path + "/thumbnail-cache"; (await getTemporaryDirectory()).path + "/thumbnail-cache";
io.Directory(_thumbnailCacheDirectory).createSync(recursive: true); io.Directory(_thumbnailCacheDirectory).createSync(recursive: true);
if (!_preferences.containsKey(tokenKey)) { if (!_preferences.containsKey(tokenKey)) {
await _secureStorage.deleteAll(); await _secureStorage.deleteAll(iOptions: _secureStorageOptionsIOS);
} else { } else {
_key = await _secureStorage.read(key: keyKey); _key = await _secureStorage.read(
_secretKey = await _secureStorage.read(key: secretKeyKey); key: keyKey,
iOptions: _secureStorageOptionsIOS,
);
_secretKey = await _secureStorage.read(
key: secretKeyKey,
iOptions: _secureStorageOptionsIOS,
);
await _migrateSecurityStorageToFirstUnlock();
} }
} }
@ -103,7 +115,7 @@ class Configuration {
} }
} }
await _preferences.clear(); await _preferences.clear();
await _secureStorage.deleteAll(); await _secureStorage.deleteAll(iOptions: _secureStorageOptionsIOS);
_key = null; _key = null;
_cachedToken = null; _cachedToken = null;
_secretKey = null; _secretKey = null;
@ -197,7 +209,7 @@ class Configuration {
attributes.memLimit, attributes.memLimit,
attributes.opsLimit, attributes.opsLimit,
); );
var key; Uint8List key;
try { try {
key = CryptoUtil.decryptSync(Sodium.base642bin(attributes.encryptedKey), key = CryptoUtil.decryptSync(Sodium.base642bin(attributes.encryptedKey),
kek, Sodium.base642bin(attributes.keyDecryptionNonce)); kek, Sodium.base642bin(attributes.keyDecryptionNonce));
@ -241,7 +253,7 @@ class Configuration {
Future<void> recover(String recoveryKey) async { Future<void> recover(String recoveryKey) async {
final keyAttributes = getKeyAttributes(); final keyAttributes = getKeyAttributes();
var masterKey; Uint8List masterKey;
try { try {
masterKey = await CryptoUtil.decrypt( masterKey = await CryptoUtil.decrypt(
Sodium.base642bin(keyAttributes.masterKeyEncryptedWithRecoveryKey), Sodium.base642bin(keyAttributes.masterKeyEncryptedWithRecoveryKey),
@ -249,7 +261,7 @@ class Configuration {
Sodium.base642bin(keyAttributes.masterKeyDecryptionNonce)); Sodium.base642bin(keyAttributes.masterKeyDecryptionNonce));
} catch (e) { } catch (e) {
_logger.severe(e); _logger.severe(e);
throw e; rethrow;
} }
await setKey(Sodium.bin2base64(masterKey)); await setKey(Sodium.bin2base64(masterKey));
} }
@ -262,9 +274,7 @@ class Configuration {
} }
String getToken() { String getToken() {
if (_cachedToken == null) { _cachedToken ??= _preferences.getString(tokenKey);
_cachedToken = _preferences.getString(tokenKey);
}
return _cachedToken; return _cachedToken;
} }
@ -309,7 +319,7 @@ class Configuration {
if (_preferences.containsKey(foldersToBackUpKey)) { if (_preferences.containsKey(foldersToBackUpKey)) {
return _preferences.getStringList(foldersToBackUpKey).toSet(); return _preferences.getStringList(foldersToBackUpKey).toSet();
} else { } else {
return Set<String>(); return <String>{};
} }
} }
@ -342,8 +352,7 @@ class Configuration {
} }
Future<void> setKeyAttributes(KeyAttributes attributes) async { Future<void> setKeyAttributes(KeyAttributes attributes) async {
await _preferences.setString( await _preferences.setString(keyAttributesKey, attributes?.toJson());
keyAttributesKey, attributes == null ? null : attributes.toJson());
} }
KeyAttributes getKeyAttributes() { KeyAttributes getKeyAttributes() {
@ -358,18 +367,32 @@ class Configuration {
Future<void> setKey(String key) async { Future<void> setKey(String key) async {
_key = key; _key = key;
if (key == null) { if (key == null) {
await _secureStorage.delete(key: keyKey); await _secureStorage.delete(
key: keyKey,
iOptions: _secureStorageOptionsIOS,
);
} else { } else {
await _secureStorage.write(key: keyKey, value: key); await _secureStorage.write(
key: keyKey,
value: key,
iOptions: _secureStorageOptionsIOS,
);
} }
} }
Future<void> setSecretKey(String secretKey) async { Future<void> setSecretKey(String secretKey) async {
_secretKey = secretKey; _secretKey = secretKey;
if (secretKey == null) { if (secretKey == null) {
await _secureStorage.delete(key: secretKeyKey); await _secureStorage.delete(
key: secretKeyKey,
iOptions: _secureStorageOptionsIOS,
);
} else { } else {
await _secureStorage.write(key: secretKeyKey, value: secretKey); await _secureStorage.write(
key: secretKeyKey,
value: secretKey,
iOptions: _secureStorageOptionsIOS,
);
} }
} }
@ -477,4 +500,25 @@ class Configuration {
bool hasSkippedBackupFolderSelection() { bool hasSkippedBackupFolderSelection() {
return _preferences.getBool(keyHasSkippedBackupFolderSelection) ?? false; return _preferences.getBool(keyHasSkippedBackupFolderSelection) ?? false;
} }
Future<void> _migrateSecurityStorageToFirstUnlock() async {
final hasMigratedSecureStorageToFirstUnlock =
_preferences.getBool(hasMigratedSecureStorageToFirstUnlockKey) ?? false;
if (!hasMigratedSecureStorageToFirstUnlock &&
_key != null &&
_secretKey != null) {
await _secureStorage.write(
key: keyKey,
value: _key,
iOptions: _secureStorageOptionsIOS,
);
await _secureStorage.write(
key: secretKeyKey,
value: _secretKey,
iOptions: _secureStorageOptionsIOS,
);
await _preferences.setBool(
hasMigratedSecureStorageToFirstUnlockKey, true);
}
}
} }

View file

@ -1,12 +1,13 @@
const int THUMBNAIL_SMALL_SIZE = 256; const int kThumbnailSmallSize = 256;
const int THUMBNAIL_QUALITY = 50; const int kThumbnailQuality = 50;
const int THUMBNAIL_LARGE_SIZE = 512; const int kThumbnailLargeSize = 512;
const int COMPRESSED_THUMBNAIL_RESOLUTION = 1080; const int kCompressedThumbnailResolution = 1080;
const int THUMBNAIL_DATA_LIMIT = 100 * 1024; const int kThumbnailDataLimit = 100 * 1024;
const String SENTRY_DSN = const String kSentryDSN =
"https://93b8ea6f54f442dc8408ebccdff6fe7a@errors.ente.io/2"; "https://93b8ea6f54f442dc8408ebccdff6fe7a@errors.ente.io/2";
const String SENTRY_DEBUG_DSN = const String kSentryDebugDSN =
"https://b31c8af8384a4ce980509b8f592a67eb@errors.ente.io/3"; "https://b31c8af8384a4ce980509b8f592a67eb@errors.ente.io/3";
const String ROADMAP_URL = "https://roadmap.ente.io"; const String kRoadmapURL = "https://roadmap.ente.io";
const int MICRO_SECONDS_IN_DAY = 86400000000; const int kMicroSecondsInDay = 86400000000;
const int ANDROID_11_SDK_INT = 30; const int kAndroid11SDKINT = 30;
const int kGalleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748

View file

@ -39,14 +39,14 @@ class CollectionsDB {
CollectionsDB._privateConstructor(); CollectionsDB._privateConstructor();
static final CollectionsDB instance = CollectionsDB._privateConstructor(); static final CollectionsDB instance = CollectionsDB._privateConstructor();
static Database _database; static Future<Database> _dbFuture;
Future<Database> get database async { Future<Database> get database async {
if (_database != null) return _database; _dbFuture ??= _initDatabase();
_database = await _initDatabase(); return _dbFuture;
return _database;
} }
_initDatabase() async { Future<Database> _initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory(); Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName); String path = join(documentsDirectory.path, _databaseName);
return await openDatabaseWithMigration(path, dbConfig); return await openDatabaseWithMigration(path, dbConfig);

View file

@ -46,28 +46,34 @@ class FilesDB {
static final columnThumbnailDecryptionHeader = 'thumbnail_decryption_header'; static final columnThumbnailDecryptionHeader = 'thumbnail_decryption_header';
static final columnMetadataDecryptionHeader = 'metadata_decryption_header'; static final columnMetadataDecryptionHeader = 'metadata_decryption_header';
static final intitialScript = [...createTable(table), ...addIndex()]; static final initializationScript = [...createTable(table)];
static final migrationScripts = [...alterDeviceFolderToAllowNULL()]; static final migrationScripts = [
...alterDeviceFolderToAllowNULL(),
...alterTimestampColumnTypes(),
...addIndices(),
];
final dbConfig = MigrationConfig( final dbConfig = MigrationConfig(
initializationScript: intitialScript, migrationScripts: migrationScripts); initializationScript: initializationScript,
migrationScripts: migrationScripts);
// make this a singleton class // make this a singleton class
FilesDB._privateConstructor(); FilesDB._privateConstructor();
static final FilesDB instance = FilesDB._privateConstructor(); static final FilesDB instance = FilesDB._privateConstructor();
// only have a single app-wide reference to the database // only have a single app-wide reference to the database
static Database _database; static Future<Database> _dbFuture;
Future<Database> get database async { Future<Database> get database async {
if (_database != null) return _database;
// lazily instantiate the db the first time it is accessed // lazily instantiate the db the first time it is accessed
_database = await _initDatabase(); _dbFuture ??= _initDatabase();
return _database; return _dbFuture;
} }
// this opens the database (and creates it if it doesn't exist) // this opens the database (and creates it if it doesn't exist)
_initDatabase() async { Future<Database> _initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory(); Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName); String path = join(documentsDirectory.path, _databaseName);
_logger.info("DB path " + path);
return await openDatabaseWithMigration(path, dbConfig); return await openDatabaseWithMigration(path, dbConfig);
} }
@ -101,13 +107,19 @@ class FilesDB {
]; ];
} }
static List<String> addIndex() { static List<String> addIndices() {
return [ return [
''' '''
CREATE INDEX collection_id_index ON $table($columnCollectionID); CREATE INDEX IF NOT EXISTS collection_id_index ON $table($columnCollectionID);
CREATE INDEX device_folder_index ON $table($columnDeviceFolder); ''',
CREATE INDEX creation_time_index ON $table($columnCreationTime); '''
CREATE INDEX updation_time_index ON $table($columnUpdationTime); CREATE INDEX IF NOT EXISTS device_folder_index ON $table($columnDeviceFolder);
''',
'''
CREATE INDEX IF NOT EXISTS creation_time_index ON $table($columnCreationTime);
''',
'''
CREATE INDEX IF NOT EXISTS updation_time_index ON $table($columnUpdationTime);
''' '''
]; ];
} }
@ -128,6 +140,67 @@ class FilesDB {
]; ];
} }
static List<String> alterTimestampColumnTypes() {
return [
'''
DROP TABLE IF EXISTS $tempTable;
''',
'''
CREATE TABLE $tempTable (
$columnGeneratedID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
$columnLocalID TEXT,
$columnUploadedFileID INTEGER DEFAULT -1,
$columnOwnerID INTEGER,
$columnCollectionID INTEGER DEFAULT -1,
$columnTitle TEXT NOT NULL,
$columnDeviceFolder TEXT,
$columnLatitude REAL,
$columnLongitude REAL,
$columnFileType INTEGER,
$columnModificationTime INTEGER NOT NULL,
$columnEncryptedKey TEXT,
$columnKeyDecryptionNonce TEXT,
$columnFileDecryptionHeader TEXT,
$columnThumbnailDecryptionHeader TEXT,
$columnMetadataDecryptionHeader TEXT,
$columnCreationTime INTEGER NOT NULL,
$columnUpdationTime INTEGER,
UNIQUE($columnLocalID, $columnUploadedFileID, $columnCollectionID)
);
''',
'''
INSERT INTO $tempTable
SELECT
$columnGeneratedID,
$columnLocalID,
$columnUploadedFileID,
$columnOwnerID,
$columnCollectionID,
$columnTitle,
$columnDeviceFolder,
$columnLatitude,
$columnLongitude,
$columnFileType,
CAST($columnModificationTime AS INTEGER),
$columnEncryptedKey,
$columnKeyDecryptionNonce,
$columnFileDecryptionHeader,
$columnThumbnailDecryptionHeader,
$columnMetadataDecryptionHeader,
CAST($columnCreationTime AS INTEGER),
CAST($columnUpdationTime AS INTEGER)
FROM $table;
''',
'''
DROP TABLE $table;
''',
'''
ALTER TABLE $tempTable
RENAME TO $table;
''',
];
}
Future<void> clearTable() async { Future<void> clearTable() async {
final db = await instance.database; final db = await instance.database;
await db.delete(table); await db.delete(table);
@ -208,7 +281,7 @@ class FilesDB {
collectionID, collectionID,
], ],
); );
final ids = Set<int>(); final ids = <int>{};
for (final result in results) { for (final result in results) {
ids.add(result[columnUploadedFileID]); ids.add(result[columnUploadedFileID]);
} }
@ -223,8 +296,8 @@ class FilesDB {
where: where:
'$columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1)', '$columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1)',
); );
final localIDs = Set<String>(); final localIDs = <String>{};
final uploadedIDs = Set<int>(); final uploadedIDs = <int>{};
for (final result in results) { for (final result in results) {
localIDs.add(result[columnLocalID]); localIDs.add(result[columnLocalID]);
uploadedIDs.add(result[columnUploadedFileID]); uploadedIDs.add(result[columnUploadedFileID]);
@ -239,7 +312,7 @@ class FilesDB {
final results = await db.query( final results = await db.query(
table, table,
where: where:
'$columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnIsDeleted = 0 AND ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1)', '$columnCreationTime >= ? AND $columnCreationTime <= ? AND ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1)',
whereArgs: [startTime, endTime], whereArgs: [startTime, endTime],
orderBy: orderBy:
'$columnCreationTime ' + order + ', $columnModificationTime ' + order, '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
@ -256,7 +329,7 @@ class FilesDB {
final results = await db.query( final results = await db.query(
table, table,
where: where:
'$columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnIsDeleted = 0 AND ($columnLocalID IS NOT NULL OR ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1))', '$columnCreationTime >= ? AND $columnCreationTime <= ? AND ($columnLocalID IS NOT NULL OR ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1))',
whereArgs: [startTime, endTime], whereArgs: [startTime, endTime],
orderBy: orderBy:
'$columnCreationTime ' + order + ', $columnModificationTime ' + order, '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
@ -279,13 +352,13 @@ class FilesDB {
final results = await db.query( final results = await db.query(
table, table,
where: where:
'$columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnIsDeleted = 0 AND (($columnLocalID IS NOT NULL AND $columnDeviceFolder IN ($inParam)) OR ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1))', '$columnCreationTime >= ? AND $columnCreationTime <= ? AND (($columnLocalID IS NOT NULL AND $columnDeviceFolder IN ($inParam)) OR ($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1))',
whereArgs: [startTime, endTime], whereArgs: [startTime, endTime],
orderBy: orderBy:
'$columnCreationTime ' + order + ', $columnModificationTime ' + order, '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
limit: limit, limit: limit,
); );
final uploadedFileIDs = Set<int>(); final uploadedFileIDs = <int>{};
final files = _convertToFiles(results); final files = _convertToFiles(results);
final List<File> deduplicatedFiles = []; final List<File> deduplicatedFiles = [];
for (final file in files) { for (final file in files) {
@ -307,7 +380,7 @@ class FilesDB {
final results = await db.query( final results = await db.query(
table, table,
where: where:
'$columnCollectionID = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnIsDeleted = 0', '$columnCollectionID = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ?',
whereArgs: [collectionID, startTime, endTime], whereArgs: [collectionID, startTime, endTime],
orderBy: orderBy:
'$columnCreationTime ' + order + ', $columnModificationTime ' + order, '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
@ -325,11 +398,11 @@ class FilesDB {
final results = await db.query( final results = await db.query(
table, table,
where: where:
'$columnDeviceFolder = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnLocalID IS NOT NULL AND $columnIsDeleted = 0', '$columnDeviceFolder = ? AND $columnCreationTime >= ? AND $columnCreationTime <= ? AND $columnLocalID IS NOT NULL',
whereArgs: [path, startTime, endTime], whereArgs: [path, startTime, endTime],
orderBy: orderBy:
'$columnCreationTime ' + order + ', $columnModificationTime ' + order, '$columnCreationTime ' + order + ', $columnModificationTime ' + order,
groupBy: '$columnLocalID', groupBy: columnLocalID,
limit: limit, limit: limit,
); );
final files = _convertToFiles(results); final files = _convertToFiles(results);
@ -340,8 +413,7 @@ class FilesDB {
final db = await instance.database; final db = await instance.database;
final results = await db.query( final results = await db.query(
table, table,
where: where: '$columnLocalID IS NOT NULL AND $columnFileType = 1',
'$columnLocalID IS NOT NULL AND $columnFileType = 1 AND $columnIsDeleted = 0',
orderBy: '$columnCreationTime DESC', orderBy: '$columnCreationTime DESC',
); );
return _convertToFiles(results); return _convertToFiles(results);
@ -351,11 +423,10 @@ class FilesDB {
final db = await instance.database; final db = await instance.database;
final results = await db.query( final results = await db.query(
table, table,
where: where: '$columnLocalID IS NOT NULL AND $columnDeviceFolder = ?',
'$columnLocalID IS NOT NULL AND $columnDeviceFolder = ? AND $columnIsDeleted = 0',
whereArgs: [path], whereArgs: [path],
orderBy: '$columnCreationTime DESC', orderBy: '$columnCreationTime DESC',
groupBy: '$columnLocalID', groupBy: columnLocalID,
); );
return _convertToFiles(results); return _convertToFiles(results);
} }
@ -376,28 +447,12 @@ class FilesDB {
} }
final results = await db.query( final results = await db.query(
table, table,
where: whereClause + " AND $columnIsDeleted = 0", where: whereClause,
orderBy: '$columnCreationTime ASC', orderBy: '$columnCreationTime ASC',
); );
return _convertToFiles(results); return _convertToFiles(results);
} }
Future<List<int>> getDeletedFileIDs() async {
final db = await instance.database;
final rows = await db.query(
table,
columns: [columnUploadedFileID],
distinct: true,
where: '$columnIsDeleted = 1',
orderBy: '$columnCreationTime DESC',
);
final result = List<int>();
for (final row in rows) {
result.add(row[columnUploadedFileID]);
}
return result;
}
Future<List<File>> getFilesToBeUploadedWithinFolders( Future<List<File>> getFilesToBeUploadedWithinFolders(
Set<String> folders) async { Set<String> folders) async {
if (folders.isEmpty) { if (folders.isEmpty) {
@ -414,7 +469,7 @@ class FilesDB {
where: where:
'($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND $columnDeviceFolder IN ($inParam)', '($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND $columnDeviceFolder IN ($inParam)',
orderBy: '$columnCreationTime DESC', orderBy: '$columnCreationTime DESC',
groupBy: '$columnLocalID', groupBy: columnLocalID,
); );
return _convertToFiles(results); return _convertToFiles(results);
} }
@ -426,7 +481,7 @@ class FilesDB {
where: where:
'($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND $columnLocalID IS NOT NULL', '($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1) AND $columnLocalID IS NOT NULL',
orderBy: '$columnCreationTime DESC', orderBy: '$columnCreationTime DESC',
groupBy: '$columnLocalID', groupBy: columnLocalID,
); );
return _convertToFiles(results); return _convertToFiles(results);
} }
@ -438,7 +493,7 @@ class FilesDB {
where: where:
'($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1) AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1)', '($columnCollectionID IS NOT NULL AND $columnCollectionID IS NOT -1) AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID IS -1)',
orderBy: '$columnCreationTime DESC', orderBy: '$columnCreationTime DESC',
groupBy: '$columnLocalID', groupBy: columnLocalID,
); );
return _convertToFiles(results); return _convertToFiles(results);
} }
@ -449,11 +504,11 @@ class FilesDB {
table, table,
columns: [columnUploadedFileID], columns: [columnUploadedFileID],
where: where:
'($columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NULL AND $columnIsDeleted = 0)', '($columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NULL)',
orderBy: '$columnCreationTime DESC', orderBy: '$columnCreationTime DESC',
distinct: true, distinct: true,
); );
final uploadedFileIDs = List<int>(); final uploadedFileIDs = <int>[];
for (final row in rows) { for (final row in rows) {
uploadedFileIDs.add(row[columnUploadedFileID]); uploadedFileIDs.add(row[columnUploadedFileID]);
} }
@ -484,7 +539,7 @@ class FilesDB {
distinct: true, distinct: true,
where: '$columnLocalID IS NOT NULL', where: '$columnLocalID IS NOT NULL',
); );
final result = Set<String>(); final result = <String>{};
for (final row in rows) { for (final row in rows) {
result.add(row[columnLocalID]); result.add(row[columnLocalID]);
} }
@ -497,7 +552,7 @@ class FilesDB {
table, table,
columns: [columnUploadedFileID], columns: [columnUploadedFileID],
where: where:
'($columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NOT NULL AND $columnIsDeleted = 0)', '($columnLocalID IS NOT NULL AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1) AND $columnUpdationTime IS NOT NULL)',
distinct: true, distinct: true,
); );
return rows.length; return rows.length;
@ -666,7 +721,7 @@ class FilesDB {
'''); ''');
final files = _convertToFiles(rows); final files = _convertToFiles(rows);
// TODO: Do this de-duplication within the SQL Query // TODO: Do this de-duplication within the SQL Query
final folderMap = Map<String, File>(); final folderMap = <String, File>{};
for (final file in files) { for (final file in files) {
if (folderMap.containsKey(file.deviceFolder)) { if (folderMap.containsKey(file.deviceFolder)) {
if (folderMap[file.deviceFolder].updationTime < file.updationTime) { if (folderMap[file.deviceFolder].updationTime < file.updationTime) {
@ -695,7 +750,7 @@ class FilesDB {
'''); ''');
final files = _convertToFiles(rows); final files = _convertToFiles(rows);
// TODO: Do this de-duplication within the SQL Query // TODO: Do this de-duplication within the SQL Query
final collectionMap = Map<int, File>(); final collectionMap = <int, File>{};
for (final file in files) { for (final file in files) {
if (collectionMap.containsKey(file.collectionID)) { if (collectionMap.containsKey(file.collectionID)) {
if (collectionMap[file.collectionID].updationTime < file.updationTime) { if (collectionMap[file.collectionID].updationTime < file.updationTime) {
@ -711,7 +766,7 @@ class FilesDB {
final db = await instance.database; final db = await instance.database;
final rows = await db.query( final rows = await db.query(
table, table,
where: '$columnCollectionID = ? AND $columnIsDeleted = 0', where: '$columnCollectionID = ?',
whereArgs: [collectionID], whereArgs: [collectionID],
orderBy: '$columnUpdationTime DESC', orderBy: '$columnUpdationTime DESC',
limit: 1, limit: 1,
@ -731,7 +786,7 @@ class FilesDB {
WHERE $columnLocalID IS NOT NULL WHERE $columnLocalID IS NOT NULL
GROUP BY $columnDeviceFolder GROUP BY $columnDeviceFolder
'''); ''');
final result = Map<String, int>(); final result = <String, int>{};
for (final row in rows) { for (final row in rows) {
result[row[columnDeviceFolder]] = row["count"]; result[row[columnDeviceFolder]] = row["count"];
} }
@ -759,7 +814,7 @@ class FilesDB {
} }
Map<String, dynamic> _getRowForFile(File file) { Map<String, dynamic> _getRowForFile(File file) {
final row = new Map<String, dynamic>(); final row = <String, dynamic>{};
if (file.generatedID != null) { if (file.generatedID != null) {
row[columnGeneratedID] = file.generatedID; row[columnGeneratedID] = file.generatedID;
} }
@ -795,7 +850,7 @@ class FilesDB {
} }
Map<String, dynamic> _getRowForFileWithoutCollection(File file) { Map<String, dynamic> _getRowForFileWithoutCollection(File file) {
final row = new Map<String, dynamic>(); final row = <String, dynamic>{};
row[columnLocalID] = file.localID; row[columnLocalID] = file.localID;
row[columnUploadedFileID] = file.uploadedFileID ?? -1; row[columnUploadedFileID] = file.uploadedFileID ?? -1;
row[columnOwnerID] = file.ownerID; row[columnOwnerID] = file.ownerID;
@ -839,11 +894,9 @@ class FilesDB {
file.location = Location(row[columnLatitude], row[columnLongitude]); file.location = Location(row[columnLatitude], row[columnLongitude]);
} }
file.fileType = getFileType(row[columnFileType]); file.fileType = getFileType(row[columnFileType]);
file.creationTime = int.parse(row[columnCreationTime]); file.creationTime = row[columnCreationTime];
file.modificationTime = int.parse(row[columnModificationTime]); file.modificationTime = row[columnModificationTime];
file.updationTime = row[columnUpdationTime] == null file.updationTime = row[columnUpdationTime] ?? -1;
? -1
: int.parse(row[columnUpdationTime]);
file.encryptedKey = row[columnEncryptedKey]; file.encryptedKey = row[columnEncryptedKey];
file.keyDecryptionNonce = row[columnKeyDecryptionNonce]; file.keyDecryptionNonce = row[columnKeyDecryptionNonce];
file.fileDecryptionHeader = row[columnFileDecryptionHeader]; file.fileDecryptionHeader = row[columnFileDecryptionHeader];

View file

@ -2,9 +2,9 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photos/models/memory.dart'; import 'package:photos/models/memory.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class MemoriesDB { class MemoriesDB {
static final _databaseName = "ente.memories.db"; static final _databaseName = "ente.memories.db";
@ -18,14 +18,13 @@ class MemoriesDB {
MemoriesDB._privateConstructor(); MemoriesDB._privateConstructor();
static final MemoriesDB instance = MemoriesDB._privateConstructor(); static final MemoriesDB instance = MemoriesDB._privateConstructor();
static Database _database; static Future<Database> _dbFuture;
Future<Database> get database async { Future<Database> get database async {
if (_database != null) return _database; _dbFuture ??= _initDatabase();
_database = await _initDatabase(); return _dbFuture;
return _database;
} }
_initDatabase() async { Future<Database> _initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory(); Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName); String path = join(documentsDirectory.path, _databaseName);
return await openDatabase( return await openDatabase(

View file

@ -2,9 +2,9 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photos/models/public_key.dart'; import 'package:photos/models/public_key.dart';
import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class PublicKeysDB { class PublicKeysDB {
static final _databaseName = "ente.public_keys.db"; static final _databaseName = "ente.public_keys.db";
@ -18,14 +18,14 @@ class PublicKeysDB {
PublicKeysDB._privateConstructor(); PublicKeysDB._privateConstructor();
static final PublicKeysDB instance = PublicKeysDB._privateConstructor(); static final PublicKeysDB instance = PublicKeysDB._privateConstructor();
static Database _database; static Future<Database> _dbFuture;
Future<Database> get database async { Future<Database> get database async {
if (_database != null) return _database; _dbFuture ??= _initDatabase();
_database = await _initDatabase(); return _dbFuture;
return _database;
} }
_initDatabase() async { Future<Database> _initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory(); Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName); String path = join(documentsDirectory.path, _databaseName);
return await openDatabase( return await openDatabase(

View file

@ -2,8 +2,8 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:path/path.dart'; import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
class UploadLocksDB { class UploadLocksDB {
static const _databaseName = "ente.upload_locks.db"; static const _databaseName = "ente.upload_locks.db";
@ -17,14 +17,13 @@ class UploadLocksDB {
UploadLocksDB._privateConstructor(); UploadLocksDB._privateConstructor();
static final UploadLocksDB instance = UploadLocksDB._privateConstructor(); static final UploadLocksDB instance = UploadLocksDB._privateConstructor();
static Database _database; static Future<Database> _dbFuture;
Future<Database> get database async { Future<Database> get database async {
if (_database != null) return _database; _dbFuture ??= _initDatabase();
_database = await _initDatabase(); return _dbFuture;
return _database;
} }
_initDatabase() async { Future<Database> _initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory(); Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName); String path = join(documentsDirectory.path, _databaseName);
return await openDatabase( return await openDatabase(

View file

@ -5,9 +5,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/configuration.dart'; import 'package:photos/core/configuration.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/network.dart'; import 'package:photos/core/network.dart';
import 'package:photos/db/upload_locks_db.dart'; import 'package:photos/db/upload_locks_db.dart';
import 'package:photos/services/billing_service.dart'; import 'package:photos/services/billing_service.dart';
@ -25,7 +26,6 @@ import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/file_uploader.dart'; import 'package:photos/utils/file_uploader.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:super_logging/super_logging.dart'; import 'package:super_logging/super_logging.dart';
import 'package:logging/logging.dart';
final _logger = Logger("main"); final _logger = Logger("main");
@ -166,7 +166,7 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
body: function, body: function,
logDirPath: (await getTemporaryDirectory()).path + "/logs", logDirPath: (await getTemporaryDirectory()).path + "/logs",
maxLogFiles: 5, maxLogFiles: 5,
sentryDsn: kDebugMode ? SENTRY_DEBUG_DSN : SENTRY_DSN, sentryDsn: kDebugMode ? kSentryDebugDSN : kSentryDSN,
enableInDebugMode: true, enableInDebugMode: true,
prefix: prefix, prefix: prefix,
)); ));

View file

@ -134,10 +134,16 @@ class File {
return localID == null && uploadedFileID != null; return localID == null && uploadedFileID != null;
} }
bool isCachedInAppSandbox() { bool isCachedInAppSandbox() {
return localID != null && localID.startsWith("ente-upload-cache"); return localID != null && localID.startsWith("ente-upload-cache");
} }
bool hasLocation() {
return location != null &&
(location.longitude != 0 || location.latitude != 0);
}
@override @override
String toString() { String toString() {
return '''File(generatedId: $generatedID, uploadedFileId: $uploadedFileID, return '''File(generatedId: $generatedID, uploadedFileId: $uploadedFileID,

View file

@ -35,9 +35,9 @@ class CollectionsService {
SharedPreferences _prefs; SharedPreferences _prefs;
Future<List<File>> _cachedLatestFiles; Future<List<File>> _cachedLatestFiles;
final _dio = Network.instance.getDio(); final _dio = Network.instance.getDio();
final _localCollections = Map<String, Collection>(); final _localCollections = <String, Collection>{};
final _collectionIDToCollections = Map<int, Collection>(); final _collectionIDToCollections = <int, Collection>{};
final _cachedKeys = Map<int, Uint8List>(); final _cachedKeys = <int, Uint8List>{};
CollectionsService._privateConstructor() { CollectionsService._privateConstructor() {
_db = CollectionsDB.instance; _db = CollectionsDB.instance;
@ -72,7 +72,7 @@ class CollectionsService {
// Might not have synced the collection fully // Might not have synced the collection fully
final fetchedCollections = final fetchedCollections =
await _fetchCollections(lastCollectionUpdationTime ?? 0); await _fetchCollections(lastCollectionUpdationTime ?? 0);
final updatedCollections = List<Collection>(); final updatedCollections = <Collection>[];
int maxUpdationTime = lastCollectionUpdationTime; int maxUpdationTime = lastCollectionUpdationTime;
for (final collection in fetchedCollections) { for (final collection in fetchedCollections) {
if (collection.isDeleted) { if (collection.isDeleted) {
@ -100,7 +100,7 @@ class CollectionsService {
return collections; return collections;
} }
Future<void> clearCache() { void clearCache() {
_localCollections.clear(); _localCollections.clear();
_collectionIDToCollections.clear(); _collectionIDToCollections.clear();
_cachedKeys.clear(); _cachedKeys.clear();
@ -108,7 +108,7 @@ class CollectionsService {
Future<List<Collection>> getCollectionsToBeSynced() async { Future<List<Collection>> getCollectionsToBeSynced() async {
final collections = await _db.getAllCollections(); final collections = await _db.getAllCollections();
final updatedCollections = List<Collection>(); final updatedCollections = <Collection>[];
for (final c in collections) { for (final c in collections) {
if (c.updationTime > getCollectionSyncTime(c.id)) { if (c.updationTime > getCollectionSyncTime(c.id)) {
updatedCollections.add(c); updatedCollections.add(c);
@ -118,18 +118,13 @@ class CollectionsService {
} }
int getCollectionSyncTime(int collectionID) { int getCollectionSyncTime(int collectionID) {
var syncTime = return _prefs
_prefs.getInt(_collectionSyncTimeKeyPrefix + collectionID.toString()); .getInt(_collectionSyncTimeKeyPrefix + collectionID.toString()) ??
if (syncTime == null) { 0;
syncTime = 0;
}
return syncTime;
} }
Future<List<File>> getLatestCollectionFiles() { Future<List<File>> getLatestCollectionFiles() {
if (_cachedLatestFiles == null) { _cachedLatestFiles ??= _filesDB.getLatestCollectionFiles();
_cachedLatestFiles = _filesDB.getLatestCollectionFiles();
}
return _cachedLatestFiles; return _cachedLatestFiles;
} }
@ -161,7 +156,7 @@ class CollectionsService {
) )
.then((response) { .then((response) {
_logger.info(response.toString()); _logger.info(response.toString());
final sharees = List<User>(); final sharees = <User>[];
for (final user in response.data["sharees"]) { for (final user in response.data["sharees"]) {
sharees.add(User.fromMap(user)); sharees.add(User.fromMap(user));
} }
@ -187,7 +182,7 @@ class CollectionsService {
if (e.response.statusCode == 402) { if (e.response.statusCode == 402) {
throw SharingNotPermittedForFreeAccountsError(); throw SharingNotPermittedForFreeAccountsError();
} }
throw e; rethrow;
} }
RemoteSyncService.instance.sync(silently: true); RemoteSyncService.instance.sync(silently: true);
} }
@ -209,7 +204,7 @@ class CollectionsService {
_db.insert([_collectionIDToCollections[collectionID]]); _db.insert([_collectionIDToCollections[collectionID]]);
} catch (e) { } catch (e) {
_logger.severe(e); _logger.severe(e);
throw e; rethrow;
} }
RemoteSyncService.instance.sync(silently: true); RemoteSyncService.instance.sync(silently: true);
} }
@ -257,7 +252,7 @@ class CollectionsService {
if (e is DioError && e.response?.statusCode == 401) { if (e is DioError && e.response?.statusCode == 401) {
throw UnauthorizedError(); throw UnauthorizedError();
} }
throw e; rethrow;
} }
} }
@ -313,7 +308,7 @@ class CollectionsService {
} }
Future<void> addToCollection(int collectionID, List<File> files) { Future<void> addToCollection(int collectionID, List<File> files) {
final params = Map<String, dynamic>(); final params = <String, dynamic>{};
params["collectionID"] = collectionID; params["collectionID"] = collectionID;
for (final file in files) { for (final file in files) {
final key = decryptFileKey(file); final key = decryptFileKey(file);
@ -344,11 +339,11 @@ class CollectionsService {
} }
Future<void> removeFromCollection(int collectionID, List<File> files) async { Future<void> removeFromCollection(int collectionID, List<File> files) async {
final params = Map<String, dynamic>(); final params = <String, dynamic>{};
params["collectionID"] = collectionID; params["collectionID"] = collectionID;
for (final file in files) { for (final file in files) {
if (params["fileIDs"] == null) { if (params["fileIDs"] == null) {
params["fileIDs"] = List<int>(); params["fileIDs"] = <int>[];
} }
params["fileIDs"].add(file.uploadedFileID); params["fileIDs"].add(file.uploadedFileID);
} }
@ -401,7 +396,7 @@ class CollectionsService {
Collection _getCollectionWithDecryptedName(Collection collection) { Collection _getCollectionWithDecryptedName(Collection collection) {
if (collection.encryptedName != null && if (collection.encryptedName != null &&
collection.encryptedName.isNotEmpty) { collection.encryptedName.isNotEmpty) {
var name; String name;
try { try {
final result = CryptoUtil.decryptSync( final result = CryptoUtil.decryptSync(
Sodium.base642bin(collection.encryptedName), Sodium.base642bin(collection.encryptedName),
@ -429,7 +424,7 @@ class CollectionsService {
if (attempt < kMaximumWriteAttempts) { if (attempt < kMaximumWriteAttempts) {
return _updateDB(collections, attempt: attempt++); return _updateDB(collections, attempt: attempt++);
} else { } else {
throw e; rethrow;
} }
} }
} }

View file

@ -27,7 +27,7 @@ class MemoriesService extends ChangeNotifier {
_cachedMemories = null; _cachedMemories = null;
}); });
await _memoriesDB.clearMemoriesSeenBeforeTime( await _memoriesDB.clearMemoriesSeenBeforeTime(
DateTime.now().microsecondsSinceEpoch - (7 * MICRO_SECONDS_IN_DAY)); DateTime.now().microsecondsSinceEpoch - (7 * kMicroSecondsInDay));
} }
void clearCache() { void clearCache() {

View file

@ -79,7 +79,7 @@ class RemoteSyncService {
Future<bool> _uploadDiff() async { Future<bool> _uploadDiff() async {
final foldersToBackUp = Configuration.instance.getPathsToBackUp(); final foldersToBackUp = Configuration.instance.getPathsToBackUp();
var filesToBeUploaded; List<File> filesToBeUploaded;
if (LocalSyncService.instance.hasGrantedLimitedPermissions() && if (LocalSyncService.instance.hasGrantedLimitedPermissions() &&
foldersToBackUp.isEmpty) { foldersToBackUp.isEmpty) {
filesToBeUploaded = await _db.getAllLocalFiles(); filesToBeUploaded = await _db.getAllLocalFiles();
@ -149,7 +149,7 @@ class RemoteSyncService {
} on UserCancelledUploadError { } on UserCancelledUploadError {
// Do nothing // Do nothing
} catch (e) { } catch (e) {
throw e; rethrow;
} }
return _completedUploads > 0; return _completedUploads > 0;
} }
@ -223,7 +223,7 @@ class RemoteSyncService {
} }
} }
await _db.insertMultiple(toBeInserted); await _db.insertMultiple(toBeInserted);
if (toBeInserted.length > 0) { if (toBeInserted.isNotEmpty) {
await _collectionsService.setCollectionSyncTime( await _collectionsService.setCollectionSyncTime(
collectionID, toBeInserted[toBeInserted.length - 1].updationTime); collectionID, toBeInserted[toBeInserted.length - 1].updationTime);
} }

View file

@ -231,7 +231,7 @@ class SyncService {
final lastNotificationShownTime = final lastNotificationShownTime =
_prefs.getInt(kLastStorageLimitExceededNotificationPushTime) ?? 0; _prefs.getInt(kLastStorageLimitExceededNotificationPushTime) ?? 0;
final now = DateTime.now().microsecondsSinceEpoch; final now = DateTime.now().microsecondsSinceEpoch;
if ((now - lastNotificationShownTime) > MICRO_SECONDS_IN_DAY) { if ((now - lastNotificationShownTime) > kMicroSecondsInDay) {
await _prefs.setInt(kLastStorageLimitExceededNotificationPushTime, now); await _prefs.setInt(kLastStorageLimitExceededNotificationPushTime, now);
NotificationService.instance.showNotification( NotificationService.instance.showNotification(
"storage limit exceeded", "sorry, we had to pause your backups"); "storage limit exceeded", "sorry, we had to pause your backups");

View file

@ -51,7 +51,7 @@ class UpdateService {
_prefs.getInt(kUpdateAvailableShownTimeKey) ?? 0; _prefs.getInt(kUpdateAvailableShownTimeKey) ?? 0;
final now = DateTime.now().microsecondsSinceEpoch; final now = DateTime.now().microsecondsSinceEpoch;
final hasBeen3DaysSinceLastNotification = final hasBeen3DaysSinceLastNotification =
(now - lastNotificationShownTime) > (3 * MICRO_SECONDS_IN_DAY); (now - lastNotificationShownTime) > (3 * kMicroSecondsInDay);
if (shouldUpdate && if (shouldUpdate &&
hasBeen3DaysSinceLastNotification && hasBeen3DaysSinceLastNotification &&
_latestVersion.shouldNotify) { _latestVersion.shouldNotify) {

View file

@ -22,9 +22,9 @@ class BlurredFileBackdrop extends StatelessWidget {
key: Key("memory_backdrop" + file.tag()), key: Key("memory_backdrop" + file.tag()),
), ),
BackdropFilter( BackdropFilter(
filter: new ImageFilter.blur(sigmaX: 64.0, sigmaY: 64.0), filter: ImageFilter.blur(sigmaX: 64.0, sigmaY: 64.0),
child: new Container( child: Container(
decoration: new BoxDecoration(color: Colors.white.withOpacity(0.0)), decoration: BoxDecoration(color: Colors.white.withOpacity(0.0)),
), ),
), ),
]), ]),

View file

@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/models/file.dart'; import 'package:photos/models/file.dart';
import 'package:photos/models/file_type.dart'; import 'package:photos/models/file_type.dart';
import 'package:photos/ui/fading_app_bar.dart'; import 'package:photos/ui/fading_app_bar.dart';
@ -208,7 +209,7 @@ class _DetailPageState extends State<DetailPage> {
} }
if (_selectedIndex == _files.length - 1 && !_hasLoadedTillEnd) { if (_selectedIndex == _files.length - 1 && !_hasLoadedTillEnd) {
final result = await widget.config.asyncLoader( final result = await widget.config.asyncLoader(
0, _files[_selectedIndex].creationTime - 1, kGalleryLoadStartTime, _files[_selectedIndex].creationTime - 1,
limit: kLoadLimit); limit: kLoadLimit);
setState(() { setState(() {
if (!result.hasMore) { if (!result.hasMore) {

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/event_bus.dart'; import 'package:photos/core/event_bus.dart';
import 'package:photos/events/event.dart'; import 'package:photos/events/event.dart';
import 'package:photos/events/files_updated_event.dart'; import 'package:photos/events/files_updated_event.dart';
@ -100,8 +101,9 @@ class _GalleryState extends State<Gallery> {
Future<FileLoadResult> _loadFiles({int limit}) async { Future<FileLoadResult> _loadFiles({int limit}) async {
_logger.info("Loading files"); _logger.info("Loading files");
final startTime = DateTime.now().microsecondsSinceEpoch; final startTime = DateTime.now().microsecondsSinceEpoch;
final result = await widget final result = await widget.asyncLoader(
.asyncLoader(0, DateTime.now().microsecondsSinceEpoch, limit: limit); kGalleryLoadStartTime, DateTime.now().microsecondsSinceEpoch,
limit: limit);
final endTime = DateTime.now().microsecondsSinceEpoch; final endTime = DateTime.now().microsecondsSinceEpoch;
final duration = Duration(microseconds: endTime - startTime); final duration = Duration(microseconds: endTime - startTime);
_logger.info("Time taken to load " + _logger.info("Time taken to load " +

View file

@ -99,7 +99,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
DateTime(galleryDate.year, galleryDate.month, galleryDate.day); DateTime(galleryDate.year, galleryDate.month, galleryDate.day);
final result = await widget.asyncLoader( final result = await widget.asyncLoader(
dayStartTime.microsecondsSinceEpoch, dayStartTime.microsecondsSinceEpoch,
dayStartTime.microsecondsSinceEpoch + MICRO_SECONDS_IN_DAY - 1); dayStartTime.microsecondsSinceEpoch + kMicroSecondsInDay - 1);
if (result.files.isEmpty) { if (result.files.isEmpty) {
// All files on this day were deleted, let gallery trigger the reload // All files on this day were deleted, let gallery trigger the reload
} else { } else {

View file

@ -319,8 +319,7 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
newFile.creationTime = widget.originalFile.creationTime; newFile.creationTime = widget.originalFile.creationTime;
newFile.collectionID = widget.originalFile.collectionID; newFile.collectionID = widget.originalFile.collectionID;
newFile.location = widget.originalFile.location; newFile.location = widget.originalFile.location;
if (newFile.location == null || if (!newFile.hasLocation() && widget.originalFile.localID != null) {
(newFile.location.latitude == 0 && newFile.location.longitude == 0)) {
final latLong = final latLong =
await (await widget.originalFile.getAsset()).latlngAsync(); await (await widget.originalFile.getAsset()).latlngAsync();
newFile.location = Location(latLong.latitude, latLong.longitude); newFile.location = Location(latLong.latitude, latLong.longitude);

View file

@ -72,7 +72,7 @@ class SupportSectionWidget extends StatelessWidget {
final isLoggedIn = Configuration.instance.getToken() != null; final isLoggedIn = Configuration.instance.getToken() != null;
final url = isLoggedIn final url = isLoggedIn
? endpoint + "?token=" + Configuration.instance.getToken() ? endpoint + "?token=" + Configuration.instance.getToken()
: ROADMAP_URL; : kRoadmapURL;
return WebPage("roadmap", url); return WebPage("roadmap", url);
}, },
), ),

View file

@ -1,12 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/cache/thumbnail_cache.dart'; import 'package:photos/core/cache/thumbnail_cache.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/errors.dart'; import 'package:photos/core/errors.dart';
import 'package:photos/core/event_bus.dart'; import 'package:photos/core/event_bus.dart';
import 'package:photos/db/files_db.dart'; import 'package:photos/db/files_db.dart';
import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/file.dart'; import 'package:photos/models/file.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/models/file_type.dart'; import 'package:photos/models/file_type.dart';
import 'package:photos/ui/common_elements.dart'; import 'package:photos/ui/common_elements.dart';
import 'package:photos/utils/thumbnail_util.dart'; import 'package:photos/utils/thumbnail_util.dart';
@ -152,7 +152,7 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
!_isLoadingThumbnail) { !_isLoadingThumbnail) {
_isLoadingThumbnail = true; _isLoadingThumbnail = true;
final cachedSmallThumbnail = final cachedSmallThumbnail =
ThumbnailLruCache.get(widget.file, THUMBNAIL_SMALL_SIZE); ThumbnailLruCache.get(widget.file, kThumbnailSmallSize);
if (cachedSmallThumbnail != null) { if (cachedSmallThumbnail != null) {
_imageProvider = Image.memory(cachedSmallThumbnail).image; _imageProvider = Image.memory(cachedSmallThumbnail).image;
_hasLoadedThumbnail = true; _hasLoadedThumbnail = true;
@ -185,11 +185,12 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
} }
return; return;
} }
if (thumbData != null && mounted) { if (thumbData != null && mounted) {
final imageProvider = Image.memory(thumbData).image; final imageProvider = Image.memory(thumbData).image;
_cacheAndRender(imageProvider); _cacheAndRender(imageProvider);
} }
ThumbnailLruCache.put(widget.file, thumbData, THUMBNAIL_SMALL_SIZE); ThumbnailLruCache.put(widget.file, thumbData, kThumbnailSmallSize);
}).catchError((e) { }).catchError((e) {
_logger.warning("Could not load image: ", e); _logger.warning("Could not load image: ", e);
_encounteredErrorLoadingThumbnail = true; _encounteredErrorLoadingThumbnail = true;

View file

@ -2,15 +2,15 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photos/core/cache/image_cache.dart'; import 'package:photos/core/cache/image_cache.dart';
import 'package:photos/core/cache/thumbnail_cache.dart'; import 'package:photos/core/cache/thumbnail_cache.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/event_bus.dart'; import 'package:photos/core/event_bus.dart';
import 'package:photos/db/files_db.dart'; import 'package:photos/db/files_db.dart';
import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/file.dart'; import 'package:photos/models/file.dart';
import 'package:photos/ui/loading_widget.dart'; import 'package:photos/ui/loading_widget.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/utils/file_util.dart'; import 'package:photos/utils/file_util.dart';
import 'package:photos/utils/thumbnail_util.dart'; import 'package:photos/utils/thumbnail_util.dart';
@ -119,7 +119,7 @@ class _ZoomableImageState extends State<ZoomableImage>
!_loadedLargeThumbnail && !_loadedLargeThumbnail &&
!_loadedFinalImage) { !_loadedFinalImage) {
final cachedThumbnail = final cachedThumbnail =
ThumbnailLruCache.get(_photo, THUMBNAIL_SMALL_SIZE); ThumbnailLruCache.get(_photo, kThumbnailSmallSize);
if (cachedThumbnail != null) { if (cachedThumbnail != null) {
_imageProvider = Image.memory(cachedThumbnail).image; _imageProvider = Image.memory(cachedThumbnail).image;
_loadedSmallThumbnail = true; _loadedSmallThumbnail = true;
@ -131,7 +131,7 @@ class _ZoomableImageState extends State<ZoomableImage>
!_loadedFinalImage) { !_loadedFinalImage) {
_loadingLargeThumbnail = true; _loadingLargeThumbnail = true;
final cachedThumbnail = final cachedThumbnail =
ThumbnailLruCache.get(_photo, THUMBNAIL_LARGE_SIZE); ThumbnailLruCache.get(_photo, kThumbnailLargeSize);
if (cachedThumbnail != null) { if (cachedThumbnail != null) {
_onLargeThumbnailLoaded(Image.memory(cachedThumbnail).image, context); _onLargeThumbnailLoaded(Image.memory(cachedThumbnail).image, context);
} else { } else {
@ -141,14 +141,14 @@ class _ZoomableImageState extends State<ZoomableImage>
return; return;
} }
asset asset
.thumbDataWithSize(THUMBNAIL_LARGE_SIZE, THUMBNAIL_LARGE_SIZE) .thumbDataWithSize(kThumbnailLargeSize, kThumbnailLargeSize)
.then((data) { .then((data) {
if (data == null) { if (data == null) {
// Deleted file // Deleted file
return; return;
} }
_onLargeThumbnailLoaded(Image.memory(data).image, context); _onLargeThumbnailLoaded(Image.memory(data).image, context);
ThumbnailLruCache.put(_photo, data, THUMBNAIL_LARGE_SIZE); ThumbnailLruCache.put(_photo, data, kThumbnailLargeSize);
}); });
}); });
} }

View file

@ -1,6 +1,6 @@
import 'dart:io' as io;
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:io' as io;
import 'package:computer/computer.dart'; import 'package:computer/computer.dart';
import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
@ -109,15 +109,15 @@ void chachaDecryptFile(Map<String, dynamic> args) {
} }
Uint8List chachaDecryptData(Map<String, dynamic> args) { Uint8List chachaDecryptData(Map<String, dynamic> args) {
final pullState = final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
Sodium.cryptoSecretstreamXchacha20poly1305InitPull(args["header"], args["key"]); args["header"], args["key"]);
final pullResult = final pullResult = Sodium.cryptoSecretstreamXchacha20poly1305Pull(
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, args["source"], null); pullState, args["source"], null);
return pullResult.m; return pullResult.m;
} }
class CryptoUtil { class CryptoUtil {
static Computer _computer = Computer(); static final Computer _computer = Computer();
static init() { static init() {
_computer.turnOn(workersCount: 4); _computer.turnOn(workersCount: 4);
@ -127,7 +127,7 @@ class CryptoUtil {
static EncryptionResult encryptSync(Uint8List source, Uint8List key) { static EncryptionResult encryptSync(Uint8List source, Uint8List key) {
final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes); final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
final args = Map<String, dynamic>(); final args = <String, dynamic>{};
args["source"] = source; args["source"] = source;
args["nonce"] = nonce; args["nonce"] = nonce;
args["key"] = key; args["key"] = key;
@ -141,7 +141,7 @@ class CryptoUtil {
Uint8List key, Uint8List key,
Uint8List nonce, Uint8List nonce,
) async { ) async {
final args = Map<String, dynamic>(); final args = <String, dynamic>{};
args["cipher"] = cipher; args["cipher"] = cipher;
args["nonce"] = nonce; args["nonce"] = nonce;
args["key"] = key; args["key"] = key;
@ -153,7 +153,7 @@ class CryptoUtil {
Uint8List key, Uint8List key,
Uint8List nonce, Uint8List nonce,
) { ) {
final args = Map<String, dynamic>(); final args = <String, dynamic>{};
args["cipher"] = cipher; args["cipher"] = cipher;
args["nonce"] = nonce; args["nonce"] = nonce;
args["key"] = key; args["key"] = key;
@ -162,7 +162,7 @@ class CryptoUtil {
static Future<EncryptionResult> encryptChaCha( static Future<EncryptionResult> encryptChaCha(
Uint8List source, Uint8List key) async { Uint8List source, Uint8List key) async {
final args = Map<String, dynamic>(); final args = <String, dynamic>{};
args["source"] = source; args["source"] = source;
args["key"] = key; args["key"] = key;
return _computer.compute(chachaEncryptData, param: args); return _computer.compute(chachaEncryptData, param: args);
@ -170,7 +170,7 @@ class CryptoUtil {
static Future<Uint8List> decryptChaCha( static Future<Uint8List> decryptChaCha(
Uint8List source, Uint8List key, Uint8List header) async { Uint8List source, Uint8List key, Uint8List header) async {
final args = Map<String, dynamic>(); final args = <String, dynamic>{};
args["source"] = source; args["source"] = source;
args["key"] = key; args["key"] = key;
args["header"] = header; args["header"] = header;
@ -182,7 +182,7 @@ class CryptoUtil {
String destinationFilePath, { String destinationFilePath, {
Uint8List key, Uint8List key,
}) { }) {
final args = Map<String, dynamic>(); final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath; args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath; args["destinationFilePath"] = destinationFilePath;
args["key"] = key; args["key"] = key;
@ -195,7 +195,7 @@ class CryptoUtil {
Uint8List header, Uint8List header,
Uint8List key, Uint8List key,
) { ) {
final args = Map<String, dynamic>(); final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath; args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath; args["destinationFilePath"] = destinationFilePath;
args["header"] = header; args["header"] = header;

View file

@ -147,7 +147,7 @@ Future<bool> deleteLocalFiles(
final List<String> deletedIDs = []; final List<String> deletedIDs = [];
if (Platform.isAndroid) { if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo; final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt < ANDROID_11_SDK_INT) { if (androidInfo.version.sdkInt < kAndroid11SDKINT) {
deletedIDs.addAll(await _deleteLocalFilesInBatches(context, localIDs)); deletedIDs.addAll(await _deleteLocalFilesInBatches(context, localIDs));
} else { } else {
deletedIDs.addAll(await _deleteLocalFilesInOneShot(context, localIDs)); deletedIDs.addAll(await _deleteLocalFilesInOneShot(context, localIDs));

View file

@ -1,14 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:io' as io; import 'dart:io' as io;
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart'; import 'package:photo_manager/photo_manager.dart';
import 'package:photos/core/constants.dart'; import 'package:photos/core/constants.dart';
import 'package:photos/core/errors.dart'; import 'package:photos/core/errors.dart';
import 'package:photos/models/location.dart';
import 'package:photos/models/file.dart' as ente; import 'package:photos/models/file.dart' as ente;
import 'package:photos/models/location.dart';
import 'file_util.dart'; import 'file_util.dart';
@ -63,12 +62,12 @@ Future<MediaUploadData> _getMediaUploadDataFromAssetFile(ente.File file) async {
throw InvalidFileError(); throw InvalidFileError();
} }
thumbnailData = await asset.thumbDataWithSize( thumbnailData = await asset.thumbDataWithSize(
THUMBNAIL_SMALL_SIZE, kThumbnailSmallSize,
THUMBNAIL_SMALL_SIZE, kThumbnailSmallSize,
quality: THUMBNAIL_QUALITY, quality: kThumbnailQuality,
); );
int compressionAttempts = 0; int compressionAttempts = 0;
while (thumbnailData.length > THUMBNAIL_DATA_LIMIT && while (thumbnailData.length > kThumbnailDataLimit &&
compressionAttempts < kMaximumThumbnailCompressionAttempts) { compressionAttempts < kMaximumThumbnailCompressionAttempts) {
_logger.info("Thumbnail size " + thumbnailData.length.toString()); _logger.info("Thumbnail size " + thumbnailData.length.toString());
thumbnailData = await compressThumbnail(thumbnailData); thumbnailData = await compressThumbnail(thumbnailData);
@ -115,7 +114,7 @@ Future<Uint8List> getThumbnailFromInAppCacheFile(ente.File file) async {
var localPath = file.localID.replaceAll(RegExp(r'ente-upload-cache:'), ''); var localPath = file.localID.replaceAll(RegExp(r'ente-upload-cache:'), '');
var thumbnailData = io.File(localPath).readAsBytesSync(); var thumbnailData = io.File(localPath).readAsBytesSync();
int compressionAttempts = 0; int compressionAttempts = 0;
while (thumbnailData.length > THUMBNAIL_DATA_LIMIT && while (thumbnailData.length > kThumbnailDataLimit &&
compressionAttempts < kMaximumThumbnailCompressionAttempts) { compressionAttempts < kMaximumThumbnailCompressionAttempts) {
_logger.info("Thumbnail size " + thumbnailData.length.toString()); _logger.info("Thumbnail size " + thumbnailData.length.toString());
thumbnailData = await compressThumbnail(thumbnailData); thumbnailData = await compressThumbnail(thumbnailData);

View file

@ -2,12 +2,12 @@ import 'dart:async';
import 'dart:io' as io; import 'dart:io' as io;
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart'; import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart';
import 'package:photos/core/cache/image_cache.dart'; import 'package:photos/core/cache/image_cache.dart';
import 'package:photos/core/cache/thumbnail_cache.dart'; import 'package:photos/core/cache/thumbnail_cache.dart';
import 'package:photos/core/cache/video_cache_manager.dart'; import 'package:photos/core/cache/video_cache_manager.dart';
@ -175,8 +175,8 @@ Uint8List decryptFileKey(ente.File file) {
Future<Uint8List> compressThumbnail(Uint8List thumbnail) { Future<Uint8List> compressThumbnail(Uint8List thumbnail) {
return FlutterImageCompress.compressWithList( return FlutterImageCompress.compressWithList(
thumbnail, thumbnail,
minHeight: COMPRESSED_THUMBNAIL_RESOLUTION, minHeight: kCompressedThumbnailResolution,
minWidth: COMPRESSED_THUMBNAIL_RESOLUTION, minWidth: kCompressedThumbnailResolution,
quality: 25, quality: 25,
); );
} }

View file

@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:io' as io;
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
@ -16,7 +16,6 @@ import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/file_util.dart'; import 'package:photos/utils/file_util.dart';
import 'dart:io' as io; import 'dart:io' as io;
import 'file_uploader_util.dart'; import 'file_uploader_util.dart';
final _logger = Logger("ThumbnailUtil"); final _logger = Logger("ThumbnailUtil");
@ -60,7 +59,7 @@ Future<Uint8List> getThumbnailFromServer(File file) async {
} }
Future<Uint8List> getThumbnailFromLocal(File file) async { Future<Uint8List> getThumbnailFromLocal(File file) async {
if (ThumbnailLruCache.get(file, THUMBNAIL_SMALL_SIZE) != null) { if (ThumbnailLruCache.get(file, kThumbnailSmallSize) != null) {
return ThumbnailLruCache.get(file); return ThumbnailLruCache.get(file);
} }
final cachedThumbnail = getCachedThumbnail(file); final cachedThumbnail = getCachedThumbnail(file);
@ -73,7 +72,7 @@ Future<Uint8List> getThumbnailFromLocal(File file) async {
return getThumbnailFromInAppCacheFile(file) return getThumbnailFromInAppCacheFile(file)
.then((data) { .then((data) {
if (data != null) { if (data != null) {
ThumbnailLruCache.put(file, data, THUMBNAIL_SMALL_SIZE); ThumbnailLruCache.put(file, data, kThumbnailSmallSize);
} }
return data; return data;
}); });
@ -83,10 +82,10 @@ Future<Uint8List> getThumbnailFromLocal(File file) async {
return null; return null;
} }
return asset return asset
.thumbDataWithSize(THUMBNAIL_SMALL_SIZE, THUMBNAIL_SMALL_SIZE, .thumbDataWithSize(kThumbnailSmallSize, kThumbnailSmallSize,
quality: THUMBNAIL_QUALITY) quality: kThumbnailQuality)
.then((data) { .then((data) {
ThumbnailLruCache.put(file, data, THUMBNAIL_SMALL_SIZE); ThumbnailLruCache.put(file, data, kThumbnailSmallSize);
return data; return data;
}); });
}); });
@ -146,7 +145,7 @@ Future<void> _downloadAndDecryptThumbnail(FileDownloadItem item) async {
Sodium.base642bin(file.thumbnailDecryptionHeader), Sodium.base642bin(file.thumbnailDecryptionHeader),
); );
final thumbnailSize = data.length; final thumbnailSize = data.length;
if (thumbnailSize > THUMBNAIL_DATA_LIMIT) { if (thumbnailSize > kThumbnailDataLimit) {
data = await compressThumbnail(data); data = await compressThumbnail(data);
} }
ThumbnailLruCache.put(item.file, data); ThumbnailLruCache.put(item.file, data);