mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-20 19:33:08 +09:00
142 lines
5.7 KiB
Swift
142 lines
5.7 KiB
Swift
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import Foundation
|
|
|
|
private let DiskReaderModeCacheSharedInstance = DiskReaderModeCache()
|
|
private let MemoryReaderModeCacheSharedInstance = MemoryReaderModeCache()
|
|
|
|
let ReaderModeCacheErrorDomain = "com.mozilla.client.readermodecache."
|
|
enum ReaderModeCacheErrorCode: Int {
|
|
case noPathsFound = 0
|
|
}
|
|
|
|
// NSObject wrapper around ReadabilityResult Swift struct for adding into the NSCache
|
|
private class ReadabilityResultWrapper: NSObject {
|
|
let result: ReadabilityResult
|
|
|
|
init(readabilityResult: ReadabilityResult) {
|
|
self.result = readabilityResult
|
|
super.init()
|
|
}
|
|
}
|
|
|
|
protocol ReaderModeCache {
|
|
func put(_ url: URL, _ readabilityResult: ReadabilityResult) throws
|
|
|
|
func get(_ url: URL) throws -> ReadabilityResult
|
|
|
|
func delete(_ url: URL, error: NSErrorPointer)
|
|
|
|
func contains(_ url: URL) -> Bool
|
|
}
|
|
|
|
/// A non-persistent cache for readerized content for times when you don't want to write reader data to disk.
|
|
/// For example, when the user is in a private tab, we want to make sure that we leave no trace on the file system
|
|
class MemoryReaderModeCache: ReaderModeCache {
|
|
var cache: NSCache<AnyObject, AnyObject>
|
|
|
|
init(cache: NSCache<AnyObject, AnyObject> = NSCache()) {
|
|
self.cache = cache
|
|
}
|
|
|
|
class var sharedInstance: ReaderModeCache {
|
|
return MemoryReaderModeCacheSharedInstance
|
|
}
|
|
|
|
func put(_ url: URL, _ readabilityResult: ReadabilityResult) throws {
|
|
cache.setObject(ReadabilityResultWrapper(readabilityResult: readabilityResult), forKey: url as AnyObject)
|
|
}
|
|
|
|
func get(_ url: URL) throws -> ReadabilityResult {
|
|
guard let resultWrapper = cache.object(forKey: url as AnyObject) as? ReadabilityResultWrapper else {
|
|
throw NSError(domain: ReaderModeCacheErrorDomain, code: ReaderModeCacheErrorCode.noPathsFound.rawValue, userInfo: nil)
|
|
}
|
|
return resultWrapper.result
|
|
}
|
|
|
|
func delete(_ url: URL, error: NSErrorPointer) {
|
|
cache.removeObject(forKey: url as AnyObject)
|
|
}
|
|
|
|
func contains(_ url: URL) -> Bool {
|
|
return cache.object(forKey: url as AnyObject) != nil
|
|
}
|
|
}
|
|
|
|
/// Really basic persistent cache to store readerized content. Has a simple hashed structure
|
|
/// to avoid storing many items in the same directory.
|
|
///
|
|
/// This currently lives in ~/Library/Caches so that the data can be pruned in case the OS needs
|
|
/// more space. Whether that is a good idea or not is not sure. We have a bug on file to investigate
|
|
/// and improve at a later time.
|
|
class DiskReaderModeCache: ReaderModeCache {
|
|
class var sharedInstance: ReaderModeCache {
|
|
return DiskReaderModeCacheSharedInstance
|
|
}
|
|
|
|
func put(_ url: URL, _ readabilityResult: ReadabilityResult) throws {
|
|
guard let (cacheDirectoryPath, contentFilePath) = cachePathsForURL(url) else {
|
|
throw NSError(domain: ReaderModeCacheErrorDomain, code: ReaderModeCacheErrorCode.noPathsFound.rawValue, userInfo: nil)
|
|
}
|
|
|
|
try FileManager.default.createDirectory(atPath: cacheDirectoryPath, withIntermediateDirectories: true, attributes: nil)
|
|
let string: String = readabilityResult.encode()
|
|
try string.write(toFile: contentFilePath, atomically: true, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
|
|
return
|
|
}
|
|
|
|
func get(_ url: URL) throws -> ReadabilityResult {
|
|
if let (_, contentFilePath) = cachePathsForURL(url), FileManager.default.fileExists(atPath: contentFilePath) {
|
|
let string = try NSString(contentsOfFile: contentFilePath, encoding: String.Encoding.utf8.rawValue)
|
|
if let value = ReadabilityResult(string: string as String) {
|
|
return value
|
|
}
|
|
}
|
|
|
|
throw NSError(domain: ReaderModeCacheErrorDomain, code: ReaderModeCacheErrorCode.noPathsFound.rawValue, userInfo: nil)
|
|
}
|
|
|
|
func delete(_ url: URL, error: NSErrorPointer) {
|
|
guard let (cacheDirectoryPath, _) = cachePathsForURL(url) else { return }
|
|
|
|
if FileManager.default.fileExists(atPath: cacheDirectoryPath) {
|
|
do {
|
|
try FileManager.default.removeItem(atPath: cacheDirectoryPath)
|
|
} catch let error1 as NSError {
|
|
error?.pointee = error1
|
|
}
|
|
}
|
|
}
|
|
|
|
func contains(_ url: URL) -> Bool {
|
|
if let (_, contentFilePath) = cachePathsForURL(url), FileManager.default.fileExists(atPath: contentFilePath) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
fileprivate func cachePathsForURL(_ url: URL) -> (cacheDirectoryPath: String, contentFilePath: String)? {
|
|
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
|
|
if !paths.isEmpty, let hashedPath = hashedPathForURL(url) {
|
|
let cacheDirectoryURL = URL(fileURLWithPath: NSString.path(withComponents: [paths[0], "ReaderView", hashedPath]))
|
|
return (cacheDirectoryURL.path, cacheDirectoryURL.appendingPathComponent("content.json").path)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
fileprivate func hashedPathForURL(_ url: URL) -> String? {
|
|
guard let hash = hashForURL(url) else { return nil }
|
|
|
|
return NSString.path(withComponents: [hash.substring(with: NSRange(location: 0, length: 2)), hash.substring(with: NSRange(location: 2, length: 2)), hash.substring(from: 4)]) as String
|
|
}
|
|
|
|
fileprivate func hashForURL(_ url: URL) -> NSString? {
|
|
guard let data = url.absoluteString.data(using: String.Encoding.utf8) else { return nil }
|
|
|
|
return data.sha1.hexEncodedString as NSString?
|
|
}
|
|
}
|