This is an automated email from the ASF dual-hosted git repository. dongjoon pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/spark-connect-swift.git
The following commit(s) were added to refs/heads/main by this push: new 69a4ac4 [SPARK-51825] Add `SparkFileUtils` 69a4ac4 is described below commit 69a4ac450cd12d482cd0056f36ac110617ea6df1 Author: Dongjoon Hyun <dongj...@apache.org> AuthorDate: Fri Apr 18 13:18:16 2025 +0900 [SPARK-51825] Add `SparkFileUtils` ### What changes were proposed in this pull request? This PR aims to add `SparkFileUtils` like - https://github.com/apache/spark/blob/master/common/utils/src/main/scala/org/apache/spark/util/SparkFileUtils.scala ### Why are the changes needed? This is required to add more features like `addArtifact`. ### Does this PR introduce _any_ user-facing change? No, this is a new addition. ### How was this patch tested? Pass the CIs. ### Was this patch authored or co-authored using generative AI tooling? No. Closes #64 from dongjoon-hyun/SPARK-51825. Authored-by: Dongjoon Hyun <dongj...@apache.org> Signed-off-by: Dongjoon Hyun <dongj...@apache.org> --- Sources/SparkConnect/SparkFileUtils.swift | 111 ++++++++++++++++++++++ Tests/SparkConnectTests/SparkFileUtilsTests.swift | 69 ++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/Sources/SparkConnect/SparkFileUtils.swift b/Sources/SparkConnect/SparkFileUtils.swift new file mode 100644 index 0000000..5cfd504 --- /dev/null +++ b/Sources/SparkConnect/SparkFileUtils.swift @@ -0,0 +1,111 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 + +/// Utility functions like `org.apache.spark.util.SparkFileUtils`. +public enum SparkFileUtils { + + /// Return a well-formed URL for the file described by a user input string. + /// + /// If the supplied path does not contain a scheme, or is a relative path, it will be + /// converted into an absolute path with a file:// scheme. + /// + /// - Parameter path: A path string. + /// - Returns: An URL + static func resolveURL(_ path: String) -> URL? { + if let url = URL(string: path) { + if url.scheme != nil { + return url.absoluteURL + } + + // make sure to handle if the path has a fragment (applies to yarn + // distributed cache) + if let fragment = url.fragment { + var components = URLComponents() + components.scheme = "file" + components.path = (path as NSString).expandingTildeInPath + components.fragment = fragment + return components.url?.absoluteURL + } + } + return URL(fileURLWithPath: (path as NSString).expandingTildeInPath).absoluteURL + } + + /// Lists files recursively. + /// - Parameter directory: <#directory description#> + /// - Returns: <#description#> + static func recursiveList(directory: URL) -> [URL] { + let fileManager = FileManager.default + var results: [URL] = [] + if let enumerator = fileManager.enumerator(at: directory, includingPropertiesForKeys: nil) { + for case let fileURL as URL in enumerator { + results.append(fileURL) + } + } + return results + } + + /// Create a directory given the abstract pathname + /// - Parameter url: An URL location. + /// - Returns: Return true if the directory is successfully created; otherwise, return false. + static func createDirectory(at url: URL) -> Bool { + let fileManager = FileManager.default + do { + try fileManager.createDirectory(at: url, withIntermediateDirectories: true) + var isDir: ObjCBool = false + let exists = fileManager.fileExists(atPath: url.path, isDirectory: &isDir) + return exists && isDir.boolValue + } catch { + print("Failed to create directory: \(url.path), error: \(error)") + return false + } + } + + /// Create a temporary directory inside the given parent directory. + /// - Parameters: + /// - root: A parent directory. + /// - namePrefix: A prefix for a new directory name. + /// - Returns: An URL for the created directory + static func createDirectory(root: String, namePrefix: String = "spark") -> URL { + let tempDir = URL(fileURLWithPath: root).appendingPathComponent( + "\(namePrefix)-\(UUID().uuidString)") + _ = createDirectory(at: tempDir) + return tempDir + } + + /// Create a new temporary directory prefixed with `spark` inside ``NSTemporaryDirectory``. + /// - Returns: An URL for the created directory + static func createTempDir() -> URL { + let dir = createDirectory(root: NSTemporaryDirectory(), namePrefix: "spark") + + return dir + } + + /// Delete a file or directory and its contents recursively. + /// Throws an exception if deletion is unsuccessful. + /// - Parameter url: An URL location. + static func deleteRecursively(_ url: URL) throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: url.path) { + try fileManager.removeItem(at: url) + } else { + throw SparkConnectError.InvalidArgumentException + } + } +} diff --git a/Tests/SparkConnectTests/SparkFileUtilsTests.swift b/Tests/SparkConnectTests/SparkFileUtilsTests.swift new file mode 100644 index 0000000..642b6ee --- /dev/null +++ b/Tests/SparkConnectTests/SparkFileUtilsTests.swift @@ -0,0 +1,69 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 +import Testing + +@testable import SparkConnect + +/// A test suite for `SparkFileUtils` +struct SparkFileUtilsTests { + let fm = FileManager.default + + @Test + func resolveURI() async throws { + let fileNameURL = SparkFileUtils.resolveURL("jar1") + #expect(fileNameURL!.absoluteString == "file://\(fm.currentDirectoryPath)/jar1") + + let homeUrl = SparkFileUtils.resolveURL("~/jar1") + #expect(homeUrl!.absoluteString == "\(fm.homeDirectoryForCurrentUser.absoluteString)jar1") + + let absolutePath = SparkFileUtils.resolveURL("file:/jar1") + #expect(absolutePath!.absoluteString == "file:/jar1") + + let hdfsPath = SparkFileUtils.resolveURL("hdfs:/root/spark.jar") + #expect(hdfsPath!.absoluteString == "hdfs:/root/spark.jar") + + let s3aPath = SparkFileUtils.resolveURL("s3a:/bucket/spark.jar") + #expect(s3aPath!.absoluteString == "s3a:/bucket/spark.jar") + } + + @Test + func directory() async throws { + // This tests three functions. + // createTempDir -> createDirectory(root: String, namePrefix: String = "spark") + // -> createDirectory(at: URL) + let dir = SparkFileUtils.createTempDir() + + var isDir: ObjCBool = false + let exists = fm.fileExists(atPath: dir.path(), isDirectory: &isDir) + #expect(exists && isDir.boolValue) + + #expect(SparkFileUtils.recursiveList(directory: dir).isEmpty) + + let emptyData = Data() + try emptyData.write(to: URL(string: dir.absoluteString + "/1")!) + + #expect(SparkFileUtils.recursiveList(directory: dir).count == 1) + + try SparkFileUtils.deleteRecursively(dir) + + #expect(!fm.fileExists(atPath: dir.path(), isDirectory: &isDir)) + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@spark.apache.org For additional commands, e-mail: commits-h...@spark.apache.org