2013-02-10 23:39:23 -05:00
package main
import (
2013-02-20 23:43:01 -05:00
"asink"
"asink/util"
2013-02-11 23:17:12 -05:00
"code.google.com/p/goconf/conf"
2013-02-10 23:39:23 -05:00
"flag"
2013-02-11 23:17:12 -05:00
"fmt"
2013-04-01 23:59:40 -04:00
"io/ioutil"
2013-02-18 20:44:00 -05:00
"os"
2013-02-10 23:39:23 -05:00
"os/user"
2013-02-11 23:17:12 -05:00
"path"
2013-08-14 21:10:16 -04:00
"path/filepath"
2013-02-10 23:39:23 -05:00
)
2013-02-18 20:44:00 -05:00
type AsinkGlobals struct {
configFileName string
syncDir string
cacheDir string
tmpDir string
2013-03-10 23:13:30 -04:00
db * AsinkDB
2013-02-18 20:44:00 -05:00
storage Storage
2013-02-20 23:43:01 -05:00
server string
port int
2013-08-28 23:05:28 -04:00
username string
password string
2013-02-18 20:44:00 -05:00
}
var globals AsinkGlobals
2013-02-10 23:39:23 -05:00
func init ( ) {
const config_usage = "Config File to use"
userHomeDir := "~"
u , err := user . Current ( )
if err == nil {
userHomeDir = u . HomeDir
2013-02-11 23:17:12 -05:00
}
2013-02-10 23:39:23 -05:00
2013-02-18 20:44:00 -05:00
flag . StringVar ( & globals . configFileName , "config" , path . Join ( userHomeDir , ".asink" , "config" ) , config_usage )
flag . StringVar ( & globals . configFileName , "c" , path . Join ( userHomeDir , ".asink" , "config" ) , config_usage + " (shorthand)" )
2013-02-10 23:39:23 -05:00
}
func main ( ) {
flag . Parse ( )
2013-08-28 23:05:28 -04:00
//make sure config file's permissions are read-write only for the current user
if ! util . FileExistsAndHasPermissions ( globals . configFileName , 384 /*0b110000000*/ ) {
2013-08-29 07:38:55 -04:00
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." )
2013-08-28 23:05:28 -04:00
return
}
2013-02-18 20:44:00 -05:00
config , err := conf . ReadConfigFile ( globals . configFileName )
2013-02-10 23:39:23 -05:00
if err != nil {
fmt . Println ( err )
2013-02-18 20:44:00 -05:00
fmt . Println ( "Error reading config file at " , globals . configFileName , ". Does it exist?" )
2013-02-10 23:39:23 -05:00
return
}
2013-02-18 20:44:00 -05:00
globals . storage , err = GetStorage ( config )
2013-02-10 23:39:23 -05:00
if err != nil {
fmt . Println ( err )
return
}
2013-02-18 20:44:00 -05:00
globals . syncDir , err = config . GetString ( "local" , "syncdir" )
globals . cacheDir , err = config . GetString ( "local" , "cachedir" )
globals . tmpDir , err = config . GetString ( "local" , "tmpdir" )
//make sure all the necessary directories exist
2013-02-20 23:43:01 -05:00
err = util . EnsureDirExists ( globals . syncDir )
2013-02-18 20:44:00 -05:00
if err != nil {
panic ( err )
}
2013-02-20 23:43:01 -05:00
err = util . EnsureDirExists ( globals . cacheDir )
2013-02-18 20:44:00 -05:00
if err != nil {
panic ( err )
}
2013-02-20 23:43:01 -05:00
err = util . EnsureDirExists ( globals . tmpDir )
2013-02-18 20:44:00 -05:00
if err != nil {
panic ( err )
}
2013-02-10 23:39:23 -05:00
2013-08-28 23:05:28 -04:00
//TODO check errors on server settings
2013-02-20 23:43:01 -05:00
globals . server , err = config . GetString ( "server" , "host" )
globals . port , err = config . GetInt ( "server" , "port" )
2013-08-28 23:05:28 -04:00
globals . username , err = config . GetString ( "server" , "username" )
globals . password , err = config . GetString ( "server" , "password" )
2013-02-20 23:43:01 -05:00
2013-02-18 20:44:00 -05:00
globals . db , err = GetAndInitDB ( config )
if err != nil {
panic ( err )
}
2013-02-10 23:39:23 -05:00
2013-08-13 21:51:19 -04:00
//spawn goroutine to handle locking file paths
go PathLocker ( globals . db )
2013-03-17 23:02:51 -04:00
//spawn goroutines to handle local events
localFileUpdates := make ( chan * asink . Event )
go StartWatching ( globals . syncDir , localFileUpdates )
//spawn goroutines to receive remote events
remoteFileUpdates := make ( chan * asink . Event )
go GetEvents ( globals , remoteFileUpdates )
go ProcessRemoteEvents ( globals , remoteFileUpdates )
2013-02-11 23:16:19 -05:00
for {
2013-03-17 23:02:51 -04:00
event := <- localFileUpdates
go ProcessLocalEvent ( globals , event )
2013-02-10 23:39:23 -05:00
}
2013-02-11 23:16:19 -05:00
}
2013-02-10 23:39:23 -05:00
2013-03-17 23:02:51 -04:00
func ProcessLocalEvent ( globals AsinkGlobals , event * asink . Event ) {
2013-08-14 21:10:16 -04:00
//make the path relative before we save/send it anywhere
var err error
absolutePath := event . Path
event . Path , err = filepath . Rel ( globals . syncDir , event . Path )
if err != nil {
panic ( err )
}
2013-08-13 21:51:19 -04:00
latestLocal := LockPath ( event . Path , true )
defer UnlockPath ( event )
if latestLocal != nil {
event . Predecessor = latestLocal . Hash
2013-02-18 20:44:00 -05:00
}
2013-02-10 23:39:23 -05:00
2013-02-11 23:16:19 -05:00
if event . IsUpdate ( ) {
2013-02-18 20:44:00 -05:00
//copy to tmp
2013-08-13 21:51:19 -04:00
//TODO upload in chunks and check modification times to make sure it hasn't been changed instead of copying the whole thing off
2013-08-14 21:10:16 -04:00
tmpfilename , err := util . CopyToTmp ( absolutePath , globals . tmpDir )
2013-08-14 07:26:52 -04:00
if err != nil {
//bail out if the file we are trying to upload already got deleted
if util . ErrorFileNotFound ( err ) {
2013-08-23 00:09:03 -04:00
event . LocalStatus |= asink . DISCARDED
2013-08-14 07:26:52 -04:00
return
}
2013-08-13 23:12:08 -04:00
panic ( err )
2013-02-18 20:44:00 -05:00
}
2013-08-14 07:26:52 -04:00
//try to collect the file's permissions
2013-08-14 21:10:16 -04:00
fileinfo , err := os . Stat ( absolutePath )
2013-08-14 07:26:52 -04:00
if err != nil {
//bail out if the file we are trying to upload already got deleted
if util . ErrorFileNotFound ( err ) {
2013-08-23 00:09:03 -04:00
event . LocalStatus |= asink . DISCARDED
2013-08-14 07:26:52 -04:00
return
}
2013-08-13 23:18:17 -04:00
panic ( err )
2013-08-14 07:26:52 -04:00
} else {
event . Permissions = fileinfo . Mode ( )
2013-08-13 23:18:17 -04:00
}
2013-02-18 20:44:00 -05:00
//get the file's hash
hash , err := HashFile ( tmpfilename )
if err != nil {
panic ( err )
}
2013-08-14 22:58:34 -04:00
event . Hash = hash
2013-02-18 20:44:00 -05:00
2013-08-13 21:55:17 -04:00
//If the file didn't actually change, squash this event
if latestLocal != nil && event . Hash == latestLocal . Hash {
os . Remove ( tmpfilename )
2013-08-23 00:09:03 -04:00
event . LocalStatus |= asink . DISCARDED
2013-08-13 21:55:17 -04:00
return
}
2013-02-18 20:44:00 -05:00
//rename to local cache w/ filename=hash
2013-08-14 07:23:32 -04:00
cachedFilename := path . Join ( globals . cacheDir , event . Hash )
err = os . Rename ( tmpfilename , cachedFilename )
2013-02-18 20:44:00 -05:00
if err != nil {
err := os . Remove ( tmpfilename )
if err != nil {
panic ( err )
}
2013-03-17 23:02:51 -04:00
panic ( err )
2013-02-18 20:44:00 -05:00
}
//upload file to remote storage
2013-08-14 07:23:32 -04:00
err = globals . storage . Put ( cachedFilename , event . Hash )
2013-02-18 20:44:00 -05:00
if err != nil {
panic ( err )
}
2013-08-14 07:26:52 -04:00
} else {
//if we're trying to delete a file that we thought was already deleted, there's no need to delete it again
if latestLocal != nil && latestLocal . IsDelete ( ) {
2013-08-23 00:09:03 -04:00
event . LocalStatus |= asink . DISCARDED
2013-08-14 07:26:52 -04:00
return
}
2013-02-10 23:39:23 -05:00
}
2013-02-18 20:44:00 -05:00
2013-02-20 23:43:01 -05:00
//finally, send it off to the server
2013-08-14 21:10:16 -04:00
err = SendEvent ( globals , event )
2013-02-22 00:06:10 -05:00
if err != nil {
panic ( err ) //TODO handle sensibly
}
}
2013-04-01 23:59:40 -04:00
func ProcessRemoteEvent ( globals AsinkGlobals , event * asink . Event ) {
2013-08-13 21:51:19 -04:00
latestLocal := LockPath ( event . Path , true )
defer UnlockPath ( event )
2013-08-14 07:26:52 -04:00
2013-08-14 21:10:16 -04:00
//get the absolute path because we may need it later
absolutePath := path . Join ( globals . syncDir , event . Path )
2013-08-13 21:51:19 -04:00
//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 . IsSameEvent ( latestLocal ) {
2013-08-23 00:09:03 -04:00
event . LocalStatus |= asink . DISCARDED
2013-08-13 21:51:19 -04:00
return
}
if latestLocal . Hash != event . Predecessor && latestLocal . Hash != event . Hash {
2013-08-23 00:09:03 -04:00
fmt . Printf ( "conflict:\n" )
fmt . Printf ( "OLD %+v\n" , latestLocal )
fmt . Printf ( "NEW %+v\n" , event )
//TODO handle conflict?
2013-08-13 21:51:19 -04:00
}
}
2013-04-01 23:59:40 -04:00
//Download event
2013-08-13 23:17:45 -04:00
if event . IsUpdate ( ) {
if latestLocal == nil || event . Hash != latestLocal . Hash {
2013-04-01 23:59:40 -04:00
2013-08-13 23:17:45 -04:00
outfile , err := ioutil . TempFile ( globals . tmpDir , "asink" )
2013-04-01 23:59:40 -04:00
if err != nil {
2013-08-13 23:17:45 -04:00
panic ( err ) //TODO handle sensibly
}
tmpfilename := outfile . Name ( )
outfile . Close ( )
err = globals . storage . Get ( tmpfilename , event . Hash )
if err != nil {
panic ( err ) //TODO handle sensibly
}
//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 {
panic ( err )
}
panic ( err )
}
2013-08-14 22:58:34 -04:00
//copy hashed file to another tmp, then rename it to the actual file.
2013-08-13 23:17:45 -04:00
tmpfilename , err = util . CopyToTmp ( hashedFilename , globals . tmpDir )
if err != nil {
panic ( err )
}
2013-08-14 22:59:40 -04:00
//make sure containing directory exists
err = util . EnsureDirExists ( path . Dir ( absolutePath ) )
if err != nil {
panic ( err )
}
2013-08-14 21:10:16 -04:00
err = os . Rename ( tmpfilename , absolutePath )
2013-08-13 23:17:45 -04:00
if err != nil {
2013-08-14 22:58:34 -04:00
err2 := os . Remove ( tmpfilename )
if err2 != nil {
panic ( err2 )
2013-08-13 23:17:45 -04:00
}
2013-04-01 23:59:40 -04:00
panic ( err )
}
}
2013-08-13 23:18:17 -04:00
if latestLocal == nil || event . Permissions != latestLocal . Permissions {
2013-08-14 21:10:16 -04:00
err := os . Chmod ( absolutePath , event . Permissions )
2013-08-13 23:18:17 -04:00
if err != nil && ! util . ErrorFileNotFound ( err ) {
panic ( err )
}
}
2013-08-13 21:55:17 -04:00
} else {
//intentionally ignore errors in case this file has been deleted out from under us
2013-08-14 21:10:16 -04:00
os . Remove ( absolutePath )
2013-08-14 22:59:40 -04:00
//delete the directory previously containing this file if its the last file
util . RecursiveRemoveEmptyDirs ( path . Dir ( absolutePath ) )
2013-04-01 23:59:40 -04:00
}
//TODO make sure file being overwritten is either unchanged or already copied off and hashed
}
2013-03-17 23:02:51 -04:00
func ProcessRemoteEvents ( globals AsinkGlobals , eventChan chan * asink . Event ) {
for event := range eventChan {
2013-04-01 23:59:40 -04:00
ProcessRemoteEvent ( globals , event )
2013-02-20 23:43:01 -05:00
}
2013-02-10 23:39:23 -05:00
}