Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bfd6eb0
implement File versioning UI + logic (wip)
jullianm Nov 28, 2025
573f74d
update cells SDK version, implement download and restore and asset pr…
jullianm Dec 2, 2025
87fe2b0
adjust asset caching system mechanism for asset version, delete local…
jullianm Dec 3, 2025
e12e01f
add UTs, code doc, lint and format
jullianm Dec 3, 2025
2dbada0
use ByteCountFormatter, update snapshots
jullianm Dec 3, 2025
fc029e8
lint and format
jullianm Dec 3, 2025
9f79b58
Merge branch 'develop' into feat/files-versioning
jullianm Dec 3, 2025
eb63eea
finish merge
jullianm Dec 3, 2025
d4d5823
lint and format
jullianm Dec 3, 2025
7f82e13
lint and format
jullianm Dec 3, 2025
b317af3
Merge branch 'develop' into feat/files-versioning
jullianm Dec 8, 2025
d6afb0f
fix polling timer value, don't allow version history action on files …
jullianm Dec 8, 2025
8d9256a
revert changes related to downloading/opening a node version asset
jullianm Dec 11, 2025
eadb684
Merge branch 'develop' into feat/files-versioning
jullianm Dec 11, 2025
4780b42
lint and format
jullianm Dec 11, 2025
57c5809
add missing @Test prefix
jullianm Dec 11, 2025
a223d57
fix PR comments
jullianm Dec 15, 2025
2ff5a5a
Merge branch 'develop' into feat/files-versioning
jullianm Dec 15, 2025
f0dd813
fix PR comments
jullianm Dec 15, 2025
daacd84
Merge branch 'develop' into feat/files-versioning
jullianm Dec 15, 2025
06854cb
attempt to fix CI snapshots failure
jullianm Dec 15, 2025
27130b0
attempt to fix CI snapshots issue
jullianm Dec 15, 2025
47dde9c
Merge branch 'develop' into feat/files-versioning
jullianm Dec 15, 2025
63ad731
Merge branch 'develop' into feat/files-versioning
jullianm Dec 15, 2025
452ebf2
inject date formatting context
jullianm Dec 15, 2025
78ddd1c
clean up: use injected calendar value
jullianm Dec 15, 2025
0af26ac
Merge branch 'develop' into feat/files-versioning
jullianm Dec 15, 2025
0f37d33
comment out failing snapshot tests, add todo
jullianm Dec 15, 2025
9beec30
add locator value for accessibility identifier
jullianm Dec 16, 2025
b02805b
Merge branch 'develop' into feat/files-versioning
jullianm Dec 16, 2025
7d1a9e3
Merge branch 'develop' into feat/files-versioning
jullianm Dec 16, 2025
eeafed9
Merge branch 'develop' into feat/files-versioning
jullianm Dec 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public extension TimeInterval {
static let fiveMinutes = 5 * oneMinute
static let oneMinute = 60 * oneSecond
static let tenSeconds = 10 * oneSecond
static let thirtySeconds = 30 * oneSecond
static let oneSecond = TimeInterval(1)

/// Number of seconds for a whole year (accounting for leap years) from now.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//

public import Foundation
public import UIKit

Check warning on line 20 in WireMessaging/Sources/WireMessagingAssembly/WireMessagingFactory.swift

View workflow job for this annotation

GitHub Actions / Test Results

Public import of 'SwiftUI' was not used in public declarations or inlinable code

Public import of 'SwiftUI' was not used in public declarations or inlinable code
public import SwiftUI
public import WireData
public import WireFoundation
Expand Down Expand Up @@ -202,6 +202,12 @@
updateTags: WireCellsUpdateTagsUseCase(nodesAPI: nodesAPI),
getTagSuggestions: WireCellsGetTagSuggestionsUseCase(nodesAPI: nodesAPI),
createFolder: WireCellsCreateFolderUseCase(nodesRepository: nodesAPI),
fetchNodeVersions: WireCellsFetchNodeVersionsUseCase(repository: nodesAPI),
restoreNodeVersion: WireCellsRestoreNodeVersionUseCase(
repository: nodesAPI,
localAssetsRepository: localAssetRepository,
nodeCache: nodeCache
),
getEditingURL: WireCellsGetEditingURLUseCase(editingURLRepository: nodesAPI),
getAssetUseCase: WireCellsGetAssetUseCase(
localAssetRepository: localAssetRepository,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// Wire
// Copyright (C) 2025 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import CellsSDK
import WireMessagingDomain
package import Foundation

package struct WireCellsNodeVersionsNetworkModel: Equatable, Hashable, Sendable {
package let versions: [Version]

package struct Version: Equatable, Hashable, Sendable {
package let contentUrl: URL?
package let contentHash: String?
package let description: String?
package let isDraft: Bool
package let eTag: String?
package let isHead: Bool?
package let mTime: UInt64?
package let ownerName: String?
package let ownerUuid: String?
package let size: UInt64?
package let versionId: UUID
package let downloadUrl: URL?
}
}

extension WireCellsNodeVersionsNetworkModel {
func toDomainModel() -> [WireCellsNodeVersion] {
versions.map {
WireCellsNodeVersion(
id: $0.versionId,
ownerName: $0.ownerName,
modified: $0.mTime.map { Date(timeIntervalSince1970: Double($0)) },
eTag: $0.eTag,
size: $0.size,
downloadUrl: $0.downloadUrl
)
}
}
}

package extension RestVersionCollection {
func toDTO() -> WireCellsNodeVersionsNetworkModel? {
guard let versions else { return nil }

let dtoVersions = versions.compactMap { value -> WireCellsNodeVersionsNetworkModel.Version? in
guard let id = UUID(uuidString: value.versionId) else { return nil }

return WireCellsNodeVersionsNetworkModel.Version(
contentUrl: value.preSignedGET?.url.flatMap(URL.init(string:)),
contentHash: value.contentHash,
description: value.description,
isDraft: value.draft ?? false,
eTag: value.eTag,
isHead: value.isHead,
mTime: value.mTime.flatMap(UInt64.init),
ownerName: value.ownerName,
ownerUuid: value.ownerUuid,
size: value.size.flatMap(UInt64.init),
versionId: id,
downloadUrl: value.preSignedGET?.url.flatMap(URL.init(string:))
)
}

return WireCellsNodeVersionsNetworkModel(
versions: dtoVersions
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,20 @@
try await restAPI.deleteVersion(uuid: nodeID, versionID: versionID)
}

package func getVersions(nodeID: UUID) async throws -> [WireCellsNodeVersion] {
let versionsDTO = try await restAPI.getVersions(uuid: nodeID)
return versionsDTO.toDomainModel()
}

package func restoreVersion(nodeID: UUID, versionID: UUID) async throws {
try await restAPI.restoreVersion(uuid: nodeID, versionID: versionID)
}

package func downloadFile(
out: URL,
cellPath: String,
onProgressUpdate: @escaping @Sendable (UInt64) -> Void
) async throws {

Check warning on line 118 in WireMessaging/Sources/WireMessagingData/WireCells/NodesAPI/NodesAPI.swift

View workflow job for this annotation

GitHub Actions / Test Results

Value 'stream' was defined but never used; consider replacing with boolean test

Value 'stream' was defined but never used; consider replacing with boolean test
guard let stream = OutputStream(url: out, append: true) else {
throw NodesAPIError.failedToCreateWriteStream
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import WireMessagingDomain

enum WireCellsNodesAPIError: Error {
case failedToDecodeNode
case failedToDecodeNodeVersions
case missingData(String)
}

Expand Down Expand Up @@ -195,6 +196,39 @@ final class RestAPI: Sendable {
)
}

func getVersions(uuid: UUID) async throws -> WireCellsNodeVersionsNetworkModel {
let query = RestNodeVersionsFilter(
filterBy: .versionsAll,
flags: [.withPreSignedURLs],
limit: nil,
offset: nil,
sortDirDesc: true,
sortField: nil
)

let response = try await NodeServiceAPI.nodeVersions(
uuid: uuid.transportString(),
query: query,
apiConfiguration: makeConfiguration()
)

guard let dto = response.toDTO() else {
throw WireCellsNodesAPIError.failedToDecodeNodeVersions
}

return dto
}

func restoreVersion(uuid: UUID, versionID: UUID) async throws {
let parameters = RestPromoteParameters(publish: false)
_ = try await NodeServiceAPI.promoteVersion(
uuid: uuid.transportString(),
versionId: versionID.transportString(),
parameters: parameters,
apiConfiguration: makeConfiguration()
)
}

/// Creates a new folder at the specified path.
///
/// - Parameters:
Expand Down Expand Up @@ -387,7 +421,7 @@ private extension WireCellsGetNodesRequest {

var lookupRequest: RestLookupRequest {
var request = RestLookupRequest(
flags: [.withPreSignedURLs],
flags: [.withPreSignedURLs, .withEditorURLs],
limit: "\(limit)",
offset: "\(offset)",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ package final class WireCellsLocalAssetRepository: WireCellsLocalAssetRepository
/// metadata from the server, updates local metadata if it has changed and deletes any cached file if it's
/// `eTag` has changed.
@MainActor
package func refreshAssetMetadata(
nodeID: UUID
) async throws -> (node: WireCellsNode, asset: WireCellsLocalAsset) {
package func refreshAssetMetadata(nodeID: UUID) async throws -> (node: WireCellsNode, asset: WireCellsLocalAsset) {
try await _refreshAssetMetadata(nodeID: nodeID)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Wire
// Copyright (C) 2025 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

public import Foundation

public struct WireCellsNodeVersion: Equatable, Identifiable, Sendable {

public init(
id: UUID,
ownerName: String?,
modified: Date?,
eTag: String?,
size: UInt64?,
downloadUrl: URL?
) {
self.id = id
self.ownerName = ownerName
self.modified = modified
self.eTag = eTag
self.size = size
self.downloadUrl = downloadUrl
}

public let id: UUID
public let ownerName: String?
public let modified: Date?
public let size: UInt64?
public let eTag: String?
public let downloadUrl: URL?
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ package protocol WireCellsNodesRepositoryProtocol: Sendable {
/// - Returns: Whether a file already exists at this path and the next available path if any.
func preCheck(nodePath: String, findAvailablePath: Bool) async throws -> WireCellsPreCheckResult

/// Retrieves all available versions for a given node.
///
/// - Parameter nodeID: The unique identifier of the node whose versions should be fetched.
/// - Returns: An array of `WireCellsNodeVersion` objects representing the node’s versions.
func getVersions(nodeID: UUID) async throws -> [WireCellsNodeVersion]

/// Restores a previous version of a node.
///
/// - Parameters:
/// - nodeID: The unique identifier of the file node to restore.
/// - versionID: The unique identifier of the version to restore.
func restoreVersion(nodeID: UUID, versionID: UUID) async throws

}

package struct WireCellsGetNodesRequest: Equatable, Sendable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@ package protocol NodesAPIProtocol: Sendable {
func updateTags(nodeID: UUID, tags: [String]) async throws

func getAllTags() async throws -> [String]

func getVersions(nodeID: UUID) async throws -> [WireCellsNodeVersion]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Wire
// Copyright (C) 2025 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

public import Foundation

public protocol WireCellsFetchNodeVersionsUseCaseProtocol: Sendable {
func invoke(nodeID: UUID) async throws -> [WireCellsNodeVersion]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Wire
// Copyright (C) 2025 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

public import Foundation

public protocol WireCellsRestoreNodeVersionUseCaseProtocol: Sendable {
func invoke(nodeID: UUID, versionID: UUID) async throws
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// Wire
// Copyright (C) 2025 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

package import Foundation
import WireLogging

package struct WireCellsFetchNodeVersionsUseCase: WireCellsFetchNodeVersionsUseCaseProtocol {

enum Failure: Error {
case unableToRetrieveNodeVersions
}

private let repository: any WireCellsNodesRepositoryProtocol

package init(
repository: any WireCellsNodesRepositoryProtocol
) {
self.repository = repository
}

package func invoke(nodeID: UUID) async throws -> [WireCellsNodeVersion] {
do {
return try await repository.getVersions(nodeID: nodeID)
} catch {
WireLogger.wireCells.error("Unable to retrieve node versions: \(error)")
throw Failure.unableToRetrieveNodeVersions
}
}

}
Loading
Loading