Remove SharedCollection

This commit is contained in:
Vishnu Mohandas 2020-10-28 20:55:32 +05:30
parent 42507c4354
commit 3630a25fb5
9 changed files with 77 additions and 277 deletions

View file

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:photos/models/collection.dart';
import 'package:photos/models/shared_collection.dart';
import 'package:sqflite/sqflite.dart';
class CollectionsDB {
@ -11,7 +10,6 @@ class CollectionsDB {
static final _databaseVersion = 1;
static final collectionsTable = 'collections';
static final sharedCollectionsTable = 'shared_collections';
static final columnID = 'collection_id';
static final columnOwnerID = 'owner_id';
@ -49,7 +47,7 @@ class CollectionsDB {
$columnID INTEGER PRIMARY KEY NOT NULL,
$columnOwnerID INTEGER NOT NULL,
$columnEncryptedKey TEXT NOT NULL,
$columnKeyDecryptionNonce TEXT NOT NULL,
$columnKeyDecryptionNonce TEXT,
$columnName TEXT NOT NULL,
$columnType TEXT NOT NULL,
$columnEncryptedPath TEXT,
@ -57,17 +55,6 @@ class CollectionsDB {
$columnCreationTime TEXT NOT NULL
)
''');
await db.execute('''
CREATE TABLE $sharedCollectionsTable (
$columnID INTEGER PRIMARY KEY NOT NULL,
$columnOwnerID INTEGER NOT NULL,
$columnEncryptedKey TEXT NOT NULL,
$columnName TEXT NOT NULL,
$columnType TEXT NOT NULL,
$columnCreationTime TEXT NOT NULL
)
''');
}
Future<List<dynamic>> insert(List<Collection> collections) async {
@ -80,18 +67,6 @@ class CollectionsDB {
return await batch.commit();
}
Future<List<dynamic>> insertSharedCollections(
List<SharedCollection> collections) async {
final db = await instance.database;
var batch = db.batch();
for (final collection in collections) {
batch.insert(
sharedCollectionsTable, _getRowForSharedCollection(collection),
conflictAlgorithm: ConflictAlgorithm.replace);
}
return await batch.commit();
}
Future<List<Collection>> getAllCollections() async {
final db = await instance.database;
final rows = await db.query(collectionsTable);
@ -102,16 +77,6 @@ class CollectionsDB {
return collections;
}
Future<List<SharedCollection>> getAllSharedCollections() async {
final db = await instance.database;
final rows = await db.query(sharedCollectionsTable);
final collections = List<SharedCollection>();
for (final row in rows) {
collections.add(_convertToSharedCollection(row));
}
return collections;
}
Future<int> getLastCollectionCreationTime() async {
final db = await instance.database;
final rows = await db.query(
@ -126,20 +91,6 @@ class CollectionsDB {
}
}
Future<int> getLastSharedCollectionCreationTime() async {
final db = await instance.database;
final rows = await db.query(
sharedCollectionsTable,
orderBy: '$columnCreationTime DESC',
limit: 1,
);
if (rows.isNotEmpty) {
return int.parse(rows[0][columnCreationTime]);
} else {
return null;
}
}
Map<String, dynamic> _getRowForCollection(Collection collection) {
var row = new Map<String, dynamic>();
row[columnID] = collection.id;
@ -168,26 +119,4 @@ class CollectionsDB {
int.parse(row[columnCreationTime]),
);
}
Map<String, dynamic> _getRowForSharedCollection(SharedCollection collection) {
var row = new Map<String, dynamic>();
row[columnID] = collection.id;
row[columnOwnerID] = collection.ownerID;
row[columnEncryptedKey] = collection.encryptedKey;
row[columnName] = collection.name;
row[columnType] = Collection.typeToString(collection.type);
row[columnCreationTime] = collection.creationTime;
return row;
}
SharedCollection _convertToSharedCollection(Map<String, dynamic> row) {
return SharedCollection(
row[columnID],
row[columnOwnerID],
row[columnEncryptedKey],
row[columnName],
Collection.typeFromString(row[columnType]),
int.parse(row[columnCreationTime]),
);
}
}

View file

@ -290,7 +290,8 @@ class FilesDB {
final db = await instance.database;
return db.delete(
table,
where: '$columnCollectionID =? AND ',
where:
'$columnCollectionID =? AND $columnUploadedFileID IN (${fileIDs.join(', ')})',
whereArgs: [collectionID],
);
}
@ -322,7 +323,7 @@ class FilesDB {
if (rows.isNotEmpty) {
return _getFileFromRow(rows[0]);
} else {
throw ("No file found in collection " + collectionID.toString());
return null;
}
}

View file

@ -1,96 +0,0 @@
import 'dart:convert';
import 'package:photos/models/collection.dart';
class SharedCollection {
final int id;
final int ownerID;
final String encryptedKey;
final String name;
final CollectionType type;
final int creationTime;
SharedCollection(
this.id,
this.ownerID,
this.encryptedKey,
this.name,
this.type,
this.creationTime,
);
SharedCollection copyWith({
int id,
int ownerID,
String encryptedKey,
String name,
CollectionType type,
int creationTime,
}) {
return SharedCollection(
id ?? this.id,
ownerID ?? this.ownerID,
encryptedKey ?? this.encryptedKey,
name ?? this.name,
type ?? this.type,
creationTime ?? this.creationTime,
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'ownerID': ownerID,
'encryptedKey': encryptedKey,
'name': name,
'type': Collection.typeToString(type),
'creationTime': creationTime,
};
}
factory SharedCollection.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return SharedCollection(
map['id'],
map['ownerID'],
map['encryptedKey'],
map['name'],
Collection.typeFromString(map['type']),
map['creationTime'],
);
}
String toJson() => json.encode(toMap());
factory SharedCollection.fromJson(String source) =>
SharedCollection.fromMap(json.decode(source));
@override
String toString() {
return 'SharedCollection(id: $id, ownerID: $ownerID, encryptedKey: $encryptedKey, name: $name, type: $type, creationTime: $creationTime)';
}
@override
bool operator ==(Object o) {
if (identical(this, o)) return true;
return o is SharedCollection &&
o.id == id &&
o.ownerID == ownerID &&
o.encryptedKey == encryptedKey &&
o.name == name &&
o.type == type &&
o.creationTime == creationTime;
}
@override
int get hashCode {
return id.hashCode ^
ownerID.hashCode ^
encryptedKey.hashCode ^
name.hashCode ^
type.hashCode ^
creationTime.hashCode;
}
}

View file

@ -9,11 +9,11 @@ import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/db/collections_db.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/events/collection_updated_event.dart';
import 'package:photos/models/collection.dart';
import 'package:photos/models/collection_file_item.dart';
import 'package:photos/models/file.dart';
import 'package:photos/models/shared_collection.dart';
import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/file_util.dart';
@ -23,8 +23,7 @@ class CollectionsService {
CollectionsDB _db;
Configuration _config;
final _localCollections = Map<String, Collection>();
final _collectionIDToOwnedCollections = Map<int, Collection>();
final _collectionIDToSharedCollections = Map<int, SharedCollection>();
final _collectionIDToCollections = Map<int, Collection>();
final _cachedKeys = Map<int, Uint8List>();
CollectionsService._privateConstructor() {
@ -38,33 +37,18 @@ class CollectionsService {
Future<void> init() async {
final collections = await _db.getAllCollections();
for (final collection in collections) {
_cacheOwnedCollectionAttributes(collection);
}
final sharedCollections = await _db.getAllSharedCollections();
for (final collection in sharedCollections) {
_collectionIDToSharedCollections[collection.id] = collection;
_cacheCollectionAttributes(collection);
}
}
Future<void> sync() async {
final lastCollectionCreationTime =
await _db.getLastCollectionCreationTime();
var collections =
await _getOwnedCollections(lastCollectionCreationTime ?? 0);
var collections = await _fetchCollections(lastCollectionCreationTime ?? 0);
await _db.insert(collections);
collections = await _db.getAllCollections();
for (final collection in collections) {
_cacheOwnedCollectionAttributes(collection);
}
final lastSharedCollectionCreationTime =
await _db.getLastCollectionCreationTime();
var sharedCollections =
await getSharedCollections(lastSharedCollectionCreationTime ?? 0);
await _db.insertSharedCollections(sharedCollections);
sharedCollections = await _db.getAllSharedCollections();
for (final collection in sharedCollections) {
_collectionIDToSharedCollections[collection.id] = collection;
_cacheCollectionAttributes(collection);
}
}
@ -72,8 +56,8 @@ class CollectionsService {
return _localCollections[path];
}
List<Collection> getOwnedCollections() {
return _collectionIDToOwnedCollections.values.toList();
List<Collection> getCollections() {
return _collectionIDToCollections.values.toList();
}
Future<List<String>> getSharees(int collectionID) {
@ -113,14 +97,13 @@ class CollectionsService {
Uint8List getCollectionKey(int collectionID) {
if (!_cachedKeys.containsKey(collectionID)) {
final collection = _collectionIDToCollections[collectionID];
var key;
if (_collectionIDToOwnedCollections.containsKey(collectionID)) {
final collection = _collectionIDToOwnedCollections[collectionID];
if (collection.ownerID == _config.getUserID()) {
final encryptedKey = Sodium.base642bin(collection.encryptedKey);
key = CryptoUtil.decryptSync(encryptedKey, _config.getKey(),
Sodium.base642bin(collection.keyDecryptionNonce));
} else {
final collection = _collectionIDToSharedCollections[collectionID];
final encryptedKey = Sodium.base642bin(collection.encryptedKey);
key = CryptoUtil.openSealSync(
encryptedKey,
@ -132,10 +115,10 @@ class CollectionsService {
return _cachedKeys[collectionID];
}
Future<List<Collection>> _getOwnedCollections(int sinceTime) {
Future<List<Collection>> _fetchCollections(int sinceTime) {
return Dio()
.get(
Configuration.instance.getHttpEndpoint() + "/collections/owned",
Configuration.instance.getHttpEndpoint() + "/collections/",
queryParameters: {
"sinceTime": sinceTime,
},
@ -154,30 +137,8 @@ class CollectionsService {
});
}
Future<List<SharedCollection>> getSharedCollections(int sinceTime) {
return Dio()
.get(
Configuration.instance.getHttpEndpoint() + "/collections/shared",
queryParameters: {
"sinceTime": sinceTime,
},
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then((response) {
final collections = List<SharedCollection>();
if (response != null) {
final c = response.data["collections"];
for (final collection in c) {
collections.add(SharedCollection.fromMap(collection));
}
}
return collections;
});
}
Collection getOwnedCollectionByID(int collectionID) {
return _collectionIDToOwnedCollections[collectionID];
Collection getCollectionByID(int collectionID) {
return _collectionIDToCollections[collectionID];
}
Future<Collection> createAlbum(String albumName) async {
@ -224,29 +185,32 @@ class CollectionsService {
params["collectionID"] = collectionID;
for (final file in files) {
final key = decryptFileKey(file);
file.collectionID = collectionID;
final encryptedKeyData =
CryptoUtil.encryptSync(key, getCollectionKey(collectionID));
file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
if (params["files"] == null) {
params["files"] = [];
}
params["files"].add(CollectionFileItem(
file.uploadedFileID,
Sodium.bin2base64(encryptedKeyData.encryptedData),
Sodium.bin2base64(encryptedKeyData.nonce),
).toMap());
file.uploadedFileID, file.encryptedKey, file.keyDecryptionNonce)
.toMap());
}
return Dio()
.post(
Configuration.instance.getHttpEndpoint() + "/collections/add-files",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then(
(value) => Bus.instance.fire(CollectionUpdatedEvent(collectionID)));
Configuration.instance.getHttpEndpoint() + "/collections/add-files",
data: params,
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then((value) async {
await FilesDB.instance.insertMultiple(files);
Bus.instance.fire(CollectionUpdatedEvent(collectionID));
});
}
Future<void> removeFromCollection(int collectionID, List<File> files) {
Future<void> removeFromCollection(int collectionID, List<File> files) async {
final params = Map<String, dynamic>();
params["collectionID"] = collectionID;
for (final file in files) {
@ -255,17 +219,15 @@ class CollectionsService {
}
params["fileIDs"].add(file.uploadedFileID);
}
return Dio()
.post(
Configuration.instance.getHttpEndpoint() +
"/collections/remove-files",
data: params,
options: Options(
headers: {"X-Auth-Token": Configuration.instance.getToken()}),
)
.then(
(value) => Bus.instance.fire(CollectionUpdatedEvent(collectionID)));
;
await Dio().post(
Configuration.instance.getHttpEndpoint() + "/collections/remove-files",
data: params,
options:
Options(headers: {"X-Auth-Token": Configuration.instance.getToken()}),
);
await FilesDB.instance
.removeFromCollection(collectionID, params["fileIDs"]);
Bus.instance.fire(CollectionUpdatedEvent(collectionID));
}
Future<Collection> createAndCacheCollection(Collection collection) async {
@ -278,12 +240,12 @@ class CollectionsService {
)
.then((response) {
final collection = Collection.fromMap(response.data["collection"]);
_cacheOwnedCollectionAttributes(collection);
_cacheCollectionAttributes(collection);
return collection;
});
}
void _cacheOwnedCollectionAttributes(Collection collection) {
void _cacheCollectionAttributes(Collection collection) {
if (collection.attributes.encryptedPath != null) {
var path = utf8.decode(CryptoUtil.decryptSync(
Sodium.base642bin(collection.attributes.encryptedPath),
@ -291,8 +253,7 @@ class CollectionsService {
Sodium.base642bin(collection.attributes.pathDecryptionNonce)));
_localCollections[path] = collection;
}
_collectionIDToOwnedCollections[collection.id] = collection;
getCollectionKey(collection.id);
_collectionIDToCollections[collection.id] = collection;
}
}

View file

@ -69,7 +69,7 @@ class FavoritesService {
Future<Collection> getFavoritesCollection() async {
if (!_preferences.containsKey(_favoritesCollectionIDKey)) {
final collections = _collectionsService.getOwnedCollections();
final collections = _collectionsService.getCollections();
for (final collection in collections) {
if (collection.type == CollectionType.favorites) {
await _preferences.setInt(_favoritesCollectionIDKey, collection.id);
@ -79,7 +79,7 @@ class FavoritesService {
return null;
}
return _collectionsService
.getOwnedCollectionByID(_preferences.getInt(_favoritesCollectionIDKey));
.getCollectionByID(_preferences.getInt(_favoritesCollectionIDKey));
}
Future<int> _getOrCreateFavoriteCollectionID() async {

View file

@ -57,8 +57,8 @@ class SyncService {
_logger.info("Syncing...");
try {
await _doSync();
} catch (e) {
throw e;
} catch (e, s) {
_logger.severe(e, s);
} finally {
_isSyncInProgress = false;
}

View file

@ -3,6 +3,8 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/events/collection_updated_event.dart';
@ -11,6 +13,7 @@ import 'package:photos/events/tab_changed_event.dart';
import 'package:photos/models/collection.dart';
import 'package:photos/models/file.dart';
import 'package:photos/repositories/file_repository.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/services/favorites_service.dart';
import 'package:photos/models/device_folder.dart';
import 'package:photos/ui/collection_page.dart';
@ -29,6 +32,7 @@ class CollectionsGalleryWidget extends StatefulWidget {
}
class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget> {
final _logger = Logger("CollectionsGallery");
StreamSubscription<LocalPhotosUpdatedEvent> _localFilesSubscription;
StreamSubscription<CollectionUpdatedEvent> _collectionUpdatesSubscription;
@ -120,17 +124,25 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget> {
.compareTo(first.thumbnail.creationTime);
});
final collections = List<CollectionWithThumbnail>();
final collectionsWithThumbnail = List<CollectionWithThumbnail>();
final favorites = FavoritesService.instance.getFavoriteFiles().toList();
favorites.sort((first, second) {
return second.creationTime.compareTo(first.creationTime);
});
if (favorites.length > 0) {
collections.add(CollectionWithThumbnail(
collectionsWithThumbnail.add(CollectionWithThumbnail(
await FavoritesService.instance.getFavoritesCollection(),
favorites[0]));
}
return CollectionItems(folders, collections);
final collections = CollectionsService.instance.getCollections();
for (final c in collections) {
if (c.ownerID != Configuration.instance.getUserID()) {
continue;
}
collectionsWithThumbnail.add(CollectionWithThumbnail(
c, await FilesDB.instance.getLatestFileInCollection(c.id)));
}
return CollectionItems(folders, collectionsWithThumbnail);
}
Widget _buildFolder(BuildContext context, DeviceFolder folder) {

View file

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/models/shared_collection.dart';
import 'package:photos/models/collection.dart';
import 'package:photos/ui/gallery.dart';
import 'package:photos/ui/gallery_app_bar_widget.dart';
class SharedCollectionPage extends StatefulWidget {
final SharedCollection collection;
final Collection collection;
const SharedCollectionPage(this.collection, {Key key}) : super(key: key);

View file

@ -3,12 +3,12 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/db/collections_db.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/events/remote_sync_event.dart';
import 'package:photos/models/file.dart';
import 'package:photos/models/shared_collection.dart';
import 'package:photos/ui/collections_gallery_widget.dart';
import 'package:photos/ui/common_elements.dart';
import 'package:photos/ui/loading_widget.dart';
import 'package:photos/ui/shared_collection_page.dart';
@ -38,12 +38,14 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery> {
@override
Widget build(BuildContext context) {
return FutureBuilder<List<SharedCollectionWithThumbnail>>(
future: CollectionsDB.instance
.getAllSharedCollections()
.then((collections) async {
final c = List<SharedCollectionWithThumbnail>();
return FutureBuilder<List<CollectionWithThumbnail>>(
future:
CollectionsDB.instance.getAllCollections().then((collections) async {
final c = List<CollectionWithThumbnail>();
for (final collection in collections) {
if (collection.ownerID == Configuration.instance.getUserID()) {
continue;
}
var thumbnail;
try {
thumbnail =
@ -51,7 +53,7 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery> {
} catch (e) {
_logger.warning(e.toString());
}
c.add(SharedCollectionWithThumbnail(collection, thumbnail));
c.add(CollectionWithThumbnail(collection, thumbnail));
}
return c;
}),
@ -73,7 +75,7 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery> {
}
Widget _getSharedCollectionsGallery(
List<SharedCollectionWithThumbnail> collections) {
List<CollectionWithThumbnail> collections) {
return Container(
margin: EdgeInsets.only(top: 24),
child: GridView.builder(
@ -91,9 +93,7 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery> {
);
}
Widget _buildCollection(
BuildContext context, SharedCollectionWithThumbnail c) {
_logger.info("Building collection " + c.collection.toString());
Widget _buildCollection(BuildContext context, CollectionWithThumbnail c) {
return GestureDetector(
child: Column(
children: <Widget>[
@ -140,10 +140,3 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery> {
super.dispose();
}
}
class SharedCollectionWithThumbnail {
final SharedCollection collection;
final File thumbnail;
SharedCollectionWithThumbnail(this.collection, this.thumbnail);
}