Integrate APIs to fetch shared folders

This commit is contained in:
Vishnu Mohandas 2020-05-22 23:52:55 +05:30
parent 496e1d2f81
commit 2f3951f124
6 changed files with 237 additions and 41 deletions

105
lib/db/folder_db.dart Normal file
View file

@ -0,0 +1,105 @@
import 'dart:io';
import 'package:path/path.dart';
import 'package:photos/models/folder.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
class FolderDB {
static final _databaseName = "ente.db";
static final _databaseVersion = 1;
static final table = 'folders';
static final columnId = 'id';
static final columnName = 'name';
static final columnOwner = 'owner';
static final columnDeviceFolder = 'device_folder';
static final columnSharedWith = 'shared_with';
static final columnUpdateTimestamp = 'update_timestamp';
FolderDB._privateConstructor();
static final FolderDB instance = FolderDB._privateConstructor();
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await _initDatabase();
return _database;
}
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion, onCreate: _onCreate);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnId INTEGER PRIMARY KEY NOT NULL,
$columnName TEXT NOT NULL,
$columnOwner TEXT NOT NULL,
$columnDeviceFolder TEXT NOT NULL,
$columnSharedWith TEXT NOT NULL,
$columnUpdateTimestamp INTEGER NOT NULL,
UNIQUE($columnOwner, $columnDeviceFolder)
)
''');
}
Future<int> putFolder(Folder folder) async {
final db = await instance.database;
return await db.insert(table, _getRowForFolder(folder),
conflictAlgorithm: ConflictAlgorithm.replace);
}
Future<List<Folder>> getFolders() async {
final db = await instance.database;
final results = await db.query(
table,
orderBy: '$columnUpdateTimestamp DESC',
);
return _convertToFolders(results);
}
Future<int> deleteFolder(Folder folder) async {
final db = await instance.database;
return db.delete(
table,
where: '$columnId =?',
whereArgs: [folder.id],
);
}
List<Folder> _convertToFolders(List<Map<String, dynamic>> results) {
var folders = List<Folder>();
for (var result in results) {
folders.add(_getFolderFromRow(result));
}
return folders;
}
Map<String, dynamic> _getRowForFolder(Folder folder) {
var row = new Map<String, dynamic>();
row[columnId] = folder.id;
row[columnName] = folder.name;
row[columnOwner] = folder.owner;
row[columnDeviceFolder] = folder.deviceFolder;
row[columnSharedWith] = folder.sharedWith.toString();
row[columnUpdateTimestamp] = folder.updateTimestamp;
return row;
}
Folder _getFolderFromRow(Map<String, dynamic> row) {
return Folder(
row[columnId],
row[columnName],
row[columnOwner],
row[columnDeviceFolder],
Set<String>.from(row[columnSharedWith]),
row[columnUpdateTimestamp],
);
}
}

View file

@ -16,6 +16,7 @@ class PhotoDB {
static final columnLocalId = 'local_id';
static final columnTitle = 'title';
static final columnDeviceFolder = 'device_folder';
static final columnRemoteFolderId = 'remote_folder_id';
static final columnRemotePath = 'remote_path';
static final columnIsDeleted = 'is_deleted';
static final columnCreateTimestamp = 'create_timestamp';
@ -51,6 +52,7 @@ class PhotoDB {
$columnUploadedFileId INTEGER NOT NULL,
$columnTitle TEXT NOT NULL,
$columnDeviceFolder TEXT NOT NULL,
$columnRemoteFolderId INTEGER DEFAULT -1,
$columnRemotePath TEXT,
$columnIsDeleted INTEGER DEFAULT 0,
$columnCreateTimestamp TEXT NOT NULL,
@ -175,6 +177,15 @@ class PhotoDB {
);
}
Future<int> deletePhotosInRemoteFolder(int folderId) async {
final db = await instance.database;
return db.delete(
table,
where: '$columnRemoteFolderId =?',
whereArgs: [folderId],
);
}
Future<List<String>> getDistinctPaths() async {
final db = await instance.database;
final rows = await db.query(
@ -205,6 +216,22 @@ class PhotoDB {
}
}
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");
}
}
Future<Photo> getLatestPhotoAmongGeneratedIds(
List<String> generatedIds) async {
final db = await instance.database;
@ -236,6 +263,7 @@ class PhotoDB {
photo.uploadedFileId == null ? -1 : photo.uploadedFileId;
row[columnTitle] = photo.title;
row[columnDeviceFolder] = photo.deviceFolder;
row[columnRemoteFolderId] = photo.remoteFolderId;
row[columnRemotePath] = photo.remotePath;
row[columnCreateTimestamp] = photo.createTimestamp;
row[columnSyncTimestamp] = photo.syncTimestamp;
@ -249,6 +277,7 @@ class PhotoDB {
photo.uploadedFileId = row[columnUploadedFileId];
photo.title = row[columnTitle];
photo.deviceFolder = row[columnDeviceFolder];
photo.remoteFolderId = row[columnRemoteFolderId];
photo.remotePath = row[columnRemotePath];
photo.createTimestamp = int.parse(row[columnCreateTimestamp]);
photo.syncTimestamp = row[columnSyncTimestamp] == null

View file

@ -1,18 +1,77 @@
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/db/folder_db.dart';
import 'package:photos/db/photo_db.dart';
import 'package:photos/models/folder.dart';
import 'package:photos/models/photo.dart';
class FolderSharingService {
final _logger = Logger("FolderSharingService");
final _dio = Dio();
static final _diffLimit = 100;
FolderSharingService._privateConstructor();
static final FolderSharingService instance =
FolderSharingService._privateConstructor();
void sync() {
// TODO
getFolders().then((f) async {
var folders = f.toSet();
var currentFolders = await FolderDB.instance.getFolders();
for (final currentFolder in currentFolders) {
if (!folders.contains(currentFolder)) {
await FolderDB.instance.deleteFolder(currentFolder);
await PhotoDB.instance.deletePhotosInRemoteFolder(currentFolder.id);
}
}
for (final folder in folders) {
await syncDiff(folder);
await FolderDB.instance.putFolder(folder);
}
});
}
Future<void> syncDiff(Folder folder) async {
int lastSyncTimestamp = 0;
try {
Photo photo =
await PhotoDB.instance.getLatestPhotoInRemoteFolder(folder.id);
lastSyncTimestamp = photo.syncTimestamp;
} catch (e) {
// Folder has never been synced
}
var photos = await getDiff(folder.id, lastSyncTimestamp, _diffLimit);
await PhotoDB.instance.insertPhotos(photos);
if (photos.length == _diffLimit) {
await syncDiff(folder);
}
}
Future<List<Photo>> getDiff(
int folderId, int sinceTimestamp, int limit) async {
Response response = await _dio.get(
Configuration.instance.getHttpEndpoint() +
"/folders/diff" +
folderId.toString(),
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
queryParameters: {
"sinceTimestamp": sinceTimestamp,
"limit": limit,
},
).catchError((e) => _logger.severe(e));
if (response != null) {
return (response.data["diff"] as List).map((p) {
Photo photo = new Photo.fromJson(p);
photo.remoteFolderId = folderId;
return photo;
}).toList();
} else {
return null;
}
}
Future<List<Folder>> getFolders() async {
@ -29,46 +88,46 @@ class FolderSharingService {
});
}
Future<Map<String, bool>> getSharingStatus(String path) async {
Future<Folder> getFolder(String deviceFolder) async {
return _dio
.get(
Configuration.instance.getHttpEndpoint() + "/folders/folder/",
queryParameters: {
"deviceFolder": deviceFolder,
},
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then((response) => Folder.fromMap(response.data));
}
Future<Map<String, bool>> getSharingStatus(Folder folder) async {
return _dio
.get(
Configuration.instance.getHttpEndpoint() + "/users",
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then((usersResponse) {
return getFolders().then((folders) {
Folder sharedFolder;
for (var folder in folders) {
if (folder.owner == Configuration.instance.getUsername() &&
folder.deviceFolder == path) {
sharedFolder = folder;
break;
}
.then((response) {
final users = (response.data["users"] as List).toList();
final result = Map<String, bool>();
for (final user in users) {
if (user != Configuration.instance.getUsername()) {
result[user] = folder.sharedWith.contains(user);
}
var sharedUsers = Set<String>();
if (sharedFolder != null) {
sharedUsers.addAll(sharedFolder.sharedWith);
}
final result = Map<String, bool>();
(usersResponse.data["users"] as List).forEach((user) {
if (user != Configuration.instance.getUsername()) {
result[user] = sharedUsers.contains(user);
}
});
return result;
});
}
return result;
});
}
Future<void> shareFolder(String name, String path, Set<String> users) {
var folder = Folder(0, name, Configuration.instance.getUsername(), path,
users.toList(), -1);
Future<void> updateFolder(Folder folder) {
log("Updating folder: " + folder.toString());
return _dio
.put(Configuration.instance.getHttpEndpoint() + "/folders/",
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
data: folder.toMap())
.then((response) => log(response.toString()));
.then((response) => log(response.toString()))
.catchError((error) => log(error.toString()));
}
}

View file

@ -7,7 +7,7 @@ class Folder {
final String name;
final String owner;
final String deviceFolder;
final List<String> sharedWith;
final Set<String> sharedWith;
final int updateTimestamp;
Folder(
@ -27,7 +27,7 @@ class Folder {
map['name'],
map['owner'],
map['deviceFolder'],
List<String>.from(map['sharedWith']),
Set<String>.from(map['sharedWith']),
map['updateTimestamp'],
);
}
@ -43,7 +43,7 @@ class Folder {
'name': name,
'owner': owner,
'deviceFolder': deviceFolder,
'sharedWith': sharedWith,
'sharedWith': sharedWith.toList(),
'updateTimestamp': updateTimestamp,
};
}
@ -61,7 +61,7 @@ class Folder {
o.name == name &&
o.owner == owner &&
o.deviceFolder == deviceFolder &&
listEquals(o.sharedWith, sharedWith) &&
setEquals(o.sharedWith, sharedWith) &&
o.updateTimestamp == updateTimestamp;
}

View file

@ -11,6 +11,7 @@ class Photo {
String localId;
String title;
String deviceFolder;
int remoteFolderId;
String remotePath;
int createTimestamp;
int syncTimestamp;

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:photos/folder_service.dart';
import 'package:photos/models/folder.dart';
import 'package:photos/ui/loading_widget.dart';
class ShareFolderWidget extends StatefulWidget {
@ -18,15 +19,18 @@ class ShareFolderWidget extends StatefulWidget {
}
class _ShareFolderWidgetState extends State<ShareFolderWidget> {
Map<String, bool> _sharingStatus;
Folder _folder;
@override
Widget build(BuildContext context) {
return FutureBuilder<Map<String, bool>>(
future: FolderSharingService.instance.getSharingStatus(widget.path),
future:
FolderSharingService.instance.getFolder(widget.path).then((folder) {
_folder = folder;
return FolderSharingService.instance.getSharingStatus(folder);
}),
builder: (context, snapshot) {
if (snapshot.hasData) {
_sharingStatus = snapshot.data;
return _getSharingDialog(snapshot.data);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
@ -52,16 +56,14 @@ class _ShareFolderWidgetState extends State<ShareFolderWidget> {
child: Text("Share"),
onPressed: () async {
var sharedWith = Set<String>();
for (var user in _sharingStatus.keys) {
if (_sharingStatus[user]) {
for (var user in sharingStatus.keys) {
if (sharingStatus[user]) {
sharedWith.add(user);
}
}
await FolderSharingService.instance.shareFolder(
"namewa",
widget.path,
sharedWith,
);
_folder.sharedWith.clear();
_folder.sharedWith.addAll(sharedWith);
await FolderSharingService.instance.updateFolder(_folder);
Navigator.of(context).pop();
},
),