diff --git a/libmuttonserver.go b/libmuttonserver.go index 6cdceaa..9566bc2 100644 --- a/libmuttonserver.go +++ b/libmuttonserver.go @@ -126,7 +126,7 @@ func main() { } // print EntryRoot, AgeDir and bool indicating OS type to stdout for client to store in config - registerResp := synccommon.RegisterResp{EntryRoot: global.EntryRoot, AgeDir: global.AgeDir, IsWindows: global.IsWindows} + registerResp := synccommon.RegisterRespT{EntryRoot: global.EntryRoot, AgeDir: global.AgeDir, IsWindows: global.IsWindows} registerRespBytes, err := json.Marshal(registerResp) if err != nil { fmt.Printf("{\"errMsg\":\"%s\"}", err.Error()) diff --git a/syncclient/client.go b/syncclient/client.go index 5297490..063b87d 100644 --- a/syncclient/client.go +++ b/syncclient/client.go @@ -18,6 +18,12 @@ import ( "golang.org/x/crypto/ssh/knownhosts" ) +type syncListsT struct { + Delete []string + Upload []string + Download []string +} + // GetSSHClient // Returns: // sshClient, @@ -103,7 +109,7 @@ func GetSSHOutput(sshClient *ssh.Client, cmd, stdin string) ([]byte, error) { // a map of remote vanityPaths to their containing folders and mod+age timestamps, // a list of queued deletions, // and the current server&client times as UNIX timestamps. -func getRemoteDataFromClient(sshClient *ssh.Client) (synccommon.EntriesMap, []synccommon.Deletion, int64, int64, error) { +func getRemoteDataFromClient(sshClient *ssh.Client) (synccommon.EntryMapT, []synccommon.Deletion, int64, int64, error) { // get remote output over SSH deviceIDList, err := global.GenDeviceIDList() if err != nil { @@ -118,7 +124,7 @@ func getRemoteDataFromClient(sshClient *ssh.Client) (synccommon.EntriesMap, []sy return nil, nil, 0, 0, errors.New("unable to run remote command: " + err.Error()) } - var fetchResp synccommon.FetchResp + var fetchResp synccommon.FetchRespT if err = json.Unmarshal(output, &fetchResp); err != nil { fmt.Println(string(output)) return nil, nil, 0, 0, errors.New("unable to unmarshal server fetch response: " + err.Error()) @@ -147,7 +153,7 @@ func getRealAgePathSFTP(vanityPath, serverAgeDir string, serverIsWindows bool) s } // sftpSync takes two slices of entries (one for downloads and one for uploads) and syncs them between the client and server using SFTP. -func sftpSync(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindows bool, downloadList, uploadList []string) error { +func sftpSync(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindows bool, syncListsV *syncListsT) error { // create an SFTP client from sshClient sftpClient, err := sftp.NewClient(sshClient) if err != nil { @@ -159,7 +165,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindow // iterate over the download list var filesTransferred bool - for _, vanityPath := range downloadList { + for _, vanityPath := range syncListsV.Download { filesTransferred = true // set a flag to indicate that files have been downloaded (used to determine whether to print a gap between download and upload messages) fmt.Println("Downloading " + back.AnsiGreen + vanityPath + back.AnsiReset) @@ -213,7 +219,7 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindow // iterate over the upload list filesTransferred = false - for _, vanityPath := range uploadList { + for _, vanityPath := range syncListsV.Upload { // determine if local file is an age file var isAgeFile bool if strings.HasPrefix(vanityPath, global.FSMisc) { @@ -292,9 +298,9 @@ func sftpSync(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindow // syncLists determines which entries need to be downloaded and uploaded // for synchronization and calls sftpSync with this information. -func syncLists(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindows bool, timeSyncedErr error, localEntryMap, remoteEntryMap synccommon.EntriesMap) ([3][]string, error) { +func syncLists(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindows bool, timeSyncedErr error, localEntryMap, remoteEntryMap synccommon.EntryMapT) (*syncListsT, error) { // initialize slices to store entries that need to be downloaded or uploaded - var downloadList, uploadList []string + var syncListsV syncListsT // iterate over client entries in local map for vanityPath, localInfo := range localEntryMap { @@ -304,26 +310,26 @@ func syncLists(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindo remoteInfo := remoteEntryMap[vanityPath] if remoteInfo.ModTime > localInfo.ModTime { fmt.Println(back.AnsiGreen+vanityPath+back.AnsiReset, "is newer on server, adding to download list") - downloadList = append(downloadList, vanityPath) + syncListsV.Download = append(syncListsV.Download, vanityPath) if remoteInfo.AgeTimestamp != nil { if err := age.Entry(vanityPath, *remoteInfo.AgeTimestamp); err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to update age timestamp for " + vanityPath + ": " + err.Error()) + return nil, errors.New("unable to update age timestamp for " + vanityPath + ": " + err.Error()) } } } else if remoteInfo.ModTime < localInfo.ModTime { fmt.Println(back.AnsiBlue+vanityPath+back.AnsiReset, "is newer on client, adding to upload list") - uploadList = append(uploadList, vanityPath) + syncListsV.Upload = append(syncListsV.Upload, vanityPath) if localInfo.AgeTimestamp != nil && localInfo.AgeTimestamp != remoteInfo.AgeTimestamp { - uploadList = append(uploadList, global.FSMisc+vanityPath) + syncListsV.Upload = append(syncListsV.Upload, global.FSMisc+vanityPath) } } // remove entry from remote map (process of elimination) delete(remoteEntryMap, vanityPath) } else { fmt.Println(back.AnsiBlue+vanityPath+back.AnsiReset, "does not exist on server, adding to upload list") - uploadList = append(uploadList, vanityPath) + syncListsV.Upload = append(syncListsV.Upload, vanityPath) if localInfo.AgeTimestamp != nil { - uploadList = append(uploadList, global.FSMisc+vanityPath) + syncListsV.Upload = append(syncListsV.Upload, global.FSMisc+vanityPath) } } } @@ -331,27 +337,27 @@ func syncLists(sshClient *ssh.Client, sshEntryRoot, sshAgeDir string, sshIsWindo // iterate over remaining entries in remote map for vanityPath, remoteInfo := range remoteEntryMap { fmt.Println(back.AnsiGreen+vanityPath+back.AnsiReset, "does not exist on client, adding to download list") - downloadList = append(downloadList, vanityPath) + syncListsV.Download = append(syncListsV.Download, vanityPath) if err := os.MkdirAll(global.GetRealPath(remoteInfo.ContainingFolder), 0700); err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to create containing folder for " + vanityPath + ": " + err.Error()) + return nil, errors.New("unable to create containing folder for " + vanityPath + ": " + err.Error()) } if remoteInfo.AgeTimestamp != nil { if err := age.Entry(vanityPath, *remoteInfo.AgeTimestamp); err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to update age timestamp for " + vanityPath + ": " + err.Error()) + return nil, errors.New("unable to update age timestamp for " + vanityPath + ": " + err.Error()) } } } // call sftpSync with the download and upload lists - if timeSyncedErr == nil && (max(len(downloadList), len(uploadList)) > 0) { // only call sftpSync if there are entries to download or upload + if timeSyncedErr == nil && (max(len(syncListsV.Download), len(syncListsV.Upload)) > 0) { // only call sftpSync if there are entries to download or upload fmt.Println() // add a gap between list-add messages and the actual sync messages from sftpSync - if err := sftpSync(sshClient, sshEntryRoot, sshAgeDir, sshIsWindows, downloadList, uploadList); err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to sync entries: " + err.Error()) + if err := sftpSync(sshClient, sshEntryRoot, sshAgeDir, sshIsWindows, &syncListsV); err != nil { + return nil, errors.New("unable to sync entries: " + err.Error()) } fmt.Println("Client is synchronized with server") } - return [3][]string{nil, downloadList, uploadList}, timeSyncedErr + return &syncListsV, timeSyncedErr } // deletionSync removes entries from the client that have been deleted on the server (multi-client deletion). @@ -377,14 +383,14 @@ func deletionSync(deletions []synccommon.Deletion) error { // RunJob runs the SSH sync job and returns deletions, downloads, // and uploads lists for the client to report to the user. -func RunJob() ([3][]string, error) { +func RunJob() (*syncListsT, error) { // get SSH client to re-use throughout the sync process sshClient, offlineMode, sshIsWindows, sshEntryRoot, sshAgeDir, err := GetSSHClient() if offlineMode { - return [3][]string{nil, nil, nil}, nil + return nil, nil } if err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to connect to SSH client: " + err.Error()) + return nil, errors.New("unable to connect to SSH client: " + err.Error()) } defer func(sshClient *ssh.Client) { _ = sshClient.Close() @@ -393,18 +399,18 @@ func RunJob() ([3][]string, error) { // fetch remote lists remoteEntryMap, deletions, serverTime, clientTime, err := getRemoteDataFromClient(sshClient) if err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to fetch remote data: " + err.Error()) + return nil, errors.New("unable to fetch remote data: " + err.Error()) } // sync deletions if err = deletionSync(deletions); err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to sync deletions: " + err.Error()) + return nil, errors.New("unable to sync deletions: " + err.Error()) } // fetch local lists localEntryMap, err := synccommon.GetAllEntryData() if err != nil { - return [3][]string{nil, nil, nil}, errors.New("unable to fetch local entry data: " + err.Error()) + return nil, errors.New("unable to fetch local entry data: " + err.Error()) } // before syncing lists, ensure the client and server clocks are synced within 45 seconds @@ -416,18 +422,17 @@ func RunJob() ([3][]string, error) { // sync new and updated entries // if time is not synced, the time sync error and upload/download lists will be returned here - lists, err := syncLists(sshClient, *sshEntryRoot, *sshAgeDir, *sshIsWindows, timeSyncedErr, localEntryMap, remoteEntryMap) + syncListsV, err := syncLists(sshClient, *sshEntryRoot, *sshAgeDir, *sshIsWindows, timeSyncedErr, localEntryMap, remoteEntryMap) if err != nil { - return lists, errors.New("unable to sync entries: " + err.Error()) + return syncListsV, errors.New("unable to sync entries: " + err.Error()) } // add deletions info to sync lists - lists[0] = []string{} for _, deletion := range deletions { if !deletion.IsAgeFile { - lists[0] = append(lists[0], deletion.VanityPath) + syncListsV.Delete = append(syncListsV.Delete, deletion.VanityPath) } } - return lists, nil + return syncListsV, nil } diff --git a/syncclient/oneOff.go b/syncclient/oneOff.go index c4fa0ed..537a3d7 100644 --- a/syncclient/oneOff.go +++ b/syncclient/oneOff.go @@ -205,7 +205,7 @@ func GenDeviceID(oldDeviceID, prefix string) (string, string, bool, error) { cleanupOnFail() return "", "", false, errors.New("unable to register device ID with server: " + err.Error()) } - var registerResp synccommon.RegisterResp + var registerResp synccommon.RegisterRespT if err = json.Unmarshal(output, ®isterResp); err != nil { cleanupOnFail() return "", "", false, errors.New("unable to unmarshal server register response: " + err.Error()) diff --git a/synccommon/common.go b/synccommon/common.go index 7df6c09..95583af 100644 --- a/synccommon/common.go +++ b/synccommon/common.go @@ -16,26 +16,26 @@ const ( AnsiDelete = "\033[38;5;1m" ) -// FetchResp defines the structure of responses from `libmuttonserver fetch`. -type FetchResp struct { +// FetchRespT defines the structure of responses from `libmuttonserver fetch`. +type FetchRespT struct { ErrMsg *string `json:"errMsg"` // nil if no error occurred ServerTime int64 `json:"serverTime"` Deletions []Deletion `json:"deletions"` - Entries EntriesMap `json:"entries"` + Entries EntryMapT `json:"entries"` } type Deletion struct { VanityPath string `json:"vanityPath"` IsAgeFile bool `json:"isAgeFile"` } -type EntriesMap map[string]Entry // map vanity paths to containing folders and mod+age timestamps -type Entry struct { +type EntryMapT map[string]EntryT // map vanity paths to containing folders and mod+age timestamps +type EntryT struct { ContainingFolder string `json:"containingFolder"` ModTime int64 `json:"modTime"` AgeTimestamp *int64 `json:"ageTimestamp"` // nil if no age file is present (non-password entry) } -// RegisterResp defines the structure of responses from `libmuttonserver register` -type RegisterResp struct { +// RegisterRespT defines the structure of responses from `libmuttonserver register` +type RegisterRespT struct { ErrMsg *string `json:"errMsg"` // nil if no error occurred EntryRoot string `json:"entryRoot"` AgeDir string `json:"ageDir"` @@ -44,14 +44,14 @@ type RegisterResp struct { // GetAllEntryData returns a map of all vanity paths to // their respective containing folders and mod+age timestamps. -func GetAllEntryData() (EntriesMap, error) { +func GetAllEntryData() (EntryMapT, error) { var err error entryList, _, err := WalkEntryDir() if err != nil { return nil, errors.New("unable to walk entry directory: " + err.Error()) } // initialize vanityPath keys in map - outputEntries := make(EntriesMap) + outputEntries := make(EntryMapT) var modInfo, ageInfo os.FileInfo for _, vanityPath := range entryList { containingFolder := vanityPath[:strings.LastIndex(vanityPath, "/")] @@ -67,7 +67,7 @@ func GetAllEntryData() (EntriesMap, error) { } else if !os.IsNotExist(err) { return nil, errors.New("unable to read age time for " + vanityPath + ": " + err.Error()) } - outputEntries[vanityPath] = Entry{ContainingFolder: containingFolder, ModTime: modInfo.ModTime().Unix(), AgeTimestamp: ageTimestamp} + outputEntries[vanityPath] = EntryT{ContainingFolder: containingFolder, ModTime: modInfo.ModTime().Unix(), AgeTimestamp: ageTimestamp} } return outputEntries, nil } diff --git a/syncserver/server.go b/syncserver/server.go index 26909f4..63c99a3 100644 --- a/syncserver/server.go +++ b/syncserver/server.go @@ -27,7 +27,7 @@ func GetRemoteDataFromServer(clientDeviceID string) { } // form response - var fetchResp synccommon.FetchResp + var fetchResp synccommon.FetchRespT //// server time fetchResp.ServerTime = time.Now().Unix() //// deletions