mirror of
https://github.com/yattee/yattee.git
synced 2024-11-09 15:58:20 +00:00
186 lines
5.6 KiB
Swift
186 lines
5.6 KiB
Swift
|
import Foundation
|
||
|
|
||
|
final class DocumentsModel: ObservableObject {
|
||
|
static var shared = DocumentsModel()
|
||
|
|
||
|
@Published private(set) var directoryURL: URL!
|
||
|
@Published private(set) var refreshID = UUID()
|
||
|
|
||
|
typealias AreInIncreasingOrder = (URL, URL) -> Bool
|
||
|
|
||
|
init(directoryURL: URL! = nil) {
|
||
|
self.directoryURL = directoryURL
|
||
|
}
|
||
|
|
||
|
private var fileManager: FileManager {
|
||
|
.default
|
||
|
}
|
||
|
|
||
|
var directoryLabel: String {
|
||
|
guard let directoryURL else { return "Documents" }
|
||
|
return displayLabelForDocument(directoryURL)
|
||
|
}
|
||
|
|
||
|
var sortPredicates: [AreInIncreasingOrder] {
|
||
|
[
|
||
|
{ self.isDirectory($0) && !self.isDirectory($1) },
|
||
|
{ $0.lastPathComponent.caseInsensitiveCompare($1.lastPathComponent) == .orderedAscending }
|
||
|
]
|
||
|
}
|
||
|
|
||
|
var sortedDirectoryContents: [URL] {
|
||
|
directoryContents.sorted { lhs, rhs in
|
||
|
for predicate in sortPredicates {
|
||
|
if !predicate(lhs, rhs), !predicate(rhs, lhs) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return predicate(lhs, rhs)
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var directoryContents: [URL] {
|
||
|
guard let directoryURL else { return [] }
|
||
|
return contents(of: directoryURL)
|
||
|
}
|
||
|
|
||
|
var documentsDirectory: URL? {
|
||
|
if let url = try? fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) {
|
||
|
return replacePrivateVar(url)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func isDocument(_ video: Video) -> Bool {
|
||
|
guard video.isLocal, let url = video.localStream?.localURL, let url = replacePrivateVar(url) else { return false }
|
||
|
return isDocument(url)
|
||
|
}
|
||
|
|
||
|
func isDocument(_ url: URL) -> Bool {
|
||
|
guard let url = replacePrivateVar(url), let documentsDirectory else { return false }
|
||
|
return url.absoluteString.starts(with: documentsDirectory.absoluteString)
|
||
|
}
|
||
|
|
||
|
func isDirectory(_ url: URL) -> Bool {
|
||
|
(try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
|
||
|
}
|
||
|
|
||
|
var creationDateFormatter: DateFormatter {
|
||
|
let formatter = DateFormatter()
|
||
|
|
||
|
formatter.dateStyle = .short
|
||
|
formatter.timeStyle = .short
|
||
|
|
||
|
return formatter
|
||
|
}
|
||
|
|
||
|
func creationDate(_ video: Video) -> Date? {
|
||
|
guard video.isLocal, let url = video.localStream?.localURL, let url = replacePrivateVar(url) else { return nil }
|
||
|
return creationDate(url)
|
||
|
}
|
||
|
|
||
|
func creationDate(_ url: URL) -> Date? {
|
||
|
try? url.resourceValues(forKeys: [.creationDateKey]).creationDate
|
||
|
}
|
||
|
|
||
|
func formattedCreationDate(_ video: Video) -> String? {
|
||
|
guard video.isLocal, let url = video.localStream?.localURL, let url = replacePrivateVar(url) else { return nil }
|
||
|
return formattedCreationDate(url)
|
||
|
}
|
||
|
|
||
|
func formattedCreationDate(_ url: URL) -> String? {
|
||
|
if let date = try? url.resourceValues(forKeys: [.creationDateKey]).creationDate {
|
||
|
return creationDateFormatter.string(from: date)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
var sizeFormatter: ByteCountFormatter {
|
||
|
let formatter = ByteCountFormatter()
|
||
|
|
||
|
formatter.allowedUnits = .useAll
|
||
|
formatter.countStyle = .file
|
||
|
formatter.includesUnit = true
|
||
|
formatter.isAdaptive = true
|
||
|
|
||
|
return formatter
|
||
|
}
|
||
|
|
||
|
func size(_ video: Video) -> Int? {
|
||
|
guard video.isLocal, let url = video.localStream?.localURL, let url = replacePrivateVar(url) else { return nil }
|
||
|
return size(url)
|
||
|
}
|
||
|
|
||
|
func size(_ url: URL) -> Int? {
|
||
|
try? url.resourceValues(forKeys: [.fileAllocatedSizeKey]).fileAllocatedSize
|
||
|
}
|
||
|
|
||
|
func formattedSize(_ video: Video) -> String? {
|
||
|
guard let size = size(video) else { return nil }
|
||
|
return sizeFormatter.string(fromByteCount: Int64(size))
|
||
|
}
|
||
|
|
||
|
func formattedSize(_ url: URL) -> String? {
|
||
|
guard let size = size(url) else { return nil }
|
||
|
return sizeFormatter.string(fromByteCount: Int64(size))
|
||
|
}
|
||
|
|
||
|
func removeDocument(_ url: URL) throws {
|
||
|
guard isDocument(url) else { return }
|
||
|
try fileManager.removeItem(at: url)
|
||
|
URLBookmarkModel.shared.removeBookmark(url)
|
||
|
refresh()
|
||
|
}
|
||
|
|
||
|
private func contents(of directory: URL) -> [URL] {
|
||
|
(try? fileManager.contentsOfDirectory(
|
||
|
at: directory,
|
||
|
includingPropertiesForKeys: [.creationDateKey, .fileAllocatedSizeKey, .isDirectoryKey],
|
||
|
options: [.includesDirectoriesPostOrder, .skipsHiddenFiles]
|
||
|
)) ?? []
|
||
|
}
|
||
|
|
||
|
private func displayLabelForDocument(_ file: URL) -> String {
|
||
|
let components = file.absoluteString.components(separatedBy: "/Documents/")
|
||
|
if components.count == 2 {
|
||
|
let component = components[1]
|
||
|
return component.isEmpty ? "Documents" : component.removingPercentEncoding ?? component
|
||
|
}
|
||
|
return "Document"
|
||
|
}
|
||
|
|
||
|
var canGoBack: Bool {
|
||
|
guard let directoryURL, let documentsDirectory else { return false }
|
||
|
return replacePrivateVar(directoryURL) != documentsDirectory
|
||
|
}
|
||
|
|
||
|
func goToURL(_ url: URL) {
|
||
|
directoryURL = url
|
||
|
}
|
||
|
|
||
|
func goBack() {
|
||
|
directoryURL = urlToGoBack
|
||
|
}
|
||
|
|
||
|
func goToTop() {
|
||
|
directoryURL = documentsDirectory
|
||
|
}
|
||
|
|
||
|
private var urlToGoBack: URL? {
|
||
|
directoryURL?.deletingLastPathComponent()
|
||
|
}
|
||
|
|
||
|
func replacePrivateVar(_ url: URL) -> URL? {
|
||
|
let urlStringWithPrivateVarRemoved = url.absoluteString.replacingFirstOccurrence(of: "/private/var/", with: "/var/")
|
||
|
return URL(string: urlStringWithPrivateVarRemoved)
|
||
|
}
|
||
|
|
||
|
func refresh() {
|
||
|
refreshID = UUID()
|
||
|
}
|
||
|
}
|