Fix user ID and display name mixed up in logic

This commit is contained in:
Ming Ming 2022-07-12 02:14:42 +08:00
parent 413c185290
commit 478c25b5d0
47 changed files with 340 additions and 128 deletions

View file

@ -2,9 +2,8 @@ import 'dart:math';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.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/type.dart';
@ -14,9 +13,9 @@ class Account with EquatableMixin {
this.id,
this.scheme,
String address,
this.username,
this.userId,
this.username2,
this.password,
this.altHomeDir,
List<String> roots,
) : address = address.trimRightAny("/"),
_roots = roots.map((e) => e.trimRightAny("/")).toList() {
@ -29,18 +28,18 @@ class Account with EquatableMixin {
String? id,
String? scheme,
String? address,
CiString? username,
CiString? userId,
String? username2,
String? password,
OrNull<CiString>? altHomeDir,
List<String>? roots,
}) {
return Account(
id ?? this.id,
scheme ?? this.scheme,
address ?? this.address,
username ?? this.username,
userId ?? this.userId,
username2 ?? this.username2,
password ?? this.password,
altHomeDir == null ? this.altHomeDir : altHomeDir.obj,
roots ?? List.of(_roots),
);
}
@ -57,49 +56,68 @@ class Account with EquatableMixin {
"id: '$id', "
"scheme: '$scheme', "
"address: '${kDebugMode ? address : "***"}', "
"username: '${kDebugMode ? username : "***"}', "
"userId: '${kDebugMode ? userId : "***"}', "
"username2: '${kDebugMode ? username2 : "***"}', "
"password: '${password.isNotEmpty == true ? (kDebugMode ? password : '***') : null}', "
"altHomeDir: '${altHomeDir == null || kDebugMode ? altHomeDir : "***"}', "
"roots: List {'${roots.join('\', \'')}'}, "
"}";
}
Account.fromJson(JsonObj json)
: id = json["id"],
scheme = json["scheme"],
address = json["address"],
username = CiString(json["username"]),
password = json["password"],
altHomeDir = (json["altHomeDir"] as String?)?.run((v) => CiString(v)),
_roots = json["roots"].cast<String>();
static Account? fromJson(
JsonObj json, {
required AccountUpgraderV1? upgraderV1,
}) {
final jsonVersion = json["version"] ?? 1;
JsonObj? result = json;
if (jsonVersion < 2) {
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() => {
"version": version,
"id": id,
"scheme": scheme,
"address": address,
"username": username.toString(),
"userId": userId.toString(),
"username2": username2,
"password": password,
"altHomeDir": altHomeDir?.toString(),
"roots": _roots,
};
@override
get props => [id, scheme, address, username, password, altHomeDir, _roots];
get props => [id, scheme, address, userId, username2, password, _roots];
List<String> get roots => _roots;
CiString get homeDir => altHomeDir ?? username;
final String id;
final String scheme;
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;
/// 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;
/// versioning of this class, use to upgrade old persisted accounts
static const version = 2;
static final _log = Logger("account.Account");
}
extension AccountExtension on Account {
@ -113,6 +131,34 @@ extension AccountExtension on Account {
bool compareServerIdentity(Account other) {
return scheme == other.scheme &&
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");
}

View file

@ -38,7 +38,7 @@ class Api {
static String getAuthorizationHeaderValue(Account account) {
final auth =
base64.encode(utf8.encode("${account.username}:${account.password}"));
base64.encode(utf8.encode("${account.username2}:${account.password}"));
return "Basic $auth";
}

View file

@ -65,10 +65,10 @@ String getFileUrlRelative(File file) {
}
String getWebdavRootUrlRelative(Account account) =>
"remote.php/dav/files/${account.homeDir}";
"remote.php/dav/files/${account.userId}";
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]
String getFacePreviewUrl(

View file

@ -1,7 +1,7 @@
import 'package:nc_photos/account.dart';
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) =>
"$className(${account.scheme}://${account.username.toCaseInsensitiveString()}@${account.address}?${account.roots.join('&')})";
"$className(${account.scheme}://${account.userId.toCaseInsensitiveString()}@${account.address}?${account.roots.join('&')})";

View file

@ -202,9 +202,9 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
final albumShares = await () async {
var temp = (ev.album.shares ?? [])
.where((s) => s.userId != ev.account.username)
.where((s) => s.userId != ev.account.userId)
.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
final ownerSharee = (await ListSharee(_c.shareeRepo)(ev.account))
.firstWhere((s) => s.shareWith == ev.album.albumFile!.ownerId);
@ -241,7 +241,7 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
Map<CiString, AlbumShare> albumShares,
List<Object> errors,
) async {
if (!album.albumFile!.isOwned(account.username)) {
if (!album.albumFile!.isOwned(account.userId)) {
// album file is always managed by the owner
return [];
}
@ -335,7 +335,7 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
var missings = managedAlbumSharees
.difference(allSharees)
// 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();
_log.info(
"[_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
// that non-managed sharees will not be listed
final ownedSharees = shares
.where((s) => s.uidOwner == account.username)
.where((s) => s.uidOwner == account.userId)
.map((s) => s.shareWith!)
.toSet();
final extras = ownedSharees.difference(albumSharees);
@ -375,13 +375,13 @@ class ListAlbumShareOutlierBloc extends Bloc<ListAlbumShareOutlierBlocEvent,
bool _isItemSharePairOfInterest(
Account account, Album album, AlbumItem item, AlbumShare share) {
if (album.albumFile!.isOwned(account.username)) {
if (album.albumFile!.isOwned(account.userId)) {
// album owner
return item.addedBy == account.username ||
return item.addedBy == account.userId ||
item.addedAt.isBefore(share.sharedAt);
} else {
// non album owner
if (item.addedBy != account.username) {
if (item.addedBy != account.userId) {
return false;
} else {
return share.userId == album.albumFile!.ownerId ||

View file

@ -469,7 +469,7 @@ class ScanAccountDirBloc
);
yield files
.where((f) =>
file_util.isSupportedFormat(f) && !f.isOwned(account.username))
file_util.isSupportedFormat(f) && !f.isOwned(account.userId))
.toList();
} catch (e, stackTrace) {
yield ExceptionEvent(e, stackTrace);

View file

@ -166,7 +166,7 @@ class AlbumSqliteDbDataSource implements AlbumDataSource {
} else {
try {
final f = SqliteFileConverter.fromSql(
account.homeDir.toString(), item["file"]);
account.userId.toString(), item["file"]);
yield SqliteAlbumConverter.fromSql(
item["album"], f, item["shares"] ?? []);
} catch (e, stackTrace) {

View file

@ -181,10 +181,10 @@ class AlbumUpgraderV5 implements AlbumUpgrader {
final CiString addedBy;
if (result.containsKey("albumFile")) {
addedBy = result["albumFile"]["ownerId"] == null
? account.username
? account.userId
: CiString(result["albumFile"]["ownerId"]);
} else {
addedBy = albumFile?.ownerId ?? account.username;
addedBy = albumFile?.ownerId ?? account.userId;
}
item["addedBy"] = addedBy.toString();
item["addedAt"] = result["lastUpdated"];

View file

@ -465,7 +465,7 @@ extension FileExtension on File {
lastModified ??
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
///

View file

@ -72,7 +72,7 @@ class SqliteAlbumConverter {
}
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(
lastUpdated: obj.lastUpdated,
fileEtag: obj.fileEtag,
@ -81,7 +81,7 @@ class SqliteFileConverter {
exif: obj.exifRaw?.run((e) => Exif.fromJson(jsonDecode(e))),
));
return File(
path: "remote.php/dav/files/$homeDir/${f.accountFile.relativePath}",
path: "remote.php/dav/files/$userId/${f.accountFile.relativePath}",
contentLength: f.file.contentLength,
contentType: f.file.contentType,
etag: f.file.etag,

View file

@ -35,7 +35,7 @@ class CompleteFileCompanion {
extension CompleteFileListExtension on List<CompleteFile> {
Future<List<app.File>> convertToAppFile(app.Account account) {
return map((f) => {
"homeDir": account.homeDir.toString(),
"userId": account.userId.toString(),
"completeFile": f,
}).computeAll(_covertSqliteDbFile);
}
@ -141,7 +141,7 @@ extension SqliteDbExtension on SqliteDb {
await into(accounts).insert(
AccountsCompanion.insert(
server: dbServer.rowId,
userId: account.username.toCaseInsensitiveString(),
userId: account.userId.toCaseInsensitiveString(),
),
mode: InsertMode.insertOrIgnore,
);
@ -153,8 +153,7 @@ extension SqliteDbExtension on SqliteDb {
useColumns: false)
])
..where(servers.address.equals(account.url))
..where(
accounts.userId.equals(account.username.toCaseInsensitiveString()))
..where(accounts.userId.equals(account.userId.toCaseInsensitiveString()))
..limit(1);
return query.map((r) => r.readTable(accounts)).getSingle();
}
@ -522,7 +521,7 @@ class FilesQueryBuilder {
query
..where(db.servers.address.equals(_appAccount!.url))
..where(db.accounts.userId
.equals(_appAccount!.username.toCaseInsensitiveString()));
.equals(_appAccount!.userId.toCaseInsensitiveString()));
}
if (_byRowId != null) {
@ -585,9 +584,9 @@ class FilesQueryBuilder {
}
app.File _covertSqliteDbFile(Map map) {
final homeDir = map["homeDir"] as String;
final userId = map["userId"] as String;
final file = map["completeFile"] as CompleteFile;
return SqliteFileConverter.fromSql(homeDir, file);
return SqliteFileConverter.fromSql(userId, file);
}
CompleteFileCompanion _convertAppFile(Map map) {

View file

@ -49,7 +49,7 @@ class MetadataTask {
account,
shareFolder,
isRecursive: false,
filter: (f) => f.ownerId != account.username,
filter: (f) => f.ownerId != account.userId,
)) {
if (!Pref().isEnableExifOr()) {
_log.info("[call] EXIF disabled, task ending immaturely");

View file

@ -1,8 +1,10 @@
import 'dart:async';
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:event_bus/event_bus.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/mobile/platform.dart'
@ -26,7 +28,19 @@ class Pref {
List<Account>? getAccounts3() {
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;
@ -235,6 +249,8 @@ class Pref {
final PrefProvider provider;
static Pref? _inst;
static final _log = Logger("pref.Pref");
}
class AccountPref {

View file

@ -277,7 +277,7 @@ class _MetadataTask {
account,
shareFolder,
isRecursive: false,
filter: (f) => f.ownerId != account.username,
filter: (f) => f.ownerId != account.userId,
)) {
if (ev is File) {
_onFileProcessed(ev);

View file

@ -108,9 +108,9 @@ class TouchTokenManager {
String _getLocalStorageName(Account account, File file) {
final strippedPath = file.strippedPath;
if (strippedPath == ".") {
return "touch/${account.url.replaceFirst('://', '_')}/${account.username}/token";
return "touch/${account.url.replaceFirst('://', '_')}/${account.userId}/token";
} else {
return "touch/${account.url.replaceFirst('://', '_')}/${account.username}/${file.strippedPath}/token";
return "touch/${account.url.replaceFirst('://', '_')}/${account.userId}/${file.strippedPath}/token";
}
}

View file

@ -72,8 +72,8 @@ class AddToAlbum {
Future<void> _shareFiles(
Account account, Album album, List<File> files) async {
final albumShares = (album.shares!.map((e) => e.userId).toList()
..add(album.albumFile!.ownerId ?? account.username))
.where((element) => element != account.username)
..add(album.albumFile!.ownerId ?? account.userId))
.where((element) => element != account.userId)
.toSet();
if (albumShares.isEmpty) {
return;

View file

@ -28,7 +28,7 @@ class ListPotentialSharedAlbum {
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) {
try {

View file

@ -21,7 +21,7 @@ class ListShare {
bool? isIncludeReshare,
}) async {
try {
if (file_util.getUserDirName(file) != account.homeDir) {
if (file_util.getUserDirName(file) != account.userId) {
file = (await FindFile(_c)(account, [file.fileId!])).first;
}
} catch (_) {

View file

@ -51,7 +51,7 @@ class PopulateAlbum {
continue;
}
products.addAll((result as List).cast<File>().map((f) => AlbumFileItem(
addedBy: account.username,
addedBy: account.userId,
addedAt: DateTime.now(),
file: f,
)));
@ -68,7 +68,7 @@ class PopulateAlbum {
final c = KiwiContainer().resolve<DiContainer>();
final files = await ListTaggedFile(c)(account, provider.tags);
products.addAll(files.map((f) => AlbumFileItem(
addedBy: account.username,
addedBy: account.userId,
addedAt: DateTime.now(),
file: f,
)));
@ -87,7 +87,7 @@ class PopulateAlbum {
return files
.where((f) => file_util.isSupportedFormat(f))
.map((f) => AlbumFileItem(
addedBy: account.username,
addedBy: account.userId,
addedAt: DateTime.now(),
file: f,
))

View file

@ -65,8 +65,8 @@ class Remove {
final itemsToRemove = provider.items
.whereType<AlbumFileItem>()
.where((i) =>
(i.file.isOwned(account.username) ||
i.addedBy == account.username) &&
(i.file.isOwned(account.userId) ||
i.addedBy == account.userId) &&
removes.any((r) => r.compareServerIdentity(i.file)))
.toList();
if (itemsToRemove.isEmpty) {
@ -76,7 +76,7 @@ class Remove {
final key = FileServerIdentityComparator(i.file);
final value = (a.shares?.map((s) => s.userId).toList() ?? [])
..add(a.albumFile!.ownerId!)
..remove(account.username);
..remove(account.userId);
(unshares[key] ??= <CiString>{}).addAll(value);
}
_log.fine(

View file

@ -51,8 +51,8 @@ class RemoveAlbum {
return;
}
final albumShares = (album.shares!.map((e) => e.userId).toList()
..add(album.albumFile!.ownerId ?? account.username))
.where((element) => element != account.username)
..add(album.albumFile!.ownerId ?? account.userId))
.where((element) => element != account.userId)
.toList();
if (albumShares.isEmpty) {
return;

View file

@ -103,8 +103,8 @@ class RemoveFromAlbum {
Future<void> _unshareFiles(
Account account, Album album, List<File> files) async {
final albumShares = (album.shares!.map((e) => e.userId).toList()
..add(album.albumFile!.ownerId ?? account.username))
.where((element) => element != account.username)
..add(album.albumFile!.ownerId ?? account.userId))
.where((element) => element != account.userId)
.toList();
if (albumShares.isNotEmpty) {
await UnshareFileFromAlbum(_c)(account, album, files, albumShares);

View file

@ -17,7 +17,7 @@ class RestoreTrashbin {
await Move(_c)(
account,
file,
"remote.php/dav/trashbin/${account.homeDir}/restore/${file.filename}",
"remote.php/dav/trashbin/${account.userId}/restore/${file.filename}",
shouldOverwrite: true,
);
KiwiContainer()

View file

@ -50,8 +50,7 @@ class ScanDirOffline {
.get();
});
return dbFiles
.map(
(f) => SqliteFileConverter.fromSql(account.homeDir.toString(), f))
.map((f) => SqliteFileConverter.fromSql(account.userId.toString(), f))
.toList();
});
}

View file

@ -45,7 +45,7 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
child: ListTile(
dense: true,
title: Text(label ?? a.url),
subtitle: label == null ? Text(a.username.toString()) : null,
subtitle: label == null ? Text(a.username2) : null,
trailing: IconButton(
icon: Icon(
Icons.close,
@ -87,7 +87,7 @@ class _AccountPickerDialogState extends State<AccountPickerDialog> {
),
subtitle: accountLabel == null
? Text(
widget.account.username.toString(),
widget.account.username2,
style: const TextStyle(fontWeight: FontWeight.bold),
)
: null,

View file

@ -133,7 +133,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
@override
@protected
get canEdit => _album?.albumFile?.isOwned(widget.account.username) == true;
get canEdit => _album?.albumFile?.isOwned(widget.account.userId) == true;
@override
enterEditMode() {
@ -274,7 +274,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
widget.account,
_album!,
actions: [
if (_album!.albumFile!.isOwned(widget.account.username) &&
if (_album!.albumFile!.isOwned(widget.account.userId) &&
Pref().isLabEnableSharedAlbumOr(false))
IconButton(
onPressed: () => _onSharePressed(context),
@ -462,8 +462,8 @@ class _AlbumBrowserState extends State<AlbumBrowser>
.takeIndex(selectedIndexes)
// can only remove owned files
.where((element) =>
_album!.albumFile!.isOwned(widget.account.username) == true ||
element.addedBy == widget.account.username)
_album!.albumFile!.isOwned(widget.account.userId) == true ||
element.addedBy == widget.account.userId)
.toList();
setState(() {
clearSelectedItems();
@ -668,7 +668,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
provider: AlbumStaticProvider.of(_editAlbum!).copyWith(
items: [
AlbumLabelItem(
addedBy: widget.account.username,
addedBy: widget.account.userId,
addedAt: DateTime.now(),
text: value,
),
@ -822,7 +822,7 @@ class _AlbumBrowserState extends State<AlbumBrowser>
Future<void> _setAlbum(Album album) async {
assert(album.provider is AlbumStaticProvider);
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 = album.copyWith(
@ -858,14 +858,13 @@ class _AlbumBrowserState extends State<AlbumBrowser>
}
bool get _canRemoveSelection {
if (_album!.albumFile!.isOwned(widget.account.username) == true) {
if (_album!.albumFile!.isOwned(widget.account.userId) == true) {
return true;
}
final selectedIndexes =
selectedListItems.whereType<_ListItem>().map((e) => e.index).toList();
final selectedItemsIt = _sortedItems.takeIndex(selectedIndexes);
return selectedItemsIt
.any((item) => item.addedBy == widget.account.username);
return selectedItemsIt.any((item) => item.addedBy == widget.account.userId);
}
static List<AlbumItem> _getAlbumItemsOf(Album a) =>

View file

@ -198,10 +198,10 @@ class _ConnectState extends State<Connect> {
Future<void> _onCheckWebDavUrlFailed(
BuildContext context, Account account) async {
final altHomeDir = await _askWebDavUrl(context, account);
if (altHomeDir != null) {
final userId = await _askWebDavUrl(context, account);
if (userId != null) {
final newAccount = account.copyWith(
altHomeDir: OrNull(altHomeDir.toCi()),
userId: userId.toCi(),
);
return _checkWebDavUrl(context, newAccount);
}
@ -286,9 +286,9 @@ class _WebDavUrlDialogState extends State<_WebDavUrlDialog> {
return L10n.global().homeFolderInputInvalidEmpty;
},
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() {
if (_formKey.currentState?.validate() == true) {
_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 {
late String altHomeDir;
late String userId;
}

View file

@ -127,7 +127,7 @@ class _DynamicAlbumBrowserState extends State<DynamicAlbumBrowser>
@override
@protected
get canEdit => _album?.albumFile?.isOwned(widget.account.username) == true;
get canEdit => _album?.albumFile?.isOwned(widget.account.userId) == true;
@override
enterEditMode() {

View file

@ -34,7 +34,7 @@ class AddSelectionToAlbumHandler {
assert(value.provider is AlbumStaticProvider);
final selected = selectedFiles
.map((f) => AlbumFileItem(
addedBy: account.username,
addedBy: account.userId,
addedAt: DateTime.now(),
file: f,
))

View file

@ -462,7 +462,7 @@ class _HomeAlbumsState extends State<HomeAlbums>
final failures = <Album>[];
for (final a in selected) {
try {
if (a.albumFile?.isOwned(widget.account.username) == true) {
if (a.albumFile?.isOwned(widget.account.userId) == true) {
// delete owned albums
await RemoveAlbum(KiwiContainer().resolve<DiContainer>())(
widget.account, a);

View file

@ -63,7 +63,7 @@ class HomeSliverAppBar extends StatelessWidget {
),
if (accountLabel == null)
Text(
account.username.toString(),
account.username2,
style: TextStyle(
fontSize: 14,
color: AppTheme.getSecondaryTextColor(context),

View file

@ -225,7 +225,7 @@ class _ShareAlbumDialogState extends State<ShareAlbumDialog> {
void _transformShareeItems(List<Sharee> sharees) {
final candidates = sharees
.where((s) =>
s.shareWith != widget.account.username &&
s.shareWith != widget.account.userId &&
// remove users already shared with
!_items.any((i) => i.shareWith == s.shareWith))
.toList();

View file

@ -129,7 +129,7 @@ class _SharedFileViewerState extends State<SharedFileViewer> {
title: Text(widget.file.strippedPath),
),
),
if (widget.shares.first.uidOwner == widget.account.username) ...[
if (widget.shares.first.uidOwner == widget.account.userId) ...[
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),

View file

@ -202,7 +202,7 @@ class _SharingBrowserState extends State<SharingBrowser> {
),
),
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().fileLastSharedByOthersDescription(
shares.first.share.displaynameOwner, dateStr),
@ -260,7 +260,7 @@ class _SharingBrowserState extends State<SharingBrowser> {
),
),
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().albumLastSharedByOthersDescription(
shares.first.share.displaynameOwner, dateStr),
@ -307,7 +307,7 @@ class _SharingBrowserState extends State<SharingBrowser> {
// group shares of the same file
final map = <String, List<ListSharingItem>>{};
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";
map[groupKey] ??= <ListSharingItem>[];
map[groupKey]!.add(i);

View file

@ -238,8 +238,8 @@ class _SignInState extends State<SignIn> {
_formValue.scheme,
_formValue.address,
_formValue.username.toCi(),
_formValue.username,
_formValue.password,
null,
[""],
);
_log.info("[_connect] Try connecting with account: $account");

View file

@ -149,7 +149,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
onPressed: () => _onRemoveFromAlbumPressed(context),
),
if (widget.album != null &&
widget.album!.albumFile?.isOwned(widget.account.username) ==
widget.album!.albumFile?.isOwned(widget.account.userId) ==
true)
_DetailPaneButton(
icon: Icons.photo_album_outlined,
@ -195,7 +195,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
title: Text(path_lib.basenameWithoutExtension(widget.file.path)),
subtitle: Text(widget.file.strippedPath),
),
if (!widget.file.isOwned(widget.account.username))
if (!widget.file.isOwned(widget.account.userId))
ListTile(
leading: ListTileCenterLeading(
child: Icon(
@ -509,7 +509,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
widget.album!.provider is! AlbumStaticProvider) {
return false;
}
if (widget.album!.albumFile?.isOwned(widget.account.username) == true) {
if (widget.album!.albumFile?.isOwned(widget.account.userId) == true) {
return true;
}
try {
@ -518,7 +518,7 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
.whereType<AlbumFileItem>()
.firstWhere(
(element) => element.file.compareServerIdentity(widget.file));
if (thisItem.addedBy == widget.account.username) {
if (thisItem.addedBy == widget.account.userId) {
return true;
}
} catch (_) {}

152
app/test/account_test.dart Normal file
View 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,
);
}

View file

@ -254,7 +254,7 @@ void _testQuerySharedAlbumMissingManagedShareOtherAdded(String description) {
/// Expect: emit empty list
void _testQuerySharedAlbumMissingManagedShareOtherReshared(String description) {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final user1Files = [
@ -450,7 +450,7 @@ void _testQuerySharedAlbumExtraShare(String description) {
/// Expect: emit the file with extra share (admin -> user2)
void _testQuerySharedAlbumExtraShareOtherAdded(String description) {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder(initialFileId: 1)
..addJpeg("admin/test1.jpg", ownerId: "user1"))
.build();
@ -510,7 +510,7 @@ void _testQuerySharedAlbumExtraShareOtherAdded(String description) {
/// Expect: emit empty list
void _testQuerySharedAlbumExtraUnmanagedShare(String description) {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder(initialFileId: 1)
..addJpeg("admin/test1.jpg", ownerId: "user1"))
.build();

View file

@ -1484,7 +1484,7 @@ void main() {
});
group("AlbumUpgraderV5", () {
final account = util.buildAccount(username: "user1");
final account = util.buildAccount(userId: "user1");
test("w/ ownerId", () {
final json = <String, dynamic>{

View file

@ -347,7 +347,7 @@ Future<void> _updaterUpdateFile() async {
/// Expect: file added to AccountFiles table
Future<void> _updaterNewSharedFile() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder()
..addDir("admin")
..addJpeg("admin/test1.jpg")
@ -385,7 +385,7 @@ Future<void> _updaterNewSharedFile() async {
/// Expect: file added to AccountFiles table
Future<void> _updaterNewSharedDir() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder()
..addDir("admin")
..addJpeg("admin/test1.jpg", ownerId: "user1")
@ -423,7 +423,7 @@ Future<void> _updaterNewSharedDir() async {
/// file remained in Files table
Future<void> _updaterDeleteSharedFile() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder()
..addDir("admin")
..addJpeg("admin/test1.jpg")
@ -465,7 +465,7 @@ Future<void> _updaterDeleteSharedFile() async {
/// file remained in Files table
Future<void> _updaterDeleteSharedDir() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder()
..addDir("admin")
..addJpeg("admin/test1.jpg")

View file

@ -328,7 +328,7 @@ class MockShareMemoryRepo extends MockShareRepo {
return shares.where((s) {
if (s.itemSource != file.fileId) {
return false;
} else if (isIncludeReshare == true || s.uidOwner == account.username) {
} else if (isIncludeReshare == true || s.uidOwner == account.userId) {
return true;
} else {
return false;
@ -342,8 +342,8 @@ class MockShareMemoryRepo extends MockShareRepo {
id: (_id++).toString(),
shareType: ShareType.user,
stime: DateTime.utc(2020, 1, 2, 3, 4, 5),
uidOwner: account.username,
displaynameOwner: account.username.toString(),
uidOwner: account.userId,
displaynameOwner: account.username2,
uidFileOwner: file.ownerId!,
path: file.strippedPath,
itemType: ShareItemType.file,
@ -383,7 +383,7 @@ class MockShareeMemoryRepo extends MockShareeRepo {
@override
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;

View file

@ -286,11 +286,12 @@ Account buildAccount({
String id = "123456-000000",
String scheme = "http",
String address = "example.com",
String username = "admin",
String userId = "admin",
String username2 = "admin",
String password = "pass",
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
///

View file

@ -187,7 +187,7 @@ Future<void> _addExistingFile() async {
/// Expect: file not added to album
Future<void> _addExistingSharedFile() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final user1Files = [

View file

@ -131,7 +131,7 @@ Future<void> _removeSharedAlbumFileInOtherAlbum() async {
/// share (admin -> user1) for the file delete
Future<void> _removeSharedAlbumResyncedFile() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files =
(util.FilesBuilder(initialFileId: 1)..addJpeg("admin/test1.jpg")).build();
final user1Files = [

View file

@ -291,7 +291,7 @@ Future<void> _removeFromSharedAlbumOwned() async {
/// unchanged
Future<void> _removeFromSharedAlbumOwnedWithOtherShare() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder(initialFileId: 1)
..addJpeg("user1/test1.jpg", ownerId: "user1"))
.build();

View file

@ -238,7 +238,7 @@ Future<void> _removeSharedAlbumFile() async {
/// file share (admin -> user2) deleted
Future<void> _removeSharedAlbumSharedFile() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder(initialFileId: 1)
..addJpeg("admin/test1.jpg", ownerId: "user1"))
.build();

View file

@ -116,7 +116,7 @@ Future<void> _unsupportedFile() async {
/// Expect: user1/test1.jpg, user1/test/test2.jpg
Future<void> _multiAccountRoot() async {
final account = util.buildAccount();
final user1Account = util.buildAccount(username: "user1");
final user1Account = util.buildAccount(userId: "user1");
final files = (util.FilesBuilder()
..addJpeg("admin/test1.jpg")
..addJpeg("admin/test/test2.jpg"))