github.com/ivotron/vio@v0.1.1-0.20160328072646-778e014d4dee/posix_backend.go (about) 1 package vio 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "strings" 10 11 "gopkg.in/ini.v1" 12 13 "github.com/tgulacsi/go-locking" 14 ) 15 16 type PosixBackend struct { 17 snapshotsPath string 18 repoPath string 19 } 20 21 func NewPosixBackend(o *ini.File) (b Backend, err error) { 22 if !o.Section("").HasKey("snapshots_path") { 23 return nil, AnError{"Expecting key 'snapshots_path' in configuration."} 24 } 25 if !o.Section("").HasKey("repo_path") { 26 return nil, AnError{"Expecting key 'repo_path' in configuration."} 27 } 28 return &PosixBackend{ 29 snapshotsPath: o.Section("").Key("snapshots_path").String(), 30 repoPath: o.Section("").Key("repo_path").String()}, nil 31 } 32 33 func (b PosixBackend) Init() (err error) { 34 if err = os.Mkdir(b.snapshotsPath, 0755); err != nil { 35 return 36 } 37 38 if _, err = os.Stat(b.snapshotsPath + "/index"); err == nil { 39 return AnError{"Repository already initialized"} 40 } 41 42 if err = ioutil.WriteFile(b.snapshotsPath+"/index", []byte(""), 0644); err != nil { 43 return 44 } 45 46 return 47 } 48 49 func (b PosixBackend) isRepoOK() (err error) { 50 if !b.IsInitialized() { 51 return AnError{"Uninitialized repository."} 52 } 53 54 hasUncommitted, err := HasUncommittedChanges(b.repoPath) 55 56 if err != nil { 57 return 58 } 59 if hasUncommitted { 60 return AnError{"Uncommitted changes in repo."} 61 } 62 63 return 64 } 65 66 func (b PosixBackend) Open() error { 67 return nil 68 } 69 70 func (b PosixBackend) IsInitialized() bool { 71 _, err := os.Stat(b.snapshotsPath + "/index") 72 return err == nil 73 } 74 75 func (b PosixBackend) GetStatus() (Status, error) { 76 return Committed, nil 77 } 78 79 func (b PosixBackend) Checkout(v *version) (err error) { 80 if err = b.isRepoOK(); err != nil { 81 return 82 } 83 84 // acquire a lock on the index file 85 flock, err := locking.NewFLock(b.snapshotsPath + "/index") 86 if err != nil { 87 return 88 } 89 if err = flock.Lock(); err != nil { 90 return 91 } 92 defer flock.Unlock() 93 94 idx, err := b.GetVersions() 95 if err != nil { 96 return 97 } 98 99 if !ContainsVersion(idx, v) { 100 return AnError{ 101 fmt.Sprintf("Version %s#%d not in index", v.revision, v.timestamp.Unix())} 102 } 103 104 return checkoutSnapshot(b.repoPath, b.snapshotsPath, v) 105 } 106 107 func (b PosixBackend) Commit(meta map[string]string) (v *version, err error) { 108 if err = b.isRepoOK(); err != nil { 109 return 110 } 111 versionedFiles, err := GetVersionedFiles(b.repoPath) 112 if err != nil { 113 return 114 } 115 116 id, err := GetCurrentCommitId(b.repoPath) 117 if err != nil { 118 return 119 } 120 121 v = NewVersionWithMeta(id, meta) 122 123 // acquire a lock on the index file 124 flock, err := locking.NewFLock(b.snapshotsPath + "/index") 125 if err != nil { 126 return 127 } 128 if err = flock.Lock(); err != nil { 129 return 130 } 131 defer flock.Unlock() 132 133 idx, err := b.GetVersions() 134 if err != nil { 135 return 136 } 137 if ContainsVersion(idx, v) { 138 return nil, AnError{"Version " + fmt.Sprintf("%v", v) + " already in index."} 139 } 140 141 if err = createSnapshot(b.repoPath, b.snapshotsPath, v, versionedFiles); err != nil { 142 return 143 } 144 145 if err = addVersionToIndex(v, b.snapshotsPath+"/index"); err != nil { 146 return 147 } 148 149 return 150 } 151 152 func checkoutSnapshot(repoPath string, snapsPath string, v *version) (err error) { 153 154 if _, err = os.Stat(snapsPath + "/" + v.revision); err != nil { 155 return 156 } 157 158 unixTime := fmt.Sprintf("%d", v.timestamp.Unix()) 159 if _, err = os.Stat(snapsPath + "/" + v.revision + "/" + unixTime); err != nil { 160 return 161 } 162 163 srcPath := snapsPath + "/" + v.revision + "/" + unixTime + "/" 164 165 var args []string 166 args = append(args, "-am") 167 168 // source 169 args = append(args, srcPath) 170 171 // destination 172 args = append(args, repoPath) 173 174 _, err = exec.Command("rsync", args...).CombinedOutput() 175 176 return 177 } 178 179 func createSnapshot(repoPath string, 180 snapsPath string, v *version, versionedFiles []string) (err error) { 181 182 if err = os.MkdirAll(snapsPath+"/"+v.revision, 0755); err != nil { 183 return 184 } 185 186 unixTime := fmt.Sprintf("%d", v.timestamp.Unix()) 187 if err = os.Mkdir(snapsPath+"/"+v.revision+"/"+unixTime, 0755); err != nil { 188 return 189 } 190 destPath := snapsPath + "/" + v.revision + "/" + unixTime 191 192 var args []string 193 args = append(args, "-a") 194 for _, vfile := range versionedFiles { 195 args = append(args, "--exclude="+vfile) 196 } 197 198 args = append(args, "--exclude=.git/") 199 200 if _, err := os.Stat(repoPath + "/.vioignore"); err == nil { 201 args = append(args, "--filter=:-_/.vioignore") 202 } 203 204 // source 205 args = append(args, repoPath+"/") 206 207 // destination 208 args = append(args, destPath) 209 210 _, err = exec.Command("rsync", args...).CombinedOutput() 211 212 return 213 } 214 215 func addVersionToIndex(v *version, filename string) (err error) { 216 f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) 217 if err != nil { 218 return 219 } 220 221 defer f.Close() 222 223 _, err = f.WriteString(fmt.Sprintf("%v\n", v)) 224 225 return 226 } 227 228 func (b PosixBackend) GetVersions() (versions []version, err error) { 229 contents, err := ioutil.ReadFile(b.snapshotsPath + "/index") 230 if err != nil { 231 return 232 } 233 234 versions = []version{} 235 lines := strings.Split(string(contents), "\n") 236 for _, line := range lines { 237 if len(strings.TrimSpace(line)) == 0 { 238 continue 239 } 240 i := strings.Index(line, ",") 241 if i < 0 { 242 return nil, AnError{"Malformed version in index: " + line} 243 } 244 v_str := line[:i] 245 meta_str := line[i+1:] 246 247 var meta map[string]string 248 249 err = json.Unmarshal([]byte(meta_str), &meta) 250 if err != nil { 251 return 252 } 253 254 v := *NewVersionWithMeta(v_str, meta) 255 versions = append(versions, v) 256 } 257 return 258 } 259 260 func (b PosixBackend) Diff(v1 *version, v2 *version, obj string) (string, error) { 261 return "", AnError{"not yet"} 262 }