// // NCDataSource.swift // Nextcloud // // Created by Marino Faggiana on 06/09/2020. // Copyright © 2020 Marino Faggiana. All rights reserved. // // Author Marino Faggiana // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // import UIKit import NextcloudKit class NCDataSource: NSObject { var metadatas: [tableMetadata] = [] var metadatasForSection: [NCMetadataForSection] = [] var directory: tableDirectory? var groupByField: String = "" private var sectionsValue: [String] = [] private var providers: [NKSearchProvider]? private var searchResults: [NKSearchResult]? private var localFiles: [tableLocalFile] = [] private var ascending: Bool = true private var sort: String = "" private var directoryOnTop: Bool = true private var favoriteOnTop: Bool = true private var filterLivePhoto: Bool = true override init() { super.init() } init(metadatas: [tableMetadata], account: String, directory: tableDirectory? = nil, sort: String? = "none", ascending: Bool? = false, directoryOnTop: Bool? = true, favoriteOnTop: Bool? = true, filterLivePhoto: Bool? = true, groupByField: String = "name", providers: [NKSearchProvider]? = nil, searchResults: [NKSearchResult]? = nil) { super.init() self.metadatas = metadatas.filter({ !(NCGlobal.shared.includeHiddenFiles.contains($0.fileNameView) || $0.isTransferInForeground) }) self.directory = directory self.localFiles = NCManageDatabase.shared.getTableLocalFile(account: account) self.sort = sort ?? "none" self.ascending = ascending ?? false self.directoryOnTop = directoryOnTop ?? true self.favoriteOnTop = favoriteOnTop ?? true self.filterLivePhoto = filterLivePhoto ?? true self.groupByField = groupByField // unified search self.providers = providers self.searchResults = searchResults createSections() } // MARK: - func clearDataSource() { self.metadatas.removeAll() self.metadatasForSection.removeAll() self.directory = nil self.sectionsValue.removeAll() self.providers = nil self.searchResults = nil self.localFiles.removeAll() } func clearDirectory() { self.directory = nil } func changeGroupByField(_ groupByField: String) { self.groupByField = groupByField print("DATASOURCE: set group by filed " + groupByField) self.metadatasForSection.removeAll() self.sectionsValue.removeAll() print("DATASOURCE: remove all sections") createSections() } func addSection(metadatas: [tableMetadata], searchResult: NKSearchResult?) { self.metadatas.append(contentsOf: metadatas) if let searchResult = searchResult { self.searchResults?.append(searchResult) } createSections() } internal func createSections() { // get all Section for metadata in self.metadatas { // skipped livePhoto if filterLivePhoto && metadata.livePhoto && (metadata.fileNameView as NSString).pathExtension.lowercased() == "mov" { continue } let section = NSLocalizedString(self.getSectionValue(metadata: metadata), comment: "") if !self.sectionsValue.contains(section) { self.sectionsValue.append(section) } } // Unified search if let providers = self.providers, !providers.isEmpty { let sectionsDictionary = ThreadSafeDictionary() for section in self.sectionsValue { if let provider = providers.filter({ $0.id == section}).first { sectionsDictionary[section] = provider.order } } self.sectionsValue.removeAll() let sectionsDictionarySorted = sectionsDictionary.sorted(by: {$0.value < $1.value }) for section in sectionsDictionarySorted { if section.key == NCGlobal.shared.appName { self.sectionsValue.insert(section.key, at: 0) } else { self.sectionsValue.append(section.key) } } } else { // normal let directory = NSLocalizedString("directory", comment: "").lowercased().firstUppercased self.sectionsValue = self.sectionsValue.sorted { if directoryOnTop && $0 == directory { return true } else if directoryOnTop && $1 == directory { return false } if self.ascending { return $0 < $1 } else { return $0 > $1 } } } for sectionValue in self.sectionsValue { if !existsMetadataForSection(sectionValue: sectionValue) { print("DATASOURCE: create metadata for section: " + sectionValue) createMetadataForSection(sectionValue: sectionValue) } } } internal func createMetadataForSection(sectionValue: String) { var searchResult: NKSearchResult? if let providers = self.providers, !providers.isEmpty, let searchResults = self.searchResults { searchResult = searchResults.filter({ $0.id == sectionValue}).first } let metadatas = self.metadatas.filter({ getSectionValue(metadata: $0) == sectionValue}) let metadataForSection = NCMetadataForSection(sectionValue: sectionValue, metadatas: metadatas, localFiles: self.localFiles, lastSearchResult: searchResult, sort: self.sort, ascending: self.ascending, directoryOnTop: self.directoryOnTop, favoriteOnTop: self.favoriteOnTop, filterLivePhoto: self.filterLivePhoto) metadatasForSection.append(metadataForSection) } func getMetadataSourceForAllSections() -> [tableMetadata] { var metadatas: [tableMetadata] = [] for section in metadatasForSection { metadatas.append(contentsOf: section.metadatas) } return metadatas } // MARK: - @discardableResult func appendMetadatasToSection(_ metadatas: [tableMetadata], metadataForSection: NCMetadataForSection, lastSearchResult: NKSearchResult) -> [IndexPath] { guard let sectionIndex = getSectionIndex(metadataForSection.sectionValue) else { return [] } var indexPaths: [IndexPath] = [] self.metadatas.append(contentsOf: metadatas) metadataForSection.metadatas.append(contentsOf: metadatas) metadataForSection.lastSearchResult = lastSearchResult metadataForSection.createMetadatas() for metadata in metadatas { if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.ocId == metadata.ocId}) { indexPaths.append(IndexPath(row: rowIndex, section: sectionIndex)) } } return indexPaths } @discardableResult func addMetadata(_ metadata: tableMetadata) -> (indexPath: IndexPath?, sameSections: Bool) { let numberOfSections = self.numberOfSections() let sectionValue = getSectionValue(metadata: metadata) // ADD metadatasSource if let rowIndex = self.metadatas.firstIndex(where: {$0.fileNameView == metadata.fileNameView || $0.ocId == metadata.ocId}) { self.metadatas[rowIndex] = metadata } else { self.metadatas.append(metadata) } // ADD metadataForSection if let sectionIndex = getSectionIndex(sectionValue), let metadataForSection = getMetadataForSection(sectionIndex) { if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.fileNameView == metadata.fileNameView || $0.ocId == metadata.ocId}) { metadataForSection.metadatas[rowIndex] = metadata return (IndexPath(row: rowIndex, section: sectionIndex), self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } else { metadataForSection.metadatas.append(metadata) metadataForSection.createMetadatas() if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.ocId == metadata.ocId}) { return (IndexPath(row: rowIndex, section: sectionIndex), self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } return (nil, self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } } else { // NEW section createSections() // get IndexPath of new section if let sectionIndex = getSectionIndex(sectionValue), let metadataForSection = getMetadataForSection(sectionIndex) { if let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.fileNameView == metadata.fileNameView || $0.ocId == metadata.ocId}) { return (IndexPath(row: rowIndex, section: sectionIndex), self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } } } return (nil, self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } func deleteMetadata(ocId: String) -> (indexPath: IndexPath?, sameSections: Bool) { let numberOfSections = self.numberOfSections() var indexPathReturn: IndexPath? var sectionValue = "" // DELETE metadataForSection (IMPORTANT FIRST) let (indexPath, metadataForSection) = self.getIndexPathMetadata(ocId: ocId) if let indexPath = indexPath, let metadataForSection = metadataForSection, indexPath.row < metadataForSection.metadatas.count { metadataForSection.metadatas.remove(at: indexPath.row) if metadataForSection.metadatas.isEmpty { // REMOVE sectionsValue / metadatasForSection sectionValue = metadataForSection.sectionValue if let sectionIndex = getSectionIndex(sectionValue) { self.sectionsValue.remove(at: sectionIndex) } if let index = getIndexMetadatasForSection(sectionValue) { self.metadatasForSection.remove(at: index) } } else { metadataForSection.createMetadatas() } indexPathReturn = indexPath } else { return (nil, false) } // DELETE metadatasSource (IMPORTANT LAST) if let rowIndex = self.metadatas.firstIndex(where: {$0.ocId == ocId}) { self.metadatas.remove(at: rowIndex) } return (indexPathReturn, self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } @discardableResult func reloadMetadata(ocId: String, ocIdTemp: String? = nil) -> (indexPath: IndexPath?, sameSections: Bool) { let numberOfSections = self.numberOfSections() var ocIdSearch = ocId guard let metadata = NCManageDatabase.shared.getMetadataFromOcId(ocId) else { return (nil, self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } if let ocIdTemp = ocIdTemp { ocIdSearch = ocIdTemp } // UPDATE metadataForSection (IMPORTANT FIRST) let (indexPath, metadataForSection) = self.getIndexPathMetadata(ocId: ocIdSearch) if let indexPath = indexPath, let metadataForSection = metadataForSection { metadataForSection.metadatas[indexPath.row] = metadata metadataForSection.createMetadatas() } // UPDATE metadatasSource (IMPORTANT LAST) if let rowIndex = self.metadatas.firstIndex(where: {$0.ocId == ocIdSearch}) { self.metadatas[rowIndex] = metadata } let result = self.getIndexPathMetadata(ocId: ocId) return (result.indexPath, self.isSameNumbersOfSections(numberOfSections: numberOfSections)) } // MARK: - func getIndexPathMetadata(ocId: String) -> (indexPath: IndexPath?, metadataForSection: NCMetadataForSection?) { guard let metadata = self.metadatas.filter({ $0.ocId == ocId}).first else { return (nil, nil) } let sectionValue = getSectionValue(metadata: metadata) guard let sectionIndex = getSectionIndex(sectionValue), let metadataForSection = getMetadataForSection(sectionValue), let rowIndex = metadataForSection.metadatas.firstIndex(where: {$0.ocId == ocId}) else { return (nil, nil) } return (IndexPath(row: rowIndex, section: sectionIndex), metadataForSection) } func isSameNumbersOfSections(numberOfSections: Int) -> Bool { guard !self.metadatasForSection.isEmpty else { return false } return numberOfSections == self.numberOfSections() } func numberOfSections() -> Int { guard !self.sectionsValue.isEmpty else { return 1 } return self.sectionsValue.count } func numberOfItemsInSection(_ section: Int) -> Int { guard !self.sectionsValue.isEmpty && !self.metadatas.isEmpty, let metadataForSection = getMetadataForSection(section) else { return 0} return metadataForSection.metadatas.count } func cellForItemAt(indexPath: IndexPath) -> tableMetadata? { guard !metadatasForSection.isEmpty && indexPath.section < metadatasForSection.count, let metadataForSection = getMetadataForSection(indexPath.section), indexPath.row < metadataForSection.metadatas.count else { return nil } return metadataForSection.metadatas[indexPath.row] } func getSectionValue(indexPath: IndexPath) -> String { guard !metadatasForSection.isEmpty, let metadataForSection = self.getMetadataForSection(indexPath.section) else { return ""} return metadataForSection.sectionValue } func getSectionValueLocalization(indexPath: IndexPath) -> String { guard !metadatasForSection.isEmpty, let metadataForSection = self.getMetadataForSection(indexPath.section) else { return ""} if let searchResults = self.searchResults, let searchResult = searchResults.filter({ $0.id == metadataForSection.sectionValue}).first { return searchResult.name } return metadataForSection.sectionValue } func getFooterInformationAllMetadatas() -> (directories: Int, files: Int, size: Int64) { var directories: Int = 0 var files: Int = 0 var size: Int64 = 0 for metadataForSection in metadatasForSection { directories += metadataForSection.numDirectory files += metadataForSection.numFile size += metadataForSection.totalSize } return (directories, files, size) } // MARK: - internal func getSectionValue(metadata: tableMetadata) -> String { switch self.groupByField { case "name": return NSLocalizedString(metadata.name, comment: "") case "classFile": return NSLocalizedString(metadata.classFile, comment: "").lowercased().firstUppercased default: return NSLocalizedString(metadata.classFile, comment: "") } } internal func getIndexMetadatasForSection(_ sectionValue: String) -> Int? { return self.metadatasForSection.firstIndex(where: {$0.sectionValue == sectionValue }) } internal func getSectionIndex(_ sectionValue: String) -> Int? { return self.sectionsValue.firstIndex(where: {$0 == sectionValue }) } internal func existsMetadataForSection(sectionValue: String) -> Bool { return !self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).isEmpty } internal func getMetadataForSection(_ section: Int) -> NCMetadataForSection? { guard section < sectionsValue.count, let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionsValue[section]}).first else { return nil } return metadataForSection } internal func getMetadataForSection(_ sectionValue: String) -> NCMetadataForSection? { guard let metadataForSection = self.metadatasForSection.filter({ $0.sectionValue == sectionValue }).first else { return nil } return metadataForSection } } // MARK: - class NCMetadataForSection: NSObject { var sectionValue: String var metadatas: [tableMetadata] var localFiles: [tableLocalFile] var lastSearchResult: NKSearchResult? var unifiedSearchInProgress: Bool = false private var sort: String private var ascending: Bool private var directoryOnTop: Bool private var favoriteOnTop: Bool private var filterLivePhoto: Bool private var metadatasSorted: [tableMetadata] = [] private var metadatasFavoriteDirectory: [tableMetadata] = [] private var metadatasFavoriteFile: [tableMetadata] = [] private var metadatasDirectory: [tableMetadata] = [] private var metadatasFile: [tableMetadata] = [] public var numDirectory: Int = 0 public var numFile: Int = 0 public var totalSize: Int64 = 0 public var metadataOffLine: [String] = [] public var directories: [tableDirectory]? init(sectionValue: String, metadatas: [tableMetadata], localFiles: [tableLocalFile], lastSearchResult: NKSearchResult?, sort: String, ascending: Bool, directoryOnTop: Bool, favoriteOnTop: Bool, filterLivePhoto: Bool) { self.sectionValue = sectionValue self.metadatas = metadatas self.localFiles = localFiles self.lastSearchResult = lastSearchResult self.sort = sort self.ascending = ascending self.directoryOnTop = directoryOnTop self.favoriteOnTop = favoriteOnTop self.filterLivePhoto = filterLivePhoto super.init() createMetadatas() } func createMetadatas() { // Clear // metadatasSorted.removeAll() metadatasFavoriteDirectory.removeAll() metadatasFavoriteFile.removeAll() metadatasDirectory.removeAll() metadatasFile.removeAll() metadataOffLine.removeAll() numDirectory = 0 numFile = 0 totalSize = 0 var ocIds: [String] = [] let metadataInSession = metadatas.filter({ !$0.session.isEmpty }) // Metadata order // if sort != "none" && !sort.isEmpty { metadatasSorted = metadatas.sorted { switch sort { case "date": if ascending { return ($0.date as Date) < ($1.date as Date) } else { return ($0.date as Date) > ($1.date as Date) } case "size": if ascending { return $0.size < $1.size } else { return $0.size > $1.size } default: if ascending { return $0.fileNameView.lowercased() < $1.fileNameView.lowercased() } else { return $0.fileNameView.lowercased() > $1.fileNameView.lowercased() } } } } else { metadatasSorted = metadatas } // Initialize datasource // for metadata in metadatasSorted { // skipped the root file if metadata.fileName == "." || metadata.serverUrl == ".." { continue } // skipped livePhoto if filterLivePhoto && metadata.livePhoto && (metadata.fileNameView as NSString).pathExtension.lowercased() == "mov" { continue } // Upload [REPLACE] skip if metadata.session.isEmpty && !metadataInSession.filter({ $0.fileNameView == metadata.fileNameView }).isEmpty { continue } // Upload [REPLACE] skip if metadata.session.isEmpty && !metadataInSession.filter({ $0.fileNameView == metadata.fileNameView }).isEmpty { continue } // Organized the metadata if metadata.favorite && favoriteOnTop { if metadata.directory { metadatasFavoriteDirectory.append(metadata) } else { metadatasFavoriteFile.append(metadata) } } else if metadata.directory && directoryOnTop { metadatasDirectory.append(metadata) } else { metadatasFile.append(metadata) } if metadata.directory { ocIds.append(metadata.ocId) numDirectory += 1 } else { numFile += 1 totalSize += metadata.size } } directories = NCManageDatabase.shared.getTablesDirectory(predicate: NSPredicate(format: "ocId IN %@", ocIds), sorted: "serverUrl", ascending: true) metadatas.removeAll() // Struct view : favorite dir -> favorite file -> directory -> files metadatas += metadatasFavoriteDirectory metadatas += metadatasFavoriteFile metadatas += metadatasDirectory metadatas += metadatasFile } }