2021-04-10 06:28:12 +02:00
|
|
|
import 'dart:typed_data';
|
|
|
|
|
2021-04-15 20:44:25 +02:00
|
|
|
import 'package:equatable/equatable.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:nc_photos/account.dart';
|
2021-09-04 17:13:52 +02:00
|
|
|
import 'package:nc_photos/debug_util.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:nc_photos/entity/exif.dart';
|
2021-04-28 21:22:33 +02:00
|
|
|
import 'package:nc_photos/or_null.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
import 'package:nc_photos/string_extension.dart';
|
2021-08-06 19:11:00 +02:00
|
|
|
import 'package:nc_photos/type.dart';
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
int compareFileDateTimeDescending(File x, File y) {
|
2021-06-20 13:40:28 +02:00
|
|
|
final tmp = y.bestDateTime.compareTo(x.bestDateTime);
|
2021-04-10 06:28:12 +02:00
|
|
|
if (tmp != 0) {
|
|
|
|
return tmp;
|
|
|
|
} else {
|
|
|
|
// compare file name if files are modified at the same time
|
|
|
|
return x.path.compareTo(y.path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Immutable object that hold metadata of a [File]
|
2021-04-15 20:44:25 +02:00
|
|
|
class Metadata with EquatableMixin {
|
2021-04-10 06:28:12 +02:00
|
|
|
Metadata({
|
2021-07-23 22:05:57 +02:00
|
|
|
DateTime? lastUpdated,
|
2021-04-10 06:28:12 +02:00
|
|
|
this.fileEtag,
|
|
|
|
this.imageWidth,
|
|
|
|
this.imageHeight,
|
|
|
|
this.exif,
|
2021-09-15 08:58:06 +02:00
|
|
|
}) : lastUpdated = (lastUpdated ?? DateTime.now()).toUtc();
|
2021-04-10 06:28:12 +02:00
|
|
|
|
2021-06-14 15:51:29 +02:00
|
|
|
@override
|
|
|
|
// ignore: hash_and_equals
|
2021-07-23 22:05:57 +02:00
|
|
|
bool operator ==(Object? other) => equals(other, isDeep: true);
|
2021-06-14 15:51:29 +02:00
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
bool equals(Object? other, {bool isDeep = false}) {
|
2021-06-14 15:51:29 +02:00
|
|
|
if (other is Metadata) {
|
|
|
|
return super == other &&
|
|
|
|
(exif == null) == (other.exif == null) &&
|
|
|
|
(exif?.equals(other.exif, isDeep: isDeep) ?? true);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-10 06:28:12 +02:00
|
|
|
/// Parse Metadata from [json]
|
|
|
|
///
|
|
|
|
/// If the version saved in json does not match the active one, the
|
|
|
|
/// corresponding upgrader will be called one by one to upgrade the json,
|
|
|
|
/// version by version until it reached the active version. If any upgrader
|
|
|
|
/// in the chain is null, the upgrade process will fail
|
2021-07-23 22:05:57 +02:00
|
|
|
static Metadata? fromJson(
|
2021-08-06 19:11:00 +02:00
|
|
|
JsonObj json, {
|
2021-07-23 22:05:57 +02:00
|
|
|
required MetadataUpgraderV1? upgraderV1,
|
|
|
|
required MetadataUpgraderV2? upgraderV2,
|
2021-04-10 06:28:12 +02:00
|
|
|
}) {
|
|
|
|
final jsonVersion = json["version"];
|
2021-08-06 19:11:00 +02:00
|
|
|
JsonObj? result = json;
|
2021-04-10 06:28:12 +02:00
|
|
|
if (jsonVersion < 2) {
|
2021-07-23 22:05:57 +02:00
|
|
|
result = upgraderV1?.call(result);
|
|
|
|
if (result == null) {
|
2021-04-10 06:28:12 +02:00
|
|
|
_log.info("[fromJson] Version $jsonVersion not compatible");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2021-04-15 00:57:23 +02:00
|
|
|
if (jsonVersion < 3) {
|
2021-07-23 22:05:57 +02:00
|
|
|
result = upgraderV2?.call(result);
|
|
|
|
if (result == null) {
|
2021-04-15 00:57:23 +02:00
|
|
|
_log.info("[fromJson] Version $jsonVersion not compatible");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2021-04-10 06:28:12 +02:00
|
|
|
return Metadata(
|
2021-07-23 22:05:57 +02:00
|
|
|
lastUpdated: result["lastUpdated"] == null
|
2021-04-10 06:28:12 +02:00
|
|
|
? null
|
2021-07-23 22:05:57 +02:00
|
|
|
: DateTime.parse(result["lastUpdated"]),
|
|
|
|
fileEtag: result["fileEtag"],
|
|
|
|
imageWidth: result["imageWidth"],
|
|
|
|
imageHeight: result["imageHeight"],
|
|
|
|
exif: result["exif"] == null
|
2021-04-10 06:28:12 +02:00
|
|
|
? null
|
2021-07-23 22:05:57 +02:00
|
|
|
: Exif.fromJson(result["exif"].cast<String, dynamic>()),
|
2021-04-10 06:28:12 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-08-06 19:11:00 +02:00
|
|
|
JsonObj toJson() {
|
2021-04-10 06:28:12 +02:00
|
|
|
return {
|
|
|
|
"version": version,
|
|
|
|
"lastUpdated": lastUpdated.toIso8601String(),
|
|
|
|
if (fileEtag != null) "fileEtag": fileEtag,
|
|
|
|
if (imageWidth != null) "imageWidth": imageWidth,
|
|
|
|
if (imageHeight != null) "imageHeight": imageHeight,
|
2021-07-23 22:05:57 +02:00
|
|
|
if (exif != null) "exif": exif!.toJson(),
|
2021-04-10 06:28:12 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
toString() {
|
|
|
|
var product = "$runtimeType {"
|
|
|
|
"lastUpdated: $lastUpdated, ";
|
|
|
|
if (fileEtag != null) {
|
|
|
|
product += "fileEtag: $fileEtag, ";
|
|
|
|
}
|
|
|
|
if (imageWidth != null) {
|
|
|
|
product += "imageWidth: $imageWidth, ";
|
|
|
|
}
|
|
|
|
if (imageHeight != null) {
|
|
|
|
product += "imageHeight: $imageHeight, ";
|
|
|
|
}
|
|
|
|
if (exif != null) {
|
|
|
|
product += "exif: $exif, ";
|
|
|
|
}
|
|
|
|
return product + "}";
|
|
|
|
}
|
|
|
|
|
2021-04-15 20:44:25 +02:00
|
|
|
@override
|
|
|
|
get props => [
|
|
|
|
lastUpdated,
|
|
|
|
fileEtag,
|
|
|
|
imageWidth,
|
|
|
|
imageHeight,
|
2021-06-14 15:51:29 +02:00
|
|
|
// exif is handled separately, see [equals]
|
2021-04-15 20:44:25 +02:00
|
|
|
];
|
|
|
|
|
2021-04-10 06:28:12 +02:00
|
|
|
final DateTime lastUpdated;
|
|
|
|
|
|
|
|
/// Etag of the parent file when the metadata is saved
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? fileEtag;
|
|
|
|
final int? imageWidth;
|
|
|
|
final int? imageHeight;
|
|
|
|
final Exif? exif;
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
/// versioning of this class, use to upgrade old persisted metadata
|
2021-04-15 00:57:23 +02:00
|
|
|
static const version = 3;
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
static final _log = Logger("entity.file.Metadata");
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class MetadataUpgrader {
|
2021-08-06 19:11:00 +02:00
|
|
|
JsonObj? call(JsonObj json);
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Upgrade v1 Metadata to v2
|
|
|
|
class MetadataUpgraderV1 implements MetadataUpgrader {
|
|
|
|
MetadataUpgraderV1({
|
2021-07-23 22:05:57 +02:00
|
|
|
required this.fileContentType,
|
2021-04-15 00:57:23 +02:00
|
|
|
this.logFilePath,
|
2021-04-10 06:28:12 +02:00
|
|
|
});
|
|
|
|
|
2021-09-15 08:58:06 +02:00
|
|
|
@override
|
2021-08-06 19:11:00 +02:00
|
|
|
JsonObj? call(JsonObj json) {
|
2021-04-10 06:28:12 +02:00
|
|
|
if (fileContentType == "image/webp") {
|
|
|
|
// Version 1 metadata for webp is bugged, drop it
|
2021-04-15 00:57:23 +02:00
|
|
|
_log.fine("[call] Upgrade v1 metadata for file: $logFilePath");
|
2021-04-10 06:28:12 +02:00
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return json;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? fileContentType;
|
2021-04-15 00:57:23 +02:00
|
|
|
|
|
|
|
/// File path for logging only
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? logFilePath;
|
2021-04-15 00:57:23 +02:00
|
|
|
|
|
|
|
static final _log = Logger("entity.file.MetadataUpgraderV1");
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Upgrade v2 Metadata to v3
|
|
|
|
class MetadataUpgraderV2 implements MetadataUpgrader {
|
|
|
|
MetadataUpgraderV2({
|
2021-07-23 22:05:57 +02:00
|
|
|
required this.fileContentType,
|
2021-04-15 00:57:23 +02:00
|
|
|
this.logFilePath,
|
|
|
|
});
|
|
|
|
|
2021-09-15 08:58:06 +02:00
|
|
|
@override
|
2021-08-06 19:11:00 +02:00
|
|
|
JsonObj? call(JsonObj json) {
|
2021-04-15 00:57:23 +02:00
|
|
|
if (fileContentType == "image/jpeg") {
|
|
|
|
// Version 2 metadata for jpeg doesn't consider orientation
|
|
|
|
if (json["exif"] != null && json["exif"].containsKey("Orientation")) {
|
|
|
|
// Check orientation
|
|
|
|
final orientation = json["exif"]["Orientation"];
|
|
|
|
if (orientation >= 5 && orientation <= 8) {
|
|
|
|
_log.fine("[call] Upgrade v2 metadata for file: $logFilePath");
|
|
|
|
final temp = json["imageWidth"];
|
|
|
|
json["imageWidth"] = json["imageHeight"];
|
|
|
|
json["imageHeight"] = temp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return json;
|
|
|
|
}
|
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? fileContentType;
|
2021-04-15 00:57:23 +02:00
|
|
|
|
|
|
|
/// File path for logging only
|
2021-07-23 22:05:57 +02:00
|
|
|
final String? logFilePath;
|
2021-04-15 00:57:23 +02:00
|
|
|
|
|
|
|
static final _log = Logger("entity.file.MetadataUpgraderV2");
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
2021-04-15 20:44:25 +02:00
|
|
|
class File with EquatableMixin {
|
2021-04-10 06:28:12 +02:00
|
|
|
File({
|
2021-07-23 22:05:57 +02:00
|
|
|
required String path,
|
2021-04-10 06:28:12 +02:00
|
|
|
this.contentLength,
|
|
|
|
this.contentType,
|
|
|
|
this.etag,
|
|
|
|
this.lastModified,
|
|
|
|
this.isCollection,
|
|
|
|
this.usedBytes,
|
|
|
|
this.hasPreview,
|
2021-05-10 07:53:05 +02:00
|
|
|
this.fileId,
|
2021-06-14 12:44:38 +02:00
|
|
|
this.ownerId,
|
2021-04-10 06:28:12 +02:00
|
|
|
this.metadata,
|
2021-05-28 20:45:00 +02:00
|
|
|
this.isArchived,
|
2021-06-21 12:39:17 +02:00
|
|
|
this.overrideDateTime,
|
2021-08-01 22:06:28 +02:00
|
|
|
this.trashbinFilename,
|
|
|
|
this.trashbinOriginalLocation,
|
|
|
|
this.trashbinDeletionTime,
|
2021-09-15 08:58:06 +02:00
|
|
|
}) : path = path.trimAny("/");
|
2021-04-10 06:28:12 +02:00
|
|
|
|
2021-06-14 15:51:29 +02:00
|
|
|
@override
|
|
|
|
// ignore: hash_and_equals
|
2021-07-23 22:05:57 +02:00
|
|
|
bool operator ==(Object? other) => equals(other, isDeep: true);
|
2021-06-14 15:51:29 +02:00
|
|
|
|
2021-07-23 22:05:57 +02:00
|
|
|
bool equals(Object? other, {bool isDeep = false}) {
|
2021-06-14 15:51:29 +02:00
|
|
|
if (other is File) {
|
|
|
|
return super == other &&
|
|
|
|
(metadata == null) == (other.metadata == null) &&
|
|
|
|
(metadata?.equals(other.metadata, isDeep: isDeep) ?? true);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-06 19:11:00 +02:00
|
|
|
factory File.fromJson(JsonObj json) {
|
2021-04-10 06:28:12 +02:00
|
|
|
return File(
|
|
|
|
path: json["path"],
|
|
|
|
contentLength: json["contentLength"],
|
|
|
|
contentType: json["contentType"],
|
|
|
|
etag: json["etag"],
|
|
|
|
lastModified: json["lastModified"] == null
|
|
|
|
? null
|
|
|
|
: DateTime.parse(json["lastModified"]),
|
|
|
|
isCollection: json["isCollection"],
|
|
|
|
usedBytes: json["usedBytes"],
|
|
|
|
hasPreview: json["hasPreview"],
|
2021-05-10 07:53:05 +02:00
|
|
|
fileId: json["fileId"],
|
2021-06-14 12:44:38 +02:00
|
|
|
ownerId: json["ownerId"],
|
2021-08-01 22:06:28 +02:00
|
|
|
trashbinFilename: json["trashbinFilename"],
|
|
|
|
trashbinOriginalLocation: json["trashbinOriginalLocation"],
|
|
|
|
trashbinDeletionTime: json["trashbinDeletionTime"] == null
|
|
|
|
? null
|
|
|
|
: DateTime.parse(json["trashbinDeletionTime"]),
|
2021-04-10 06:28:12 +02:00
|
|
|
metadata: json["metadata"] == null
|
|
|
|
? null
|
|
|
|
: Metadata.fromJson(
|
|
|
|
json["metadata"].cast<String, dynamic>(),
|
|
|
|
upgraderV1: MetadataUpgraderV1(
|
|
|
|
fileContentType: json["contentType"],
|
2021-04-15 00:57:23 +02:00
|
|
|
logFilePath: json["path"],
|
|
|
|
),
|
|
|
|
upgraderV2: MetadataUpgraderV2(
|
|
|
|
fileContentType: json["contentType"],
|
|
|
|
logFilePath: json["path"],
|
2021-04-10 06:28:12 +02:00
|
|
|
),
|
|
|
|
),
|
2021-05-28 20:45:00 +02:00
|
|
|
isArchived: json["isArchived"],
|
2021-06-21 12:39:17 +02:00
|
|
|
overrideDateTime: json["overrideDateTime"] == null
|
|
|
|
? null
|
|
|
|
: DateTime.parse(json["overrideDateTime"]),
|
2021-04-10 06:28:12 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
toString() {
|
|
|
|
var product = "$runtimeType {"
|
|
|
|
"path: '$path', ";
|
|
|
|
if (contentLength != null) {
|
|
|
|
product += "contentLength: $contentLength, ";
|
|
|
|
}
|
|
|
|
if (contentType != null) {
|
|
|
|
product += "contentType: '$contentType', ";
|
|
|
|
}
|
|
|
|
if (etag != null) {
|
|
|
|
product += "etag: '$etag', ";
|
|
|
|
}
|
|
|
|
if (lastModified != null) {
|
|
|
|
product += "lastModified: $lastModified, ";
|
|
|
|
}
|
|
|
|
if (isCollection != null) {
|
|
|
|
product += "isCollection: $isCollection, ";
|
|
|
|
}
|
|
|
|
if (usedBytes != null) {
|
|
|
|
product += "usedBytes: $usedBytes, ";
|
|
|
|
}
|
|
|
|
if (hasPreview != null) {
|
|
|
|
product += "hasPreview: $hasPreview, ";
|
|
|
|
}
|
2021-05-10 07:53:05 +02:00
|
|
|
if (fileId != null) {
|
2021-06-14 11:36:40 +02:00
|
|
|
product += "fileId: $fileId, ";
|
2021-05-10 07:53:05 +02:00
|
|
|
}
|
2021-06-14 12:44:38 +02:00
|
|
|
if (ownerId != null) {
|
|
|
|
product += "ownerId: '$ownerId', ";
|
|
|
|
}
|
2021-08-01 22:06:28 +02:00
|
|
|
if (trashbinFilename != null) {
|
|
|
|
product += "trashbinFilename: '$trashbinFilename', ";
|
|
|
|
}
|
|
|
|
if (trashbinOriginalLocation != null) {
|
|
|
|
product += "trashbinOriginalLocation: '$trashbinOriginalLocation', ";
|
|
|
|
}
|
|
|
|
if (trashbinDeletionTime != null) {
|
|
|
|
product += "trashbinDeletionTime: $trashbinDeletionTime, ";
|
|
|
|
}
|
2021-04-10 06:28:12 +02:00
|
|
|
if (metadata != null) {
|
|
|
|
product += "metadata: $metadata, ";
|
|
|
|
}
|
2021-05-28 20:45:00 +02:00
|
|
|
if (isArchived != null) {
|
|
|
|
product += "isArchived: $isArchived, ";
|
|
|
|
}
|
2021-06-21 12:39:17 +02:00
|
|
|
if (overrideDateTime != null) {
|
|
|
|
product += "overrideDateTime: $overrideDateTime, ";
|
|
|
|
}
|
2021-04-10 06:28:12 +02:00
|
|
|
return product + "}";
|
|
|
|
}
|
|
|
|
|
2021-08-06 19:11:00 +02:00
|
|
|
JsonObj toJson() {
|
2021-04-10 06:28:12 +02:00
|
|
|
return {
|
|
|
|
"path": path,
|
|
|
|
if (contentLength != null) "contentLength": contentLength,
|
|
|
|
if (contentType != null) "contentType": contentType,
|
|
|
|
if (etag != null) "etag": etag,
|
2021-06-21 11:10:38 +02:00
|
|
|
if (lastModified != null)
|
2021-07-23 22:05:57 +02:00
|
|
|
"lastModified": lastModified!.toUtc().toIso8601String(),
|
2021-04-10 06:28:12 +02:00
|
|
|
if (isCollection != null) "isCollection": isCollection,
|
|
|
|
if (usedBytes != null) "usedBytes": usedBytes,
|
|
|
|
if (hasPreview != null) "hasPreview": hasPreview,
|
2021-05-10 07:53:05 +02:00
|
|
|
if (fileId != null) "fileId": fileId,
|
2021-06-14 12:44:38 +02:00
|
|
|
if (ownerId != null) "ownerId": ownerId,
|
2021-08-01 22:06:28 +02:00
|
|
|
if (trashbinFilename != null) "trashbinFilename": trashbinFilename,
|
|
|
|
if (trashbinOriginalLocation != null)
|
|
|
|
"trashbinOriginalLocation": trashbinOriginalLocation,
|
|
|
|
if (trashbinDeletionTime != null)
|
|
|
|
"trashbinDeletionTime": trashbinDeletionTime!.toUtc().toIso8601String(),
|
2021-07-23 22:05:57 +02:00
|
|
|
if (metadata != null) "metadata": metadata!.toJson(),
|
2021-05-28 20:45:00 +02:00
|
|
|
if (isArchived != null) "isArchived": isArchived,
|
2021-06-21 12:39:17 +02:00
|
|
|
if (overrideDateTime != null)
|
2021-07-23 22:05:57 +02:00
|
|
|
"overrideDateTime": overrideDateTime!.toUtc().toIso8601String(),
|
2021-04-10 06:28:12 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
File copyWith({
|
2021-07-23 22:05:57 +02:00
|
|
|
String? path,
|
|
|
|
int? contentLength,
|
|
|
|
String? contentType,
|
|
|
|
String? etag,
|
|
|
|
DateTime? lastModified,
|
|
|
|
bool? isCollection,
|
|
|
|
int? usedBytes,
|
|
|
|
bool? hasPreview,
|
|
|
|
int? fileId,
|
|
|
|
String? ownerId,
|
2021-08-01 22:06:28 +02:00
|
|
|
String? trashbinFilename,
|
|
|
|
String? trashbinOriginalLocation,
|
|
|
|
DateTime? trashbinDeletionTime,
|
2021-07-23 22:05:57 +02:00
|
|
|
OrNull<Metadata>? metadata,
|
|
|
|
OrNull<bool>? isArchived,
|
|
|
|
OrNull<DateTime>? overrideDateTime,
|
2021-04-10 06:28:12 +02:00
|
|
|
}) {
|
|
|
|
return File(
|
|
|
|
path: path ?? this.path,
|
|
|
|
contentLength: contentLength ?? this.contentLength,
|
|
|
|
contentType: contentType ?? this.contentType,
|
|
|
|
etag: etag ?? this.etag,
|
|
|
|
lastModified: lastModified ?? this.lastModified,
|
|
|
|
isCollection: isCollection ?? this.isCollection,
|
|
|
|
usedBytes: usedBytes ?? this.usedBytes,
|
|
|
|
hasPreview: hasPreview ?? this.hasPreview,
|
2021-05-10 07:53:05 +02:00
|
|
|
fileId: fileId ?? this.fileId,
|
2021-06-14 12:44:38 +02:00
|
|
|
ownerId: ownerId ?? this.ownerId,
|
2021-08-01 22:06:28 +02:00
|
|
|
trashbinFilename: trashbinFilename ?? this.trashbinFilename,
|
|
|
|
trashbinOriginalLocation:
|
|
|
|
trashbinOriginalLocation ?? this.trashbinOriginalLocation,
|
|
|
|
trashbinDeletionTime: trashbinDeletionTime ?? this.trashbinDeletionTime,
|
2021-04-28 21:22:33 +02:00
|
|
|
metadata: metadata == null ? this.metadata : metadata.obj,
|
2021-05-28 20:45:00 +02:00
|
|
|
isArchived: isArchived == null ? this.isArchived : isArchived.obj,
|
2021-06-21 12:39:17 +02:00
|
|
|
overrideDateTime: overrideDateTime == null
|
|
|
|
? this.overrideDateTime
|
|
|
|
: overrideDateTime.obj,
|
2021-04-10 06:28:12 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the path of this file with the DAV part stripped
|
2021-05-25 08:58:18 +02:00
|
|
|
///
|
|
|
|
/// WebDAV file path: remote.php/dav/files/{username}/{strippedPath}
|
2021-04-10 06:28:12 +02:00
|
|
|
String get strippedPath {
|
|
|
|
if (path.contains("remote.php/dav/files")) {
|
2021-05-25 08:58:18 +02:00
|
|
|
final position = path.indexOf("/", "remote.php/dav/files/".length) + 1;
|
|
|
|
if (position == 0) {
|
|
|
|
// root dir path
|
|
|
|
return ".";
|
|
|
|
} else {
|
|
|
|
return path.substring(position);
|
|
|
|
}
|
2021-04-10 06:28:12 +02:00
|
|
|
} else {
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 20:44:25 +02:00
|
|
|
@override
|
|
|
|
get props => [
|
|
|
|
path,
|
|
|
|
contentLength,
|
|
|
|
contentType,
|
|
|
|
etag,
|
|
|
|
lastModified,
|
|
|
|
isCollection,
|
|
|
|
usedBytes,
|
|
|
|
hasPreview,
|
2021-05-10 07:53:05 +02:00
|
|
|
fileId,
|
2021-06-14 12:44:38 +02:00
|
|
|
ownerId,
|
2021-08-01 22:06:28 +02:00
|
|
|
trashbinFilename,
|
|
|
|
trashbinOriginalLocation,
|
|
|
|
trashbinDeletionTime,
|
2021-06-14 15:51:29 +02:00
|
|
|
// metadata is handled separately, see [equals]
|
2021-05-28 20:45:00 +02:00
|
|
|
isArchived,
|
2021-06-21 12:39:17 +02:00
|
|
|
overrideDateTime,
|
2021-04-15 20:44:25 +02:00
|
|
|
];
|
|
|
|
|
2021-04-10 06:28:12 +02:00
|
|
|
final String path;
|
2021-07-23 22:05:57 +02:00
|
|
|
final int? contentLength;
|
|
|
|
final String? contentType;
|
|
|
|
final String? etag;
|
|
|
|
final DateTime? lastModified;
|
|
|
|
final bool? isCollection;
|
|
|
|
final int? usedBytes;
|
|
|
|
final bool? hasPreview;
|
2021-05-10 07:53:05 +02:00
|
|
|
// maybe null when loaded from old cache
|
2021-07-23 22:05:57 +02:00
|
|
|
final int? fileId;
|
|
|
|
final String? ownerId;
|
2021-08-01 22:06:28 +02:00
|
|
|
final String? trashbinFilename;
|
|
|
|
final String? trashbinOriginalLocation;
|
|
|
|
final DateTime? trashbinDeletionTime;
|
2021-04-10 06:28:12 +02:00
|
|
|
// metadata
|
2021-07-23 22:05:57 +02:00
|
|
|
final Metadata? metadata;
|
|
|
|
final bool? isArchived;
|
|
|
|
final DateTime? overrideDateTime;
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|
|
|
|
|
2021-06-20 13:40:28 +02:00
|
|
|
extension FileExtension on File {
|
|
|
|
DateTime get bestDateTime {
|
2021-09-04 17:13:52 +02:00
|
|
|
try {
|
|
|
|
return overrideDateTime ??
|
|
|
|
metadata?.exif?.dateTimeOriginal ??
|
|
|
|
lastModified ??
|
|
|
|
DateTime.now().toUtc();
|
|
|
|
} catch (e) {
|
|
|
|
_log.severe(
|
|
|
|
"[bestDateTime] Non standard EXIF DateTimeOriginal '${metadata?.exif?.data["DateTimeOriginal"]}'" +
|
|
|
|
(shouldLogFileName ? " for file: '$path'" : ""),
|
|
|
|
e);
|
|
|
|
return lastModified ?? DateTime.now().toUtc();
|
|
|
|
}
|
2021-06-20 13:40:28 +02:00
|
|
|
}
|
2021-07-22 08:16:56 +02:00
|
|
|
|
2021-09-05 13:13:07 +02:00
|
|
|
bool isOwned(String username) =>
|
|
|
|
ownerId == null || ownerId?.toLowerCase() == username.toLowerCase();
|
2021-09-04 17:13:52 +02:00
|
|
|
|
|
|
|
static final _log = Logger("entity.file.FileExtension");
|
2021-06-20 13:40:28 +02:00
|
|
|
}
|
|
|
|
|
2021-04-10 06:28:12 +02:00
|
|
|
class FileRepo {
|
2021-08-16 21:05:00 +02:00
|
|
|
const FileRepo(this.dataSrc);
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
/// See [FileDataSource.list]
|
|
|
|
Future<List<File>> list(Account account, File root) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.list(account, root);
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
/// See [FileDataSource.remove]
|
|
|
|
Future<void> remove(Account account, File file) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.remove(account, file);
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
/// See [FileDataSource.getBinary]
|
|
|
|
Future<Uint8List> getBinary(Account account, File file) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.getBinary(account, file);
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
/// See [FileDataSource.putBinary]
|
|
|
|
Future<void> putBinary(Account account, String path, Uint8List content) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.putBinary(account, path, content);
|
2021-04-10 06:28:12 +02:00
|
|
|
|
|
|
|
/// See [FileDataSource.updateMetadata]
|
2021-05-28 19:15:09 +02:00
|
|
|
Future<void> updateProperty(
|
|
|
|
Account account,
|
|
|
|
File file, {
|
2021-07-23 22:05:57 +02:00
|
|
|
OrNull<Metadata>? metadata,
|
|
|
|
OrNull<bool>? isArchived,
|
|
|
|
OrNull<DateTime>? overrideDateTime,
|
2021-05-28 19:15:09 +02:00
|
|
|
}) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.updateProperty(
|
|
|
|
account,
|
|
|
|
file,
|
|
|
|
metadata: metadata,
|
|
|
|
isArchived: isArchived,
|
|
|
|
overrideDateTime: overrideDateTime,
|
|
|
|
);
|
2021-04-10 06:28:12 +02:00
|
|
|
|
2021-05-20 17:43:53 +02:00
|
|
|
/// See [FileDataSource.copy]
|
|
|
|
Future<void> copy(
|
|
|
|
Account account,
|
|
|
|
File f,
|
|
|
|
String destination, {
|
2021-07-23 22:05:57 +02:00
|
|
|
bool? shouldOverwrite,
|
2021-05-20 17:43:53 +02:00
|
|
|
}) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.copy(
|
|
|
|
account,
|
|
|
|
f,
|
|
|
|
destination,
|
|
|
|
shouldOverwrite: shouldOverwrite,
|
|
|
|
);
|
2021-05-20 17:43:53 +02:00
|
|
|
|
2021-05-20 17:45:06 +02:00
|
|
|
/// See [FileDataSource.move]
|
|
|
|
Future<void> move(
|
|
|
|
Account account,
|
|
|
|
File f,
|
|
|
|
String destination, {
|
2021-07-23 22:05:57 +02:00
|
|
|
bool? shouldOverwrite,
|
2021-05-20 17:45:06 +02:00
|
|
|
}) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.move(
|
|
|
|
account,
|
|
|
|
f,
|
|
|
|
destination,
|
|
|
|
shouldOverwrite: shouldOverwrite,
|
|
|
|
);
|
2021-05-20 17:45:06 +02:00
|
|
|
|
2021-05-20 17:47:24 +02:00
|
|
|
/// See [FileDataSource.createDir]
|
|
|
|
Future<void> createDir(Account account, String path) =>
|
2021-09-15 08:58:06 +02:00
|
|
|
dataSrc.createDir(account, path);
|
2021-05-20 17:47:24 +02:00
|
|
|
|
2021-04-10 06:28:12 +02:00
|
|
|
final FileDataSource dataSrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class FileDataSource {
|
|
|
|
/// List all files under [f]
|
|
|
|
Future<List<File>> list(Account account, File f);
|
|
|
|
|
|
|
|
/// Remove file
|
|
|
|
Future<void> remove(Account account, File f);
|
|
|
|
|
|
|
|
/// Read file as binary array
|
|
|
|
Future<Uint8List> getBinary(Account account, File f);
|
|
|
|
|
|
|
|
/// Upload content to [path]
|
|
|
|
Future<void> putBinary(Account account, String path, Uint8List content);
|
|
|
|
|
2021-05-28 19:15:09 +02:00
|
|
|
/// Update one or more properties of a file
|
|
|
|
Future<void> updateProperty(
|
|
|
|
Account account,
|
|
|
|
File f, {
|
2021-07-23 22:05:57 +02:00
|
|
|
OrNull<Metadata>? metadata,
|
|
|
|
OrNull<bool>? isArchived,
|
|
|
|
OrNull<DateTime>? overrideDateTime,
|
2021-05-28 19:15:09 +02:00
|
|
|
});
|
2021-05-20 17:43:53 +02:00
|
|
|
|
|
|
|
/// Copy [f] to [destination]
|
|
|
|
///
|
|
|
|
/// [destination] should be a relative WebDAV path like
|
|
|
|
/// remote.php/dav/files/admin/new/location
|
|
|
|
Future<void> copy(
|
|
|
|
Account account,
|
|
|
|
File f,
|
|
|
|
String destination, {
|
2021-07-23 22:05:57 +02:00
|
|
|
bool? shouldOverwrite,
|
2021-05-20 17:43:53 +02:00
|
|
|
});
|
2021-05-20 17:45:06 +02:00
|
|
|
|
|
|
|
/// Move [f] to [destination]
|
|
|
|
///
|
|
|
|
/// [destination] should be a relative WebDAV path like
|
|
|
|
/// remote.php/dav/files/admin/new/location
|
|
|
|
Future<void> move(
|
|
|
|
Account account,
|
|
|
|
File f,
|
|
|
|
String destination, {
|
2021-07-23 22:05:57 +02:00
|
|
|
bool? shouldOverwrite,
|
2021-05-20 17:45:06 +02:00
|
|
|
});
|
2021-05-20 17:47:24 +02:00
|
|
|
|
|
|
|
/// Create a directory at [path]
|
|
|
|
///
|
|
|
|
/// [path] should be a relative WebDAV path like
|
|
|
|
/// remote.php/dav/files/admin/new/dir
|
|
|
|
Future<void> createDir(Account account, String path);
|
2021-04-10 06:28:12 +02:00
|
|
|
}
|