collection.dart 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import 'dart:core';
  2. import 'package:flutter/foundation.dart';
  3. import "package:photos/models/api/collection/public_url.dart";
  4. import "package:photos/models/api/collection/user.dart";
  5. import "package:photos/models/metadata/collection_magic.dart";
  6. import "package:photos/models/metadata/common_keys.dart";
  7. class Collection {
  8. final int id;
  9. final User? owner;
  10. final String encryptedKey;
  11. final String? keyDecryptionNonce;
  12. @Deprecated("Use collectionName instead")
  13. String? name;
  14. // encryptedName & nameDecryptionNonce will be null for collections
  15. // created before we started encrypting collection name
  16. final String? encryptedName;
  17. final String? nameDecryptionNonce;
  18. final CollectionType type;
  19. final CollectionAttributes attributes;
  20. final List<User?>? sharees;
  21. final List<PublicURL?>? publicURLs;
  22. final int updationTime;
  23. final bool isDeleted;
  24. // In early days before public launch, we used to store collection name
  25. // un-encrypted. decryptName will be value either decrypted value for
  26. // encryptedName or name itself.
  27. String? decryptedName;
  28. // decryptedPath will be null for collections now owned by user, deleted
  29. // collections, && collections which don't have a path. The path is used
  30. // to map local on-device album on mobile to remote collection on ente.
  31. String? decryptedPath;
  32. String? mMdEncodedJson;
  33. String? mMdPubEncodedJson;
  34. String? sharedMmdJson;
  35. int mMdVersion = 0;
  36. int mMbPubVersion = 0;
  37. int sharedMmdVersion = 0;
  38. CollectionMagicMetadata? _mmd;
  39. CollectionPubMagicMetadata? _pubMmd;
  40. ShareeMagicMetadata? _sharedMmd;
  41. CollectionMagicMetadata get magicMetadata =>
  42. _mmd ?? CollectionMagicMetadata.fromEncodedJson(mMdEncodedJson ?? '{}');
  43. CollectionPubMagicMetadata get pubMagicMetadata =>
  44. _pubMmd ??
  45. CollectionPubMagicMetadata.fromEncodedJson(mMdPubEncodedJson ?? '{}');
  46. ShareeMagicMetadata get sharedMagicMetadata =>
  47. _sharedMmd ?? ShareeMagicMetadata.fromEncodedJson(sharedMmdJson ?? '{}');
  48. set magicMetadata(CollectionMagicMetadata? val) => _mmd = val;
  49. set pubMagicMetadata(CollectionPubMagicMetadata? val) => _pubMmd = val;
  50. set sharedMagicMetadata(ShareeMagicMetadata? val) => _sharedMmd = val;
  51. String get displayName => decryptedName ?? name ?? "Unnamed Album";
  52. // set the value for both name and decryptedName till we finish migration
  53. void setName(String newName) {
  54. name = newName;
  55. decryptedName = newName;
  56. }
  57. Collection(
  58. this.id,
  59. this.owner,
  60. this.encryptedKey,
  61. this.keyDecryptionNonce,
  62. this.name,
  63. this.encryptedName,
  64. this.nameDecryptionNonce,
  65. this.type,
  66. this.attributes,
  67. this.sharees,
  68. this.publicURLs,
  69. this.updationTime, {
  70. this.isDeleted = false,
  71. });
  72. bool isArchived() {
  73. return mMdVersion > 0 && magicMetadata.visibility == archiveVisibility;
  74. }
  75. bool hasShareeArchived() {
  76. return sharedMmdVersion > 0 &&
  77. sharedMagicMetadata.visibility == archiveVisibility;
  78. }
  79. // hasLink returns true if there's any link attached to the collection
  80. // including expired links
  81. bool get hasLink => publicURLs != null && publicURLs!.isNotEmpty;
  82. bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0;
  83. // hasSharees returns true if the collection is shared with other ente users
  84. bool get hasSharees => sharees != null && sharees!.isNotEmpty;
  85. bool get isPinned => (magicMetadata.order ?? 0) != 0;
  86. bool isHidden() {
  87. if (isDefaultHidden()) {
  88. return true;
  89. }
  90. return mMdVersion > 0 && (magicMetadata.visibility == hiddenVisibility);
  91. }
  92. bool isDefaultHidden() {
  93. return (magicMetadata.subType ?? 0) == subTypeDefaultHidden;
  94. }
  95. bool isSharedFilesCollection() {
  96. return (magicMetadata.subType ?? 0) == subTypeSharedFilesCollection;
  97. }
  98. List<User> getSharees() {
  99. final List<User> result = [];
  100. if (sharees == null) {
  101. return result;
  102. }
  103. for (final User? u in sharees!) {
  104. if (u != null) {
  105. result.add(u);
  106. }
  107. }
  108. return result;
  109. }
  110. bool isOwner(int userID) {
  111. return (owner?.id ?? 0) == userID;
  112. }
  113. CollectionParticipantRole getRole(int userID) {
  114. if (isOwner(userID)) {
  115. return CollectionParticipantRole.owner;
  116. }
  117. if (sharees == null) {
  118. return CollectionParticipantRole.unknown;
  119. }
  120. for (final User? u in sharees!) {
  121. if (u != null && u.id == userID) {
  122. if (u.isViewer) {
  123. return CollectionParticipantRole.viewer;
  124. } else if (u.isCollaborator) {
  125. return CollectionParticipantRole.collaborator;
  126. }
  127. }
  128. }
  129. return CollectionParticipantRole.unknown;
  130. }
  131. // canLinkToDevicePath returns true if the collection can be linked to local
  132. // device album based on path. The path is nothing but the name of the device
  133. // album.
  134. bool canLinkToDevicePath(int userID) {
  135. return isOwner(userID) && !isDeleted && attributes.encryptedPath != null;
  136. }
  137. void updateSharees(List<User> newSharees) {
  138. sharees?.clear();
  139. sharees?.addAll(newSharees);
  140. }
  141. static CollectionType typeFromString(String type) {
  142. switch (type) {
  143. case "folder":
  144. return CollectionType.folder;
  145. case "favorites":
  146. return CollectionType.favorites;
  147. case "uncategorized":
  148. return CollectionType.uncategorized;
  149. case "album":
  150. return CollectionType.album;
  151. case "unknown":
  152. return CollectionType.unknown;
  153. }
  154. debugPrint("unexpected collection type $type");
  155. return CollectionType.unknown;
  156. }
  157. static String typeToString(CollectionType type) {
  158. switch (type) {
  159. case CollectionType.folder:
  160. return "folder";
  161. case CollectionType.favorites:
  162. return "favorites";
  163. case CollectionType.album:
  164. return "album";
  165. case CollectionType.uncategorized:
  166. return "uncategorized";
  167. case CollectionType.unknown:
  168. return "unknown";
  169. }
  170. }
  171. Collection copyWith({
  172. int? id,
  173. User? owner,
  174. String? encryptedKey,
  175. String? keyDecryptionNonce,
  176. String? name,
  177. String? encryptedName,
  178. String? nameDecryptionNonce,
  179. CollectionType? type,
  180. CollectionAttributes? attributes,
  181. List<User>? sharees,
  182. List<PublicURL>? publicURLs,
  183. int? updationTime,
  184. bool? isDeleted,
  185. String? mMdEncodedJson,
  186. int? mMdVersion,
  187. String? decryptedName,
  188. String? decryptedPath,
  189. }) {
  190. final Collection result = Collection(
  191. id ?? this.id,
  192. owner ?? this.owner,
  193. encryptedKey ?? this.encryptedKey,
  194. keyDecryptionNonce ?? this.keyDecryptionNonce,
  195. name ?? this.name,
  196. encryptedName ?? this.encryptedName,
  197. nameDecryptionNonce ?? this.nameDecryptionNonce,
  198. type ?? this.type,
  199. attributes ?? this.attributes,
  200. sharees ?? this.sharees,
  201. publicURLs ?? this.publicURLs,
  202. updationTime ?? this.updationTime,
  203. isDeleted: isDeleted ?? this.isDeleted,
  204. );
  205. result.mMdVersion = mMdVersion ?? this.mMdVersion;
  206. result.mMdEncodedJson = mMdEncodedJson ?? this.mMdEncodedJson;
  207. result.decryptedName = decryptedName ?? this.decryptedName;
  208. result.decryptedPath = decryptedPath ?? this.decryptedPath;
  209. result.mMbPubVersion = mMbPubVersion;
  210. result.mMdPubEncodedJson = mMdPubEncodedJson;
  211. result.sharedMmdVersion = sharedMmdVersion;
  212. result.sharedMmdJson = sharedMmdJson;
  213. return result;
  214. }
  215. static fromMap(Map<String, dynamic>? map) {
  216. if (map == null) return null;
  217. final sharees = (map['sharees'] == null || map['sharees'].length == 0)
  218. ? <User>[]
  219. : List<User>.from(map['sharees'].map((x) => User.fromMap(x)));
  220. final publicURLs =
  221. (map['publicURLs'] == null || map['publicURLs'].length == 0)
  222. ? <PublicURL>[]
  223. : List<PublicURL>.from(
  224. map['publicURLs'].map((x) => PublicURL.fromMap(x)),
  225. );
  226. return Collection(
  227. map['id'],
  228. User.fromMap(map['owner']),
  229. map['encryptedKey'],
  230. map['keyDecryptionNonce'],
  231. map['name'],
  232. map['encryptedName'],
  233. map['nameDecryptionNonce'],
  234. typeFromString(map['type']),
  235. CollectionAttributes.fromMap(map['attributes']),
  236. sharees,
  237. publicURLs,
  238. map['updationTime'],
  239. isDeleted: map['isDeleted'] ?? false,
  240. );
  241. }
  242. }
  243. enum CollectionType {
  244. folder,
  245. favorites,
  246. uncategorized,
  247. album,
  248. unknown,
  249. }
  250. extension CollectionTypeExtn on CollectionType {
  251. bool get canDelete =>
  252. this != CollectionType.favorites && this != CollectionType.uncategorized;
  253. }
  254. enum CollectionParticipantRole {
  255. unknown,
  256. viewer,
  257. collaborator,
  258. owner,
  259. }
  260. extension CollectionParticipantRoleExtn on CollectionParticipantRole {
  261. static CollectionParticipantRole fromString(String? val) {
  262. if ((val ?? '') == '') {
  263. return CollectionParticipantRole.viewer;
  264. }
  265. for (var x in CollectionParticipantRole.values) {
  266. if (x.name.toUpperCase() == val!.toUpperCase()) {
  267. return x;
  268. }
  269. }
  270. return CollectionParticipantRole.unknown;
  271. }
  272. String toStringVal() {
  273. return name.toUpperCase();
  274. }
  275. }
  276. class CollectionAttributes {
  277. final String? encryptedPath;
  278. final String? pathDecryptionNonce;
  279. final int? version;
  280. CollectionAttributes({
  281. this.encryptedPath,
  282. this.pathDecryptionNonce,
  283. this.version,
  284. });
  285. Map<String, dynamic> toMap() {
  286. final map = <String, dynamic>{};
  287. if (encryptedPath != null) {
  288. map['encryptedPath'] = encryptedPath;
  289. }
  290. if (pathDecryptionNonce != null) {
  291. map['pathDecryptionNonce'] = pathDecryptionNonce;
  292. }
  293. map['version'] = version ?? 0;
  294. return map;
  295. }
  296. static fromMap(Map<String, dynamic>? map) {
  297. if (map == null) return null;
  298. return CollectionAttributes(
  299. encryptedPath: map['encryptedPath'],
  300. pathDecryptionNonce: map['pathDecryptionNonce'],
  301. version: map['version'] ?? 0,
  302. );
  303. }
  304. }