feat(sync): WebDAV vault sync
All checks were successful
CI / Windows build (push) Successful in 12m32s
All checks were successful
CI / Windows build (push) Successful in 12m32s
Two-way sync of the vault folder to a user-configured WebDAV server, so annotations (which travel with the file) sync with the file. - WebDavSyncService.syncNow: per-file decision — local-only uploads, remote-only downloads, and when BOTH sides changed since the last sync it keeps the loser as <file>.conflict-<mtime> on both sides (last-write-wins by mtime) so no data is ever lost. Creates dirs as needed; deletes are conservative. - The decision logic is pure and unit-tested against a fake WebDAV client; the real client is a thin http adapter (no dio dependency). - Settings: WebDAV URL / user / password / remote folder, Test connection, Sync now (with status + last-synced), and an auto-sync toggle (default OFF). Real server round-trips are device/server-validated. Credentials are in SharedPreferences for now (TODO secure-storage). analyze clean, 432 tests.
This commit is contained in:
@@ -787,6 +787,132 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Vault folder updated'**
|
||||
String get vaultUpdated;
|
||||
|
||||
/// No description provided for @syncSection.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sync (WebDAV)'**
|
||||
String get syncSection;
|
||||
|
||||
/// No description provided for @syncServerUrl.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Server URL'**
|
||||
String get syncServerUrl;
|
||||
|
||||
/// No description provided for @syncServerUrlHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'https://dav.example.com/remote.php/dav/files/me'**
|
||||
String get syncServerUrlHint;
|
||||
|
||||
/// No description provided for @syncUsername.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Username'**
|
||||
String get syncUsername;
|
||||
|
||||
/// No description provided for @syncPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Password'**
|
||||
String get syncPassword;
|
||||
|
||||
/// No description provided for @syncRemoteFolder.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Remote folder'**
|
||||
String get syncRemoteFolder;
|
||||
|
||||
/// No description provided for @syncRemoteFolderHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'BadNote'**
|
||||
String get syncRemoteFolderHint;
|
||||
|
||||
/// No description provided for @syncSave.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save'**
|
||||
String get syncSave;
|
||||
|
||||
/// No description provided for @syncSaved.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sync settings saved'**
|
||||
String get syncSaved;
|
||||
|
||||
/// No description provided for @syncTestConnection.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Test connection'**
|
||||
String get syncTestConnection;
|
||||
|
||||
/// No description provided for @syncTestOk.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connection OK'**
|
||||
String get syncTestOk;
|
||||
|
||||
/// No description provided for @syncTestFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connection failed: {error}'**
|
||||
String syncTestFailed(String error);
|
||||
|
||||
/// No description provided for @syncNow.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sync now'**
|
||||
String get syncNow;
|
||||
|
||||
/// No description provided for @syncRunning.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Syncing…'**
|
||||
String get syncRunning;
|
||||
|
||||
/// No description provided for @syncNeverRun.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Never synced'**
|
||||
String get syncNeverRun;
|
||||
|
||||
/// No description provided for @syncLastRun.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Last synced: {when}'**
|
||||
String syncLastRun(String when);
|
||||
|
||||
/// No description provided for @syncResultSummary.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{uploaded} uploaded · {downloaded} downloaded · {conflicts} conflicts'**
|
||||
String syncResultSummary(int uploaded, int downloaded, int conflicts);
|
||||
|
||||
/// No description provided for @syncFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sync failed: {error}'**
|
||||
String syncFailed(String error);
|
||||
|
||||
/// No description provided for @syncAuto.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sync automatically on launch'**
|
||||
String get syncAuto;
|
||||
|
||||
/// No description provided for @syncCredentialsNote.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Credentials are stored locally in plain text. Use a dedicated app password.'**
|
||||
String get syncCredentialsNote;
|
||||
|
||||
/// No description provided for @syncNotConfigured.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter a server URL to enable sync.'**
|
||||
String get syncNotConfigured;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
Reference in New Issue
Block a user