Merge remote-tracking branch 'origin/master' into share_to_ente
This commit is contained in:
commit
77c2489714
28 changed files with 301 additions and 203 deletions
4
lib/core/cache/thumbnail_cache.dart
vendored
4
lib/core/cache/thumbnail_cache.dart
vendored
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
));
|
));
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 " +
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue