github.com/atlassian/git-lob@v0.0.0-20150806085256-2386a5ed291a/providers/smart/persistent.go (about) 1 package smart 2 3 import ( 4 "bufio" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 ) 10 11 // Transport implementation that uses a persistent connection to perform many 12 // operations in serial, without having to initiate a connection each time 13 // Most common use of this is SSH, althouth the underlying connection streams are 14 // abstracted to allow other connections if required. 15 type PersistentTransport struct { 16 // The persistent connection we're using (implemented by another class) 17 Connection io.ReadWriteCloser 18 // Buffered reader we use to scan for ends of JSON 19 BufferedReader *bufio.Reader 20 } 21 22 // Note *not* using net/rpc and net/rpc/jsonrpc because we want more control 23 // golang's rpc requires a certain method format (Object.Method) and also doesn't easily 24 // support interleaving with raw byte streams like we need to. 25 // as per http://www.jsonrpc.org/specification 26 type JsonRequest struct { 27 // Go's JSON support only uses public fields but JSON-RPC requires lower case 28 Id int 29 Method string 30 // RawMessage allows us to store late-resolved, message-specific nested types 31 // requires an extra couple of steps though; even though RawMessage is a []byte, it's not 32 // JSON itself. You need to convert JSON to/from RawMessage as well as JSON to/from the structure 33 // - see RawMessage's own UnmarshalJSON/MarshalJSON for this extra step 34 Params *json.RawMessage 35 } 36 type JsonResponse struct { 37 Id int 38 Error interface{} 39 // RawMessage allows us to store late-resolved, message-specific nested types 40 // requires an extra couple of steps though; even though RawMessage is a []byte, it's not 41 // JSON itself. You need to convert JSON to/from RawMessage as well as JSON to/from the structure 42 // - see RawMessage's own UnmarshalJSON/MarshalJSON for this extra step 43 Result *json.RawMessage 44 } 45 46 var ( 47 latestRequestId int = 1 48 ) 49 50 func NewJsonRequest(method string, params interface{}) (*JsonRequest, error) { 51 ret := &JsonRequest{ 52 Id: latestRequestId, 53 Method: method, 54 } 55 var err error 56 ret.Params, err = embedStructInJsonRawMessage(params) 57 latestRequestId++ 58 return ret, err 59 } 60 61 func NewJsonResponse(id int, result interface{}) (*JsonResponse, error) { 62 ret := &JsonResponse{ 63 Id: id, 64 } 65 var err error 66 ret.Result, err = embedStructInJsonRawMessage(result) 67 return ret, err 68 } 69 func NewJsonErrorResponse(id int, err interface{}) *JsonResponse { 70 ret := &JsonResponse{ 71 Id: id, 72 Error: err, 73 } 74 return ret 75 } 76 77 func embedStructInJsonRawMessage(in interface{}) (*json.RawMessage, error) { 78 // Encode nested struct ready for transmission so that it can be late unmarshalled at the other end 79 // Need to do this & declare as RawMessage rather than interface{} in struct otherwise unmarshalling 80 // at other end will turn it into a simple array/map 81 // Doesn't affect the wire bytes; they're still nested JSON in the same way as if you marshalled the whole struct 82 // this is just a golang method to defer resolving on unmarshal 83 ret := &json.RawMessage{} 84 innerbytes, err := json.Marshal(in) 85 if err != nil { 86 return ret, fmt.Errorf("Unable to marshal struct to JSON: %v %v", in, err.Error()) 87 } 88 err = ret.UnmarshalJSON(innerbytes) 89 if err != nil { 90 return ret, fmt.Errorf("Unable to convert JSON to RawMessage: %v %v", string(innerbytes), err.Error()) 91 } 92 93 return ret, nil 94 95 } 96 97 // Create a new persistent transport & connect 98 func NewPersistentTransport(conn io.ReadWriteCloser) *PersistentTransport { 99 return &PersistentTransport{ 100 Connection: conn, 101 BufferedReader: bufio.NewReader(conn), 102 } 103 } 104 105 type ExitRequest struct { 106 } 107 type ExitResponse struct { 108 } 109 110 // Release any resources associated with this transport (including any persostent connections) 111 func (self *PersistentTransport) Release() { 112 if self.Connection != nil { 113 // terminate server-side 114 params := ExitRequest{} 115 resp := ExitResponse{} 116 err := self.doFullJSONRequestResponse("Exit", ¶ms, &resp) 117 if err != nil { 118 fmt.Println("Problem exiting persistent transport:", err) 119 } 120 121 self.Connection.Close() 122 self.Connection = nil 123 } 124 self.BufferedReader = nil 125 } 126 127 // Perform a full JSON-RPC style call with JSON request and response 128 func (self *PersistentTransport) doFullJSONRequestResponse(method string, params interface{}, result interface{}) error { 129 130 req, err := NewJsonRequest(method, params) 131 if err != nil { 132 return err 133 } 134 err = self.sendJSONRequest(req) 135 if err != nil { 136 return err 137 } 138 err = self.readFullJSONResponse(req, result) 139 if err != nil { 140 return err 141 } 142 // result is now populated 143 return nil 144 145 } 146 147 // Perform a JSON request that results in a byte stream as a response & download to out (with callbacks if required) 148 func (self *PersistentTransport) doJSONRequestDownload(method string, params interface{}, 149 sz int64, out io.Writer, callback TransportProgressCallback) error { 150 151 req, err := NewJsonRequest(method, params) 152 if err != nil { 153 return err 154 } 155 err = self.sendJSONRequest(req) 156 if err != nil { 157 return err 158 } 159 err = self.receiveRawData(sz, out, callback) 160 if err != nil { 161 return err 162 } 163 164 return nil 165 166 } 167 168 // Late-bind a method-specific structure from the raw message 169 func ExtractStructFromJsonRawMessage(raw *json.RawMessage, out interface{}) error { 170 nestedbytes, err := raw.MarshalJSON() 171 if err != nil { 172 return fmt.Errorf("Unable to extract type-specific JSON from: %v\n%v", string(*raw), err.Error()) 173 } 174 err = json.Unmarshal(nestedbytes, &out) 175 if err != nil { 176 return fmt.Errorf("Unable to decode type-specific result: %v\n%v", string(nestedbytes), err.Error()) 177 } 178 return nil 179 180 } 181 182 // Send a JSON request but don't read any response 183 func (self *PersistentTransport) sendJSONRequest(req interface{}) error { 184 if self.Connection == nil || self.BufferedReader == nil { 185 return errors.New("Not connected") 186 } 187 188 reqbytes, err := json.Marshal(req) 189 if err != nil { 190 return fmt.Errorf("Error encoding %v to JSON: %v", err.Error()) 191 } 192 // Append the binary 0 delimiter that server uses to read up to 193 reqbytes = append(reqbytes, byte(0)) 194 _, err = self.Connection.Write(reqbytes) 195 if err != nil { 196 return fmt.Errorf("Error writing request bytes to connection: %v", err.Error()) 197 } 198 199 return nil 200 } 201 202 func (self *PersistentTransport) readJSONResponse() (*JsonResponse, error) { 203 jsonbytes, err := self.BufferedReader.ReadBytes(byte(0)) 204 if err != nil { 205 return nil, fmt.Errorf("Unable to read response from server: %v", err.Error()) 206 } 207 // remove terminator before unmarshalling 208 jsonbytes = jsonbytes[:len(jsonbytes)-1] 209 response := &JsonResponse{} 210 err = json.Unmarshal(jsonbytes, response) 211 if err != nil { 212 return nil, fmt.Errorf("Unable to decode JSON response from server: %v\n%v", string(jsonbytes), err.Error()) 213 } 214 return response, nil 215 } 216 217 // Check a response object; req can be nil, if so doesn't check that Ids match 218 func (self *PersistentTransport) checkJSONResponse(req *JsonRequest, resp *JsonResponse) error { 219 if resp.Error != nil { 220 return fmt.Errorf("Error response from server: %v", resp.Error) 221 } 222 if req != nil && req.Id != resp.Id { 223 return fmt.Errorf("Response from server has wrong Id, request: %d response: %d", req.Id, resp.Id) 224 } 225 return nil 226 } 227 228 // Read a JSON response, check it, and pull out the nested method-specific & write to result 229 // originalReq is optional and can be left nil but if supplied Ids will be checked for matching 230 func (self *PersistentTransport) readFullJSONResponse(originalReq *JsonRequest, result interface{}) error { 231 // read response (buffered) up to binary 0 which terminates JSON 232 response, err := self.readJSONResponse() 233 if err != nil { 234 return err 235 } 236 // early validation 237 err = self.checkJSONResponse(originalReq, response) 238 if err != nil { 239 return err 240 } 241 // response.Result is left as raw since it depends on the type of the expected result 242 // so now unmarshal the nested part 243 err = ExtractStructFromJsonRawMessage(response.Result, &result) 244 if err != nil { 245 return err 246 } 247 return nil 248 } 249 250 const PersistentTransportBufferSize = int64(131072) 251 252 func (self *PersistentTransport) sendRawData(sz int64, source io.Reader, callback TransportProgressCallback) error { 253 254 if sz == 0 { 255 return nil 256 } 257 258 var copysize int64 = 0 259 for { 260 c := PersistentTransportBufferSize 261 if (sz - copysize) < c { 262 c = sz - copysize 263 } 264 if c <= 0 { 265 break 266 } 267 n, err := io.CopyN(self.Connection, source, c) 268 copysize += n 269 if n > 0 && callback != nil && sz > 0 { 270 callback(copysize, sz) 271 } 272 if err != nil { 273 return err 274 } 275 } 276 if copysize != sz { 277 return fmt.Errorf("Transferred bytes did not match expected size; transferred %d, expected %d", copysize, sz) 278 } 279 280 return nil 281 } 282 283 func (self *PersistentTransport) receiveRawData(sz int64, out io.Writer, callback TransportProgressCallback) error { 284 285 if sz == 0 { 286 return nil 287 } 288 289 var copysize int64 = 0 290 for { 291 c := PersistentTransportBufferSize 292 if (sz - copysize) < c { 293 c = sz - copysize 294 } 295 if c <= 0 { 296 break 297 } 298 // Must read from buffered reader consistently 299 n, err := io.CopyN(out, self.BufferedReader, c) 300 copysize += n 301 if n > 0 && callback != nil && sz > 0 { 302 callback(copysize, sz) 303 } 304 if err != nil { 305 return err 306 } 307 } 308 if copysize != sz { 309 return fmt.Errorf("Transferred bytes did not match expected size; transferred %d, expected %d", copysize, sz) 310 } 311 312 return nil 313 } 314 315 // Just a specially identified persistent connection error so we can re-try 316 type ConnectionError error 317 318 type QueryCapsRequest struct { 319 } 320 321 type QueryCapsResponse struct { 322 Caps []string 323 } 324 325 // Ask the server for a list of capabilities 326 func (self *PersistentTransport) QueryCaps() ([]string, error) { 327 params := QueryCapsRequest{} 328 resp := QueryCapsResponse{} 329 err := self.doFullJSONRequestResponse("QueryCaps", ¶ms, &resp) 330 if err != nil { 331 return nil, err 332 } 333 return resp.Caps, nil 334 } 335 336 type SetEnabledCapsRequest struct { 337 EnableCaps []string 338 } 339 type SetEnabledCapsResponse struct { 340 } 341 342 // Request that the server enable capabilities for this exchange (note, non-persistent transports can store & send this with every request) 343 func (self *PersistentTransport) SetEnabledCaps(caps []string) error { 344 params := SetEnabledCapsRequest{EnableCaps: caps} 345 resp := SetEnabledCapsResponse{} 346 err := self.doFullJSONRequestResponse("SetEnabledCaps", ¶ms, &resp) 347 if err != nil { 348 return err 349 } 350 return nil 351 } 352 353 type FileExistsRequest struct { 354 LobSHA string 355 Type string 356 ChunkIdx int 357 } 358 type FileExistsResponse struct { 359 Exists bool 360 Size int64 361 } 362 363 // Return whether LOB metadata exists on the server 364 func (self *PersistentTransport) MetadataExists(lobsha string) (bool, int64, error) { 365 params := FileExistsRequest{ 366 LobSHA: lobsha, 367 Type: "meta", 368 } 369 resp := FileExistsResponse{} 370 err := self.doFullJSONRequestResponse("FileExists", ¶ms, &resp) 371 if err != nil { 372 return false, 0, err 373 } 374 return resp.Exists, resp.Size, nil 375 } 376 377 // Return whether LOB chunk content exists on the server 378 func (self *PersistentTransport) ChunkExists(lobsha string, chunk int) (bool, int64, error) { 379 params := FileExistsRequest{ 380 LobSHA: lobsha, 381 Type: "chunk", 382 ChunkIdx: chunk, 383 } 384 resp := FileExistsResponse{} 385 err := self.doFullJSONRequestResponse("FileExists", ¶ms, &resp) 386 if err != nil { 387 return false, 0, err 388 } 389 return resp.Exists, resp.Size, nil 390 } 391 392 type FileExistsOfSizeRequest struct { 393 LobSHA string 394 Type string 395 ChunkIdx int 396 Size int64 397 } 398 type FileExistsOfSizeResponse struct { 399 Result bool 400 } 401 402 // Return whether LOB chunk content exists on the server, and is of a specific size 403 func (self *PersistentTransport) ChunkExistsAndIsOfSize(lobsha string, chunk int, sz int64) (bool, error) { 404 params := FileExistsOfSizeRequest{ 405 LobSHA: lobsha, 406 Type: "chunk", 407 ChunkIdx: chunk, 408 Size: sz, 409 } 410 resp := FileExistsOfSizeResponse{} 411 err := self.doFullJSONRequestResponse("FileExistsOfSize", ¶ms, &resp) 412 if err != nil { 413 return false, err 414 } 415 return resp.Result, nil 416 } 417 418 type LOBExistsRequest struct { 419 LobSHA string 420 } 421 type LOBExistsResponse struct { 422 Exists bool 423 Size int64 424 } 425 426 // Return whether LOB exists in entirety on the server 427 func (self *PersistentTransport) LOBExists(lobsha string) (bool, int64, error) { 428 params := LOBExistsRequest{ 429 LobSHA: lobsha, 430 } 431 resp := LOBExistsResponse{} 432 err := self.doFullJSONRequestResponse("LOBExists", ¶ms, &resp) 433 if err != nil { 434 return false, 0, err 435 } 436 return resp.Exists, resp.Size, nil 437 } 438 439 type UploadFileRequest struct { 440 LobSHA string 441 Type string 442 ChunkIdx int 443 Size int64 444 } 445 type UploadFileStartResponse struct { 446 OKToSend bool 447 } 448 type UploadFileCompleteResponse struct { 449 ReceivedOK bool 450 } 451 452 // Upload metadata for a LOB (from a stream); no progress callback as very small 453 func (self *PersistentTransport) UploadMetadata(lobsha string, sz int64, data io.Reader) error { 454 params := UploadFileRequest{ 455 LobSHA: lobsha, 456 Type: "meta", 457 Size: sz, 458 } 459 resp := UploadFileStartResponse{} 460 err := self.doFullJSONRequestResponse("UploadFile", ¶ms, &resp) 461 if err != nil { 462 return fmt.Errorf("Error while uploading metadata for %v (while sending UploadFile JSON request): %v", lobsha, err.Error()) 463 } 464 if resp.OKToSend { 465 // Send that data (all at once, metafiles aren't big) 466 err = self.sendRawData(sz, data, nil) 467 if err != nil { 468 return fmt.Errorf("Error while uploading metadata for %v (while sending raw content): %v", lobsha, err.Error()) 469 } 470 // Now read response to sent data 471 received := UploadFileCompleteResponse{} 472 err = self.readFullJSONResponse(nil, &received) 473 if err != nil { 474 return fmt.Errorf("Error while uploading metadata for %v (response to raw content): %v", lobsha, err.Error()) 475 } 476 if !received.ReceivedOK { 477 return fmt.Errorf("Data not fully received while uploading metadata for %v: Unknown server error", lobsha) 478 } 479 480 } else { 481 return fmt.Errorf("Server rejected request to upload metadata for %v (no other error)", lobsha) 482 } 483 return nil 484 } 485 486 // Upload chunk content for a LOB (from a stream); must call back progress 487 func (self *PersistentTransport) UploadChunk(lobsha string, chunk int, sz int64, data io.Reader, callback TransportProgressCallback) error { 488 params := UploadFileRequest{ 489 LobSHA: lobsha, 490 Type: "chunk", 491 ChunkIdx: chunk, 492 Size: sz, 493 } 494 resp := UploadFileStartResponse{} 495 err := self.doFullJSONRequestResponse("UploadFile", ¶ms, &resp) 496 if err != nil { 497 return fmt.Errorf("Error while uploading chunk %d for %v (while sending UploadFile JSON request): %v", chunk, lobsha, err.Error()) 498 } 499 if resp.OKToSend { 500 // Send data, this does it in batches and calls back 501 err = self.sendRawData(sz, data, callback) 502 if err != nil { 503 return fmt.Errorf("Error while uploading chunk %d for %v (while sending raw content): %v", chunk, lobsha, err.Error()) 504 } 505 // Now read response to sent data 506 received := UploadFileCompleteResponse{} 507 err = self.readFullJSONResponse(nil, &received) 508 if err != nil { 509 return fmt.Errorf("Error while uploading chunk %d for %v (response to raw content): %v", chunk, lobsha, err.Error()) 510 } 511 if !received.ReceivedOK { 512 return fmt.Errorf("Data not fully received while uploading chunk %d for %v: Unknown server error", chunk, lobsha) 513 } 514 515 } else { 516 return fmt.Errorf("Server rejected request to upload chunk %d for %v (no other error)", chunk, lobsha) 517 } 518 return nil 519 } 520 521 type DownloadFilePrepareRequest struct { 522 LobSHA string 523 Type string 524 ChunkIdx int 525 } 526 type DownloadFilePrepareResponse struct { 527 Size int64 528 } 529 type DownloadFileStartRequest struct { 530 LobSHA string 531 Type string 532 ChunkIdx int 533 Size int64 534 } 535 536 // Download metadata for a LOB (to a stream); no progress callback as very small 537 func (self *PersistentTransport) DownloadMetadata(lobsha string, out io.Writer) error { 538 prepparams := DownloadFilePrepareRequest{ 539 LobSHA: lobsha, 540 Type: "meta", 541 } 542 resp := DownloadFilePrepareResponse{} 543 err := self.doFullJSONRequestResponse("DownloadFilePrepare", &prepparams, &resp) 544 if err != nil { 545 return fmt.Errorf("Error while downloading metadata for %v (while sending DownloadFilePrepare JSON request): %v", lobsha, err.Error()) 546 } 547 startparams := DownloadFileStartRequest{ 548 LobSHA: lobsha, 549 Type: "meta", 550 Size: resp.Size, 551 } 552 553 // Response is just raw byte data - no callback as small enough not to need one 554 err = self.doJSONRequestDownload("DownloadFileStart", &startparams, resp.Size, out, nil) 555 if err != nil { 556 return fmt.Errorf("Error while downloading metadata for %v (during download): %v", lobsha, err.Error()) 557 } 558 559 return nil 560 } 561 562 // Download chunk content for a LOB (from a stream); must call back progress 563 // This is a non-delta download operation, just provide entire chunk content 564 func (self *PersistentTransport) DownloadChunk(lobsha string, chunk int, out io.Writer, callback TransportProgressCallback) error { 565 prepparams := DownloadFilePrepareRequest{ 566 LobSHA: lobsha, 567 Type: "chunk", 568 ChunkIdx: chunk, 569 } 570 resp := DownloadFilePrepareResponse{} 571 err := self.doFullJSONRequestResponse("DownloadFilePrepare", &prepparams, &resp) 572 if err != nil { 573 return fmt.Errorf("Error while downloading chunk %d for %v (while sending DownloadFilePrepare JSON request): %v", chunk, lobsha, err.Error()) 574 } 575 startparams := DownloadFileStartRequest{ 576 LobSHA: lobsha, 577 Type: "chunk", 578 ChunkIdx: chunk, 579 Size: resp.Size, 580 } 581 582 // Response is just raw byte data - no callback as small enough not to need one 583 err = self.doJSONRequestDownload("DownloadFileStart", &startparams, resp.Size, out, callback) 584 if err != nil { 585 return fmt.Errorf("Error while downloading chunk %d for %v (during download): %v", chunk, lobsha, err.Error()) 586 } 587 588 return nil 589 590 } 591 592 type GetFirstCompleteLOBFromListRequest struct { 593 LobSHAs []string 594 } 595 type GetFirstCompleteLOBFromListResponse struct { 596 FirstSHA string 597 } 598 599 // Return the LOB which the server has a complete copy of, from a list of candidates 600 // Server must test in the order provided & return the earliest one which is complete on the server 601 // Server doesn't have to test full integrity of LOB, just completeness (check size against meta) 602 // Return a blank string if none are available 603 func (self *PersistentTransport) GetFirstCompleteLOBFromList(candidateSHAs []string) (string, error) { 604 params := GetFirstCompleteLOBFromListRequest{candidateSHAs} 605 resp := GetFirstCompleteLOBFromListResponse{} 606 err := self.doFullJSONRequestResponse("PickCompleteLOB", ¶ms, &resp) 607 if err != nil { 608 return "", fmt.Errorf("Error asking server for first LOB from list %v: %v", candidateSHAs, err.Error()) 609 } 610 return resp.FirstSHA, nil 611 } 612 613 type UploadDeltaRequest struct { 614 BaseLobSHA string 615 TargetLobSHA string 616 Size int64 617 } 618 type UploadDeltaStartResponse struct { 619 OKToSend bool 620 } 621 type UploadDeltaCompleteResponse struct { 622 ReceivedOK bool 623 } 624 625 // Upload a binary delta to apply against a LOB the server already has, to generate a new LOB 626 // Deltas apply to whole LOB content and are not per-chunk 627 // Returns a boolean to determine whether the upload was accepted or not (server may prefer not to accept, not an error) 628 // In the case of false return, client will fall back to non-delta upload. 629 // On true, server must return nil error only after data is fully received, applied, saved as targetSHA and the 630 // integrity confirmed by recalculating the SHA of the final patched data. 631 // This only does the content, not the metadata; you should have already called UploadMetadata beforehand. 632 func (self *PersistentTransport) UploadDelta(baseSHA, targetSHA string, deltaSize int64, data io.Reader, callback TransportProgressCallback) (bool, error) { 633 params := UploadDeltaRequest{ 634 BaseLobSHA: baseSHA, 635 TargetLobSHA: targetSHA, 636 Size: deltaSize, 637 } 638 resp := UploadDeltaStartResponse{} 639 err := self.doFullJSONRequestResponse("UploadDelta", ¶ms, &resp) 640 if err != nil { 641 return false, fmt.Errorf("Error calling UploadDelta JSON request from %v to %v: %v", baseSHA, targetSHA, err.Error()) 642 } 643 // Server can opt not to accept the delta, caller should fall back to simpler upload if so 644 var sentOK bool 645 if resp.OKToSend { 646 // Send data, this does it in batches and calls back 647 err = self.sendRawData(deltaSize, data, callback) 648 if err != nil { 649 return false, fmt.Errorf("Error uploading delta content from %v to %v: %v", baseSHA, targetSHA, err.Error()) 650 } 651 // Now read response to sent data 652 received := UploadDeltaCompleteResponse{} 653 err = self.readFullJSONResponse(nil, &received) 654 if err != nil { 655 return false, fmt.Errorf("Error in UploadDelta from %v to %v (response to raw content): %v", baseSHA, targetSHA, err.Error()) 656 } 657 if !received.ReceivedOK { 658 return false, fmt.Errorf("Data not fully received in UploadDelta from %v to %v: Unknown server error", baseSHA, targetSHA) 659 } 660 sentOK = true 661 662 } 663 return sentOK, nil 664 } 665 666 type DownloadDeltaPrepareRequest struct { 667 BaseLobSHA string 668 TargetLobSHA string 669 } 670 type DownloadDeltaPrepareResponse struct { 671 Size int64 672 } 673 type DownloadDeltaStartRequest struct { 674 BaseLobSHA string 675 TargetLobSHA string 676 Size int64 677 } 678 679 // Prepare a binary delta between 2 LOBs and report the size 680 func (self *PersistentTransport) DownloadDeltaPrepare(baseSHA, targetSHA string) (int64, error) { 681 prepparams := DownloadDeltaPrepareRequest{ 682 BaseLobSHA: baseSHA, 683 TargetLobSHA: targetSHA, 684 } 685 resp := DownloadDeltaPrepareResponse{} 686 err := self.doFullJSONRequestResponse("DownloadDeltaPrepare", &prepparams, &resp) 687 if err != nil { 688 return 0, fmt.Errorf("Error in DownloadDeltaPrepare from %v to %v: %v", baseSHA, targetSHA, err.Error()) 689 } 690 return resp.Size, nil 691 } 692 693 // Generate and download a binary delta that the client can apply locally to generate a new LOB 694 // Deltas apply to whole LOB content and are not per-chunk 695 // The server should respect sizeLimit and if the delta is larger than that, abandon the process 696 // Return a bool to indicate whether the delta went ahead or not (client will fall back to non-delta on false) 697 func (self *PersistentTransport) DownloadDelta(baseSHA, targetSHA string, sizeLimit int64, out io.Writer, callback TransportProgressCallback) (bool, error) { 698 sz, err := self.DownloadDeltaPrepare(baseSHA, targetSHA) 699 if err != nil { 700 return false, err 701 } 702 if sz > sizeLimit { 703 // delta is too big 704 return false, nil 705 } 706 startparams := DownloadDeltaStartRequest{ 707 BaseLobSHA: baseSHA, 708 TargetLobSHA: targetSHA, 709 Size: sz, 710 } 711 712 // Response is just raw byte data - no callback as small enough not to need one 713 err = self.doJSONRequestDownload("DownloadDeltaStart", &startparams, sz, out, callback) 714 if err != nil { 715 return false, fmt.Errorf("Error while downloading LOB delta from %v to %v: %v", baseSHA, targetSHA, err.Error()) 716 } 717 // It's up to the caller to apply the delta 718 return true, nil 719 }