package client // action is what the agent does to one database on one pass. type action int const ( actNothing action = iota // actPull writes the server copy over the local file. actPull // actPush uploads the local file against the version it was based on. actPush // actForcePush uploads the local file and overwrites a newer server copy. actForcePush // actServerWins keeps the local file as a conflict copy, then pulls. actServerWins // actRestore is actServerWins forced on the client by read-only access. actRestore // actManual writes the server copy beside the local file and waits for a // human. actManual ) func (a action) String() string { switch a { case actPull: return "pull" case actPush: return "push" case actForcePush: return "force-push" case actServerWins: return "server-wins" case actRestore: return "restore" case actManual: return "manual-conflict" } return "nothing" } // situation is everything the agent knows about one database at one moment. type situation struct { LocalExists bool // LocalSHA is the digest of the file on disk now. LocalSHA string // SyncedSHA and SyncedVersion are what this client last agreed with the // server. An empty SyncedSHA means this client has never synced the file. SyncedSHA string SyncedVersion int64 RemoteSHA string RemoteVersion int64 CanWrite bool Strategy string // ConflictSeen is set when this exact manual conflict was already // reported, so it is not reported again on every pass. ConflictSeen bool } // decide is the whole client-side synchronization policy, kept pure so the // table of cases can be read and tested in one place. func decide(s situation) action { localChanged := s.LocalExists && s.LocalSHA != s.SyncedSHA remoteChanged := s.RemoteVersion != s.SyncedVersion || s.RemoteSHA != s.SyncedSHA switch { case !s.LocalExists: if s.RemoteVersion > 0 { return actPull } return actNothing case localChanged && !remoteChanged: if !s.CanWrite { return actRestore } return actPush case !localChanged && remoteChanged: return actPull case localChanged && remoteChanged: switch { case !s.CanWrite: return actRestore case s.Strategy == "client-wins": return actForcePush case s.Strategy == "manual": if s.ConflictSeen { return actNothing } return actManual default: return actServerWins } } return actNothing }