2020-03-24 19:59:36 +00:00
|
|
|
import 'dart:io';
|
|
|
|
|
2020-05-01 18:20:12 +00:00
|
|
|
import 'package:photos/models/photo.dart';
|
2020-03-24 19:59:36 +00:00
|
|
|
import 'package:path/path.dart';
|
|
|
|
import 'package:sqflite/sqflite.dart';
|
|
|
|
import 'package:path_provider/path_provider.dart';
|
|
|
|
|
2020-05-18 15:38:15 +00:00
|
|
|
class PhotoDB {
|
2020-04-30 16:15:18 +00:00
|
|
|
static final _databaseName = "ente.db";
|
2020-03-24 19:59:36 +00:00
|
|
|
static final _databaseVersion = 1;
|
|
|
|
|
2020-03-27 16:07:55 +00:00
|
|
|
static final table = 'photos';
|
2020-03-26 14:39:31 +00:00
|
|
|
|
2020-04-17 20:37:04 +00:00
|
|
|
static final columnGeneratedId = '_id';
|
2020-04-12 12:38:49 +00:00
|
|
|
static final columnUploadedFileId = 'uploaded_file_id';
|
2020-04-11 21:29:07 +00:00
|
|
|
static final columnLocalId = 'local_id';
|
2020-04-24 12:40:24 +00:00
|
|
|
static final columnTitle = 'title';
|
2020-05-17 12:39:38 +00:00
|
|
|
static final columnDeviceFolder = 'device_folder';
|
2020-05-22 18:22:55 +00:00
|
|
|
static final columnRemoteFolderId = 'remote_folder_id';
|
2020-04-24 12:40:24 +00:00
|
|
|
static final columnRemotePath = 'remote_path';
|
2020-04-12 12:38:49 +00:00
|
|
|
static final columnIsDeleted = 'is_deleted';
|
2020-04-13 11:28:01 +00:00
|
|
|
static final columnCreateTimestamp = 'create_timestamp';
|
2020-03-27 16:07:55 +00:00
|
|
|
static final columnSyncTimestamp = 'sync_timestamp';
|
2020-03-24 19:59:36 +00:00
|
|
|
|
|
|
|
// make this a singleton class
|
2020-05-18 15:38:15 +00:00
|
|
|
PhotoDB._privateConstructor();
|
|
|
|
static final PhotoDB instance = PhotoDB._privateConstructor();
|
2020-03-24 19:59:36 +00:00
|
|
|
|
|
|
|
// only have a single app-wide reference to the database
|
|
|
|
static Database _database;
|
|
|
|
Future<Database> get database async {
|
|
|
|
if (_database != null) return _database;
|
|
|
|
// lazily instantiate the db the first time it is accessed
|
|
|
|
_database = await _initDatabase();
|
|
|
|
return _database;
|
|
|
|
}
|
2020-03-26 14:39:31 +00:00
|
|
|
|
2020-03-24 19:59:36 +00:00
|
|
|
// this opens the database (and creates it if it doesn't exist)
|
|
|
|
_initDatabase() async {
|
|
|
|
Directory documentsDirectory = await getApplicationDocumentsDirectory();
|
|
|
|
String path = join(documentsDirectory.path, _databaseName);
|
|
|
|
return await openDatabase(path,
|
2020-03-26 14:39:31 +00:00
|
|
|
version: _databaseVersion, onCreate: _onCreate);
|
2020-03-24 19:59:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SQL code to create the database table
|
|
|
|
Future _onCreate(Database db, int version) async {
|
|
|
|
await db.execute('''
|
|
|
|
CREATE TABLE $table (
|
2020-04-17 20:37:04 +00:00
|
|
|
$columnGeneratedId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
2020-04-11 21:29:07 +00:00
|
|
|
$columnLocalId TEXT,
|
2020-04-12 12:38:49 +00:00
|
|
|
$columnUploadedFileId INTEGER NOT NULL,
|
2020-04-24 12:40:24 +00:00
|
|
|
$columnTitle TEXT NOT NULL,
|
2020-05-17 12:39:38 +00:00
|
|
|
$columnDeviceFolder TEXT NOT NULL,
|
2020-05-22 18:22:55 +00:00
|
|
|
$columnRemoteFolderId INTEGER DEFAULT -1,
|
2020-04-24 12:40:24 +00:00
|
|
|
$columnRemotePath TEXT,
|
2020-04-12 12:38:49 +00:00
|
|
|
$columnIsDeleted INTEGER DEFAULT 0,
|
2020-04-13 11:28:01 +00:00
|
|
|
$columnCreateTimestamp TEXT NOT NULL,
|
2020-03-28 13:56:06 +00:00
|
|
|
$columnSyncTimestamp TEXT
|
2020-03-24 19:59:36 +00:00
|
|
|
)
|
|
|
|
''');
|
|
|
|
}
|
2020-03-26 14:39:31 +00:00
|
|
|
|
2020-03-27 16:07:55 +00:00
|
|
|
Future<int> insertPhoto(Photo photo) async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
2020-03-30 14:28:46 +00:00
|
|
|
return await db.insert(table, _getRowForPhoto(photo));
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<dynamic>> insertPhotos(List<Photo> photos) async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
2020-03-30 14:28:46 +00:00
|
|
|
var batch = db.batch();
|
|
|
|
int batchCounter = 0;
|
|
|
|
for (Photo photo in photos) {
|
|
|
|
if (batchCounter == 400) {
|
|
|
|
await batch.commit();
|
|
|
|
batch = db.batch();
|
|
|
|
}
|
|
|
|
batch.insert(table, _getRowForPhoto(photo));
|
|
|
|
batchCounter++;
|
|
|
|
}
|
|
|
|
return await batch.commit();
|
2020-03-26 14:39:31 +00:00
|
|
|
}
|
|
|
|
|
2020-03-27 16:07:55 +00:00
|
|
|
Future<List<Photo>> getAllPhotos() async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
|
|
|
final results = await db.query(
|
|
|
|
table,
|
|
|
|
where: '$columnIsDeleted = 0',
|
|
|
|
orderBy: '$columnCreateTimestamp DESC',
|
|
|
|
);
|
2020-04-12 12:38:49 +00:00
|
|
|
return _convertToPhotos(results);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<Photo>> getAllDeletedPhotos() async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
|
|
|
final results = await db.query(
|
|
|
|
table,
|
|
|
|
where: '$columnIsDeleted = 1',
|
|
|
|
orderBy: '$columnCreateTimestamp DESC',
|
|
|
|
);
|
2020-03-28 13:56:06 +00:00
|
|
|
return _convertToPhotos(results);
|
2020-03-27 16:07:55 +00:00
|
|
|
}
|
|
|
|
|
2020-03-28 13:56:06 +00:00
|
|
|
Future<List<Photo>> getPhotosToBeUploaded() async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
|
|
|
final results = await db.query(
|
|
|
|
table,
|
|
|
|
where: '$columnUploadedFileId = -1',
|
|
|
|
);
|
2020-03-28 13:56:06 +00:00
|
|
|
return _convertToPhotos(results);
|
2020-03-24 19:59:36 +00:00
|
|
|
}
|
|
|
|
|
2020-05-17 18:42:03 +00:00
|
|
|
Future<Photo> getMatchingPhoto(String localId, String title,
|
|
|
|
String deviceFolder, int createTimestamp) async {
|
|
|
|
final db = await instance.database;
|
|
|
|
final rows = await db.query(
|
|
|
|
table,
|
|
|
|
where:
|
|
|
|
'$columnLocalId=? AND $columnTitle=? AND $columnDeviceFolder=? AND $columnCreateTimestamp=?',
|
|
|
|
whereArgs: [localId, title, deviceFolder, createTimestamp],
|
|
|
|
);
|
|
|
|
if (rows.isNotEmpty) {
|
|
|
|
return _getPhotoFromRow(rows[0]);
|
|
|
|
} else {
|
|
|
|
throw ("No matching photo found");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<int> updatePhoto(int generatedId, int uploadedId, String remotePath,
|
|
|
|
int syncTimestamp) async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
|
|
|
final values = new Map<String, dynamic>();
|
2020-05-17 18:42:03 +00:00
|
|
|
values[columnUploadedFileId] = uploadedId;
|
2020-04-24 12:40:24 +00:00
|
|
|
values[columnRemotePath] = remotePath;
|
|
|
|
values[columnSyncTimestamp] = syncTimestamp;
|
2020-05-06 18:13:24 +00:00
|
|
|
return await db.update(
|
|
|
|
table,
|
|
|
|
values,
|
|
|
|
where: '$columnGeneratedId = ?',
|
|
|
|
whereArgs: [generatedId],
|
|
|
|
);
|
2020-03-24 19:59:36 +00:00
|
|
|
}
|
|
|
|
|
2020-04-09 19:03:11 +00:00
|
|
|
Future<Photo> getPhotoByPath(String path) async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
|
|
|
final rows = await db.query(
|
|
|
|
table,
|
|
|
|
where: '$columnRemotePath =?',
|
|
|
|
whereArgs: [path],
|
|
|
|
);
|
|
|
|
if (rows.isNotEmpty) {
|
2020-05-17 18:42:03 +00:00
|
|
|
return _getPhotoFromRow(rows[0]);
|
2020-04-05 14:45:04 +00:00
|
|
|
} else {
|
|
|
|
throw ("No cached photo");
|
|
|
|
}
|
2020-03-24 19:59:36 +00:00
|
|
|
}
|
2020-03-26 14:39:31 +00:00
|
|
|
|
2020-05-17 18:42:03 +00:00
|
|
|
// TODO: Remove deleted photos on remote
|
2020-04-18 18:46:38 +00:00
|
|
|
Future<int> markPhotoForDeletion(Photo photo) async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
2020-04-12 12:38:49 +00:00
|
|
|
var values = new Map<String, dynamic>();
|
|
|
|
values[columnIsDeleted] = 1;
|
2020-05-06 18:13:24 +00:00
|
|
|
return db.update(
|
|
|
|
table,
|
|
|
|
values,
|
|
|
|
where: '$columnGeneratedId =?',
|
|
|
|
whereArgs: [photo.generatedId],
|
|
|
|
);
|
2020-04-12 12:38:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<int> deletePhoto(Photo photo) async {
|
2020-05-06 18:13:24 +00:00
|
|
|
final db = await instance.database;
|
|
|
|
return db.delete(
|
|
|
|
table,
|
|
|
|
where: '$columnGeneratedId =?',
|
|
|
|
whereArgs: [photo.generatedId],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-22 18:22:55 +00:00
|
|
|
Future<int> deletePhotosInRemoteFolder(int folderId) async {
|
|
|
|
final db = await instance.database;
|
|
|
|
return db.delete(
|
|
|
|
table,
|
|
|
|
where: '$columnRemoteFolderId =?',
|
|
|
|
whereArgs: [folderId],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:13:24 +00:00
|
|
|
Future<List<String>> getDistinctPaths() async {
|
|
|
|
final db = await instance.database;
|
|
|
|
final rows = await db.query(
|
|
|
|
table,
|
2020-05-17 12:39:38 +00:00
|
|
|
columns: [columnDeviceFolder],
|
2020-05-06 18:13:24 +00:00
|
|
|
distinct: true,
|
|
|
|
);
|
|
|
|
List<String> result = List<String>();
|
|
|
|
for (final row in rows) {
|
2020-05-17 12:39:38 +00:00
|
|
|
result.add(row[columnDeviceFolder]);
|
2020-05-06 18:13:24 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<Photo> getLatestPhotoInPath(String path) async {
|
|
|
|
final db = await instance.database;
|
|
|
|
var rows = await db.query(
|
|
|
|
table,
|
2020-05-17 12:39:38 +00:00
|
|
|
where: '$columnDeviceFolder =?',
|
2020-05-06 18:13:24 +00:00
|
|
|
whereArgs: [path],
|
|
|
|
orderBy: '$columnCreateTimestamp DESC',
|
|
|
|
limit: 1,
|
|
|
|
);
|
|
|
|
if (rows.isNotEmpty) {
|
2020-05-17 18:42:03 +00:00
|
|
|
return _getPhotoFromRow(rows[0]);
|
2020-05-06 18:13:24 +00:00
|
|
|
} else {
|
|
|
|
throw ("No photo found in path");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 18:22:55 +00:00
|
|
|
Future<Photo> getLatestPhotoInRemoteFolder(int folderId) async {
|
|
|
|
final db = await instance.database;
|
|
|
|
var rows = await db.query(
|
|
|
|
table,
|
|
|
|
where: '$columnRemoteFolderId =?',
|
|
|
|
whereArgs: [folderId],
|
|
|
|
orderBy: '$columnCreateTimestamp DESC',
|
|
|
|
limit: 1,
|
|
|
|
);
|
|
|
|
if (rows.isNotEmpty) {
|
|
|
|
return _getPhotoFromRow(rows[0]);
|
|
|
|
} else {
|
|
|
|
throw ("No photo found in path");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-06 18:13:24 +00:00
|
|
|
Future<Photo> getLatestPhotoAmongGeneratedIds(
|
|
|
|
List<String> generatedIds) async {
|
|
|
|
final db = await instance.database;
|
|
|
|
var rows = await db.query(
|
|
|
|
table,
|
|
|
|
where: '$columnGeneratedId IN (${generatedIds.join(",")})',
|
|
|
|
orderBy: '$columnCreateTimestamp DESC',
|
|
|
|
limit: 1,
|
|
|
|
);
|
|
|
|
if (rows.isNotEmpty) {
|
2020-05-17 18:42:03 +00:00
|
|
|
return _getPhotoFromRow(rows[0]);
|
2020-05-06 18:13:24 +00:00
|
|
|
} else {
|
|
|
|
throw ("No photo found with ids " + generatedIds.join(", ").toString());
|
|
|
|
}
|
2020-04-12 12:38:49 +00:00
|
|
|
}
|
|
|
|
|
2020-03-28 13:56:06 +00:00
|
|
|
List<Photo> _convertToPhotos(List<Map<String, dynamic>> results) {
|
|
|
|
var photos = List<Photo>();
|
|
|
|
for (var result in results) {
|
2020-05-17 18:42:03 +00:00
|
|
|
photos.add(_getPhotoFromRow(result));
|
2020-03-28 13:56:06 +00:00
|
|
|
}
|
|
|
|
return photos;
|
|
|
|
}
|
2020-03-30 14:28:46 +00:00
|
|
|
|
|
|
|
Map<String, dynamic> _getRowForPhoto(Photo photo) {
|
|
|
|
var row = new Map<String, dynamic>();
|
2020-04-11 21:29:07 +00:00
|
|
|
row[columnLocalId] = photo.localId;
|
2020-04-12 12:38:49 +00:00
|
|
|
row[columnUploadedFileId] =
|
|
|
|
photo.uploadedFileId == null ? -1 : photo.uploadedFileId;
|
2020-04-24 12:40:24 +00:00
|
|
|
row[columnTitle] = photo.title;
|
2020-05-17 12:39:38 +00:00
|
|
|
row[columnDeviceFolder] = photo.deviceFolder;
|
2020-05-22 18:22:55 +00:00
|
|
|
row[columnRemoteFolderId] = photo.remoteFolderId;
|
2020-04-24 12:40:24 +00:00
|
|
|
row[columnRemotePath] = photo.remotePath;
|
2020-04-13 11:28:01 +00:00
|
|
|
row[columnCreateTimestamp] = photo.createTimestamp;
|
2020-03-30 14:28:46 +00:00
|
|
|
row[columnSyncTimestamp] = photo.syncTimestamp;
|
|
|
|
return row;
|
|
|
|
}
|
2020-04-12 12:38:49 +00:00
|
|
|
|
2020-05-17 18:42:03 +00:00
|
|
|
Photo _getPhotoFromRow(Map<String, dynamic> row) {
|
2020-04-12 12:38:49 +00:00
|
|
|
Photo photo = Photo();
|
2020-04-17 20:37:04 +00:00
|
|
|
photo.generatedId = row[columnGeneratedId];
|
2020-04-12 12:38:49 +00:00
|
|
|
photo.localId = row[columnLocalId];
|
|
|
|
photo.uploadedFileId = row[columnUploadedFileId];
|
2020-04-24 12:40:24 +00:00
|
|
|
photo.title = row[columnTitle];
|
2020-05-17 12:39:38 +00:00
|
|
|
photo.deviceFolder = row[columnDeviceFolder];
|
2020-05-22 18:22:55 +00:00
|
|
|
photo.remoteFolderId = row[columnRemoteFolderId];
|
2020-04-24 12:40:24 +00:00
|
|
|
photo.remotePath = row[columnRemotePath];
|
2020-04-13 11:28:01 +00:00
|
|
|
photo.createTimestamp = int.parse(row[columnCreateTimestamp]);
|
2020-04-12 12:38:49 +00:00
|
|
|
photo.syncTimestamp = row[columnSyncTimestamp] == null
|
|
|
|
? -1
|
|
|
|
: int.parse(row[columnSyncTimestamp]);
|
|
|
|
return photo;
|
|
|
|
}
|
2020-03-26 14:39:31 +00:00
|
|
|
}
|