github.com/atlassian/git-lob@v0.0.0-20150806085256-2386a5ed291a/git-lob-serve/serverstore.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 11 "github.com/atlassian/git-lob/core" 12 "github.com/atlassian/git-lob/providers/smart" 13 "github.com/atlassian/git-lob/util" 14 ) 15 16 // A server could choose to store LOBs however it likes 17 // For simplicity, this server chooses to store the LOB files in the same structure as the client does, 18 // with the addition that it also stores cached binary deltas. 19 20 // We re-use a bunch of the client code here for storage and utility functions but it's important to realise 21 // that a server implementation doesn't have to adhere to the same rules as the client, it only has to 22 // implement the smart protocol. The re-use here is simply to avoid code duplication given that we're storing 23 // in the same structure, and is not a requirement for any alternative server implementations. 24 25 // Get the absolute path to the root directory containing LOB files for the config & path 26 // Does not create the directory nor validate that config is correct 27 func getLOBRoot(config *Config, path string) string { 28 return filepath.Join(config.BasePath, path) 29 } 30 31 // Get the absolute path of a LOB chunk file 32 // Does not create the directory nor validate that config is correct 33 func getLOBChunkFilePath(sha string, chunk int, config *Config, path string) string { 34 return filepath.Join(getLOBRoot(config, path), core.GetLOBChunkRelativePath(sha, chunk)) 35 } 36 37 // Get the absolute path of a LOB meta file 38 // Does not create the directory nor validate that config is correct 39 func getLOBMetaFilePath(sha string, config *Config, path string) string { 40 return filepath.Join(getLOBRoot(config, path), core.GetLOBMetaRelativePath(sha)) 41 } 42 43 // Generic method to get file path based on type (meta/chunk) 44 // Does not create the directory nor validate that config is correct 45 func getLOBFilePath(sha, filetype string, chunk int, config *Config, path string) string { 46 if filetype == "chunk" { 47 return getLOBChunkFilePath(sha, chunk, config, path) 48 } else if filetype == "meta" { 49 return getLOBMetaFilePath(sha, config, path) 50 } 51 // error 52 return "" 53 } 54 55 // Gets the path to a file which contains delta from one sha to another 56 func getLOBDeltaFilePath(basesha, targetsha string, config *Config, path string) string { 57 return filepath.Join(config.DeltaCachePath, fmt.Sprintf("%v_%v", basesha, targetsha)) 58 } 59 60 func fileExists(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 61 freq := smart.FileExistsRequest{} 62 err := smart.ExtractStructFromJsonRawMessage(req.Params, &freq) 63 if err != nil { 64 return smart.NewJsonErrorResponse(req.Id, err.Error()) 65 } 66 result := smart.FileExistsResponse{} 67 file := getLOBFilePath(freq.LobSHA, freq.Type, freq.ChunkIdx, config, path) 68 if file == "" { 69 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Unsupported file type: %v", freq.Type)) 70 } 71 s, err := os.Stat(file) 72 if err == nil { 73 result.Exists = true 74 result.Size = s.Size() 75 } // otherwise defaults false/0 76 77 resp, err := smart.NewJsonResponse(req.Id, result) 78 if err != nil { 79 return smart.NewJsonErrorResponse(req.Id, err.Error()) 80 } 81 return resp 82 } 83 84 func fileExistsOfSize(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 85 freq := smart.FileExistsOfSizeRequest{} 86 err := smart.ExtractStructFromJsonRawMessage(req.Params, &freq) 87 if err != nil { 88 return smart.NewJsonErrorResponse(req.Id, err.Error()) 89 } 90 result := smart.FileExistsOfSizeResponse{} 91 file := getLOBFilePath(freq.LobSHA, freq.Type, freq.ChunkIdx, config, path) 92 if file == "" { 93 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Unsupported file type: %v", freq.Type)) 94 } 95 96 result.Result = util.FileExistsAndIsOfSize(file, freq.Size) 97 98 resp, err := smart.NewJsonResponse(req.Id, result) 99 if err != nil { 100 return smart.NewJsonErrorResponse(req.Id, err.Error()) 101 } 102 return resp 103 } 104 105 func ensureDirExists(dir string, cfg *Config) error { 106 if !util.DirExists(dir) { 107 // Get permissions from base path & match (or default to user/group write) 108 mode := os.FileMode(0775) 109 s, err := os.Stat(cfg.BasePath) 110 if err == nil { 111 mode = s.Mode() 112 } 113 return os.MkdirAll(dir, mode) 114 } 115 return nil 116 } 117 118 const transferBufferSize = int64(128 * 1024) 119 120 func uploadFile(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 121 upreq := smart.UploadFileRequest{} 122 err := smart.ExtractStructFromJsonRawMessage(req.Params, &upreq) 123 if err != nil { 124 return smart.NewJsonErrorResponse(req.Id, err.Error()) 125 } 126 startresult := smart.UploadFileStartResponse{} 127 startresult.OKToSend = true 128 // Send start response immediately 129 resp, err := smart.NewJsonResponse(req.Id, startresult) 130 if err != nil { 131 return smart.NewJsonErrorResponse(req.Id, err.Error()) 132 } 133 err = sendResponse(resp, out) 134 if err != nil { 135 return smart.NewJsonErrorResponse(req.Id, err.Error()) 136 } 137 // Next from client should be byte stream of exactly the stated number of bytes 138 // Write to temporary file then move to final on success 139 file := getLOBFilePath(upreq.LobSHA, upreq.Type, upreq.ChunkIdx, config, path) 140 if file == "" { 141 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Unsupported file type: %v", upreq.Type)) 142 } 143 144 // Now open temp file to write to 145 outf, err := ioutil.TempFile("", "tempchunk") 146 defer outf.Close() 147 n, err := io.CopyN(outf, in, upreq.Size) 148 if err != nil { 149 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Unable to read data: %v", err.Error())) 150 } else if n != upreq.Size { 151 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Received wrong number of bytes %d (expected %d)", n, upreq.Size)) 152 } 153 154 receivedresult := smart.UploadFileCompleteResponse{} 155 receivedresult.ReceivedOK = true 156 var receiveerr string 157 // force close now before defer so we can copy 158 err = outf.Close() 159 if err != nil { 160 receivedresult.ReceivedOK = false 161 receiveerr = fmt.Sprintf("Error when closing temp file: %v", err.Error()) 162 } else { 163 // ensure final directory exists 164 ensureDirExists(filepath.Dir(file), config) 165 // Move temp file to final location 166 err = os.Rename(outf.Name(), file) 167 if err != nil { 168 receivedresult.ReceivedOK = false 169 receiveerr = fmt.Sprintf("Error when closing temp file: %v", err.Error()) 170 } 171 172 } 173 174 resp, _ = smart.NewJsonResponse(req.Id, receivedresult) 175 if receiveerr != "" { 176 resp.Error = receiveerr 177 } 178 179 return resp 180 181 } 182 183 func downloadFilePrepare(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 184 downreq := smart.DownloadFilePrepareRequest{} 185 err := smart.ExtractStructFromJsonRawMessage(req.Params, &downreq) 186 if err != nil { 187 return smart.NewJsonErrorResponse(req.Id, err.Error()) 188 } 189 file := getLOBFilePath(downreq.LobSHA, downreq.Type, downreq.ChunkIdx, config, path) 190 if file == "" { 191 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Unsupported file type: %v", downreq.Type)) 192 } 193 result := smart.DownloadFilePrepareResponse{} 194 s, err := os.Stat(file) 195 if err != nil { 196 // file doesn't exist, this should not have been called 197 return smart.NewJsonErrorResponse(req.Id, "File doesn't exist") 198 } 199 result.Size = s.Size() 200 resp, err := smart.NewJsonResponse(req.Id, result) 201 if err != nil { 202 return smart.NewJsonErrorResponse(req.Id, err.Error()) 203 } 204 return resp 205 206 } 207 208 func downloadFileStart(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 209 downreq := smart.DownloadFileStartRequest{} 210 err := smart.ExtractStructFromJsonRawMessage(req.Params, &downreq) 211 if err != nil { 212 // Serve() copes with converting this to stderr rather than JSON response 213 return smart.NewJsonErrorResponse(req.Id, err.Error()) 214 } 215 file := getLOBFilePath(downreq.LobSHA, downreq.Type, downreq.ChunkIdx, config, path) 216 if file == "" { 217 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Unsupported file type: %v", downreq.Type)) 218 } 219 // check size 220 s, err := os.Stat(file) 221 if err != nil { 222 // file doesn't exist, this should not have been called 223 return smart.NewJsonErrorResponse(req.Id, "File doesn't exist") 224 } 225 if s.Size() != downreq.Size { 226 // This won't work! 227 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("File sizes disagree (client: %d server: %d)", downreq.Size, s.Size())) 228 } 229 230 f, err := os.OpenFile(file, os.O_RDONLY, 0644) 231 if err != nil { 232 return smart.NewJsonErrorResponse(req.Id, err.Error()) 233 } 234 defer f.Close() 235 236 n, err := io.Copy(out, f) 237 if err != nil { 238 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Error copying data to output: %v", err.Error())) 239 } 240 if n != s.Size() { 241 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Amount of data copied disagrees (expected: %d actual: %d)", s.Size(), n)) 242 } 243 if err != nil { 244 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Error copying data to output: %v", err.Error())) 245 } 246 if n != s.Size() { 247 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Amount of data copied disagrees (expected: %d actual: %d)", s.Size(), n)) 248 } 249 250 // Don't return a response, only response is byte stream above except in error cases 251 return nil 252 } 253 254 func pickCompleteLOB(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 255 params := smart.GetFirstCompleteLOBFromListRequest{} 256 err := smart.ExtractStructFromJsonRawMessage(req.Params, ¶ms) 257 if err != nil { 258 return smart.NewJsonErrorResponse(req.Id, err.Error()) 259 } 260 result := smart.GetFirstCompleteLOBFromListResponse{} 261 for _, candidatesha := range params.LobSHAs { 262 // We need to stop on the first valid & complete SHA 263 // Only checking presence & size here, not checking hash 264 if core.CheckLOBFilesForSHA(candidatesha, getLOBRoot(config, path), false) == nil { 265 result.FirstSHA = candidatesha 266 break 267 } 268 269 } 270 // If we didn't find any, result.FirstSHA = "" which is correct per protocol 271 resp, err := smart.NewJsonResponse(req.Id, result) 272 if err != nil { 273 return smart.NewJsonErrorResponse(req.Id, err.Error()) 274 } 275 return resp 276 } 277 278 func lobExists(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 279 params := smart.LOBExistsRequest{} 280 err := smart.ExtractStructFromJsonRawMessage(req.Params, ¶ms) 281 if err != nil { 282 return smart.NewJsonErrorResponse(req.Id, err.Error()) 283 } 284 result := smart.LOBExistsResponse{} 285 _, sz, err := core.GetLOBFilesForSHA(params.LobSHA, getLOBRoot(config, path), true, false) 286 // in the case of error, assume missing so return default false 287 if err == nil { 288 result.Exists = true 289 result.Size = sz 290 } 291 resp, err := smart.NewJsonResponse(req.Id, result) 292 if err != nil { 293 return smart.NewJsonErrorResponse(req.Id, err.Error()) 294 } 295 return resp 296 } 297 298 func uploadDelta(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 299 upreq := smart.UploadDeltaRequest{} 300 err := smart.ExtractStructFromJsonRawMessage(req.Params, &upreq) 301 if err != nil { 302 return smart.NewJsonErrorResponse(req.Id, err.Error()) 303 } 304 startresult := smart.UploadDeltaStartResponse{} 305 startresult.OKToSend = true 306 if upreq.Size > config.DeltaSizeLimit { 307 // reject this, cause client to fall back 308 startresult.OKToSend = false 309 resp, err := smart.NewJsonResponse(req.Id, startresult) 310 if err != nil { 311 return smart.NewJsonErrorResponse(req.Id, err.Error()) 312 } 313 return resp 314 } 315 316 // Otherwise continue 317 // Send start response immediately 318 resp, err := smart.NewJsonResponse(req.Id, startresult) 319 if err != nil { 320 return smart.NewJsonErrorResponse(req.Id, err.Error()) 321 } 322 err = sendResponse(resp, out) 323 if err != nil { 324 return smart.NewJsonErrorResponse(req.Id, err.Error()) 325 } 326 // Next from client should be byte stream of exactly the stated number of bytes 327 // Write to temporary file then move to final on success 328 outf, err := ioutil.TempFile("", "tempchunk") 329 if err != nil { 330 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Error when opening temp file: %v", err.Error())) 331 } 332 // If any errors, delete the temp file automatically (will fail silently if already moved) 333 defer os.Remove(outf.Name()) 334 defer outf.Close() 335 n, err := io.CopyN(outf, in, upreq.Size) 336 if err != nil { 337 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Unable to read data: %v", err.Error())) 338 } else if n != upreq.Size { 339 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Received wrong number of bytes %d (expected %d)", n, upreq.Size)) 340 } 341 342 receivedresult := smart.UploadDeltaCompleteResponse{} 343 receivedresult.ReceivedOK = true 344 // force close now before defer so we can copy, if this works 345 tempdeltafilename := outf.Name() 346 err = outf.Close() 347 348 if err != nil { 349 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Error when closing temp file: %v", err.Error())) 350 } 351 352 // Apply the patch from the temp file, to make sure it applies ok 353 // Other servers might choose just to store the delta and to not store the applied result, but we will 354 // we sacrifice some data storage for saved CPU work later 355 indeltaf, err := os.OpenFile(tempdeltafilename, os.O_RDONLY, 0644) 356 if err != nil { 357 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Error re-opening delta file for apply: %v", err.Error())) 358 } 359 defer indeltaf.Close() 360 lobroot := getLOBRoot(config, path) 361 ensureDirExists(lobroot, config) 362 err = core.ApplyLOBDeltaInBaseDir(lobroot, upreq.BaseLobSHA, upreq.TargetLobSHA, indeltaf) 363 if err != nil { 364 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Error when applying delta: %v", err.Error())) 365 } 366 367 // Now save the delta so we can use it later on in DownloadDelta for other clients 368 // Ignore any errors on renaming, just means it won't be in the cache (inconvenient but not fatal, temp will be deleted on return) 369 file := getLOBDeltaFilePath(upreq.BaseLobSHA, upreq.TargetLobSHA, config, path) 370 if file != "" { 371 // ensure final directory exists 372 ensureDirExists(filepath.Dir(file), config) 373 // Move temp file to final location 374 // We keep all deltas, we can use them to send to clients too (saves calculating) 375 // Should have a cron which deletes old ones 376 os.Rename(outf.Name(), file) 377 } 378 379 resp, err = smart.NewJsonResponse(req.Id, receivedresult) 380 if err != nil { 381 return smart.NewJsonErrorResponse(req.Id, err.Error()) 382 } 383 return resp 384 385 } 386 387 func downloadDeltaPrepare(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 388 downreq := smart.DownloadDeltaPrepareRequest{} 389 err := smart.ExtractStructFromJsonRawMessage(req.Params, &downreq) 390 if err != nil { 391 return smart.NewJsonErrorResponse(req.Id, err.Error()) 392 } 393 result := smart.DownloadDeltaPrepareResponse{} 394 // First see if we have this delta in the cache already 395 deltafile := getLOBDeltaFilePath(downreq.BaseLobSHA, downreq.TargetLobSHA, config, path) 396 s, err := os.Stat(deltafile) 397 if err == nil { 398 result.Size = s.Size() 399 } else { 400 // either there was no cache file or we need to regen 401 lobroot := getLOBRoot(config, path) 402 var deltabuf bytes.Buffer 403 sz, err := core.GenerateLOBDeltaInBaseDir(lobroot, downreq.BaseLobSHA, downreq.TargetLobSHA, &deltabuf) 404 if err != nil { 405 return smart.NewJsonErrorResponse(req.Id, err.Error()) 406 } 407 result.Size = sz 408 409 // Write this delta to cache, via temp + rename to ensure not interrupted 410 tempf, err := ioutil.TempFile("", "deltatemp") 411 if err == nil { 412 defer os.Remove(tempf.Name()) // in case any errors 413 n, err := tempf.Write(deltabuf.Bytes()) 414 tempf.Close() 415 if err == nil && n == deltabuf.Len() { 416 // only rename to final if correct size & no errors (don't want to bake incorrect delta 417 // don't check error here, if it doesn't work we just don't store in cache (and defer deletes)) 418 os.Rename(tempf.Name(), deltafile) 419 } 420 } 421 } 422 423 resp, err := smart.NewJsonResponse(req.Id, result) 424 if err != nil { 425 return smart.NewJsonErrorResponse(req.Id, err.Error()) 426 } 427 return resp 428 } 429 func downloadDeltaStart(req *smart.JsonRequest, in io.Reader, out io.Writer, config *Config, path string) *smart.JsonResponse { 430 downreq := smart.DownloadDeltaStartRequest{} 431 err := smart.ExtractStructFromJsonRawMessage(req.Params, &downreq) 432 if err != nil { 433 return smart.NewJsonErrorResponse(req.Id, err.Error()) 434 } 435 deltafile := getLOBDeltaFilePath(downreq.BaseLobSHA, downreq.TargetLobSHA, config, path) 436 if !util.FileExistsAndIsOfSize(deltafile, downreq.Size) { 437 // Caller will turn this into stderr output 438 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Delta file for %v/%v is not present or is wrong size (not %d), cannot send. Did you call 'prepare'?", 439 downreq.BaseLobSHA, downreq.TargetLobSHA, downreq.Size)) 440 } 441 442 deltaf, err := os.OpenFile(deltafile, os.O_RDONLY, 0644) 443 if err != nil { 444 return smart.NewJsonErrorResponse(req.Id, err) 445 } 446 n, err := io.Copy(out, deltaf) 447 if err != nil { 448 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Error copying delta data to output: %v", err.Error())) 449 } 450 if n != downreq.Size { 451 return smart.NewJsonErrorResponse(req.Id, fmt.Sprintf("Amount of delta data copied disagrees (expected: %d actual: %d)", downreq.Size, n)) 452 } 453 454 // There is no response, just data above 455 return nil 456 }