Esempio n. 1
0
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
}
Esempio n. 2
0
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)
		}
	}
}
Esempio n. 3
0
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
}