Mac 文件处理库,源码分析

JohnSundell/Files 这个文件处理库,封装的挺好

本文看一下其源代码

设计

磁盘上的分为两种,文件和文件夹
public enum LocationKind {
    case file
    case folder
}

文件和文件夹的共性是,他有一个路径,

操作他们,都要 FileManager

将文件和文件夹的共性,封装为 Storage

增删改查的具体实现,交给这个类

public final class Storage{
    fileprivate private(set) var path: String
    private let fileManager: FileManager
}

将文件和文件夹,抽象为 Location

通用的增删改查方法,放在 Location 里面。

Location 分为两种,使用枚举 LocationKind 区分类型

具体信息,记录在 Storage 里面

public protocol Location: Equatable, CustomStringConvertible {
    static var kind: LocationKind { get }
    var storage: Storage<Self> { get }
    init(storage: Storage<Self>)
}

将文件,封装为 File

文件的读写,放在这个结构体中

public struct File: Location {
    public let storage: Storage<File>

    public init(storage: Storage<File>) {
        self.storage = storage
    }
}
将文件夹,封装为 Folder

处理子文件夹,根目录等

public struct Folder: Location {
    public let storage: Storage<Folder>

    public init(storage: Storage<Folder>) {
        self.storage = storage
    }
}

功能: 怎样实现 ls 文件 list

1, 实例化文件夹

Location 协议有一个扩展方法,

拿文件夹路径去实例化

// 里面调用 `Folder ` 遵守 `Location` 协议,实现的 `init(storage: Storage<Self>)`

public extension Location {
	init(path: String) throws {
        try self.init(storage: Storage(
            path: path,
            fileManager: .default
        ))
    }
}

2, 获取文件目录

里面调用 storage 的创建子列表的方法

public extension Folder {
	var files: ChildSequence<File> {
        return storage.makeChildSequence()
    }
}

创建子列表的方法,实现

一环套一环,层层封装,

调用了一个定义在 Folder 里面的类 ChildSequence

private extension Storage where LocationType == Folder {
    func makeChildSequence<T: Location>() -> Folder.ChildSequence<T> {
        return Folder.ChildSequence(
            folder: Folder(storage: self),
            fileManager: fileManager,
            isRecursive: false,
            includeHidden: false
        )
    }
}

Folder 里面的类 ChildSequence

路径信息在 folder 里面,

有两个可选的属性,

isRecursive, 要不要递归,查看所有层次的文件夹,

includeHidden, 要不要查看隐藏的文件和文件夹

默认从上到下排序

struct ChildSequence<Child: Location>: Sequence {
        fileprivate let folder: Folder
        fileprivate let fileManager: FileManager
        fileprivate var isRecursive: Bool
        fileprivate var includeHidden: Bool

        public func makeIterator() -> ChildIterator<Child> {
            return ChildIterator(
                folder: folder,
                fileManager: fileManager,
                isRecursive: isRecursive,
                includeHidden: includeHidden,
                reverseTopLevelTraversal: false
            )
        }
    }

到底了,最后一层

有文件夹路径,

有操作方式,要不要看里面文件夹的信息,要不要看隐藏的

剩下的,就好办了

如果不用递归获取,就比较简单

loadItemNames() 方法中,获取这一层的文件名

有一个索引 index 记录,遍历到了,换下一个


struct ChildIterator<Child: Location>: IteratorProtocol {
        private let folder: Folder
        private let fileManager: FileManager
        private let isRecursive: Bool
        private let includeHidden: Bool
        private let reverseTopLevelTraversal: Bool
        private lazy var itemNames = loadItemNames()
        private var index = 0
        private var nestedIterators = [ChildIterator<Child>]()


        // 初始化,获取信息
        fileprivate init(folder: Folder,
                         fileManager: FileManager,
                         isRecursive: Bool,
                         includeHidden: Bool,
                         reverseTopLevelTraversal: Bool) {
            self.folder = folder
            self.fileManager = fileManager
            self.isRecursive = isRecursive
            self.includeHidden = includeHidden
            self.reverseTopLevelTraversal = reverseTopLevelTraversal
        }

        
        //  遵守 IteratorProtocol 协议,需要实现 next() 方法
        public mutating func next() -> Child? {
            guard index < itemNames.count else {
                guard var nested = nestedIterators.first else {
                    return nil
                }

                guard let child = nested.next() else {
                    nestedIterators.removeFirst()
                    return next()
                }

                nestedIterators[0] = nested
                return child
            }

            let name = itemNames[index]
            index += 1

            if !includeHidden {
                guard !name.hasPrefix(".") else { return next() }
            }

            let childPath = folder.path + name.removingPrefix("/")
            let childStorage = try? Storage<Child>(path: childPath, fileManager: fileManager)
            let child = childStorage.map(Child.init)

            if isRecursive {
                let childFolder = (child as? Folder) ?? (try? Folder(
                    storage: Storage(path: childPath, fileManager: fileManager)
                ))

                if let childFolder = childFolder {
                    let nested = ChildIterator(
                        folder: childFolder,
                        fileManager: fileManager,
                        isRecursive: true,
                        includeHidden: includeHidden,
                        reverseTopLevelTraversal: false
                    )

                    nestedIterators.append(nested)
                }
            }

            return child ?? next()
        }

        private func loadItemNames() -> [String] {
            let contents = try? fileManager.contentsOfDirectory(atPath: folder.path)
            let names = contents?.sorted() ?? []
            return reverseTopLevelTraversal ? names.reversed() : names
        }
    }

应用:文件夹内查重复文件

Mac 访达中,重复文件命名方式是,

扩展前,文件名后 + (1)

默认排序,重复文件在前面,原文件在后面

找到重复文件,使用一个指针保存 var current: File? = nil

再看后一个文件

let path = "\(NSHomeDirectory())/Downloads"
let folder = try Folder(path: path)
var current: File? = nil
for file in folder.files {
    if let c = current{
        if c.sheerName.contains(file.sheerName){
            print(c.name)
            print(file.name)
            // 查看文件大小
            do {
                let attr = try FileManager.default.attributesOfItem(atPath: c.path)
                let fileSize = attr[FileAttributeKey.size] as! UInt64
                print("fileSize: \(fileSize.sheerSize)")
                
            } catch { fatalError()}
            print("----\n")
            
        }
        current = nil
    }
    if file.name.contains("(1"){
        current = file
    }
}

github repo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值