func NewLocalStorage(config *conf.ConfigFile) (*LocalStorage, error) { storageDir, err := config.GetString("storage", "dir") if err != nil { return nil, errors.New("Error: LocalStorage indicated in config file, but lacking local storage directory ('dir = some/dir').") } ls := new(LocalStorage) ls.storageDir = storageDir ls.tmpSubdir = path.Join(storageDir, ".asink-tmpdir") //make sure the base directory and tmp subdir exist err = util.EnsureDirExists(ls.storageDir) if err != nil { return nil, err } err = util.EnsureDirExists(ls.tmpSubdir) if err != nil { return nil, err } return ls, nil }
func StartClient(args []string) { const config_usage = "Config File to use" userHomeDir := "~" u, err := user.Current() if err == nil { userHomeDir = u.HomeDir } flags := flag.NewFlagSet("start", flag.ExitOnError) flags.StringVar(&globals.configFileName, "config", path.Join(userHomeDir, ".asink", "config"), config_usage) flags.StringVar(&globals.configFileName, "c", path.Join(userHomeDir, ".asink", "config"), config_usage+" (shorthand)") flags.Parse(args) //make sure config file's permissions are read-write only for the current user if !util.FileExistsAndHasPermissions(globals.configFileName, 384 /*0b110000000*/) { fmt.Println("Error: Either the file at " + globals.configFileName + " doesn't exist, or it doesn't have permissions such that the current user is the only one allowed to read and write.") return } config, err := conf.ReadConfigFile(globals.configFileName) if err != nil { fmt.Println(err) fmt.Println("Error reading config file at ", globals.configFileName, ". Does it exist?") return } globals.storage, err = GetStorage(config) if err != nil { fmt.Println(err) return } globals.syncDir, err = config.GetString("local", "syncdir") globals.cacheDir, err = config.GetString("local", "cachedir") globals.tmpDir, err = config.GetString("local", "tmpdir") globals.rpcSock, err = config.GetString("local", "socket") //TODO make sure this exists //make sure all the necessary directories exist err = util.EnsureDirExists(globals.syncDir) if err != nil { panic(err) } err = util.EnsureDirExists(globals.cacheDir) if err != nil { panic(err) } err = util.EnsureDirExists(globals.tmpDir) if err != nil { panic(err) } //TODO check errors on server settings globals.server, err = config.GetString("server", "host") globals.port, err = config.GetInt("server", "port") globals.username, err = config.GetString("server", "username") globals.password, err = config.GetString("server", "password") //TODO check errors on encryption settings globals.encrypted, err = config.GetBool("encryption", "enabled") if globals.encrypted { globals.key, err = config.GetString("encryption", "key") } globals.db, err = GetAndInitDB(config) if err != nil { panic(err) } //spawn goroutine to handle locking file paths go PathLocker(globals.db) //spawn goroutines to handle local events go SendEvents(&globals) localFileUpdates := make(chan *asink.Event) initialWalkComplete := make(chan int) go StartWatching(globals.syncDir, localFileUpdates, initialWalkComplete) //spawn goroutines to receive remote events remoteFileUpdates := make(chan *asink.Event) go GetEvents(&globals, remoteFileUpdates) rpcTornDown := make(chan int) go StartRPC(globals.rpcSock, rpcTornDown) defer func() { <-rpcTornDown }() //make chan with which to wait for exit exitChan := make(chan int) asink.WaitOnExitChan(exitChan) //create all the contexts startupContext := NewStartupContext(&globals, localFileUpdates, remoteFileUpdates, initialWalkComplete, exitChan) normalContext := NewNormalContext(&globals, localFileUpdates, remoteFileUpdates, exitChan) //begin running contexts err = startupContext.Run() if err != nil && ErrorRequiresExit(err) { fmt.Println(err) if !ErrorWasExit(err) { asink.Exit(1) } return } err = normalContext.Run() if err != nil { fmt.Println(err) if !ErrorWasExit(err) { asink.Exit(1) } } }
func ProcessRemoteEvent(globals *AsinkGlobals, event *asink.Event) error { var err error StatStartRemoteUpdate() defer StatStopRemoteUpdate() latestLocal := LockPath(event.Path, false) defer func() { if err != nil { event.LocalStatus |= asink.DISCARDED } UnlockPath(event) }() //get the absolute path because we may need it later absolutePath := path.Join(globals.syncDir, event.Path) //if we already have this event, or if it is older than our most recent event, bail out if latestLocal != nil { if event.Timestamp < latestLocal.Timestamp { event.LocalStatus |= asink.DISCARDED return nil } if event.IsSameEvent(latestLocal) { return nil } if latestLocal.Hash != event.Predecessor && latestLocal.Hash != event.Hash { err = handleConflict(globals, latestLocal, path.Join(globals.cacheDir, latestLocal.Hash)) if err != nil { return ProcessingError{PERMANENT, err} } } } //Download event if event.IsUpdate() { if latestLocal == nil || event.Hash != latestLocal.Hash { outfile, err := ioutil.TempFile(globals.tmpDir, "asink") if err != nil { return ProcessingError{CONFIG, err} } tmpfilename := outfile.Name() StatStartDownload() downloadReadCloser, err := globals.storage.Get(event.Hash) if err != nil { StatStopDownload() return ProcessingError{STORAGE, err} } defer downloadReadCloser.Close() if globals.encrypted { decrypter, err := NewDecrypter(downloadReadCloser, globals.key) if err != nil { StatStopDownload() return ProcessingError{STORAGE, err} } _, err = io.Copy(outfile, decrypter) } else { _, err = io.Copy(outfile, downloadReadCloser) } outfile.Close() StatStopDownload() if err != nil { return ProcessingError{STORAGE, err} } //rename to local hashed filename hashedFilename := path.Join(globals.cacheDir, event.Hash) err = os.Rename(tmpfilename, hashedFilename) if err != nil { err = os.Remove(tmpfilename) if err != nil { return ProcessingError{PERMANENT, err} } return ProcessingError{PERMANENT, err} } //copy hashed file to another tmp, then rename it to the actual file. tmpfilename, err = util.CopyToTmp(hashedFilename, globals.tmpDir) if err != nil { return ProcessingError{PERMANENT, err} } //make sure containing directory exists err = util.EnsureDirExists(path.Dir(absolutePath)) if err != nil { return ProcessingError{PERMANENT, err} } err = os.Rename(tmpfilename, absolutePath) if err != nil { err2 := os.Remove(tmpfilename) if err2 != nil { return ProcessingError{PERMANENT, err2} } return ProcessingError{PERMANENT, err} } } if latestLocal == nil || event.Hash != latestLocal.Hash || event.Permissions != latestLocal.Permissions { err = os.Chmod(absolutePath, event.Permissions) if err != nil && !util.ErrorFileNotFound(err) { return ProcessingError{PERMANENT, err} } } } else { //intentionally ignore errors in case this file has been deleted out from under us os.Remove(absolutePath) //delete the directory previously containing this file if its the last file util.RecursiveRemoveEmptyDirs(path.Dir(absolutePath)) } return nil }