-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: jld3103 <[email protected]>
- Loading branch information
1 parent
159b94d
commit dfd0724
Showing
17 changed files
with
1,113 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,4 +26,5 @@ rules: | |
- nextcloud_test | ||
- release | ||
- sort_box | ||
- synchronize | ||
- tool |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../assets/BSD-3-Clause.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# synchronize | ||
|
||
A simple generic implementation of https://unterwaditzer.net/2016/sync-algorithm.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include: package:neon_lints/dart.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import 'package:meta/meta.dart'; | ||
import 'package:synchronize/src/object.dart'; | ||
|
||
/// Action to be executed in the sync process. | ||
@internal | ||
@immutable | ||
sealed class SyncAction<T> { | ||
/// Creates a new action. | ||
const SyncAction(this.object); | ||
|
||
/// The object that is part of the action. | ||
final SyncObject<T> object; | ||
|
||
@override | ||
String toString() => 'SyncAction<$T>(object: $object)'; | ||
} | ||
|
||
/// Action to delete on object from A. | ||
@internal | ||
@immutable | ||
interface class SyncActionDeleteFromA<T1, T2> extends SyncAction<T1> { | ||
/// Creates a new action to delete an object from A. | ||
const SyncActionDeleteFromA(super.object); | ||
|
||
@override | ||
String toString() => 'SyncActionDeleteFromA<$T1, $T2>(object: $object)'; | ||
} | ||
|
||
/// Action to delete an object from B. | ||
@internal | ||
@immutable | ||
interface class SyncActionDeleteFromB<T1, T2> extends SyncAction<T2> { | ||
/// Creates a new action to delete an object from B. | ||
const SyncActionDeleteFromB(super.object); | ||
|
||
@override | ||
String toString() => 'SyncActionDeleteFromB<$T1, $T2>(object: $object)'; | ||
} | ||
|
||
/// Action to write an object to A. | ||
@internal | ||
@immutable | ||
interface class SyncActionWriteToA<T1, T2> extends SyncAction<T2> { | ||
/// Creates a new action to write an object to A. | ||
const SyncActionWriteToA(super.object); | ||
|
||
@override | ||
String toString() => 'SyncActionWriteToA<$T1, $T2>(object: $object)'; | ||
} | ||
|
||
/// Action to write an object to B. | ||
@internal | ||
@immutable | ||
interface class SyncActionWriteToB<T1, T2> extends SyncAction<T1> { | ||
/// Creates a new action to write an object to B. | ||
const SyncActionWriteToB(super.object); | ||
|
||
@override | ||
String toString() => 'SyncActionWriteToB<$T1, $T2>(object: $object)'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import 'package:meta/meta.dart'; | ||
import 'package:synchronize/src/object.dart'; | ||
|
||
/// Contains information about a conflict that appeared during sync. | ||
@immutable | ||
class SyncConflict<T1, T2> { | ||
/// Creates a new conflict. | ||
const SyncConflict({ | ||
required this.id, | ||
required this.type, | ||
required this.objectA, | ||
required this.objectB, | ||
this.skipped = false, | ||
}); | ||
|
||
/// Id of the objects involved in the conflict. | ||
final String id; | ||
|
||
/// Type of the conflict that appeared. See [SyncConflictType] for more info. | ||
final SyncConflictType type; | ||
|
||
/// Object A involved in the conflict. | ||
final SyncObject<T1> objectA; | ||
|
||
/// Object B involved in the conflict. | ||
final SyncObject<T2> objectB; | ||
|
||
/// Whether the conflict was skipped by the user, useful for ignoring it later on. | ||
final bool skipped; | ||
|
||
@override | ||
bool operator ==(Object other) => other is SyncConflict && other.id == id; | ||
|
||
@override | ||
int get hashCode => id.hashCode; | ||
|
||
@override | ||
String toString() => | ||
'SyncConflict<$T1, $T2>(id: $id, type: $type, objectA: $objectA, objectB: $objectB, skipped: $skipped)'; | ||
} | ||
|
||
/// Types of conflicts that can appear during sync. | ||
enum SyncConflictType { | ||
/// New objects with the same id exist on both sides. | ||
bothNew, | ||
|
||
/// Both objects with the same id have changed. | ||
bothChanged, | ||
} | ||
|
||
/// Ways to resolve [SyncConflict]s. | ||
enum SyncConflictSolution { | ||
/// Overwrite the content of object A with the content of object B. | ||
overwriteA, | ||
|
||
/// Overwrite the content of object B with the content of object A. | ||
overwriteB, | ||
|
||
/// Skip the conflict and just do nothing. | ||
skip, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import 'package:json_annotation/json_annotation.dart'; | ||
import 'package:synchronize/src/journal_entry.dart'; | ||
|
||
part 'journal.g.dart'; | ||
|
||
/// Contains the journal. | ||
/// | ||
/// Used for detecting changes and new or deleted files. | ||
@JsonSerializable() | ||
class SyncJournal { | ||
/// Creates a new journal. | ||
// Note: This must not be const as otherwise the entries are not modifiable when a const set is used! | ||
SyncJournal([Set<SyncJournalEntry>? entries]) : entries = entries ?? {}; | ||
|
||
/// Deserializes a journal from [json]. | ||
factory SyncJournal.fromJson(Map<String, dynamic> json) => _$SyncJournalFromJson(json); | ||
|
||
/// Serializes a journal to JSON. | ||
Map<String, dynamic> toJson() => _$SyncJournalToJson(this); | ||
|
||
/// All entries contained in the journal. | ||
final Set<SyncJournalEntry> entries; | ||
|
||
/// Updates an [entry]. | ||
void updateEntry(SyncJournalEntry entry) { | ||
entries | ||
..remove(entry) | ||
..add(entry); | ||
} | ||
|
||
@override | ||
String toString() => 'SyncJournal(entries: $entries)'; | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import 'package:collection/collection.dart'; | ||
import 'package:json_annotation/json_annotation.dart'; | ||
import 'package:meta/meta.dart'; | ||
import 'package:synchronize/src/journal.dart'; | ||
|
||
part 'journal_entry.g.dart'; | ||
|
||
/// Stores a single entry in the [SyncJournal]. | ||
/// | ||
/// It contains an [id] and ETags for each object, [etagA] and [etagB] respectively. | ||
@immutable | ||
@JsonSerializable() | ||
class SyncJournalEntry { | ||
/// Creates a new journal entry. | ||
const SyncJournalEntry( | ||
this.id, | ||
this.etagA, | ||
this.etagB, | ||
); | ||
|
||
/// Deserializes a journal entry from [json]. | ||
factory SyncJournalEntry.fromJson(Map<String, dynamic> json) => _$SyncJournalEntryFromJson(json); | ||
|
||
/// Serializes a journal entry to JSON. | ||
Map<String, dynamic> toJson() => _$SyncJournalEntryToJson(this); | ||
|
||
/// Unique ID of the journal entry. | ||
final String id; | ||
|
||
/// ETag of the object A. | ||
final String etagA; | ||
|
||
/// ETag of the object B. | ||
final String etagB; | ||
|
||
@override | ||
bool operator ==(Object other) => other is SyncJournalEntry && other.id == id; | ||
|
||
@override | ||
int get hashCode => id.hashCode; | ||
|
||
@override | ||
String toString() => 'SyncJournalEntry(id: $id, etagA: $etagA, etagB: $etagB)'; | ||
} | ||
|
||
/// Extension to find a [SyncJournalEntry]. | ||
extension SyncJournalEntriesFind on Iterable<SyncJournalEntry> { | ||
/// Finds the first [SyncJournalEntry] that has the [SyncJournalEntry.id] set to [id]. | ||
/// | ||
/// Returns `null` if no matching [SyncJournalEntry] was found. | ||
SyncJournalEntry? tryFind(String id) => firstWhereOrNull((entry) => entry.id == id); | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import 'package:collection/collection.dart'; | ||
|
||
/// Wraps the actual data contained on each side. | ||
typedef SyncObject<T> = ({String id, T data}); | ||
|
||
/// Extension to find a [SyncObject]. | ||
extension SyncObjectsFind<T> on Iterable<SyncObject<T>> { | ||
/// Finds the first [SyncObject] that has the `id` set to [id]. | ||
/// | ||
/// Returns `null` if no matching [SyncObject] was found. | ||
SyncObject<T>? tryFind(String id) => firstWhereOrNull((object) => object.id == id); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:meta/meta.dart'; | ||
import 'package:synchronize/src/conflict.dart'; | ||
import 'package:synchronize/src/object.dart'; | ||
|
||
/// The source the sync uses to sync from and to. | ||
@immutable | ||
abstract interface class SyncSource<T1, T2> { | ||
/// List all the objects. | ||
FutureOr<List<SyncObject<T1>>> listObjects(); | ||
|
||
/// Calculates the ETag of a given [object]. | ||
/// | ||
/// Must be something easy to compute like the mtime of a file and preferably not the hash of the whole content in order to be fast. | ||
FutureOr<String> getObjectETag(SyncObject<T1> object); | ||
|
||
/// Writes the given [object]. | ||
FutureOr<SyncObject<T1>> writeObject(SyncObject<T2> object); | ||
|
||
/// Deletes the given [object]. | ||
FutureOr<void> deleteObject(SyncObject<T1> object); | ||
} | ||
|
||
/// The sources the sync uses to sync from and to. | ||
@immutable | ||
abstract interface class SyncSources<T1, T2> { | ||
/// Source A. | ||
SyncSource<T1, T2> get sourceA; | ||
|
||
/// Source B. | ||
SyncSource<T2, T1> get sourceB; | ||
|
||
/// Automatically find a solution for conflicts that don't matter. Useful e.g. for ignoring new directories. | ||
SyncConflictSolution? findSolution(SyncObject<T1> objectA, SyncObject<T2> objectB); | ||
|
||
@override | ||
String toString() => 'SyncSources<$T1, $T2>(sourceA: $sourceA, sourceB: $sourceB)'; | ||
} |
Oops, something went wrong.