275 lines
10 KiB
Swift
275 lines
10 KiB
Swift
//
|
||
// MP_CacheManager.swift
|
||
// MusicPlayer
|
||
//
|
||
// Created by Mr.Zhou on 2024/5/23.
|
||
//
|
||
|
||
import Foundation
|
||
///缓存/归档管理工具
|
||
class MP_CacheAndArchiverManager {
|
||
// 单例模式,提供全局访问点
|
||
static let shared = MP_CacheAndArchiverManager()
|
||
//文件管理器
|
||
let fileManager = FileManager.default
|
||
///归档指定数据字典
|
||
// private var archiverDic:[String: MP_PlayerRequestModel]!
|
||
///缓存状态更新
|
||
private var isCompleteds:[String:Bool] = [:]
|
||
//缓存过期时间
|
||
private var zhoujunfeng_cacheOutTimes:TimeInterval = 172800
|
||
private init() {
|
||
}
|
||
///检查缓存文件夹总大小
|
||
func zhoujunfeng_checkAndCleanCacheFolder() {
|
||
//存放所有的缓存文件URL
|
||
let contents:[URL]!
|
||
guard let cacheDirectory = createCachePath() else {return}
|
||
do{
|
||
contents = try fileManager.contentsOfDirectory(at: URL(fileURLWithPath: cacheDirectory), includingPropertiesForKeys: [.creationDateKey], options: .skipsHiddenFiles)
|
||
}catch {
|
||
print("获取缓存文件夹内容失败:\(error.localizedDescription)")
|
||
return
|
||
}
|
||
//计算缓存文件夹所有的文件总大小
|
||
var folderSize: UInt64 = 0
|
||
for content in contents {
|
||
if let fileSize = try? content.resourceValues(forKeys: [.fileSizeKey]).fileSize {
|
||
folderSize += UInt64(fileSize)
|
||
}
|
||
}
|
||
//超过规定值,就清理最旧的文件
|
||
if folderSize > 1024 * 1024 * 1024 * 2 {
|
||
let sortedFiles = contents.sorted { (url1, url2) -> Bool in
|
||
let creationDate1 = try? url1.resourceValues(forKeys: [.creationDateKey]).creationDate
|
||
let creationDate2 = try? url2.resourceValues(forKeys: [.creationDateKey]).creationDate
|
||
return creationDate1 ?? Date() < creationDate2 ?? Date()
|
||
}
|
||
for file in sortedFiles {
|
||
print("清理\(file.absoluteString)的缓存文件")
|
||
try? fileManager.removeItem(at: file)
|
||
folderSize -= UInt64((try? file.resourceValues(forKeys: [.fileSizeKey]).fileSize) ?? 0)
|
||
if folderSize <= 1024 * 1024 * 1024 * 2 {
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
///创建播放器缓存文件
|
||
func createCachePath() -> String? {
|
||
// 所有缓存文件都放在了沙盒Cache文件夹下MP_PlayerCache文件夹里
|
||
let cacheDirectory = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!
|
||
let path = cacheDirectory+"/"+"MP_PlayerCache"
|
||
//检索这个缓存文件夹是否存在
|
||
guard fileManager.fileExists(atPath: path) == false else {
|
||
//存在,直接返回这个文件夹
|
||
return path
|
||
}
|
||
//不存在
|
||
do {
|
||
//创建这个缓存文件夹
|
||
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
|
||
return path
|
||
} catch {
|
||
//创建失败
|
||
print("创建播放器缓存文件夹失败,失败原因:\(error)")
|
||
return nil
|
||
}
|
||
}
|
||
///创建一个临时文件
|
||
func createTempFile(_ videoID:String) -> Bool {
|
||
//设置临时文件
|
||
let path = tempPath(videoID)
|
||
if fileManager.fileExists(atPath: path) {
|
||
//存在这个临时文件,将其删除
|
||
do{
|
||
try fileManager.removeItem(atPath: path)
|
||
}catch {
|
||
print("删除临时文件失败,error: \(error)")
|
||
}
|
||
}
|
||
//生成临时文件
|
||
return fileManager.createFile(atPath: path, contents: nil, attributes: nil)
|
||
}
|
||
///往临时文件中写入缓存数据
|
||
func writeDataToAudioFileTempPathWithData(_ data:Data, videoId:String) {
|
||
guard let handle = FileHandle(forWritingAtPath: tempPath(videoId)) else {return}
|
||
if #available(iOS 13.4, *) {
|
||
do{
|
||
try handle.seekToEnd()
|
||
}catch {
|
||
print("Seek到末尾失败,失败原因:\(error)")
|
||
}
|
||
} else {
|
||
handle.seekToEndOfFile()
|
||
}
|
||
//在最后写入数据
|
||
if #available(iOS 13.4, *) {
|
||
do{
|
||
try handle.write(contentsOf: data)
|
||
}catch{
|
||
print("写入数据失败,失败原因:\(error)")
|
||
}
|
||
} else {
|
||
handle.write(data)
|
||
}
|
||
do {
|
||
if #available(iOS 13.4, *) {
|
||
try? handle.close() // 尝试关闭文件句柄
|
||
} else {
|
||
handle.closeFile()
|
||
}
|
||
}
|
||
}
|
||
///读取临时文件数据
|
||
func readTempFileDataWithOffset(_ offset:UInt64, length:Int, videoId:String) -> Data? {
|
||
var allhandle:FileHandle!
|
||
//检索当前数据是否下载完毕了
|
||
if isCompleteds[videoId] == true {
|
||
//临时文件已经销毁,当前下载内容已经移动到缓存文件中,从缓存文件中读取
|
||
let path = audioCachedPath() ?? ""
|
||
let audioName = "/"+videoId+".mp4"
|
||
let cachePath = path+audioName
|
||
guard let handle = FileHandle(forReadingAtPath: cachePath) else {return nil}
|
||
allhandle = handle
|
||
}else {
|
||
let path = tempPath(videoId)
|
||
guard let handle = FileHandle(forReadingAtPath: path) else {return nil}
|
||
allhandle = handle
|
||
}
|
||
if #available(iOS 13.0, *) {
|
||
do{
|
||
try allhandle.seek(toOffset: offset)
|
||
}catch{
|
||
print("Seek到指定位置失败,失败原因:\(error)")
|
||
try? allhandle.close()
|
||
}
|
||
} else {
|
||
allhandle.seek(toFileOffset: offset)
|
||
}
|
||
if #available(iOS 13.4, *) {
|
||
do{
|
||
let data = try allhandle.read(upToCount: length)
|
||
try allhandle.close()
|
||
return data
|
||
}catch{
|
||
print("读取到指定位置失败,失败原因:\(error)")
|
||
return nil
|
||
}
|
||
} else {
|
||
let data = allhandle.readData(ofLength: length)
|
||
allhandle.closeFile()
|
||
return data
|
||
}
|
||
}
|
||
|
||
/// 保存临时文件到缓存文件夹
|
||
/// - Parameter videoId: 使用videoId作为文件名
|
||
func moveAudioFileFromTempPathToCachePath(_ videoId: String) -> Bool {
|
||
guard let path = audioCachedPath() else {return false }
|
||
// 1. 判断文件夹是否存在
|
||
if !(fileManager.fileExists(atPath: path)) {
|
||
//不存在
|
||
do{
|
||
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true)
|
||
}catch {
|
||
print("创建缓存夹失败,失败原因:\(error)")
|
||
return false
|
||
}
|
||
}
|
||
let audioName = "/"+videoId+".mp4"
|
||
let cachePath = path+audioName
|
||
// 检查目标文件是否存在,如果存在,则删除
|
||
if fileManager.fileExists(atPath: cachePath) {
|
||
// do {
|
||
// print("缓存文件已经存在")
|
||
// try fileManager.removeItem(atPath: cachePath)
|
||
// } catch {
|
||
// print("删除现有文件失败,失败原因: \(error)")
|
||
// return false
|
||
// }
|
||
return true
|
||
}
|
||
// 2. 确保源文件存在
|
||
let tempFilePath = tempPath(videoId)
|
||
guard fileManager.fileExists(atPath: tempFilePath) else {
|
||
// print("源文件不存在于临时路径: \(tempFilePath)")
|
||
return false
|
||
}
|
||
// 3. 尝试移动文件
|
||
do {
|
||
try fileManager.moveItem(atPath: tempFilePath, toPath: cachePath)
|
||
// print("\(videoId)下载完毕,移动到:\(cachePath)")
|
||
//当前加载器加载完成
|
||
isCompleteds[videoId] = true
|
||
return true
|
||
} catch {
|
||
print("移动 \(videoId) 缓存文件失败,失败原因: \(error)")
|
||
// deleteKeyValueIfHaveArchivedWithVideoId(videoId) // 执行额外的清理操作
|
||
return false
|
||
}
|
||
}
|
||
///生成缓存文件
|
||
private func audioCachedPath() -> String? {
|
||
return (createCachePath() ?? "")
|
||
}
|
||
|
||
///创建一个固定的临时文件路径
|
||
private func tempPath(_ videoID:String) -> String {
|
||
return (NSTemporaryDirectory())+"/"+"\(videoID).mp4"
|
||
}
|
||
|
||
///根据VideoID获取缓存文件
|
||
func zhoujunfeng_getCachePath(_ videoId:String) -> String? {
|
||
//获取缓存文件夹
|
||
guard let path = MP_CacheAndArchiverManager.shared.createCachePath() else {
|
||
return nil
|
||
}
|
||
//根据videoId获取缓存文件
|
||
let audioName = "/"+videoId+".mp4"
|
||
//缓存文件地址
|
||
let audioPath = path+audioName
|
||
//检查文件是否存在
|
||
if fileManager.fileExists(atPath: audioPath) {
|
||
var attributes:[FileAttributeKey : Any]
|
||
//存在,检查是否缓存过期
|
||
do{
|
||
// 获取指定文件的属性
|
||
attributes = try fileManager.attributesOfItem(atPath: audioPath)
|
||
}catch {
|
||
//获取文件属性失败
|
||
print("获取\(videoId)的属性失败,失败原因:\(error.localizedDescription)")
|
||
return nil
|
||
}
|
||
// 从属性字典中提取创建时间
|
||
if let creationDate = attributes[.creationDate] as? Date {
|
||
// print("\(videoId)的创建时间是:",creationDate)
|
||
//检索文件是否过期
|
||
if Date().timeIntervalSince(creationDate) < zhoujunfeng_cacheOutTimes {
|
||
//没过期
|
||
return audioPath
|
||
}else {
|
||
//过期了
|
||
zhoujunfeng_deleteCachePath(audioPath, videoId: videoId)
|
||
return nil
|
||
}
|
||
} else {
|
||
print("\(videoId)无法获取文件的创建时间。")
|
||
return nil
|
||
}
|
||
}else {
|
||
//不存在
|
||
return nil
|
||
}
|
||
}
|
||
//删除缓存过期文件
|
||
func zhoujunfeng_deleteCachePath(_ path:String, videoId:String) {
|
||
do{
|
||
try fileManager.removeItem(atPath: path)
|
||
print("\(videoId)的缓存文件已经删除")
|
||
}catch{
|
||
print("\(videoId)的缓存文件删除失败,失败原因:\(error.localizedDescription)")
|
||
}
|
||
}
|
||
}
|