Sources/MockoloFramework/Utils/FileScanner.swift (113 lines of code) (raw):
//
// Copyright (c) 2018. Uber Technologies
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
public var scanConcurrencyLimit: Int? = nil
func semaphore(_ numThreads: Int?) -> DispatchSemaphore? {
let limit = concurrencyLimit(numThreads)
var sema: DispatchSemaphore?
if limit > 1 {
sema = DispatchSemaphore(value: limit)
}
return sema
}
func queue(_ numThreads: Int?) -> DispatchQueue? {
var q: DispatchQueue?
if concurrencyLimit(numThreads) > 1 {
q = DispatchQueue(label: "mockolo-queue", qos: DispatchQoS.userInteractive, attributes: DispatchQueue.Attributes.concurrent)
}
return q
}
func concurrencyLimit(_ numThreads: Int?) -> Int {
var limit = 1
if let n = numThreads {
limit = n
} else if let n = scanConcurrencyLimit {
limit = n
}
return limit
}
public func scan(_ paths: [String],
isDirectory: Bool,
numThreads: Int? = nil,
block: @escaping (_ path: String, _ lock: NSLock?) -> ()) {
if isDirectory {
scan(dirs: paths, block: block)
} else {
scan(paths, block: block)
}
}
public func scan(dirs: [String],
numThreads: Int? = nil,
block: @escaping (_ path: String, _ lock: NSLock?) -> ()) {
if let queue = queue(numThreads) {
let sema = semaphore(numThreads)
let lock = NSLock()
scanDirs(dirs) { filePath in
_ = sema?.wait(timeout: DispatchTime.distantFuture)
queue.async {
block(filePath, lock)
sema?.signal()
}
}
// Wait for queue to drain
queue.sync(flags: .barrier) {}
} else {
scanDirs(dirs) { filePath in
block(filePath, nil)
}
}
}
public func scan<T>(_ elements: [T],
numThreads: Int? = nil,
block: @escaping (T, NSLock?) -> ()) {
if let queue = queue(numThreads) {
let sema = semaphore(numThreads)
let lock = NSLock()
for element in elements {
_ = sema?.wait(timeout: DispatchTime.distantFuture)
queue.async {
block(element, lock)
sema?.signal()
}
}
queue.sync(flags: .barrier) { }
} else {
for element in elements {
block(element, nil)
}
}
}
public func scan<T, U>(_ elements: [T: U],
numThreads: Int? = nil,
block: @escaping (T, U, NSLock?) -> ()) {
if let queue = queue(numThreads) {
let sema = semaphore(numThreads)
let lock = NSLock()
for element in elements {
_ = sema?.wait(timeout: DispatchTime.distantFuture)
queue.async {
block(element.key, element.value, lock)
sema?.signal()
}
}
queue.sync(flags: .barrier) { }
} else {
for element in elements {
block(element.key, element.value, nil)
}
}
}
public func scanDirs(_ paths: [String], with callBack: (String) -> Void) {
for path in paths {
scanDir(path, with: callBack)
}
}
func scanDir(_ path: String, with callBack: (String) -> Void) {
let errorHandler = { (url: URL, error: Error) -> Bool in
fatalError("Failed to traverse \(url) with error \(error).")
}
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path, isDirectory: true), includingPropertiesForKeys: nil, options: [.skipsHiddenFiles], errorHandler: errorHandler) {
while let nextObjc = enumerator.nextObject() {
if let fileUrl = nextObjc as? URL {
callBack(fileUrl.path)
}
}
}
}