NCCollectionViewDataSource.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. //
  2. // NCCollectionViewDataSource.swift
  3. // Nextcloud
  4. //
  5. // Created by Marino Faggiana on 06/09/2020.
  6. // Copyright © 2020 Marino Faggiana. All rights reserved.
  7. //
  8. // Author Marino Faggiana <marino.faggiana@nextcloud.com>
  9. //
  10. // This program is free software: you can redistribute it and/or modify
  11. // it under the terms of the GNU General Public License as published by
  12. // the Free Software Foundation, either version 3 of the License, or
  13. // (at your option) any later version.
  14. //
  15. // This program is distributed in the hope that it will be useful,
  16. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. // GNU General Public License for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License
  21. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. //
  23. import UIKit
  24. import NextcloudKit
  25. import RealmSwift
  26. class NCCollectionViewDataSource: NSObject {
  27. private let utilityFileSystem = NCUtilityFileSystem()
  28. private let utility = NCUtility()
  29. private let global = NCGlobal.shared
  30. private var sectionsValue: [String] = []
  31. private var providers: [NKSearchProvider]?
  32. private var searchResults: [NKSearchResult]?
  33. private var results: Results<tableMetadata>?
  34. private var metadatas: [tableMetadata] = []
  35. private var metadatasForSection: [NCMetadataForSection] = []
  36. private var layoutForView: NCDBLayoutForView?
  37. private var metadataIndexPath = ThreadSafeDictionary<IndexPath, tableMetadata>()
  38. override init() { super.init() }
  39. init(results: Results<tableMetadata>?,
  40. layoutForView: NCDBLayoutForView? = nil,
  41. providers: [NKSearchProvider]? = nil,
  42. searchResults: [NKSearchResult]? = nil) {
  43. super.init()
  44. removeAll()
  45. self.results = results
  46. if let results {
  47. self.metadatas = Array(results.freeze())
  48. } else {
  49. self.metadatas = []
  50. }
  51. self.layoutForView = layoutForView
  52. /// unified search
  53. self.providers = providers
  54. self.searchResults = searchResults
  55. if let providers, !providers.isEmpty || (layoutForView?.groupBy != "none") {
  56. createSections()
  57. }
  58. }
  59. // MARK: -
  60. func removeAll() {
  61. self.metadatas.removeAll()
  62. self.metadataIndexPath.removeAll()
  63. self.results = nil
  64. self.metadatasForSection.removeAll()
  65. self.sectionsValue.removeAll()
  66. self.providers = nil
  67. self.searchResults = nil
  68. }
  69. func addSection(metadatas: [tableMetadata], searchResult: NKSearchResult?) {
  70. self.metadatas.append(contentsOf: metadatas)
  71. if let searchResult = searchResult {
  72. self.searchResults?.append(searchResult)
  73. }
  74. createSections()
  75. }
  76. internal func createSections() {
  77. for metadata in self.metadatas {
  78. /// skipped livePhoto VIDEO part
  79. if metadata.isLivePhoto, metadata.classFile == NKCommon.TypeClassFile.video.rawValue {
  80. continue
  81. }
  82. let section = NSLocalizedString(self.getSectionValue(metadata: metadata), comment: "")
  83. if !self.sectionsValue.contains(section) {
  84. self.sectionsValue.append(section)
  85. }
  86. }
  87. /// Unified search
  88. if let providers = self.providers, !providers.isEmpty {
  89. let sectionsDictionary = ThreadSafeDictionary<String, Int>()
  90. for section in self.sectionsValue {
  91. if let provider = providers.filter({ $0.id == section}).first {
  92. sectionsDictionary[section] = provider.order
  93. }
  94. }
  95. self.sectionsValue.removeAll()
  96. let sectionsDictionarySorted = sectionsDictionary.sorted(by: {$0.value < $1.value })
  97. for section in sectionsDictionarySorted {
  98. if section.key == global.appName {
  99. self.sectionsValue.insert(section.key, at: 0)
  100. } else {
  101. self.sectionsValue.append(section.key)
  102. }
  103. }
  104. } else {
  105. /// normal
  106. let directory = NSLocalizedString("directory", comment: "").lowercased().firstUppercased
  107. self.sectionsValue = self.sectionsValue.sorted {
  108. if let directoryOnTop = layoutForView?.directoryOnTop,
  109. directoryOnTop,
  110. $0 == directory {
  111. return true
  112. } else if let directoryOnTop = layoutForView?.directoryOnTop,
  113. directoryOnTop,
  114. $1 == directory {
  115. return false
  116. }
  117. if let ascending = layoutForView?.ascending,
  118. ascending {
  119. return $0 < $1
  120. } else {
  121. return $0 > $1
  122. }
  123. }
  124. }
  125. for sectionValue in self.sectionsValue {
  126. if !existsMetadataForSection(sectionValue: sectionValue) {
  127. print("DATASOURCE: create metadata for section: " + sectionValue)
  128. createMetadataForSection(sectionValue: sectionValue)
  129. }
  130. }
  131. }
  132. internal func createMetadataForSection(sectionValue: String) {
  133. var searchResult: NKSearchResult?
  134. if let providers = self.providers, !providers.isEmpty, let searchResults = self.searchResults {
  135. searchResult = searchResults.filter({ $0.id == sectionValue}).first
  136. }
  137. let metadatas = self.metadatas.filter({ getSectionValue(metadata: $0) == sectionValue})
  138. let metadataForSection = NCMetadataForSection(sectionValue: sectionValue,
  139. metadatas: metadatas,
  140. lastSearchResult: searchResult,
  141. layoutForView: self.layoutForView)
  142. metadatasForSection.append(metadataForSection)
  143. }
  144. // MARK: -
  145. func appendMetadatasToSection(_ metadatas: [tableMetadata], metadataForSection: NCMetadataForSection, lastSearchResult: NKSearchResult) {
  146. guard let sectionIndex = getSectionIndex(metadataForSection.sectionValue) else { return }
  147. var indexPaths: [IndexPath] = []
  148. self.metadatas.append(contentsOf: metadatas)
  149. metadataForSection.metadatas.append(contentsOf: metadatas)
  150. metadataForSection.lastSearchResult = lastSearchResult
  151. metadataForSection.createMetadatas()
  152. for metadata in metadatas {
  153. if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.ocId == metadata.ocId}) {
  154. indexPaths.append(IndexPath(row: rowIndex, section: sectionIndex))
  155. }
  156. }
  157. }
  158. // MARK: -
  159. func getMetadatas() -> [tableMetadata] {
  160. return self.metadatas
  161. }
  162. func isEmpty() -> Bool {
  163. return self.metadatas.isEmpty
  164. }
  165. func getIndexPathMetadata(ocId: String) -> IndexPath? {
  166. guard self.sectionsValue.isEmpty else { return nil }
  167. let validMetadatas = self.metadatas.filter { !$0.isInvalidated }
  168. if let rowIndex = validMetadatas.firstIndex(where: {$0.ocId == ocId}) {
  169. return IndexPath(row: rowIndex, section: 0)
  170. }
  171. return nil
  172. }
  173. func numberOfSections() -> Int {
  174. guard !self.sectionsValue.isEmpty else { return 1 }
  175. return self.sectionsValue.count
  176. }
  177. func numberOfItemsInSection(_ section: Int) -> Int {
  178. if self.sectionsValue.isEmpty {
  179. let validMetadatas = metadatas.filter { !$0.isInvalidated }
  180. return validMetadatas.count
  181. }
  182. guard !self.metadatas.isEmpty,
  183. let metadataForSection = getMetadataForSection(section)
  184. else { return 0}
  185. return metadataForSection.metadatas.count
  186. }
  187. func getSectionValueLocalization(indexPath: IndexPath) -> String {
  188. guard !metadatasForSection.isEmpty, let metadataForSection = self.getMetadataForSection(indexPath.section) else { return ""}
  189. if let searchResults = self.searchResults, let searchResult = searchResults.filter({ $0.id == metadataForSection.sectionValue}).first {
  190. return searchResult.name
  191. }
  192. return metadataForSection.sectionValue
  193. }
  194. func getFooterInformation() -> (directories: Int, files: Int, size: Int64) {
  195. let validMetadatas = metadatas.filter { !$0.isInvalidated }
  196. let directories = validMetadatas.filter({ $0.directory == true})
  197. let files = validMetadatas.filter({ $0.directory == false})
  198. var size: Int64 = 0
  199. files.forEach { metadata in
  200. size += metadata.size
  201. }
  202. return (directories.count, files.count, size)
  203. }
  204. func getResultMetadata(indexPath: IndexPath) -> tableMetadata? {
  205. let validMetadatas = metadatas.filter { !$0.isInvalidated }
  206. if indexPath.row < validMetadatas.count {
  207. return validMetadatas[indexPath.row]
  208. }
  209. return nil
  210. }
  211. func getMetadata(indexPath: IndexPath) -> tableMetadata? {
  212. if !metadatasForSection.isEmpty, indexPath.section < metadatasForSection.count {
  213. if let metadataForSection = getMetadataForSection(indexPath.section),
  214. indexPath.row < metadataForSection.metadatas.count,
  215. !metadataForSection.metadatas[indexPath.row].isInvalidated {
  216. return tableMetadata(value: metadataForSection.metadatas[indexPath.row])
  217. }
  218. } else if indexPath.row < self.metadatas.count {
  219. if let metadata = metadataIndexPath[indexPath] {
  220. return metadata
  221. } else {
  222. let validMetadatas = self.metadatas.filter { !$0.isInvalidated }
  223. let metadata = tableMetadata(value: validMetadatas[indexPath.row])
  224. metadataIndexPath[indexPath] = metadata
  225. return metadata
  226. }
  227. }
  228. return nil
  229. }
  230. func caching(metadatas: [tableMetadata], dataSourceMetadatas: [tableMetadata], completion: @escaping (_ update: Bool) -> Void) {
  231. var counter: Int = 0
  232. var updated: Bool = dataSourceMetadatas.isEmpty
  233. DispatchQueue.global().async {
  234. for metadata in metadatas {
  235. let indexPath = IndexPath(row: counter, section: 0)
  236. if indexPath.row < dataSourceMetadatas.count {
  237. if !metadata.isEqual(dataSourceMetadatas[indexPath.row]) {
  238. updated = true
  239. }
  240. } else {
  241. updated = true
  242. }
  243. self.metadataIndexPath[indexPath] = tableMetadata(value: metadata)
  244. /// caching preview
  245. ///
  246. if metadata.isImageOrVideo,
  247. NCImageCache.shared.getImageCache(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt256) == nil,
  248. let image = self.utility.getImage(ocId: metadata.ocId, etag: metadata.etag, ext: self.global.previewExt256) {
  249. NCImageCache.shared.addImageCache(ocId: metadata.ocId, etag: metadata.etag, image: image, ext: self.global.previewExt256, cost: counter)
  250. }
  251. counter += 1
  252. }
  253. DispatchQueue.main.async {
  254. return completion(updated)
  255. }
  256. }
  257. }
  258. func removeImageCache() {
  259. DispatchQueue.global().async {
  260. for metadata in self.metadatas {
  261. NCImageCache.shared.removeImageCache(ocIdPlusEtag: metadata.ocId + metadata.etag)
  262. }
  263. }
  264. }
  265. // MARK: -
  266. internal func isSameNumbersOfSections(numberOfSections: Int) -> Bool {
  267. guard !self.metadatasForSection.isEmpty else { return false }
  268. return numberOfSections == self.numberOfSections()
  269. }
  270. internal func getSectionValue(metadata: tableMetadata) -> String {
  271. switch self.layoutForView?.groupBy {
  272. case "name", "none":
  273. return NSLocalizedString(metadata.name, comment: "")
  274. case "classFile":
  275. return NSLocalizedString(metadata.classFile, comment: "").lowercased().firstUppercased
  276. default:
  277. return NSLocalizedString(metadata.name, comment: "")
  278. }
  279. }
  280. internal func getIndexMetadatasForSection(_ sectionValue: String) -> Int? {
  281. return self.metadatasForSection.firstIndex(where: {$0.sectionValue == sectionValue })
  282. }
  283. internal func getSectionIndex(_ sectionValue: String) -> Int? {
  284. return self.sectionsValue.firstIndex(where: {$0 == sectionValue })
  285. }
  286. internal func existsMetadataForSection(sectionValue: String) -> Bool {
  287. return !self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).isEmpty
  288. }
  289. internal func getMetadataForSection(_ section: Int) -> NCMetadataForSection? {
  290. guard section < sectionsValue.count, let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionsValue[section]}).first else { return nil }
  291. return metadataForSection
  292. }
  293. internal func getMetadataForSection(_ sectionValue: String) -> NCMetadataForSection? {
  294. guard let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).first else { return nil }
  295. return metadataForSection
  296. }
  297. }
  298. // MARK: -
  299. class NCMetadataForSection: NSObject {
  300. var sectionValue: String
  301. var metadatas: [tableMetadata]
  302. var lastSearchResult: NKSearchResult?
  303. var unifiedSearchInProgress: Bool = false
  304. var layoutForView: NCDBLayoutForView?
  305. private var metadatasSorted: [tableMetadata] = []
  306. private var metadatasFavoriteDirectory: [tableMetadata] = []
  307. private var metadatasFavoriteFile: [tableMetadata] = []
  308. private var metadatasDirectory: [tableMetadata] = []
  309. private var metadatasFile: [tableMetadata] = []
  310. public var numDirectory: Int = 0
  311. public var numFile: Int = 0
  312. public var totalSize: Int64 = 0
  313. init(sectionValue: String, metadatas: [tableMetadata], lastSearchResult: NKSearchResult?, layoutForView: NCDBLayoutForView?) {
  314. self.sectionValue = sectionValue
  315. self.metadatas = metadatas
  316. self.lastSearchResult = lastSearchResult
  317. self.layoutForView = layoutForView
  318. super.init()
  319. createMetadatas()
  320. }
  321. func createMetadatas() {
  322. // Clear
  323. //
  324. metadatasSorted.removeAll()
  325. metadatasFavoriteDirectory.removeAll()
  326. metadatasFavoriteFile.removeAll()
  327. metadatasDirectory.removeAll()
  328. metadatasFile.removeAll()
  329. numDirectory = 0
  330. numFile = 0
  331. totalSize = 0
  332. var ocIds: [String] = []
  333. let metadataInSession = metadatas.filter({ !$0.session.isEmpty })
  334. // Metadata order
  335. //
  336. if let layoutForView = self.layoutForView, layoutForView.sort != "none" && !layoutForView.sort.isEmpty {
  337. metadatasSorted = metadatas.sorted {
  338. switch layoutForView.sort {
  339. case "date":
  340. if layoutForView.ascending {
  341. return ($0.date as Date) < ($1.date as Date)
  342. } else {
  343. return ($0.date as Date) > ($1.date as Date)
  344. }
  345. case "size":
  346. if layoutForView.ascending {
  347. return $0.size < $1.size
  348. } else {
  349. return $0.size > $1.size
  350. }
  351. default:
  352. if layoutForView.ascending {
  353. return $0.fileNameView.lowercased() < $1.fileNameView.lowercased()
  354. } else {
  355. return $0.fileNameView.lowercased() > $1.fileNameView.lowercased()
  356. }
  357. }
  358. }
  359. } else {
  360. metadatasSorted = metadatas
  361. }
  362. // Initialize datasource
  363. //
  364. for metadata in metadatasSorted {
  365. // skipped the root file
  366. if metadata.fileName == "." || metadata.serverUrl == ".." {
  367. continue
  368. }
  369. // skipped livePhoto VIDEO part
  370. if metadata.isLivePhoto,
  371. metadata.classFile == NKCommon.TypeClassFile.video.rawValue {
  372. continue
  373. }
  374. // Upload [REPLACE] skip
  375. if metadata.session.isEmpty && !metadataInSession.filter({ $0.fileNameView == metadata.fileNameView }).isEmpty {
  376. continue
  377. }
  378. // Upload [REPLACE] skip
  379. if metadata.session.isEmpty && !metadataInSession.filter({ $0.fileNameView == metadata.fileNameView }).isEmpty {
  380. continue
  381. }
  382. // Organized the metadata
  383. if metadata.favorite {
  384. if metadata.directory {
  385. metadatasFavoriteDirectory.append(metadata)
  386. } else {
  387. metadatasFavoriteFile.append(metadata)
  388. }
  389. } else if metadata.directory && layoutForView?.directoryOnTop ?? true {
  390. metadatasDirectory.append(metadata)
  391. } else {
  392. metadatasFile.append(metadata)
  393. }
  394. if metadata.directory {
  395. ocIds.append(metadata.ocId)
  396. numDirectory += 1
  397. } else {
  398. numFile += 1
  399. totalSize += metadata.size
  400. }
  401. }
  402. metadatas.removeAll()
  403. // Struct view : favorite dir -> favorite file -> directory -> files
  404. metadatas += metadatasFavoriteDirectory
  405. metadatas += metadatasFavoriteFile
  406. metadatas += metadatasDirectory
  407. metadatas += metadatasFile
  408. }
  409. }