mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 10:28:50 +01:00
Fix user ID and display name mixed up in logic
This commit is contained in:
parent
413c185290
commit
478c25b5d0
47 changed files with 340 additions and 128 deletions
|
@ -2,9 +2,8 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/ci_string.dart';
|
import 'package:nc_photos/ci_string.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:nc_photos/or_null.dart';
|
|
||||||
import 'package:nc_photos/string_extension.dart';
|
import 'package:nc_photos/string_extension.dart';
|
||||||
import 'package:nc_photos/type.dart';
|
import 'package:nc_photos/type.dart';
|
||||||
|
|
||||||
|
@ -14,9 +13,9 @@ class Account with EquatableMixin {
|
||||||
this.id,
|
this.id,
|
||||||
this.scheme,
|
this.scheme,
|
||||||
String address,
|
String address,
|
||||||
this.username,
|
this.userId,
|
||||||
|
this.username2,
|
||||||
this.password,
|
this.password,
|
||||||
this.altHomeDir,
|
|
||||||
List<String> roots,
|
List<String> roots,
|
||||||
) : address = address.trimRightAny("/"),
|
) : address = address.trimRightAny("/"),
|
||||||
_roots = roots.map((e) => e.trimRightAny("/")).toList() {
|
_roots = roots.map((e) => e.trimRightAny("/")).toList() {
|
||||||
|
@ -29,18 +28,18 @@ class Account with EquatableMixin {
|
||||||
String? id,
|
String? id,
|
||||||
String? scheme,
|
String? scheme,
|
||||||
String? address,
|
String? address,
|
||||||
CiString? username,
|
CiString? userId,
|
||||||
|
String? username2,
|
||||||
String? password,
|
String? password,
|
||||||
OrNull<CiString>? altHomeDir,
|
|
||||||
List<String>? roots,
|
List<String>? roots,
|
||||||
}) {
|
}) {
|
||||||
return Account(
|
return Account(
|
||||||
id ?? this.id,
|
id ?? this.id,
|
||||||
scheme ?? this.scheme,
|
scheme ?? this.scheme,
|
||||||
address ?? this.address,
|
address ?? this.address,
|
||||||
username ?? this.username,
|
userId ?? this.userId,
|
||||||
|
username2 ?? this.username2,
|
||||||
password ?? this.password,
|
password ?? this.password,
|
||||||
altHomeDir == null ? this.altHomeDir : altHomeDir.obj,
|
|
||||||
roots ?? List.of(_roots),
|
roots ?? List.of(_roots),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -57,49 +56,68 @@ class Account with EquatableMixin {
|
||||||
"id: '$id', "
|
"id: '$id', "
|
||||||
"scheme: '$scheme', "
|
"scheme: '$scheme', "
|
||||||
"address: '${kDebugMode ? address : "***"}', "
|
"address: '${kDebugMode ? address : "***"}', "
|
||||||
"username: '${kDebugMode ? username : "***"}', "
|
"userId: '${kDebugMode ? userId : "***"}', "
|
||||||
|
"username2: '${kDebugMode ? username2 : "***"}', "
|
||||||
"password: '${password.isNotEmpty == true ? (kDebugMode ? password : '***') : null}', "
|
"password: '${password.isNotEmpty == true ? (kDebugMode ? password : '***') : null}', "
|
||||||
"altHomeDir: '${altHomeDir == null || kDebugMode ? altHomeDir : "***"}', "
|
|
||||||
"roots: List {'${roots.join('\', \'')}'}, "
|
"roots: List {'${roots.join('\', \'')}'}, "
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
|
|
||||||
Account.fromJson(JsonObj json)
|
static Account? fromJson(
|
||||||
: id = json["id"],
|
JsonObj json, {
|
||||||
scheme = json["scheme"],
|
required AccountUpgraderV1? upgraderV1,
|
||||||
address = json["address"],
|
}) {
|
||||||
username = CiString(json["username"]),
|
final jsonVersion = json["version"] ?? 1;
|
||||||
password = json["password"],
|
JsonObj? result = json;
|
||||||
altHomeDir = (json["altHomeDir"] as String?)?.run((v) => CiString(v)),
|
if (jsonVersion < 2) {
|
||||||
_roots = json["roots"].cast<String>();
|
result = upgraderV1?.call(result);
|
||||||
|
if (result == null) {
|
||||||
|
_log.info("[fromJson] Version $jsonVersion not compatible");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Account(
|
||||||
|
result["id"],
|
||||||
|
result["scheme"],
|
||||||
|
result["address"],
|
||||||
|
CiString(result["userId"]),
|
||||||
|
result["username2"],
|
||||||
|
result["password"],
|
||||||
|
result["roots"].cast<String>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
JsonObj toJson() => {
|
JsonObj toJson() => {
|
||||||
|
"version": version,
|
||||||
"id": id,
|
"id": id,
|
||||||
"scheme": scheme,
|
"scheme": scheme,
|
||||||
"address": address,
|
"address": address,
|
||||||
"username": username.toString(),
|
"userId": userId.toString(),
|
||||||
|
"username2": username2,
|
||||||
"password": password,
|
"password": password,
|
||||||
"altHomeDir": altHomeDir?.toString(),
|
|
||||||
"roots": _roots,
|
"roots": _roots,
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get props => [id, scheme, address, username, password, altHomeDir, _roots];
|
get props => [id, scheme, address, userId, username2, password, _roots];
|
||||||
|
|
||||||
List<String> get roots => _roots;
|
List<String> get roots => _roots;
|
||||||
|
|
||||||
CiString get homeDir => altHomeDir ?? username;
|
|
||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final String scheme;
|
final String scheme;
|
||||||
final String address;
|
final String address;
|
||||||
final CiString username;
|
// For non LDAP users, this is the username used to sign in
|
||||||
|
final CiString userId;
|
||||||
|
// Username used to sign in. For non-LDAP users, this is identical to userId
|
||||||
|
final String username2;
|
||||||
final String password;
|
final String password;
|
||||||
|
|
||||||
/// Name of the user home dir. Normally [username] is used as the home dir
|
|
||||||
/// name, but for LDAP users, their home dir might be named differently
|
|
||||||
final CiString? altHomeDir;
|
|
||||||
final List<String> _roots;
|
final List<String> _roots;
|
||||||
|
|
||||||
|
/// versioning of this class, use to upgrade old persisted accounts
|
||||||
|
static const version = 2;
|
||||||
|
|
||||||
|
static final _log = Logger("account.Account");
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AccountExtension on Account {
|
extension AccountExtension on Account {
|
||||||
|
@ -113,6 +131,34 @@ extension AccountExtension on Account {
|
||||||
bool compareServerIdentity(Account other) {
|
bool compareServerIdentity(Account other) {
|
||||||
return scheme == other.scheme &&
|
return scheme == other.scheme &&
|
||||||
address == other.address &&
|
address == other.address &&
|
||||||
username == other.username;
|
userId == other.userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class AccountUpgrader {
|
||||||
|
JsonObj? call(JsonObj json);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountUpgraderV1 implements AccountUpgrader {
|
||||||
|
const AccountUpgraderV1({
|
||||||
|
this.logAccountId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
call(JsonObj json) {
|
||||||
|
// clarify user id and display name v1
|
||||||
|
_log.fine("[call] Upgrade v1 Account: $logAccountId");
|
||||||
|
final result = JsonObj.of(json);
|
||||||
|
result["userId"] = json["altHomeDir"] ?? json["username"];
|
||||||
|
result["username2"] = json["username"];
|
||||||
|
result
|
||||||
|
..remove("username")
|
||||||
|
..remove("altHomeDir");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Account ID for logging only
|
||||||
|
final String? logAccountId;
|
||||||
|
|
||||||
|
static final _log = Logger("account.AccountUpgraderV1");
|
||||||
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class Api {
|
||||||
|
|
||||||
static String getAuthorizationHeaderValue(Account account) {
|
static String getAuthorizationHeaderValue(Account account) {
|
||||||
final auth =
|
final auth =
|
||||||
base64.encode(utf8.encode("${account.username}:${account.password}"));
|
base64.encode(utf8.encode("${account.username2}:${account.password}"));
|
||||||
return "Basic $auth";
|
return "Basic $auth";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,10 @@ String getFileUrlRelative(File file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getWebdavRootUrlRelative(Account account) =>
|
String getWebdavRootUrlRelative(Account account) =>
|
||||||
"remote.php/dav/files/${account.homeDir}";
|
"remote.php/dav/files/${account.userId}";
|
||||||
|
|
||||||
String getTrashbinPath(Account account) =>
|
String getTrashbinPath(Account account) =>
|
||||||
"remote.php/dav/trashbin/${account.homeDir}/trash";
|
"remote.php/dav/trashbin/${account.userId}/trash";
|
||||||
|
|
||||||
/// Return the face image URL. See [getFacePreviewUrlRelative]
|
/// Return the face image URL. See [getFacePreviewUrlRelative]
|
||||||
String getFacePreviewUrl(
|
String getFacePreviewUrl(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
|
||||||
String getInstNameForAccount(String className, Account account) =>
|
String getInstNameForAccount(String className, Account account) =>
|
||||||
"$className(${account.scheme}://${account.username.toCaseInsensitiveString()}@${account.address})";
|
"$className(${account.scheme}://${account.userId.toCaseInsensitiveString()}@${account.address})";
|
||||||
|
|
||||||
String getInstNameForRootAwareAccount(String className, Account account) =>
|
String getInstNameForRootAwareAccount(String className, Account account) =>
|
||||||
"$className(${account.scheme}://${account.username.toCaseInsensitiveString()}@${account.address}?${account.roots.join('&')})";
|
"$className(${account.scheme}://${account.userId.toCaseInsensitiveString()}@${account.address}?${account.roots.join('&')})";
|
||||||
|
|
|
@ -202,9 +202,9 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
||||||
|
|
||||||
final albumShares = await () async {
|
final albumShares = await () async {
|
||||||
var temp = (ev.album.shares ?? [])
|
var temp = (ev.album.shares ?? [])
|
||||||
.where((s) => s.userId != ev.account.username)
|
.where((s) => s.userId != ev.account.userId)
|
||||||
.toList();
|
.toList();
|
||||||
if (ev.album.albumFile!.ownerId != ev.account.username) {
|
if (ev.album.albumFile!.ownerId != ev.account.userId) {
|
||||||
// add owner if the album is not owned by this account
|
// add owner if the album is not owned by this account
|
||||||
final ownerSharee = (await ListSharee(_c.shareeRepo)(ev.account))
|
final ownerSharee = (await ListSharee(_c.shareeRepo)(ev.account))
|
||||||
.firstWhere((s) => s.shareWith == ev.album.albumFile!.ownerId);
|
.firstWhere((s) => s.shareWith == ev.album.albumFile!.ownerId);
|
||||||
|
@ -241,7 +241,7 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
||||||
Map<CiString, AlbumShare> albumShares,
|
Map<CiString, AlbumShare> albumShares,
|
||||||
List<Object> errors,
|
List<Object> errors,
|
||||||
) async {
|
) async {
|
||||||
if (!album.albumFile!.isOwned(account.username)) {
|
if (!album.albumFile!.isOwned(account.userId)) {
|
||||||
// album file is always managed by the owner
|
// album file is always managed by the owner
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -335,7 +335,7 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
||||||
var missings = managedAlbumSharees
|
var missings = managedAlbumSharees
|
||||||
.difference(allSharees)
|
.difference(allSharees)
|
||||||
// Can't share to ourselves or the file owner
|
// Can't share to ourselves or the file owner
|
||||||
.where((s) => s != account.username && s != fileItem.file.ownerId)
|
.where((s) => s != account.userId && s != fileItem.file.ownerId)
|
||||||
.toList();
|
.toList();
|
||||||
_log.info(
|
_log.info(
|
||||||
"[_processSingleFileItem] Missing shares: ${missings.toReadableString()} for file: ${logFilename(fileItem.file.path)}");
|
"[_processSingleFileItem] Missing shares: ${missings.toReadableString()} for file: ${logFilename(fileItem.file.path)}");
|
||||||
|
@ -348,7 +348,7 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
||||||
// check owned shares against all album sharees. Use all album sharees such
|
// check owned shares against all album sharees. Use all album sharees such
|
||||||
// that non-managed sharees will not be listed
|
// that non-managed sharees will not be listed
|
||||||
final ownedSharees = shares
|
final ownedSharees = shares
|
||||||
.where((s) => s.uidOwner == account.username)
|
.where((s) => s.uidOwner == account.userId)
|
||||||
.map((s) => s.shareWith!)
|
.map((s) => s.shareWith!)
|
||||||
.toSet();
|
.toSet();
|
||||||
final extras = ownedSharees.difference(albumSharees);
|
final extras = ownedSharees.difference(albumSharees);
|
||||||
|
@ -375,13 +375,13 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
|
||||||
|
|
||||||
bool _isItemSharePairOfInterest(
|
bool _isItemSharePairOfInterest(
|
||||||
Account account, Album album, AlbumItem item, AlbumShare share) {
|
Account account, Album album, AlbumItem item, AlbumShare share) {
|
||||||
if (album.albumFile!.isOwned(account.username)) {
|
if (album.albumFile!.isOwned(account.userId)) {
|
||||||
// album owner
|
// album owner
|
||||||
return item.addedBy == account.username ||
|
return item.addedBy == account.userId ||
|
||||||
item.addedAt.isBefore(share.sharedAt);
|
item.addedAt.isBefore(share.sharedAt);
|
||||||
} else {
|
} else {
|
||||||
// non album owner
|
// non album owner
|
||||||
if (item.addedBy != account.username) {
|
if (item.addedBy != account.userId) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return share.userId == album.albumFile!.ownerId ||
|
return share.userId == album.albumFile!.ownerId ||
|
||||||
|
|
|
@ -469,7 +469,7 @@ class ScanAccountDirBloc
|
||||||
);
|
);
|
||||||
yield files
|
yield files
|
||||||
.where((f) =>
|
.where((f) =>
|
||||||
file_util.isSupportedFormat(f) && !f.isOwned(account.username))
|
file_util.isSupportedFormat(f) && !f.isOwned(account.userId))
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
yield ExceptionEvent(e, stackTrace);
|
yield ExceptionEvent(e, stackTrace);
|
||||||
|
|
|
@ -166,7 +166,7 @@ class AlbumSqliteDbDataSource implements AlbumDataSource {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
final f = SqliteFileConverter.fromSql(
|
final f = SqliteFileConverter.fromSql(
|
||||||
account.homeDir.toString(), item["file"]);
|
account.userId.toString(), item["file"]);
|
||||||
yield SqliteAlbumConverter.fromSql(
|
yield SqliteAlbumConverter.fromSql(
|
||||||
item["album"], f, item["shares"] ?? []);
|
item["album"], f, item["shares"] ?? []);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
|
|
|
@ -181,10 +181,10 @@ class AlbumUpgraderV5 implements AlbumUpgrader {
|
||||||
final CiString addedBy;
|
final CiString addedBy;
|
||||||
if (result.containsKey("albumFile")) {
|
if (result.containsKey("albumFile")) {
|
||||||
addedBy = result["albumFile"]["ownerId"] == null
|
addedBy = result["albumFile"]["ownerId"] == null
|
||||||
? account.username
|
? account.userId
|
||||||
: CiString(result["albumFile"]["ownerId"]);
|
: CiString(result["albumFile"]["ownerId"]);
|
||||||
} else {
|
} else {
|
||||||
addedBy = albumFile?.ownerId ?? account.username;
|
addedBy = albumFile?.ownerId ?? account.userId;
|
||||||
}
|
}
|
||||||
item["addedBy"] = addedBy.toString();
|
item["addedBy"] = addedBy.toString();
|
||||||
item["addedAt"] = result["lastUpdated"];
|
item["addedAt"] = result["lastUpdated"];
|
||||||
|
|
|
@ -465,7 +465,7 @@ extension FileExtension on File {
|
||||||
lastModified ??
|
lastModified ??
|
||||||
DateTime.now().toUtc();
|
DateTime.now().toUtc();
|
||||||
|
|
||||||
bool isOwned(CiString username) => ownerId == null || ownerId == username;
|
bool isOwned(CiString userId) => ownerId == null || ownerId == userId;
|
||||||
|
|
||||||
/// Return the path of this file with the DAV part stripped
|
/// Return the path of this file with the DAV part stripped
|
||||||
///
|
///
|
||||||
|
|
|
@ -72,7 +72,7 @@ class SqliteAlbumConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SqliteFileConverter {
|
class SqliteFileConverter {
|
||||||
static File fromSql(String homeDir, sql.CompleteFile f) {
|
static File fromSql(String userId, sql.CompleteFile f) {
|
||||||
final metadata = f.image?.run((obj) => Metadata(
|
final metadata = f.image?.run((obj) => Metadata(
|
||||||
lastUpdated: obj.lastUpdated,
|
lastUpdated: obj.lastUpdated,
|
||||||
fileEtag: obj.fileEtag,
|
fileEtag: obj.fileEtag,
|
||||||
|
@ -81,7 +81,7 @@ class SqliteFileConverter {
|
||||||
exif: obj.exifRaw?.run((e) => Exif.fromJson(jsonDecode(e))),
|
exif: obj.exifRaw?.run((e) => Exif.fromJson(jsonDecode(e))),
|
||||||
));
|
));
|
||||||
return File(
|
return File(
|
||||||
path: "remote.php/dav/files/$homeDir/${f.accountFile.relativePath}",
|
path: "remote.php/dav/files/$userId/${f.accountFile.relativePath}",
|
||||||
contentLength: f.file.contentLength,
|
contentLength: f.file.contentLength,
|
||||||
contentType: f.file.contentType,
|
contentType: f.file.contentType,
|
||||||
etag: f.file.etag,
|
etag: f.file.etag,
|
||||||
|
|
|
@ -35,7 +35,7 @@ class CompleteFileCompanion {
|
||||||
extension CompleteFileListExtension on List<CompleteFile> {
|
extension CompleteFileListExtension on List<CompleteFile> {
|
||||||
Future<List<app.File>> convertToAppFile(app.Account account) {
|
Future<List<app.File>> convertToAppFile(app.Account account) {
|
||||||
return map((f) => {
|
return map((f) => {
|
||||||
"homeDir": account.homeDir.toString(),
|
"userId": account.userId.toString(),
|
||||||
"completeFile": f,
|
"completeFile": f,
|
||||||
}).computeAll(_covertSqliteDbFile);
|
}).computeAll(_covertSqliteDbFile);
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ extension SqliteDbExtension on SqliteDb {
|
||||||
await into(accounts).insert(
|
await into(accounts).insert(
|
||||||
AccountsCompanion.insert(
|
AccountsCompanion.insert(
|
||||||
server: dbServer.rowId,
|
server: dbServer.rowId,
|
||||||
userId: account.username.toCaseInsensitiveString(),
|
userId: account.userId.toCaseInsensitiveString(),
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrIgnore,
|
mode: InsertMode.insertOrIgnore,
|
||||||
);
|
);
|
||||||
|
@ -153,8 +153,7 @@ extension SqliteDbExtension on SqliteDb {
|
||||||
useColumns: false)
|
useColumns: false)
|
||||||
])
|
])
|
||||||
..where(servers.address.equals(account.url))
|
..where(servers.address.equals(account.url))
|
||||||
..where(
|
..where(accounts.userId.equals(account.userId.toCaseInsensitiveString()))
|
||||||
accounts.userId.equals(account.username.toCaseInsensitiveString()))
|
|
||||||
..limit(1);
|
..limit(1);
|
||||||
return query.map((r) => r.readTable(accounts)).getSingle();
|
return query.map((r) => r.readTable(accounts)).getSingle();
|
||||||
}
|
}
|
||||||
|
@ -522,7 +521,7 @@ class FilesQueryBuilder {
|
||||||
query
|
query
|
||||||
..where(db.servers.address.equals(_appAccount!.url))
|
..where(db.servers.address.equals(_appAccount!.url))
|
||||||
..where(db.accounts.userId
|
..where(db.accounts.userId
|
||||||
.equals(_appAccount!.username.toCaseInsensitiveString()));
|
.equals(_appAccount!.userId.toCaseInsensitiveString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_byRowId != null) {
|
if (_byRowId != null) {
|
||||||
|
@ -585,9 +584,9 @@ class FilesQueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.File _covertSqliteDbFile(Map map) {
|
app.File _covertSqliteDbFile(Map map) {
|
||||||
final homeDir = map["homeDir"] as String;
|
final userId = map["userId"] as String;
|
||||||
final file = map["completeFile"] as CompleteFile;
|
final file = map["completeFile"] as CompleteFile;
|
||||||
return SqliteFileConverter.fromSql(homeDir, file);
|
return SqliteFileConverter.fromSql(userId, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
CompleteFileCompanion _convertAppFile(Map map) {
|
CompleteFileCompanion _convertAppFile(Map map) {
|
||||||
|
|
|
@ -49,7 +49,7 @@ class MetadataTask {
|
||||||
account,
|
account,
|
||||||
shareFolder,
|
shareFolder,
|
||||||
isRecursive: false,
|
isRecursive: false,
|
||||||
filter: (f) => f.ownerId != account.username,
|
filter: (f) => f.ownerId != account.userId,
|
||||||
)) {
|
)) {
|
||||||
if (!Pref().isEnableExifOr()) {
|
if (!Pref().isEnableExifOr()) {
|
||||||
_log.info("[call] EXIF disabled, task ending immaturely");
|
_log.info("[call] EXIF disabled, task ending immaturely");
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/mobile/platform.dart'
|
import 'package:nc_photos/mobile/platform.dart'
|
||||||
|
@ -26,7 +28,19 @@ class Pref {
|
||||||
|
|
||||||
List<Account>? getAccounts3() {
|
List<Account>? getAccounts3() {
|
||||||
final jsonObjs = provider.getStringList(PrefKey.accounts3);
|
final jsonObjs = provider.getStringList(PrefKey.accounts3);
|
||||||
return jsonObjs?.map((e) => Account.fromJson(jsonDecode(e))).toList();
|
return jsonObjs
|
||||||
|
?.map((e) => Account.fromJson(
|
||||||
|
jsonDecode(e),
|
||||||
|
upgraderV1: const AccountUpgraderV1(),
|
||||||
|
))
|
||||||
|
.where((e) {
|
||||||
|
if (e == null) {
|
||||||
|
_log.shout("[getAccounts3] Failed upgrading account");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.whereNotNull()
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Account> getAccounts3Or(List<Account> def) => getAccounts3() ?? def;
|
List<Account> getAccounts3Or(List<Account> def) => getAccounts3() ?? def;
|
||||||
|
@ -235,6 +249,8 @@ class Pref {
|
||||||
final PrefProvider provider;
|
final PrefProvider provider;
|
||||||
|
|
||||||
static Pref? _inst;
|
static Pref? _inst;
|
||||||
|
|
||||||
|
static final _log = Logger("pref.Pref");
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountPref {
|
class AccountPref {
|
||||||
|
|
|
@ -277,7 +277,7 @@ class _MetadataTask {
|
||||||
account,
|
account,
|
||||||
shareFolder,
|
shareFolder,
|
||||||
isRecursive: false,
|
isRecursive: false,
|
||||||
filter: (f) => f.ownerId != account.username,
|
filter: (f) => f.ownerId != account.userId,
|
||||||
)) {
|
)) {
|
||||||
if (ev is File) {
|
if (ev is File) {
|
||||||
_onFileProcessed(ev);
|
_onFileProcessed(ev);
|
||||||
|
|
|
@ -108,9 +108,9 @@ class TouchTokenManager {
|
||||||
String _getLocalStorageName(Account account, File file) {
|
String _getLocalStorageName(Account account, File file) {
|
||||||
final strippedPath = file.strippedPath;
|
final strippedPath = file.strippedPath;
|
||||||
if (strippedPath == ".") {
|
if (strippedPath == ".") {
|
||||||
return "touch/${account.url.replaceFirst('://', '_')}/${account.username}/token";
|
return "touch/${account.url.replaceFirst('://', '_')}/${account.userId}/token";
|
||||||
} else {
|
} else {
|
||||||
return "touch/${account.url.replaceFirst('://', '_')}/${account.username}/${file.strippedPath}/token";
|
return "touch/${account.url.replaceFirst('://', '_')}/${account.userId}/${file.strippedPath}/token";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,8 +72,8 @@ class AddToAlbum {
|
||||||
Future<void> _shareFiles(
|
Future<void> _shareFiles(
|
||||||
Account account, Album album, List<File> files) async {
|
Account account, Album album, List<File> files) async {
|
||||||
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
||||||
..add(album.albumFile!.ownerId ?? account.username))
|
..add(album.albumFile!.ownerId ?? account.userId))
|
||||||
.where((element) => element != account.username)
|
.where((element) => element != account.userId)
|
||||||
.toSet();
|
.toSet();
|
||||||
if (albumShares.isEmpty) {
|
if (albumShares.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -28,7 +28,7 @@ class ListPotentialSharedAlbum {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _checkOwner(Account account, File f) => !f.isOwned(account.username);
|
bool _checkOwner(Account account, File f) => !f.isOwned(account.userId);
|
||||||
|
|
||||||
bool _checkFileName(File f) {
|
bool _checkFileName(File f) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ListShare {
|
||||||
bool? isIncludeReshare,
|
bool? isIncludeReshare,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
if (file_util.getUserDirName(file) != account.homeDir) {
|
if (file_util.getUserDirName(file) != account.userId) {
|
||||||
file = (await FindFile(_c)(account, [file.fileId!])).first;
|
file = (await FindFile(_c)(account, [file.fileId!])).first;
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
|
@ -51,7 +51,7 @@ class PopulateAlbum {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
products.addAll((result as List).cast<File>().map((f) => AlbumFileItem(
|
products.addAll((result as List).cast<File>().map((f) => AlbumFileItem(
|
||||||
addedBy: account.username,
|
addedBy: account.userId,
|
||||||
addedAt: DateTime.now(),
|
addedAt: DateTime.now(),
|
||||||
file: f,
|
file: f,
|
||||||
)));
|
)));
|
||||||
|
@ -68,7 +68,7 @@ class PopulateAlbum {
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final files = await ListTaggedFile(c)(account, provider.tags);
|
final files = await ListTaggedFile(c)(account, provider.tags);
|
||||||
products.addAll(files.map((f) => AlbumFileItem(
|
products.addAll(files.map((f) => AlbumFileItem(
|
||||||
addedBy: account.username,
|
addedBy: account.userId,
|
||||||
addedAt: DateTime.now(),
|
addedAt: DateTime.now(),
|
||||||
file: f,
|
file: f,
|
||||||
)));
|
)));
|
||||||
|
@ -87,7 +87,7 @@ class PopulateAlbum {
|
||||||
return files
|
return files
|
||||||
.where((f) => file_util.isSupportedFormat(f))
|
.where((f) => file_util.isSupportedFormat(f))
|
||||||
.map((f) => AlbumFileItem(
|
.map((f) => AlbumFileItem(
|
||||||
addedBy: account.username,
|
addedBy: account.userId,
|
||||||
addedAt: DateTime.now(),
|
addedAt: DateTime.now(),
|
||||||
file: f,
|
file: f,
|
||||||
))
|
))
|
||||||
|
|
|
@ -65,8 +65,8 @@ class Remove {
|
||||||
final itemsToRemove = provider.items
|
final itemsToRemove = provider.items
|
||||||
.whereType<AlbumFileItem>()
|
.whereType<AlbumFileItem>()
|
||||||
.where((i) =>
|
.where((i) =>
|
||||||
(i.file.isOwned(account.username) ||
|
(i.file.isOwned(account.userId) ||
|
||||||
i.addedBy == account.username) &&
|
i.addedBy == account.userId) &&
|
||||||
removes.any((r) => r.compareServerIdentity(i.file)))
|
removes.any((r) => r.compareServerIdentity(i.file)))
|
||||||
.toList();
|
.toList();
|
||||||
if (itemsToRemove.isEmpty) {
|
if (itemsToRemove.isEmpty) {
|
||||||
|
@ -76,7 +76,7 @@ class Remove {
|
||||||
final key = FileServerIdentityComparator(i.file);
|
final key = FileServerIdentityComparator(i.file);
|
||||||
final value = (a.shares?.map((s) => s.userId).toList() ?? [])
|
final value = (a.shares?.map((s) => s.userId).toList() ?? [])
|
||||||
..add(a.albumFile!.ownerId!)
|
..add(a.albumFile!.ownerId!)
|
||||||
..remove(account.username);
|
..remove(account.userId);
|
||||||
(unshares[key] ??= <CiString>{}).addAll(value);
|
(unshares[key] ??= <CiString>{}).addAll(value);
|
||||||
}
|
}
|
||||||
_log.fine(
|
_log.fine(
|
||||||
|
|
|
@ -51,8 +51,8 @@ class RemoveAlbum {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
||||||
..add(album.albumFile!.ownerId ?? account.username))
|
..add(album.albumFile!.ownerId ?? account.userId))
|
||||||
.where((element) => element != account.username)
|
.where((element) => element != account.userId)
|
||||||
.toList();
|
.toList();
|
||||||
if (albumShares.isEmpty) {
|
if (albumShares.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -103,8 +103,8 @@ class RemoveFromAlbum {
|
||||||
Future<void> _unshareFiles(
|
Future<void> _unshareFiles(
|
||||||
Account account, Album album, List<File> files) async {
|
Account account, Album album, List<File> files) async {
|
||||||
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
final albumShares = (album.shares!.map((e) => e.userId).toList()
|
||||||
..add(album.albumFile!.ownerId ?? account.username))
|
..add(album.albumFile!.ownerId ?? account.userId))
|
||||||
.where((element) => element != account.username)
|
.where((element) => element != account.userId)
|
||||||
.toList();
|
.toList();
|
||||||
if (albumShares.isNotEmpty) {
|
if (albumShares.isNotEmpty) {
|
||||||
await UnshareFileFromAlbum(_c)(account, album, files, albumShares);
|
await UnshareFileFromAlbum(_c)(account, album, files, albumShares);
|
||||||
|
|
|
@ -17,7 +17,7 @@ class RestoreTrashbin {
|
||||||
await Move(_c)(
|
await Move(_c)(
|
||||||
account,
|
account,
|
||||||
file,
|
file,
|
||||||
"remote.php/dav/trashbin/${account.homeDir}/restore/${file.filename}",
|
"remote.php/dav/trashbin/${account.userId}/restore/${file.filename}",
|
||||||
shouldOverwrite: true,
|
shouldOverwrite: true,
|
||||||
);
|
);
|
||||||
KiwiContainer()
|
KiwiContainer()
|
||||||
|
|
|
@ -50,8 +50,7 @@ class ScanDirOffline {
|
||||||
.get();
|
.get();
|
||||||
});
|
});
|
||||||
return dbFiles
|
return dbFiles
|
||||||
.map(
|
.map((f) => SqliteFileConverter.fromSql(account.userId.toString(), f))
|
||||||
(f) => SqliteFileConverter.fromSql(account.homeDir.toString(), f))
|
|
||||||
.toList();
|
.toList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
title: Text(label ?? a.url),
|
title: Text(label ?? a.url),
|
||||||
subtitle: label == null ? Text(a.username.toString()) : null,
|
subtitle: label == null ? Text(a.username2) : null,
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.close,
|
Icons.close,
|
||||||
|
@ -87,7 +87,7 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
|
||||||
),
|
),
|
||||||
subtitle: accountLabel == null
|
subtitle: accountLabel == null
|
||||||
? Text(
|
? Text(
|
||||||
widget.account.username.toString(),
|
widget.account.username2,
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|
|
@ -133,7 +133,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@protected
|
@protected
|
||||||
get canEdit => _album?.albumFile?.isOwned(widget.account.username) == true;
|
get canEdit => _album?.albumFile?.isOwned(widget.account.userId) == true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
enterEditMode() {
|
enterEditMode() {
|
||||||
|
@ -274,7 +274,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
widget.account,
|
widget.account,
|
||||||
_album!,
|
_album!,
|
||||||
actions: [
|
actions: [
|
||||||
if (_album!.albumFile!.isOwned(widget.account.username) &&
|
if (_album!.albumFile!.isOwned(widget.account.userId) &&
|
||||||
Pref().isLabEnableSharedAlbumOr(false))
|
Pref().isLabEnableSharedAlbumOr(false))
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => _onSharePressed(context),
|
onPressed: () => _onSharePressed(context),
|
||||||
|
@ -462,8 +462,8 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
.takeIndex(selectedIndexes)
|
.takeIndex(selectedIndexes)
|
||||||
// can only remove owned files
|
// can only remove owned files
|
||||||
.where((element) =>
|
.where((element) =>
|
||||||
_album!.albumFile!.isOwned(widget.account.username) == true ||
|
_album!.albumFile!.isOwned(widget.account.userId) == true ||
|
||||||
element.addedBy == widget.account.username)
|
element.addedBy == widget.account.userId)
|
||||||
.toList();
|
.toList();
|
||||||
setState(() {
|
setState(() {
|
||||||
clearSelectedItems();
|
clearSelectedItems();
|
||||||
|
@ -668,7 +668,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
provider: AlbumStaticProvider.of(_editAlbum!).copyWith(
|
provider: AlbumStaticProvider.of(_editAlbum!).copyWith(
|
||||||
items: [
|
items: [
|
||||||
AlbumLabelItem(
|
AlbumLabelItem(
|
||||||
addedBy: widget.account.username,
|
addedBy: widget.account.userId,
|
||||||
addedAt: DateTime.now(),
|
addedAt: DateTime.now(),
|
||||||
text: value,
|
text: value,
|
||||||
),
|
),
|
||||||
|
@ -822,7 +822,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
Future<void> _setAlbum(Album album) async {
|
Future<void> _setAlbum(Album album) async {
|
||||||
assert(album.provider is AlbumStaticProvider);
|
assert(album.provider is AlbumStaticProvider);
|
||||||
final items = await PreProcessAlbum(_c)(widget.account, album);
|
final items = await PreProcessAlbum(_c)(widget.account, album);
|
||||||
if (album.albumFile!.isOwned(widget.account.username)) {
|
if (album.albumFile!.isOwned(widget.account.userId)) {
|
||||||
album = await _updateAlbumPostResync(album, items);
|
album = await _updateAlbumPostResync(album, items);
|
||||||
}
|
}
|
||||||
album = album.copyWith(
|
album = album.copyWith(
|
||||||
|
@ -858,14 +858,13 @@ class _AlbumBrowserState extends State<AlbumBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _canRemoveSelection {
|
bool get _canRemoveSelection {
|
||||||
if (_album!.albumFile!.isOwned(widget.account.username) == true) {
|
if (_album!.albumFile!.isOwned(widget.account.userId) == true) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final selectedIndexes =
|
final selectedIndexes =
|
||||||
selectedListItems.whereType<_ListItem>().map((e) => e.index).toList();
|
selectedListItems.whereType<_ListItem>().map((e) => e.index).toList();
|
||||||
final selectedItemsIt = _sortedItems.takeIndex(selectedIndexes);
|
final selectedItemsIt = _sortedItems.takeIndex(selectedIndexes);
|
||||||
return selectedItemsIt
|
return selectedItemsIt.any((item) => item.addedBy == widget.account.userId);
|
||||||
.any((item) => item.addedBy == widget.account.username);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AlbumItem> _getAlbumItemsOf(Album a) =>
|
static List<AlbumItem> _getAlbumItemsOf(Album a) =>
|
||||||
|
|
|
@ -198,10 +198,10 @@ class _ConnectState extends State<Connect> {
|
||||||
|
|
||||||
Future<void> _onCheckWebDavUrlFailed(
|
Future<void> _onCheckWebDavUrlFailed(
|
||||||
BuildContext context, Account account) async {
|
BuildContext context, Account account) async {
|
||||||
final altHomeDir = await _askWebDavUrl(context, account);
|
final userId = await _askWebDavUrl(context, account);
|
||||||
if (altHomeDir != null) {
|
if (userId != null) {
|
||||||
final newAccount = account.copyWith(
|
final newAccount = account.copyWith(
|
||||||
altHomeDir: OrNull(altHomeDir.toCi()),
|
userId: userId.toCi(),
|
||||||
);
|
);
|
||||||
return _checkWebDavUrl(context, newAccount);
|
return _checkWebDavUrl(context, newAccount);
|
||||||
}
|
}
|
||||||
|
@ -286,9 +286,9 @@ class _WebDavUrlDialogState extends State<_WebDavUrlDialog> {
|
||||||
return L10n.global().homeFolderInputInvalidEmpty;
|
return L10n.global().homeFolderInputInvalidEmpty;
|
||||||
},
|
},
|
||||||
onSaved: (value) {
|
onSaved: (value) {
|
||||||
_formValue.altHomeDir = value!.trimAny("/");
|
_formValue.userId = value!.trimAny("/");
|
||||||
},
|
},
|
||||||
initialValue: widget.account.homeDir.toString(),
|
initialValue: widget.account.userId.toString(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -309,7 +309,7 @@ class _WebDavUrlDialogState extends State<_WebDavUrlDialog> {
|
||||||
void _onOkPressed() {
|
void _onOkPressed() {
|
||||||
if (_formKey.currentState?.validate() == true) {
|
if (_formKey.currentState?.validate() == true) {
|
||||||
_formKey.currentState!.save();
|
_formKey.currentState!.save();
|
||||||
Navigator.of(context).pop(_formValue.altHomeDir);
|
Navigator.of(context).pop(_formValue.userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,5 +322,5 @@ class _WebDavUrlDialogState extends State<_WebDavUrlDialog> {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FormValue {
|
class _FormValue {
|
||||||
late String altHomeDir;
|
late String userId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,7 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@protected
|
@protected
|
||||||
get canEdit => _album?.albumFile?.isOwned(widget.account.username) == true;
|
get canEdit => _album?.albumFile?.isOwned(widget.account.userId) == true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
enterEditMode() {
|
enterEditMode() {
|
||||||
|
|
|
@ -34,7 +34,7 @@ class AddSelectionToAlbumHandler {
|
||||||
assert(value.provider is AlbumStaticProvider);
|
assert(value.provider is AlbumStaticProvider);
|
||||||
final selected = selectedFiles
|
final selected = selectedFiles
|
||||||
.map((f) => AlbumFileItem(
|
.map((f) => AlbumFileItem(
|
||||||
addedBy: account.username,
|
addedBy: account.userId,
|
||||||
addedAt: DateTime.now(),
|
addedAt: DateTime.now(),
|
||||||
file: f,
|
file: f,
|
||||||
))
|
))
|
||||||
|
|
|
@ -462,7 +462,7 @@ class _HomeAlbumsState extends State<HomeAlbums>
|
||||||
final failures = <Album>[];
|
final failures = <Album>[];
|
||||||
for (final a in selected) {
|
for (final a in selected) {
|
||||||
try {
|
try {
|
||||||
if (a.albumFile?.isOwned(widget.account.username) == true) {
|
if (a.albumFile?.isOwned(widget.account.userId) == true) {
|
||||||
// delete owned albums
|
// delete owned albums
|
||||||
await RemoveAlbum(KiwiContainer().resolve<DiContainer>())(
|
await RemoveAlbum(KiwiContainer().resolve<DiContainer>())(
|
||||||
widget.account, a);
|
widget.account, a);
|
||||||
|
|
|
@ -63,7 +63,7 @@ class HomeSliverAppBar extends StatelessWidget {
|
||||||
),
|
),
|
||||||
if (accountLabel == null)
|
if (accountLabel == null)
|
||||||
Text(
|
Text(
|
||||||
account.username.toString(),
|
account.username2,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: AppTheme.getSecondaryTextColor(context),
|
color: AppTheme.getSecondaryTextColor(context),
|
||||||
|
|
|
@ -225,7 +225,7 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
|
||||||
void _transformShareeItems(List<Sharee> sharees) {
|
void _transformShareeItems(List<Sharee> sharees) {
|
||||||
final candidates = sharees
|
final candidates = sharees
|
||||||
.where((s) =>
|
.where((s) =>
|
||||||
s.shareWith != widget.account.username &&
|
s.shareWith != widget.account.userId &&
|
||||||
// remove users already shared with
|
// remove users already shared with
|
||||||
!_items.any((i) => i.shareWith == s.shareWith))
|
!_items.any((i) => i.shareWith == s.shareWith))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
|
@ -129,7 +129,7 @@ class _SharedFileViewerState extends State<SharedFileViewer> {
|
||||||
title: Text(widget.file.strippedPath),
|
title: Text(widget.file.strippedPath),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.shares.first.uidOwner == widget.account.username) ...[
|
if (widget.shares.first.uidOwner == widget.account.userId) ...[
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
|
|
|
@ -202,7 +202,7 @@ class _SharingBrowserState extends State<SharingBrowser> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: shares.first.share.filename,
|
label: shares.first.share.filename,
|
||||||
description: shares.first.share.uidOwner == widget.account.username
|
description: shares.first.share.uidOwner == widget.account.userId
|
||||||
? L10n.global().fileLastSharedDescription(dateStr)
|
? L10n.global().fileLastSharedDescription(dateStr)
|
||||||
: L10n.global().fileLastSharedByOthersDescription(
|
: L10n.global().fileLastSharedByOthersDescription(
|
||||||
shares.first.share.displaynameOwner, dateStr),
|
shares.first.share.displaynameOwner, dateStr),
|
||||||
|
@ -260,7 +260,7 @@ class _SharingBrowserState extends State<SharingBrowser> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: firstItem.album.name,
|
label: firstItem.album.name,
|
||||||
description: shares.first.share.uidOwner == widget.account.username
|
description: shares.first.share.uidOwner == widget.account.userId
|
||||||
? L10n.global().fileLastSharedDescription(dateStr)
|
? L10n.global().fileLastSharedDescription(dateStr)
|
||||||
: L10n.global().albumLastSharedByOthersDescription(
|
: L10n.global().albumLastSharedByOthersDescription(
|
||||||
shares.first.share.displaynameOwner, dateStr),
|
shares.first.share.displaynameOwner, dateStr),
|
||||||
|
@ -307,7 +307,7 @@ class _SharingBrowserState extends State<SharingBrowser> {
|
||||||
// group shares of the same file
|
// group shares of the same file
|
||||||
final map = <String, List<ListSharingItem>>{};
|
final map = <String, List<ListSharingItem>>{};
|
||||||
for (final i in items) {
|
for (final i in items) {
|
||||||
final isSharedByMe = (i.share.uidOwner == widget.account.username);
|
final isSharedByMe = (i.share.uidOwner == widget.account.userId);
|
||||||
final groupKey = "${i.share.path}?$isSharedByMe";
|
final groupKey = "${i.share.path}?$isSharedByMe";
|
||||||
map[groupKey] ??= <ListSharingItem>[];
|
map[groupKey] ??= <ListSharingItem>[];
|
||||||
map[groupKey]!.add(i);
|
map[groupKey]!.add(i);
|
||||||
|
|
|
@ -238,8 +238,8 @@ class _SignInState extends State<SignIn> {
|
||||||
_formValue.scheme,
|
_formValue.scheme,
|
||||||
_formValue.address,
|
_formValue.address,
|
||||||
_formValue.username.toCi(),
|
_formValue.username.toCi(),
|
||||||
|
_formValue.username,
|
||||||
_formValue.password,
|
_formValue.password,
|
||||||
null,
|
|
||||||
[""],
|
[""],
|
||||||
);
|
);
|
||||||
_log.info("[_connect] Try connecting with account: $account");
|
_log.info("[_connect] Try connecting with account: $account");
|
||||||
|
|
|
@ -149,7 +149,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
onPressed: () => _onRemoveFromAlbumPressed(context),
|
onPressed: () => _onRemoveFromAlbumPressed(context),
|
||||||
),
|
),
|
||||||
if (widget.album != null &&
|
if (widget.album != null &&
|
||||||
widget.album!.albumFile?.isOwned(widget.account.username) ==
|
widget.album!.albumFile?.isOwned(widget.account.userId) ==
|
||||||
true)
|
true)
|
||||||
_DetailPaneButton(
|
_DetailPaneButton(
|
||||||
icon: Icons.photo_album_outlined,
|
icon: Icons.photo_album_outlined,
|
||||||
|
@ -195,7 +195,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
title: Text(path_lib.basenameWithoutExtension(widget.file.path)),
|
title: Text(path_lib.basenameWithoutExtension(widget.file.path)),
|
||||||
subtitle: Text(widget.file.strippedPath),
|
subtitle: Text(widget.file.strippedPath),
|
||||||
),
|
),
|
||||||
if (!widget.file.isOwned(widget.account.username))
|
if (!widget.file.isOwned(widget.account.userId))
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: ListTileCenterLeading(
|
leading: ListTileCenterLeading(
|
||||||
child: Icon(
|
child: Icon(
|
||||||
|
@ -509,7 +509,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
widget.album!.provider is! AlbumStaticProvider) {
|
widget.album!.provider is! AlbumStaticProvider) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (widget.album!.albumFile?.isOwned(widget.account.username) == true) {
|
if (widget.album!.albumFile?.isOwned(widget.account.userId) == true) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -518,7 +518,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
.whereType<AlbumFileItem>()
|
.whereType<AlbumFileItem>()
|
||||||
.firstWhere(
|
.firstWhere(
|
||||||
(element) => element.file.compareServerIdentity(widget.file));
|
(element) => element.file.compareServerIdentity(widget.file));
|
||||||
if (thisItem.addedBy == widget.account.username) {
|
if (thisItem.addedBy == widget.account.userId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
152
app/test/account_test.dart
Normal file
152
app/test/account_test.dart
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/ci_string.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group("Account", () {
|
||||||
|
group("constructor", () {
|
||||||
|
test("trim address", _constructTrimAddress);
|
||||||
|
test("invalid scheme", _constructInvalidScheme);
|
||||||
|
});
|
||||||
|
test("fromJson", _fromJson);
|
||||||
|
});
|
||||||
|
group("AccountUpgraderV1", () {
|
||||||
|
test("normal", _upgraderV1);
|
||||||
|
test("ldap", _upgraderV1Ldap);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert json obj to Account
|
||||||
|
///
|
||||||
|
/// Expect: Account constructed
|
||||||
|
void _fromJson() {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": Account.version,
|
||||||
|
"id": "123456",
|
||||||
|
"scheme": "https",
|
||||||
|
"address": "example.com",
|
||||||
|
"userId": "00000000-1111-aaaa-bbbb-223344ccddee",
|
||||||
|
"username2": "admin",
|
||||||
|
"password": "123456",
|
||||||
|
"roots": ["test1", "test2"],
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
Account.fromJson(
|
||||||
|
json,
|
||||||
|
upgraderV1: null,
|
||||||
|
),
|
||||||
|
Account(
|
||||||
|
"123456",
|
||||||
|
"https",
|
||||||
|
"example.com",
|
||||||
|
"00000000-1111-aaaa-bbbb-223344ccddee".toCi(),
|
||||||
|
"admin",
|
||||||
|
"123456",
|
||||||
|
["test1", "test2"],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upgrade v1 Account json to v2 Account json
|
||||||
|
///
|
||||||
|
/// Expect: v2.userId = v1.username;
|
||||||
|
/// v2.username2 = v1.username
|
||||||
|
void _upgraderV1() {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": 1,
|
||||||
|
"id": "123456",
|
||||||
|
"scheme": "https",
|
||||||
|
"address": "example.com",
|
||||||
|
"username": "admin",
|
||||||
|
"password": "123456",
|
||||||
|
"roots": ["test1", "test2"],
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
const AccountUpgraderV1()(json),
|
||||||
|
<String, dynamic>{
|
||||||
|
"version": 1,
|
||||||
|
"id": "123456",
|
||||||
|
"scheme": "https",
|
||||||
|
"address": "example.com",
|
||||||
|
"userId": "admin",
|
||||||
|
"username2": "admin",
|
||||||
|
"password": "123456",
|
||||||
|
"roots": ["test1", "test2"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Upgrade v1 Account json to v2 Account json for a LDAP account
|
||||||
|
///
|
||||||
|
/// Expect: v2.userId = v1.altHomeDir;
|
||||||
|
/// v2.username2 = v1.username
|
||||||
|
void _upgraderV1Ldap() {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"version": 1,
|
||||||
|
"id": "123456",
|
||||||
|
"scheme": "https",
|
||||||
|
"address": "example.com",
|
||||||
|
"username": "admin",
|
||||||
|
"altHomeDir": "00000000-1111-aaaa-bbbb-223344ccddee",
|
||||||
|
"password": "123456",
|
||||||
|
"roots": ["test1", "test2"],
|
||||||
|
};
|
||||||
|
expect(
|
||||||
|
const AccountUpgraderV1()(json),
|
||||||
|
<String, dynamic>{
|
||||||
|
"version": 1,
|
||||||
|
"id": "123456",
|
||||||
|
"scheme": "https",
|
||||||
|
"address": "example.com",
|
||||||
|
"userId": "00000000-1111-aaaa-bbbb-223344ccddee",
|
||||||
|
"username2": "admin",
|
||||||
|
"password": "123456",
|
||||||
|
"roots": ["test1", "test2"],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new Account, with address ending with /
|
||||||
|
///
|
||||||
|
/// Expect: Account constructed;
|
||||||
|
/// Trailing / in address removed
|
||||||
|
void _constructTrimAddress() {
|
||||||
|
expect(
|
||||||
|
Account(
|
||||||
|
"123456",
|
||||||
|
"https",
|
||||||
|
"example.com/",
|
||||||
|
"00000000-1111-aaaa-bbbb-223344ccddee".toCi(),
|
||||||
|
"admin",
|
||||||
|
"123456",
|
||||||
|
["test1", "test2"],
|
||||||
|
),
|
||||||
|
Account(
|
||||||
|
"123456",
|
||||||
|
"https",
|
||||||
|
"example.com",
|
||||||
|
"00000000-1111-aaaa-bbbb-223344ccddee".toCi(),
|
||||||
|
"admin",
|
||||||
|
"123456",
|
||||||
|
["test1", "test2"],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a new Account, with scheme != http/https
|
||||||
|
///
|
||||||
|
/// Expect: FormatException
|
||||||
|
void _constructInvalidScheme() {
|
||||||
|
expect(
|
||||||
|
() => Account(
|
||||||
|
"123456",
|
||||||
|
"ssh",
|
||||||
|
"example.com/",
|
||||||
|
"00000000-1111-aaaa-bbbb-223344ccddee".toCi(),
|
||||||
|
"admin",
|
||||||
|
"123456",
|
||||||
|
["test1", "test2"],
|
||||||
|
),
|
||||||
|
throwsFormatException,
|
||||||
|
);
|
||||||
|
}
|
|
@ -254,7 +254,7 @@ void _testQuerySharedAlbumMissingManagedShareOtherAdded(String description) {
|
||||||
/// Expect: emit empty list
|
/// Expect: emit empty list
|
||||||
void _testQuerySharedAlbumMissingManagedShareOtherReshared(String description) {
|
void _testQuerySharedAlbumMissingManagedShareOtherReshared(String description) {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files =
|
final files =
|
||||||
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
||||||
final user1Files = [
|
final user1Files = [
|
||||||
|
@ -450,7 +450,7 @@ void _testQuerySharedAlbumExtraShare(String description) {
|
||||||
/// Expect: emit the file with extra share (admin -> user2)
|
/// Expect: emit the file with extra share (admin -> user2)
|
||||||
void _testQuerySharedAlbumExtraShareOtherAdded(String description) {
|
void _testQuerySharedAlbumExtraShareOtherAdded(String description) {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder(initialFileId: 1)
|
final files = (util.FilesBuilder(initialFileId: 1)
|
||||||
..addJpeg("admin/test1.jpg", ownerId: "user1"))
|
..addJpeg("admin/test1.jpg", ownerId: "user1"))
|
||||||
.build();
|
.build();
|
||||||
|
@ -510,7 +510,7 @@ void _testQuerySharedAlbumExtraShareOtherAdded(String description) {
|
||||||
/// Expect: emit empty list
|
/// Expect: emit empty list
|
||||||
void _testQuerySharedAlbumExtraUnmanagedShare(String description) {
|
void _testQuerySharedAlbumExtraUnmanagedShare(String description) {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder(initialFileId: 1)
|
final files = (util.FilesBuilder(initialFileId: 1)
|
||||||
..addJpeg("admin/test1.jpg", ownerId: "user1"))
|
..addJpeg("admin/test1.jpg", ownerId: "user1"))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -1484,7 +1484,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
group("AlbumUpgraderV5", () {
|
group("AlbumUpgraderV5", () {
|
||||||
final account = util.buildAccount(username: "user1");
|
final account = util.buildAccount(userId: "user1");
|
||||||
|
|
||||||
test("w/ ownerId", () {
|
test("w/ ownerId", () {
|
||||||
final json = <String, dynamic>{
|
final json = <String, dynamic>{
|
||||||
|
|
|
@ -347,7 +347,7 @@ Future<void> _updaterUpdateFile() async {
|
||||||
/// Expect: file added to AccountFiles table
|
/// Expect: file added to AccountFiles table
|
||||||
Future<void> _updaterNewSharedFile() async {
|
Future<void> _updaterNewSharedFile() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder()
|
final files = (util.FilesBuilder()
|
||||||
..addDir("admin")
|
..addDir("admin")
|
||||||
..addJpeg("admin/test1.jpg")
|
..addJpeg("admin/test1.jpg")
|
||||||
|
@ -385,7 +385,7 @@ Future<void> _updaterNewSharedFile() async {
|
||||||
/// Expect: file added to AccountFiles table
|
/// Expect: file added to AccountFiles table
|
||||||
Future<void> _updaterNewSharedDir() async {
|
Future<void> _updaterNewSharedDir() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder()
|
final files = (util.FilesBuilder()
|
||||||
..addDir("admin")
|
..addDir("admin")
|
||||||
..addJpeg("admin/test1.jpg", ownerId: "user1")
|
..addJpeg("admin/test1.jpg", ownerId: "user1")
|
||||||
|
@ -423,7 +423,7 @@ Future<void> _updaterNewSharedDir() async {
|
||||||
/// file remained in Files table
|
/// file remained in Files table
|
||||||
Future<void> _updaterDeleteSharedFile() async {
|
Future<void> _updaterDeleteSharedFile() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder()
|
final files = (util.FilesBuilder()
|
||||||
..addDir("admin")
|
..addDir("admin")
|
||||||
..addJpeg("admin/test1.jpg")
|
..addJpeg("admin/test1.jpg")
|
||||||
|
@ -465,7 +465,7 @@ Future<void> _updaterDeleteSharedFile() async {
|
||||||
/// file remained in Files table
|
/// file remained in Files table
|
||||||
Future<void> _updaterDeleteSharedDir() async {
|
Future<void> _updaterDeleteSharedDir() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder()
|
final files = (util.FilesBuilder()
|
||||||
..addDir("admin")
|
..addDir("admin")
|
||||||
..addJpeg("admin/test1.jpg")
|
..addJpeg("admin/test1.jpg")
|
||||||
|
|
|
@ -328,7 +328,7 @@ class MockShareMemoryRepo extends MockShareRepo {
|
||||||
return shares.where((s) {
|
return shares.where((s) {
|
||||||
if (s.itemSource != file.fileId) {
|
if (s.itemSource != file.fileId) {
|
||||||
return false;
|
return false;
|
||||||
} else if (isIncludeReshare == true || s.uidOwner == account.username) {
|
} else if (isIncludeReshare == true || s.uidOwner == account.userId) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -342,8 +342,8 @@ class MockShareMemoryRepo extends MockShareRepo {
|
||||||
id: (_id++).toString(),
|
id: (_id++).toString(),
|
||||||
shareType: ShareType.user,
|
shareType: ShareType.user,
|
||||||
stime: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
stime: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
uidOwner: account.username,
|
uidOwner: account.userId,
|
||||||
displaynameOwner: account.username.toString(),
|
displaynameOwner: account.username2,
|
||||||
uidFileOwner: file.ownerId!,
|
uidFileOwner: file.ownerId!,
|
||||||
path: file.strippedPath,
|
path: file.strippedPath,
|
||||||
itemType: ShareItemType.file,
|
itemType: ShareItemType.file,
|
||||||
|
@ -383,7 +383,7 @@ class MockShareeMemoryRepo extends MockShareeRepo {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
list(Account account) async {
|
list(Account account) async {
|
||||||
return sharees.where((s) => s.shareWith != account.username).toList();
|
return sharees.where((s) => s.shareWith != account.userId).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<Sharee> sharees;
|
final List<Sharee> sharees;
|
||||||
|
|
|
@ -286,11 +286,12 @@ Account buildAccount({
|
||||||
String id = "123456-000000",
|
String id = "123456-000000",
|
||||||
String scheme = "http",
|
String scheme = "http",
|
||||||
String address = "example.com",
|
String address = "example.com",
|
||||||
String username = "admin",
|
String userId = "admin",
|
||||||
|
String username2 = "admin",
|
||||||
String password = "pass",
|
String password = "pass",
|
||||||
List<String> roots = const [""],
|
List<String> roots = const [""],
|
||||||
}) =>
|
}) =>
|
||||||
Account(id, scheme, address, username.toCi(), password, null, roots);
|
Account(id, scheme, address, userId.toCi(), username2, password, roots);
|
||||||
|
|
||||||
/// Build a mock [File] pointing to a album JSON file
|
/// Build a mock [File] pointing to a album JSON file
|
||||||
///
|
///
|
||||||
|
|
|
@ -187,7 +187,7 @@ Future<void> _addExistingFile() async {
|
||||||
/// Expect: file not added to album
|
/// Expect: file not added to album
|
||||||
Future<void> _addExistingSharedFile() async {
|
Future<void> _addExistingSharedFile() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files =
|
final files =
|
||||||
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
||||||
final user1Files = [
|
final user1Files = [
|
||||||
|
|
|
@ -131,7 +131,7 @@ Future<void> _removeSharedAlbumFileInOtherAlbum() async {
|
||||||
/// share (admin -> user1) for the file delete
|
/// share (admin -> user1) for the file delete
|
||||||
Future<void> _removeSharedAlbumResyncedFile() async {
|
Future<void> _removeSharedAlbumResyncedFile() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files =
|
final files =
|
||||||
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
|
||||||
final user1Files = [
|
final user1Files = [
|
||||||
|
|
|
@ -291,7 +291,7 @@ Future<void> _removeFromSharedAlbumOwned() async {
|
||||||
/// unchanged
|
/// unchanged
|
||||||
Future<void> _removeFromSharedAlbumOwnedWithOtherShare() async {
|
Future<void> _removeFromSharedAlbumOwnedWithOtherShare() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder(initialFileId: 1)
|
final files = (util.FilesBuilder(initialFileId: 1)
|
||||||
..addJpeg("user1/test1.jpg", ownerId: "user1"))
|
..addJpeg("user1/test1.jpg", ownerId: "user1"))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -238,7 +238,7 @@ Future<void> _removeSharedAlbumFile() async {
|
||||||
/// file share (admin -> user2) deleted
|
/// file share (admin -> user2) deleted
|
||||||
Future<void> _removeSharedAlbumSharedFile() async {
|
Future<void> _removeSharedAlbumSharedFile() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder(initialFileId: 1)
|
final files = (util.FilesBuilder(initialFileId: 1)
|
||||||
..addJpeg("admin/test1.jpg", ownerId: "user1"))
|
..addJpeg("admin/test1.jpg", ownerId: "user1"))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -116,7 +116,7 @@ Future<void> _unsupportedFile() async {
|
||||||
/// Expect: user1/test1.jpg, user1/test/test2.jpg
|
/// Expect: user1/test1.jpg, user1/test/test2.jpg
|
||||||
Future<void> _multiAccountRoot() async {
|
Future<void> _multiAccountRoot() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(username: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final files = (util.FilesBuilder()
|
final files = (util.FilesBuilder()
|
||||||
..addJpeg("admin/test1.jpg")
|
..addJpeg("admin/test1.jpg")
|
||||||
..addJpeg("admin/test/test2.jpg"))
|
..addJpeg("admin/test/test2.jpg"))
|
||||||
|
|
Loading…
Reference in a new issue