Cleanup
This commit is contained in:
@@ -4,9 +4,6 @@
|
||||
import Foundation
|
||||
|
||||
// MARK: - Backup Format Specification v2.0
|
||||
// Platform-neutral backup format for cross-platform compatibility
|
||||
// This format ensures portability between iOS and Android while maintaining
|
||||
// platform-specific implementations
|
||||
|
||||
/// Root structure for OpenClimb backup data
|
||||
struct ClimbDataBackup: Codable {
|
||||
@@ -37,7 +34,7 @@ struct ClimbDataBackup: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform-neutral gym representation for backup/restore
|
||||
// Platform-neutral gym representation for backup/restore
|
||||
struct BackupGym: Codable {
|
||||
let id: String
|
||||
let name: String
|
||||
@@ -46,8 +43,8 @@ struct BackupGym: Codable {
|
||||
let difficultySystems: [DifficultySystem]
|
||||
let customDifficultyGrades: [String]
|
||||
let notes: String?
|
||||
let createdAt: String // ISO 8601 format
|
||||
let updatedAt: String // ISO 8601 format
|
||||
let createdAt: String
|
||||
let updatedAt: String
|
||||
|
||||
/// Initialize from native iOS Gym model
|
||||
init(from gym: Gym) {
|
||||
@@ -114,7 +111,7 @@ struct BackupGym: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform-neutral problem representation for backup/restore
|
||||
// Platform-neutral problem representation for backup/restore
|
||||
struct BackupProblem: Codable {
|
||||
let id: String
|
||||
let gymId: String
|
||||
@@ -128,8 +125,8 @@ struct BackupProblem: Codable {
|
||||
let isActive: Bool
|
||||
let dateSet: String? // ISO 8601 format
|
||||
let notes: String?
|
||||
let createdAt: String // ISO 8601 format
|
||||
let updatedAt: String // ISO 8601 format
|
||||
let createdAt: String
|
||||
let updatedAt: String
|
||||
|
||||
/// Initialize from native iOS Problem model
|
||||
init(from problem: Problem) {
|
||||
@@ -239,7 +236,7 @@ struct BackupProblem: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform-neutral climb session representation for backup/restore
|
||||
// Platform-neutral climb session representation for backup/restore
|
||||
struct BackupClimbSession: Codable {
|
||||
let id: String
|
||||
let gymId: String
|
||||
@@ -249,8 +246,8 @@ struct BackupClimbSession: Codable {
|
||||
let duration: Int64? // Duration in seconds
|
||||
let status: SessionStatus
|
||||
let notes: String?
|
||||
let createdAt: String // ISO 8601 format
|
||||
let updatedAt: String // ISO 8601 format
|
||||
let createdAt: String
|
||||
let updatedAt: String
|
||||
|
||||
/// Initialize from native iOS ClimbSession model
|
||||
init(from session: ClimbSession) {
|
||||
@@ -327,7 +324,7 @@ struct BackupClimbSession: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform-neutral attempt representation for backup/restore
|
||||
// Platform-neutral attempt representation for backup/restore
|
||||
struct BackupAttempt: Codable {
|
||||
let id: String
|
||||
let sessionId: String
|
||||
@@ -337,8 +334,8 @@ struct BackupAttempt: Codable {
|
||||
let notes: String?
|
||||
let duration: Int64? // Duration in seconds
|
||||
let restTime: Int64? // Rest time in seconds
|
||||
let timestamp: String // ISO 8601 format
|
||||
let createdAt: String // ISO 8601 format
|
||||
let timestamp: String
|
||||
let createdAt: String
|
||||
|
||||
/// Initialize from native iOS Attempt model
|
||||
init(from attempt: Attempt) {
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Manages the overall data state timestamp for sync purposes. This tracks when any data in the
|
||||
/// local database was last modified, independent of individual entity timestamps.
|
||||
/// Manages the overall data state timestamp for sync purposes
|
||||
class DataStateManager {
|
||||
|
||||
private let userDefaults = UserDefaults.standard
|
||||
@@ -14,7 +13,6 @@ class DataStateManager {
|
||||
static let initialized = "openclimb_data_state_initialized"
|
||||
}
|
||||
|
||||
/// Shared instance for app-wide use
|
||||
static let shared = DataStateManager()
|
||||
|
||||
private init() {
|
||||
|
||||
@@ -4,54 +4,36 @@
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
|
||||
/// Utility for creating consistent image filenames across iOS and Android platforms.
|
||||
/// Uses deterministic naming based on problem ID and timestamp to ensure sync compatibility.
|
||||
/// Utility for creating consistent image filenames across platforms
|
||||
class ImageNamingUtils {
|
||||
|
||||
private static let imageExtension = ".jpg"
|
||||
private static let hashLength = 12 // First 12 chars of SHA-256
|
||||
private static let hashLength = 12
|
||||
|
||||
/// Generates a deterministic filename for a problem image.
|
||||
/// Format: "problem_{hash}_{index}.jpg"
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - problemId: The ID of the problem this image belongs to
|
||||
/// - timestamp: ISO8601 timestamp when the image was created
|
||||
/// - imageIndex: The index of this image for the problem (0, 1, 2, etc.)
|
||||
/// - Returns: A consistent filename that will be the same across platforms
|
||||
/// Generates a deterministic filename for a problem image
|
||||
static func generateImageFilename(problemId: String, timestamp: String, imageIndex: Int)
|
||||
-> String
|
||||
{
|
||||
// Create a deterministic hash from problemId + timestamp + index
|
||||
|
||||
let input = "\(problemId)_\(timestamp)_\(imageIndex)"
|
||||
let hash = createHash(from: input)
|
||||
|
||||
return "problem_\(hash)_\(imageIndex)\(imageExtension)"
|
||||
}
|
||||
|
||||
/// Generates a deterministic filename for a problem image using current timestamp.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - problemId: The ID of the problem this image belongs to
|
||||
/// - imageIndex: The index of this image for the problem (0, 1, 2, etc.)
|
||||
/// - Returns: A consistent filename
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// Extracts problem ID from an image filename created by this utility.
|
||||
/// Returns nil if the filename doesn't match our naming convention.
|
||||
///
|
||||
/// - Parameter filename: The image filename
|
||||
/// - Returns: The hash identifier or nil if not a valid filename
|
||||
/// Extracts problem ID from an image filename
|
||||
static func extractProblemIdFromFilename(_ filename: String) -> String? {
|
||||
guard filename.hasPrefix("problem_") && filename.hasSuffix(imageExtension) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format: problem_{hash}_{index}.jpg
|
||||
let nameWithoutExtension = String(filename.dropLast(imageExtension.count))
|
||||
let parts = nameWithoutExtension.components(separatedBy: "_")
|
||||
|
||||
@@ -59,14 +41,10 @@ class ImageNamingUtils {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the hash as identifier
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
/// Validates if a filename follows our naming convention.
|
||||
///
|
||||
/// - Parameter filename: The filename to validate
|
||||
/// - Returns: true if it matches our convention, false otherwise
|
||||
/// Validates if a filename follows our naming convention
|
||||
static func isValidImageFilename(_ filename: String) -> Bool {
|
||||
guard filename.hasPrefix("problem_") && filename.hasSuffix(imageExtension) else {
|
||||
return false
|
||||
@@ -79,32 +57,19 @@ class ImageNamingUtils {
|
||||
&& Int(parts[2]) != nil
|
||||
}
|
||||
|
||||
/// Migrates an existing UUID-based filename to our naming convention.
|
||||
/// This is used during sync to rename downloaded images.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - oldFilename: The existing filename (UUID-based)
|
||||
/// - problemId: The problem ID this image belongs to
|
||||
/// - imageIndex: The index of this image
|
||||
/// - Returns: The new filename following our convention
|
||||
/// Migrates an existing filename to our naming convention
|
||||
static func migrateFilename(oldFilename: String, problemId: String, imageIndex: Int) -> String {
|
||||
// If it's already using our convention, keep it
|
||||
|
||||
if isValidImageFilename(oldFilename) {
|
||||
return oldFilename
|
||||
}
|
||||
|
||||
// Generate new deterministic name
|
||||
// Use current timestamp to maintain some consistency
|
||||
let timestamp = ISO8601DateFormatter().string(from: Date())
|
||||
return generateImageFilename(
|
||||
problemId: problemId, timestamp: timestamp, imageIndex: imageIndex)
|
||||
}
|
||||
|
||||
/// Creates a deterministic hash from input string.
|
||||
/// Uses SHA-256 and takes first 12 characters for filename safety.
|
||||
///
|
||||
/// - Parameter input: The input string to hash
|
||||
/// - Returns: First 12 characters of SHA-256 hash in lowercase
|
||||
/// Creates a deterministic hash from input string
|
||||
private static func createHash(from input: String) -> String {
|
||||
let inputData = Data(input.utf8)
|
||||
let hashed = SHA256.hash(data: inputData)
|
||||
@@ -112,13 +77,7 @@ class ImageNamingUtils {
|
||||
return String(hashString.prefix(hashLength))
|
||||
}
|
||||
|
||||
/// Batch renames images for a problem to use our naming convention.
|
||||
/// Returns a mapping of old filename -> new filename.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - problemId: The problem ID
|
||||
/// - existingFilenames: List of current image filenames for this problem
|
||||
/// - Returns: Dictionary mapping old filename to new filename
|
||||
/// Batch renames images for a problem to use our naming convention
|
||||
static func batchRenameForProblem(problemId: String, existingFilenames: [String]) -> [String:
|
||||
String]
|
||||
{
|
||||
@@ -135,10 +94,7 @@ class ImageNamingUtils {
|
||||
return renameMap
|
||||
}
|
||||
|
||||
/// Validates that a collection of filenames follow our naming convention.
|
||||
///
|
||||
/// - Parameter filenames: Array of filenames to validate
|
||||
/// - Returns: Dictionary with validation results
|
||||
/// Validates that a collection of filenames follow our naming convention
|
||||
static func validateFilenames(_ filenames: [String]) -> ImageValidationResult {
|
||||
var validImages: [String] = []
|
||||
var invalidImages: [String] = []
|
||||
@@ -159,7 +115,7 @@ class ImageNamingUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of image filename validation
|
||||
// Result of image filename validation
|
||||
struct ImageValidationResult {
|
||||
let totalImages: Int
|
||||
let validImages: [String]
|
||||
|
||||
Reference in New Issue
Block a user