From 958381d30ac712b5a9509f58440c35109c56669b Mon Sep 17 00:00:00 2001 From: Brian Gomberg Date: Tue, 12 Mar 2024 13:46:11 -0700 Subject: [PATCH 1/5] Added saveWithOptions() --- AutomergeUniffi/automerge.swift | 20 +++++++++++++++++++ .../_CAutomergeUniffi/include/automergeFFI.h | 5 +++++ rust/src/automerge.udl | 1 + rust/src/doc.rs | 8 ++++++++ 4 files changed, 34 insertions(+) diff --git a/AutomergeUniffi/automerge.swift b/AutomergeUniffi/automerge.swift index b5b180f9..cadc4ace 100644 --- a/AutomergeUniffi/automerge.swift +++ b/AutomergeUniffi/automerge.swift @@ -506,6 +506,8 @@ public protocol DocProtocol: AnyObject { func save() -> [UInt8] + func saveWithOptions(msg: String, time: Int64) -> [UInt8] + func setActor(actor: ActorId) func splice(obj: ObjId, start: UInt64, delete: Int64, values: [ScalarValue]) throws @@ -521,6 +523,7 @@ public protocol DocProtocol: AnyObject { func values(obj: ObjId) throws -> [Value] func valuesAt(obj: ObjId, heads: [ChangeHash]) throws -> [Value] + } public class Doc: @@ -565,6 +568,7 @@ public class Doc: }) } + public func actorId() -> ActorId { try! FfiConverterTypeActorId.lift( try! @@ -1232,6 +1236,19 @@ public class Doc: ) } + public func saveWithOptions(msg: String, time: Int64) -> [UInt8] { + try! FfiConverterSequenceUInt8.lift( + try! + rustCall { + uniffi_uniffi_automerge_fn_method_doc_save_with_options( + self.uniffiClonePointer(), + FfiConverterString.lower(msg), + FfiConverterInt64.lower(time),$0 + ) + } + ) + } + public func setActor(actor: ActorId) { try! rustCall { @@ -2970,6 +2987,9 @@ private var initializationResult: InitializationResult { if uniffi_uniffi_automerge_checksum_method_doc_save() != 20308 { return InitializationResult.apiChecksumMismatch } + if uniffi_uniffi_automerge_checksum_method_doc_save_with_options() != 11279 { + return InitializationResult.apiChecksumMismatch + } if uniffi_uniffi_automerge_checksum_method_doc_set_actor() != 64337 { return InitializationResult.apiChecksumMismatch } diff --git a/Sources/_CAutomergeUniffi/include/automergeFFI.h b/Sources/_CAutomergeUniffi/include/automergeFFI.h index b4f76296..f5f78d9f 100644 --- a/Sources/_CAutomergeUniffi/include/automergeFFI.h +++ b/Sources/_CAutomergeUniffi/include/automergeFFI.h @@ -158,6 +158,8 @@ RustBuffer uniffi_uniffi_automerge_fn_method_doc_receive_sync_message_with_patch ); RustBuffer uniffi_uniffi_automerge_fn_method_doc_save(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status ); +RustBuffer uniffi_uniffi_automerge_fn_method_doc_save_with_options(void*_Nonnull ptr, RustBuffer msg, int64_t time, RustCallStatus *_Nonnull out_status +); void uniffi_uniffi_automerge_fn_method_doc_set_actor(void*_Nonnull ptr, RustBuffer actor, RustCallStatus *_Nonnull out_status ); void uniffi_uniffi_automerge_fn_method_doc_splice(void*_Nonnull ptr, RustBuffer obj, uint64_t start, int64_t delete, RustBuffer values, RustCallStatus *_Nonnull out_status @@ -450,6 +452,9 @@ uint16_t uniffi_uniffi_automerge_checksum_method_doc_receive_sync_message_with_p ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_save(void +); +uint16_t uniffi_uniffi_automerge_checksum_method_doc_save_with_options(void + ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_set_actor(void diff --git a/rust/src/automerge.udl b/rust/src/automerge.udl index 7d9703aa..36c9f417 100644 --- a/rust/src/automerge.udl +++ b/rust/src/automerge.udl @@ -223,6 +223,7 @@ interface Doc { sequence changes(); sequence save(); + sequence save_with_options(string msg, i64 time); [Throws=DocError] void merge(Doc other); diff --git a/rust/src/doc.rs b/rust/src/doc.rs index 6168074b..121bf5ae 100644 --- a/rust/src/doc.rs +++ b/rust/src/doc.rs @@ -500,6 +500,14 @@ impl Doc { doc.save() } + pub fn save_with_options(&self, message: String, time: i64) -> Vec { + let mut doc = self.0.write().unwrap(); + doc.commit_with(automerge::transaction::CommitOptions::default() + .with_message(message) + .with_time(time)); + doc.save() + } + pub fn load(bytes: Vec) -> Result { let ac = automerge::AutoCommit::load(bytes.as_slice())?; Ok(Doc(RwLock::new(ac))) From 612782977de4d192c0770646f82f582b7ac604f4 Mon Sep 17 00:00:00 2001 From: Brian Gomberg Date: Tue, 12 Mar 2024 15:18:55 -0700 Subject: [PATCH 2/5] Added change(hash:) --- AutomergeUniffi/automerge.swift | 156 +++++++++++++++++- Sources/Automerge/Change.swift | 22 +++ Sources/Automerge/Document.swift | 30 ++++ .../_CAutomergeUniffi/include/automergeFFI.h | 7 +- .../DocTests/AutomergeDocTests.swift | 30 ++++ rust/src/automerge.udl | 11 ++ rust/src/change.rs | 1 + rust/src/doc.rs | 8 +- rust/src/lib.rs | 2 + 9 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 Sources/Automerge/Change.swift diff --git a/AutomergeUniffi/automerge.swift b/AutomergeUniffi/automerge.swift index cadc4ace..29911eaa 100644 --- a/AutomergeUniffi/automerge.swift +++ b/AutomergeUniffi/automerge.swift @@ -418,6 +418,8 @@ public protocol DocProtocol: AnyObject { func changes() -> [ChangeHash] + func changeByHash(hash: ChangeHash) -> Change? + func cursor(obj: ObjId, position: UInt64) throws -> Cursor func cursorAt(obj: ObjId, position: UInt64, heads: [ChangeHash]) throws -> Cursor @@ -506,7 +508,7 @@ public protocol DocProtocol: AnyObject { func save() -> [UInt8] - func saveWithOptions(msg: String, time: Int64) -> [UInt8] + func saveWithOptions(msg: String, time: Int64) -> [UInt8] func setActor(actor: ActorId) @@ -568,7 +570,6 @@ public class Doc: }) } - public func actorId() -> ActorId { try! FfiConverterTypeActorId.lift( try! @@ -618,6 +619,20 @@ public class Doc: ) } + public func changeByHash(hash: ChangeHash) -> Change? { + try! FfiConverterOptionTypeChange.lift( + try! + rustCall { + uniffi_uniffi_automerge_fn_method_doc_change_by_hash( + self.uniffiClonePointer(), + + FfiConverterTypeChangeHash.lower(hash), + $0 + ) + } + ) + } + public func cursor(obj: ObjId, position: UInt64) throws -> Cursor { try FfiConverterTypeCursor.lift( rustCallWithError(FfiConverterTypeDocError.lift) { @@ -1236,7 +1251,7 @@ public class Doc: ) } - public func saveWithOptions(msg: String, time: Int64) -> [UInt8] { + public func saveWithOptions(msg: String, time: Int64) -> [UInt8] { try! FfiConverterSequenceUInt8.lift( try! rustCall { @@ -1512,6 +1527,96 @@ public func FfiConverterTypeSyncState_lower(_ value: SyncState) -> UnsafeMutable FfiConverterTypeSyncState.lower(value) } +public struct Change { + public var actorId: ActorId + public var message: String? + public var deps: [ChangeHash] + public var timestamp: Int64 + public var bytes: [UInt8] + public var hash: ChangeHash + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init( + actorId: ActorId, + message: String?, + deps: [ChangeHash], + timestamp: Int64, + bytes: [UInt8], + hash: ChangeHash + ) { + self.actorId = actorId + self.message = message + self.deps = deps + self.timestamp = timestamp + self.bytes = bytes + self.hash = hash + } +} + +extension Change: Equatable, Hashable { + public static func == (lhs: Change, rhs: Change) -> Bool { + if lhs.actorId != rhs.actorId { + return false + } + if lhs.message != rhs.message { + return false + } + if lhs.deps != rhs.deps { + return false + } + if lhs.timestamp != rhs.timestamp { + return false + } + if lhs.bytes != rhs.bytes { + return false + } + if lhs.hash != rhs.hash { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(actorId) + hasher.combine(message) + hasher.combine(deps) + hasher.combine(timestamp) + hasher.combine(bytes) + hasher.combine(hash) + } +} + +public struct FfiConverterTypeChange: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Change { + try Change( + actorId: FfiConverterTypeActorId.read(from: &buf), + message: FfiConverterOptionString.read(from: &buf), + deps: FfiConverterSequenceTypeChangeHash.read(from: &buf), + timestamp: FfiConverterInt64.read(from: &buf), + bytes: FfiConverterSequenceUInt8.read(from: &buf), + hash: FfiConverterTypeChangeHash.read(from: &buf) + ) + } + + public static func write(_ value: Change, into buf: inout [UInt8]) { + FfiConverterTypeActorId.write(value.actorId, into: &buf) + FfiConverterOptionString.write(value.message, into: &buf) + FfiConverterSequenceTypeChangeHash.write(value.deps, into: &buf) + FfiConverterInt64.write(value.timestamp, into: &buf) + FfiConverterSequenceUInt8.write(value.bytes, into: &buf) + FfiConverterTypeChangeHash.write(value.hash, into: &buf) + } +} + +public func FfiConverterTypeChange_lift(_ buf: RustBuffer) throws -> Change { + try FfiConverterTypeChange.lift(buf) +} + +public func FfiConverterTypeChange_lower(_ value: Change) -> RustBuffer { + FfiConverterTypeChange.lower(value) +} + public struct KeyValue { public var key: String public var value: Value @@ -2408,6 +2513,48 @@ public func FfiConverterTypeValue_lower(_ value: Value) -> RustBuffer { extension Value: Equatable, Hashable {} +private struct FfiConverterOptionString: FfiConverterRustBuffer { + typealias SwiftType = String? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterString.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterString.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +private struct FfiConverterOptionTypeChange: FfiConverterRustBuffer { + typealias SwiftType = Change? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeChange.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeChange.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + private struct FfiConverterOptionTypeValue: FfiConverterRustBuffer { typealias SwiftType = Value? @@ -2852,6 +2999,9 @@ private var initializationResult: InitializationResult { if uniffi_uniffi_automerge_checksum_method_doc_apply_encoded_changes_with_patches() != 63928 { return InitializationResult.apiChecksumMismatch } + if uniffi_uniffi_automerge_checksum_method_doc_change_by_hash() != 44577 { + return InitializationResult.apiChecksumMismatch + } if uniffi_uniffi_automerge_checksum_method_doc_changes() != 1878 { return InitializationResult.apiChecksumMismatch } diff --git a/Sources/Automerge/Change.swift b/Sources/Automerge/Change.swift new file mode 100644 index 00000000..2655ab8e --- /dev/null +++ b/Sources/Automerge/Change.swift @@ -0,0 +1,22 @@ +import struct AutomergeUniffi.Change +import Foundation + +typealias FfiChange = AutomergeUniffi.Change + +public struct Change: Equatable { + public let actorId: ActorId + public let message: String? + public let deps: [ChangeHash] + public let timestamp: Date + public let bytes: [UInt8] + public let hash: ChangeHash + + init(_ ffi: FfiChange) { + actorId = ActorId(bytes: ffi.actorId) + message = ffi.message + deps = ffi.deps.map(ChangeHash.init(bytes:)) + timestamp = Date(timeIntervalSince1970: TimeInterval(ffi.timestamp)) + bytes = ffi.bytes + hash = .init(bytes: ffi.hash) + } +} diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index a974c383..2affefae 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -783,6 +783,24 @@ public final class Document: @unchecked Sendable { } } + /// Encode the Automerge document in a compressed binary format. + /// + /// - Parameters: + /// - message: A message to attach to the auto-committed change (if any). + /// - timestamp: The timestamp to attach to the auto-committed change (if any). + /// - Returns: The data that represents all the changes within this document. + /// + /// The `saveWithOptions` function also compacts the memory footprint of an Automerge document and increments the + /// result of ``heads()``, which indicates a specific point in time for the history of the document. + public func saveWithOptions(message: String, timestamp: Date) -> Data { + sync { + self.doc.wrapErrors { + sendObjectWillChange() + return Data($0.saveWithOptions(msg: message, time: Int64(timestamp.timeIntervalSince1970))) + } + } + } + /// Update the sync state you provide and return a sync message to send to a peer. /// /// - Parameter state: The instance of ``SyncState`` that represents the peer you're syncing with. @@ -922,6 +940,18 @@ public final class Document: @unchecked Sendable { } } + /// Returns an list of changes that represent the causal sequence of changes to the document. + /// + /// - Returns: A``Change`` object for the given hash. + public func change(hash: ChangeHash) -> Change? { + sync { + guard let change = self.doc.wrapErrors(f: { $0.changeByHash(hash: hash.bytes) }) else { + return nil + } + return .init(change) + } + } + /// Get the path to an object within the document. /// /// - Parameter obj: The identifier of an array, dictionary or text object. diff --git a/Sources/_CAutomergeUniffi/include/automergeFFI.h b/Sources/_CAutomergeUniffi/include/automergeFFI.h index f5f78d9f..51bd52fe 100644 --- a/Sources/_CAutomergeUniffi/include/automergeFFI.h +++ b/Sources/_CAutomergeUniffi/include/automergeFFI.h @@ -68,6 +68,8 @@ void uniffi_uniffi_automerge_fn_method_doc_apply_encoded_changes(void*_Nonnull p ); RustBuffer uniffi_uniffi_automerge_fn_method_doc_apply_encoded_changes_with_patches(void*_Nonnull ptr, RustBuffer changes, RustCallStatus *_Nonnull out_status ); +RustBuffer uniffi_uniffi_automerge_fn_method_doc_change_by_hash(void*_Nonnull ptr, RustBuffer hash, RustCallStatus *_Nonnull out_status +); RustBuffer uniffi_uniffi_automerge_fn_method_doc_changes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_uniffi_automerge_fn_method_doc_cursor(void*_Nonnull ptr, RustBuffer obj, uint64_t position, RustCallStatus *_Nonnull out_status @@ -317,6 +319,9 @@ uint16_t uniffi_uniffi_automerge_checksum_method_doc_apply_encoded_changes(void ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_apply_encoded_changes_with_patches(void +); +uint16_t uniffi_uniffi_automerge_checksum_method_doc_change_by_hash(void + ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_changes(void @@ -454,7 +459,7 @@ uint16_t uniffi_uniffi_automerge_checksum_method_doc_save(void ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_save_with_options(void - + ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_set_actor(void diff --git a/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift b/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift index c1a36fe6..2403969f 100644 --- a/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift +++ b/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift @@ -239,4 +239,34 @@ final class AutomergeDocTests: XCTestCase { let text = try doc.text(obj: textId) XCTAssertEqual(text, "🇬🇧😀") } + + func testSaveWithOptions() throws { + struct ColorList: Codable { + var colors: [String] + } + + // Create the document + let doc = Document() + let encoder = AutomergeEncoder(doc: doc) + + // Make an initial change + var myColors = ColorList(colors: ["blue", "red"]) + try encoder.encode(myColors) + _ = doc.saveWithOptions(message: "Change 1", timestamp: Date(timeIntervalSince1970: 10)) + + // Make another change + myColors.colors.append("green") + try encoder.encode(myColors) + _ = doc.saveWithOptions(message: "Change 2", timestamp: Date(timeIntervalSince1970: 20)) + + let history = doc.getHistory() + XCTAssertEqual(history.count, 2) + + let changes = history.map({ doc.change(hash: $0) }) + XCTAssertEqual(changes.count, 2) + XCTAssertEqual(changes[0]?.message, "Change 1") + XCTAssertEqual(changes[0]?.timestamp, Date(timeIntervalSince1970: 10)) + XCTAssertEqual(changes[1]?.message, "Change 2") + XCTAssertEqual(changes[1]?.timestamp, Date(timeIntervalSince1970: 20)) + } } diff --git a/rust/src/automerge.udl b/rust/src/automerge.udl index 36c9f417..f43cc1c8 100644 --- a/rust/src/automerge.udl +++ b/rust/src/automerge.udl @@ -103,6 +103,15 @@ dictionary PathElement { ObjId obj; }; +dictionary Change { + ActorId actor_id; + string? message; + sequence deps; + i64 timestamp; + sequence bytes; + ChangeHash hash; +}; + dictionary Patch { sequence path; PatchAction action; @@ -222,6 +231,8 @@ interface Doc { sequence changes(); + Change? change_by_hash(ChangeHash hash); + sequence save(); sequence save_with_options(string msg, i64 time); diff --git a/rust/src/change.rs b/rust/src/change.rs index 982b87ca..4998c03b 100644 --- a/rust/src/change.rs +++ b/rust/src/change.rs @@ -36,6 +36,7 @@ pub enum DecodeChangeError { Internal(#[from] am::LoadChangeError), } +#[allow(dead_code)] pub fn decode_change(bytes: Vec) -> Result { am::Change::try_from(bytes.as_slice()) .map(Change::from) diff --git a/rust/src/doc.rs b/rust/src/doc.rs index 121bf5ae..2e4c4ae7 100644 --- a/rust/src/doc.rs +++ b/rust/src/doc.rs @@ -7,7 +7,7 @@ use automerge::{transaction::Transactable, ReadDoc}; use crate::actor_id::ActorId; use crate::mark::{ExpandMark, Mark}; use crate::patches::Patch; -use crate::{ChangeHash, Cursor, ObjId, ObjType, PathElement, ScalarValue, SyncState, Value}; +use crate::{Change, ChangeHash, Cursor, ObjId, ObjType, PathElement, ScalarValue, SyncState, Value}; #[derive(Debug, thiserror::Error)] pub enum DocError { @@ -581,6 +581,12 @@ impl Doc { changes.into_iter().map(|h| h.hash().into()).collect() } + pub fn change_by_hash(&self, hash: ChangeHash) -> Option { + let doc = self.0.write().unwrap(); + doc.get_change_by_hash(&am::ChangeHash::from(hash)) + .map(|m| Change::from(m.clone())) + } + pub fn path(&self, obj: ObjId) -> Result, DocError> { let doc = self.0.read().unwrap(); let obj = am::ObjId::from(obj); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6733f0e9..3484ae7c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -4,6 +4,8 @@ mod actor_id; use actor_id::ActorId; mod cursor; use cursor::Cursor; +mod change; +use change::Change; mod change_hash; use change_hash::ChangeHash; mod doc; From da24c6ae3f38d0b2cf101a977f0f4ea041205d82 Mon Sep 17 00:00:00 2001 From: Brian Gomberg Date: Wed, 13 Mar 2024 14:33:26 -0700 Subject: [PATCH 3/5] Addressed PR feedback --- AutomergeUniffi/automerge.swift | 35 ++++++----- Sources/Automerge/Change.swift | 6 +- Sources/Automerge/Document.swift | 29 ++++----- .../_CAutomergeUniffi/include/automergeFFI.h | 10 ++-- .../DocTests/AutomergeDocTests.swift | 60 +++++++++++++------ rust/src/automerge.udl | 3 +- rust/src/doc.rs | 14 +++-- 7 files changed, 90 insertions(+), 67 deletions(-) diff --git a/AutomergeUniffi/automerge.swift b/AutomergeUniffi/automerge.swift index 29911eaa..f9fdb479 100644 --- a/AutomergeUniffi/automerge.swift +++ b/AutomergeUniffi/automerge.swift @@ -420,6 +420,8 @@ public protocol DocProtocol: AnyObject { func changeByHash(hash: ChangeHash) -> Change? + func commitWith(msg: String?, time: Int64) + func cursor(obj: ObjId, position: UInt64) throws -> Cursor func cursorAt(obj: ObjId, position: UInt64, heads: [ChangeHash]) throws -> Cursor @@ -508,8 +510,6 @@ public protocol DocProtocol: AnyObject { func save() -> [UInt8] - func saveWithOptions(msg: String, time: Int64) -> [UInt8] - func setActor(actor: ActorId) func splice(obj: ObjId, start: UInt64, delete: Int64, values: [ScalarValue]) throws @@ -1238,6 +1238,18 @@ public class Doc: } ) } + public func commitWith(msg: String?, time: Int64) { + try! + rustCall { + uniffi_uniffi_automerge_fn_method_doc_commit_with( + self.uniffiClonePointer(), + + FfiConverterOptionString.lower(msg), + FfiConverterInt64.lower(time), + $0 + ) + } + } public func save() -> [UInt8] { try! FfiConverterSequenceUInt8.lift( @@ -1251,19 +1263,6 @@ public class Doc: ) } - public func saveWithOptions(msg: String, time: Int64) -> [UInt8] { - try! FfiConverterSequenceUInt8.lift( - try! - rustCall { - uniffi_uniffi_automerge_fn_method_doc_save_with_options( - self.uniffiClonePointer(), - FfiConverterString.lower(msg), - FfiConverterInt64.lower(time),$0 - ) - } - ) - } - public func setActor(actor: ActorId) { try! rustCall { @@ -3005,6 +3004,9 @@ private var initializationResult: InitializationResult { if uniffi_uniffi_automerge_checksum_method_doc_changes() != 1878 { return InitializationResult.apiChecksumMismatch } + if uniffi_uniffi_automerge_checksum_method_doc_commit_with() != 65319 { + return InitializationResult.apiChecksumMismatch + } if uniffi_uniffi_automerge_checksum_method_doc_cursor() != 18441 { return InitializationResult.apiChecksumMismatch } @@ -3137,9 +3139,6 @@ private var initializationResult: InitializationResult { if uniffi_uniffi_automerge_checksum_method_doc_save() != 20308 { return InitializationResult.apiChecksumMismatch } - if uniffi_uniffi_automerge_checksum_method_doc_save_with_options() != 11279 { - return InitializationResult.apiChecksumMismatch - } if uniffi_uniffi_automerge_checksum_method_doc_set_actor() != 64337 { return InitializationResult.apiChecksumMismatch } diff --git a/Sources/Automerge/Change.swift b/Sources/Automerge/Change.swift index 2655ab8e..9d93d291 100644 --- a/Sources/Automerge/Change.swift +++ b/Sources/Automerge/Change.swift @@ -8,7 +8,7 @@ public struct Change: Equatable { public let message: String? public let deps: [ChangeHash] public let timestamp: Date - public let bytes: [UInt8] + public let bytes: Data public let hash: ChangeHash init(_ ffi: FfiChange) { @@ -16,7 +16,7 @@ public struct Change: Equatable { message = ffi.message deps = ffi.deps.map(ChangeHash.init(bytes:)) timestamp = Date(timeIntervalSince1970: TimeInterval(ffi.timestamp)) - bytes = ffi.bytes - hash = .init(bytes: ffi.hash) + bytes = Data(ffi.bytes) + hash = ChangeHash(bytes: ffi.hash) } } diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 2affefae..6029cb78 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -768,35 +768,34 @@ public final class Document: @unchecked Sendable { } } - /// Encode the Automerge document in a compressed binary format. + /// Commit the auto-generated transaction with options. /// - /// - Returns: The data that represents all the changes within this document. + /// - Parameters: + /// - message: An optional message to attach to the auto-committed change (if any). + /// - timestamp: An optional timestamp to attach to the auto-committed change (if any). /// - /// The `save` function also compacts the memory footprint of an Automerge document and increments the result of - /// ``heads()``, which indicates a specific point in time for the history of the document. - public func save() -> Data { + /// The `commitWith` function also compacts the memory footprint of an Automerge document and increments the + /// result of ``heads()``, which indicates a specific point in time for the history of the document. + public func commitWith(message: String? = nil, timestamp: Date = Date()) { sync { self.doc.wrapErrors { sendObjectWillChange() - return Data($0.save()) + $0.commitWith(msg: message, time: Int64(timestamp.timeIntervalSince1970)) } } } /// Encode the Automerge document in a compressed binary format. /// - /// - Parameters: - /// - message: A message to attach to the auto-committed change (if any). - /// - timestamp: The timestamp to attach to the auto-committed change (if any). /// - Returns: The data that represents all the changes within this document. /// - /// The `saveWithOptions` function also compacts the memory footprint of an Automerge document and increments the - /// result of ``heads()``, which indicates a specific point in time for the history of the document. - public func saveWithOptions(message: String, timestamp: Date) -> Data { + /// The `save` function also compacts the memory footprint of an Automerge document and increments the result of + /// ``heads()``, which indicates a specific point in time for the history of the document. + public func save() -> Data { sync { self.doc.wrapErrors { sendObjectWillChange() - return Data($0.saveWithOptions(msg: message, time: Int64(timestamp.timeIntervalSince1970))) + return Data($0.save()) } } } @@ -940,9 +939,7 @@ public final class Document: @unchecked Sendable { } } - /// Returns an list of changes that represent the causal sequence of changes to the document. - /// - /// - Returns: A``Change`` object for the given hash. + /// Returns the contents of the change associated with the change hash you provide. public func change(hash: ChangeHash) -> Change? { sync { guard let change = self.doc.wrapErrors(f: { $0.changeByHash(hash: hash.bytes) }) else { diff --git a/Sources/_CAutomergeUniffi/include/automergeFFI.h b/Sources/_CAutomergeUniffi/include/automergeFFI.h index 51bd52fe..8839d382 100644 --- a/Sources/_CAutomergeUniffi/include/automergeFFI.h +++ b/Sources/_CAutomergeUniffi/include/automergeFFI.h @@ -72,6 +72,8 @@ RustBuffer uniffi_uniffi_automerge_fn_method_doc_change_by_hash(void*_Nonnull pt ); RustBuffer uniffi_uniffi_automerge_fn_method_doc_changes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status ); +void uniffi_uniffi_automerge_fn_method_doc_commit_with(void*_Nonnull ptr, RustBuffer msg, int64_t time, RustCallStatus *_Nonnull out_status +); RustBuffer uniffi_uniffi_automerge_fn_method_doc_cursor(void*_Nonnull ptr, RustBuffer obj, uint64_t position, RustCallStatus *_Nonnull out_status ); RustBuffer uniffi_uniffi_automerge_fn_method_doc_cursor_at(void*_Nonnull ptr, RustBuffer obj, uint64_t position, RustBuffer heads, RustCallStatus *_Nonnull out_status @@ -160,8 +162,6 @@ RustBuffer uniffi_uniffi_automerge_fn_method_doc_receive_sync_message_with_patch ); RustBuffer uniffi_uniffi_automerge_fn_method_doc_save(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status ); -RustBuffer uniffi_uniffi_automerge_fn_method_doc_save_with_options(void*_Nonnull ptr, RustBuffer msg, int64_t time, RustCallStatus *_Nonnull out_status -); void uniffi_uniffi_automerge_fn_method_doc_set_actor(void*_Nonnull ptr, RustBuffer actor, RustCallStatus *_Nonnull out_status ); void uniffi_uniffi_automerge_fn_method_doc_splice(void*_Nonnull ptr, RustBuffer obj, uint64_t start, int64_t delete, RustBuffer values, RustCallStatus *_Nonnull out_status @@ -325,6 +325,9 @@ uint16_t uniffi_uniffi_automerge_checksum_method_doc_change_by_hash(void ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_changes(void +); +uint16_t uniffi_uniffi_automerge_checksum_method_doc_commit_with(void + ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_cursor(void @@ -457,9 +460,6 @@ uint16_t uniffi_uniffi_automerge_checksum_method_doc_receive_sync_message_with_p ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_save(void -); -uint16_t uniffi_uniffi_automerge_checksum_method_doc_save_with_options(void - ); uint16_t uniffi_uniffi_automerge_checksum_method_doc_set_actor(void diff --git a/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift b/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift index 2403969f..b0696303 100644 --- a/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift +++ b/Tests/AutomergeTests/DocTests/AutomergeDocTests.swift @@ -240,33 +240,57 @@ final class AutomergeDocTests: XCTestCase { XCTAssertEqual(text, "🇬🇧😀") } - func testSaveWithOptions() throws { - struct ColorList: Codable { - var colors: [String] + func testCommitWith() throws { + struct Dog: Codable { + var name: String + var age: Int } // Create the document let doc = Document() let encoder = AutomergeEncoder(doc: doc) - // Make an initial change - var myColors = ColorList(colors: ["blue", "red"]) - try encoder.encode(myColors) - _ = doc.saveWithOptions(message: "Change 1", timestamp: Date(timeIntervalSince1970: 10)) - - // Make another change - myColors.colors.append("green") - try encoder.encode(myColors) - _ = doc.saveWithOptions(message: "Change 2", timestamp: Date(timeIntervalSince1970: 20)) + // Make an initial change with a message and timestamp + var myDog = Dog(name: "Fido", age: 1) + try encoder.encode(myDog) + doc.commitWith(message: "Change 1", timestamp: Date(timeIntervalSince1970: 10)) + + // Make another change with the default timestamp + myDog.age = 2 + try encoder.encode(myDog) + doc.commitWith(message: "Change 2") + let change2Time = Date().timeIntervalSince1970 + + // Make another change with no message + myDog.age = 3 + try encoder.encode(myDog) + doc.commitWith(message: nil, timestamp: Date(timeIntervalSince1970: 20)) + + // Make another change with no message and the default timestamp + myDog.age = 4 + try encoder.encode(myDog) + doc.commitWith() + let change4Time = Date().timeIntervalSince1970 + + // Make another change by just calling save() (meaning no commit options will be set) + myDog.age = 5 + try encoder.encode(myDog) + _ = doc.save() let history = doc.getHistory() - XCTAssertEqual(history.count, 2) + XCTAssertEqual(history.count, 5) let changes = history.map({ doc.change(hash: $0) }) - XCTAssertEqual(changes.count, 2) - XCTAssertEqual(changes[0]?.message, "Change 1") - XCTAssertEqual(changes[0]?.timestamp, Date(timeIntervalSince1970: 10)) - XCTAssertEqual(changes[1]?.message, "Change 2") - XCTAssertEqual(changes[1]?.timestamp, Date(timeIntervalSince1970: 20)) + XCTAssertEqual(changes.count, 5) + XCTAssertEqual(changes[0]!.message, "Change 1") + XCTAssertEqual(changes[0]!.timestamp, Date(timeIntervalSince1970: 10)) + XCTAssertEqual(changes[1]!.message, "Change 2") + XCTAssertEqual(changes[1]!.timestamp.timeIntervalSince1970, change2Time, accuracy: 3) + XCTAssertNil(changes[2]!.message) + XCTAssertEqual(changes[2]!.timestamp, Date(timeIntervalSince1970: 20)) + XCTAssertNil(changes[3]!.message) + XCTAssertEqual(changes[3]!.timestamp.timeIntervalSince1970, change4Time, accuracy: 3) + XCTAssertNil(changes[4]!.message) + XCTAssertEqual(changes[4]!.timestamp.timeIntervalSince1970, 0) } } diff --git a/rust/src/automerge.udl b/rust/src/automerge.udl index f43cc1c8..9f3d79b4 100644 --- a/rust/src/automerge.udl +++ b/rust/src/automerge.udl @@ -233,8 +233,9 @@ interface Doc { Change? change_by_hash(ChangeHash hash); + void commit_with(string? msg, i64 time); + sequence save(); - sequence save_with_options(string msg, i64 time); [Throws=DocError] void merge(Doc other); diff --git a/rust/src/doc.rs b/rust/src/doc.rs index 2e4c4ae7..3830ce81 100644 --- a/rust/src/doc.rs +++ b/rust/src/doc.rs @@ -495,16 +495,18 @@ impl Doc { }) } - pub fn save(&self) -> Vec { + pub fn commit_with(&self, message: Option, time: i64) { let mut doc = self.0.write().unwrap(); - doc.save() + let mut options = automerge::transaction::CommitOptions::default(); + options.set_time(time); + if let Some(message) = message { + options.set_message(message); + } + doc.commit_with(options); } - pub fn save_with_options(&self, message: String, time: i64) -> Vec { + pub fn save(&self) -> Vec { let mut doc = self.0.write().unwrap(); - doc.commit_with(automerge::transaction::CommitOptions::default() - .with_message(message) - .with_time(time)); doc.save() } From ddfe0e7b224735e83fed2035ddf8e955b9a0326a Mon Sep 17 00:00:00 2001 From: Brian Gomberg Date: Wed, 13 Mar 2024 15:26:53 -0700 Subject: [PATCH 4/5] Fix format --- rust/src/doc.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/src/doc.rs b/rust/src/doc.rs index 3830ce81..c7d081ba 100644 --- a/rust/src/doc.rs +++ b/rust/src/doc.rs @@ -7,7 +7,9 @@ use automerge::{transaction::Transactable, ReadDoc}; use crate::actor_id::ActorId; use crate::mark::{ExpandMark, Mark}; use crate::patches::Patch; -use crate::{Change, ChangeHash, Cursor, ObjId, ObjType, PathElement, ScalarValue, SyncState, Value}; +use crate::{ + Change, ChangeHash, Cursor, ObjId, ObjType, PathElement, ScalarValue, SyncState, Value +}; #[derive(Debug, thiserror::Error)] pub enum DocError { From d99cbb84fd147de2a6133983ab69123e741ff57e Mon Sep 17 00:00:00 2001 From: Brian Gomberg Date: Thu, 14 Mar 2024 13:02:17 -0700 Subject: [PATCH 5/5] Update comment --- Sources/Automerge/Document.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 6029cb78..a7520212 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -772,10 +772,7 @@ public final class Document: @unchecked Sendable { /// /// - Parameters: /// - message: An optional message to attach to the auto-committed change (if any). - /// - timestamp: An optional timestamp to attach to the auto-committed change (if any). - /// - /// The `commitWith` function also compacts the memory footprint of an Automerge document and increments the - /// result of ``heads()``, which indicates a specific point in time for the history of the document. + /// - timestamp: A timestamp to attach to the auto-committed change (if any), defaulting to Date(). public func commitWith(message: String? = nil, timestamp: Date = Date()) { sync { self.doc.wrapErrors {