iOS Build 23

This commit is contained in:
2025-10-11 18:54:24 -06:00
parent e7c46634da
commit 53fa74cc83
23 changed files with 1351 additions and 285 deletions

View File

@@ -852,4 +852,73 @@ class ImageManager {
print("ERROR: Failed to migrate from previous Application Support: \(error)")
}
}
func migrateImageNamesToDeterministic(dataManager: ClimbingDataManager) {
print("Starting migration of image names to deterministic format...")
var migrationCount = 0
var updatedProblems: [Problem] = []
for problem in dataManager.problems {
guard !problem.imagePaths.isEmpty else { continue }
var newImagePaths: [String] = []
var problemNeedsUpdate = false
for (index, imagePath) in problem.imagePaths.enumerated() {
let currentFilename = URL(fileURLWithPath: imagePath).lastPathComponent
if ImageNamingUtils.isValidImageFilename(currentFilename) {
newImagePaths.append(imagePath)
continue
}
let deterministicName = ImageNamingUtils.generateImageFilename(
problemId: problem.id.uuidString, imageIndex: index)
let oldPath = imagesDirectory.appendingPathComponent(currentFilename)
let newPath = imagesDirectory.appendingPathComponent(deterministicName)
if fileManager.fileExists(atPath: oldPath.path) {
do {
try fileManager.moveItem(at: oldPath, to: newPath)
let oldBackupPath = backupDirectory.appendingPathComponent(currentFilename)
let newBackupPath = backupDirectory.appendingPathComponent(
deterministicName)
if fileManager.fileExists(atPath: oldBackupPath.path) {
try? fileManager.moveItem(at: oldBackupPath, to: newBackupPath)
}
newImagePaths.append(deterministicName)
problemNeedsUpdate = true
migrationCount += 1
print("Migrated: \(currentFilename)\(deterministicName)")
} catch {
print("Failed to migrate \(currentFilename): \(error)")
newImagePaths.append(imagePath)
}
} else {
print("Warning: Image file not found: \(currentFilename)")
newImagePaths.append(imagePath)
}
}
if problemNeedsUpdate {
let updatedProblem = problem.updated(imagePaths: newImagePaths)
updatedProblems.append(updatedProblem)
}
}
for updatedProblem in updatedProblems {
dataManager.updateProblem(updatedProblem)
}
print(
"Migration completed: \(migrationCount) images renamed, \(updatedProblems.count) problems updated"
)
}
}

View File

@@ -11,21 +11,18 @@ class ImageNamingUtils {
private static let hashLength = 12
/// Generates a deterministic filename for a problem image
static func generateImageFilename(problemId: String, timestamp: String, imageIndex: Int)
-> String
{
let input = "\(problemId)_\(timestamp)_\(imageIndex)"
static func generateImageFilename(problemId: String, imageIndex: Int) -> String {
let input = "\(problemId)_\(imageIndex)"
let hash = createHash(from: input)
return "problem_\(hash)_\(imageIndex)\(imageExtension)"
}
/// Generates a deterministic filename using current timestamp
static func generateImageFilename(problemId: String, imageIndex: Int) -> String {
let timestamp = ISO8601DateFormatter().string(from: Date())
return generateImageFilename(
problemId: problemId, timestamp: timestamp, imageIndex: imageIndex)
/// Legacy method for backward compatibility
static func generateImageFilename(problemId: String, timestamp: String, imageIndex: Int)
-> String
{
return generateImageFilename(problemId: problemId, imageIndex: imageIndex)
}
/// Extracts problem ID from an image filename
@@ -64,9 +61,7 @@ class ImageNamingUtils {
return oldFilename
}
let timestamp = ISO8601DateFormatter().string(from: Date())
return generateImageFilename(
problemId: problemId, timestamp: timestamp, imageIndex: imageIndex)
return generateImageFilename(problemId: problemId, imageIndex: imageIndex)
}
/// Creates a deterministic hash from input string
@@ -84,8 +79,7 @@ class ImageNamingUtils {
var renameMap: [String: String] = [:]
for (index, oldFilename) in existingFilenames.enumerated() {
let newFilename = migrateFilename(
oldFilename: oldFilename, problemId: problemId, imageIndex: index)
let newFilename = generateImageFilename(problemId: problemId, imageIndex: index)
if newFilename != oldFilename {
renameMap[oldFilename] = newFilename
}
@@ -113,6 +107,40 @@ class ImageNamingUtils {
invalidImages: invalidImages
)
}
/// Generates the canonical filename that should be used for a problem image
static func getCanonicalImageFilename(problemId: String, imageIndex: Int) -> String {
return generateImageFilename(problemId: problemId, imageIndex: imageIndex)
}
/// Creates a mapping of existing server filenames to canonical filenames
static func createServerMigrationMap(
problemId: String,
serverImageFilenames: [String],
localImageCount: Int
) -> [String: String] {
var migrationMap: [String: String] = [:]
for imageIndex in 0..<localImageCount {
let canonicalName = getCanonicalImageFilename(
problemId: problemId, imageIndex: imageIndex)
if serverImageFilenames.contains(canonicalName) {
continue
}
for serverFilename in serverImageFilenames {
if isValidImageFilename(serverFilename)
&& !migrationMap.values.contains(serverFilename)
{
migrationMap[serverFilename] = canonicalName
break
}
}
}
return migrationMap
}
}
// Result of image filename validation