github.com/ZuluSpl0it/Sia@v1.3.7/node/api/renter.go (about) 1 package api 2 3 import ( 4 "fmt" 5 "net/http" 6 "path/filepath" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/NebulousLabs/Sia/build" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/modules/renter" 14 "github.com/NebulousLabs/Sia/types" 15 16 "github.com/julienschmidt/httprouter" 17 ) 18 19 var ( 20 // recommendedHosts is the number of hosts that the renter will form 21 // contracts with if the value is not specified explicitly in the call to 22 // SetSettings. 23 recommendedHosts = build.Select(build.Var{ 24 Standard: uint64(50), 25 Dev: uint64(2), 26 Testing: uint64(1), 27 }).(uint64) 28 29 // requiredHosts specifies the minimum number of hosts that must be set in 30 // the renter settings for the renter settings to be valid. This minimum is 31 // there to prevent users from shooting themselves in the foot. 32 requiredHosts = build.Select(build.Var{ 33 Standard: uint64(20), 34 Dev: uint64(1), 35 Testing: uint64(1), 36 }).(uint64) 37 38 // requiredParityPieces specifies the minimum number of parity pieces that 39 // must be used when uploading a file. This minimum exists to prevent users 40 // from shooting themselves in the foot. 41 requiredParityPieces = build.Select(build.Var{ 42 Standard: int(12), 43 Dev: int(0), 44 Testing: int(0), 45 }).(int) 46 47 // requiredRedundancy specifies the minimum redundancy that will be 48 // accepted by the renter when uploading a file. This minimum exists to 49 // prevent users from shooting themselves in the foot. 50 requiredRedundancy = build.Select(build.Var{ 51 Standard: float64(2), 52 Dev: float64(1), 53 Testing: float64(1), 54 }).(float64) 55 56 // requiredRenewWindow establishes the minimum allowed renew window for the 57 // renter settings. This minimum is here to prevent users from shooting 58 // themselves in the foot. 59 requiredRenewWindow = build.Select(build.Var{ 60 Standard: types.BlockHeight(288), 61 Dev: types.BlockHeight(1), 62 Testing: types.BlockHeight(1), 63 }).(types.BlockHeight) 64 ) 65 66 type ( 67 // RenterGET contains various renter metrics. 68 RenterGET struct { 69 Settings modules.RenterSettings `json:"settings"` 70 FinancialMetrics modules.ContractorSpending `json:"financialmetrics"` 71 CurrentPeriod types.BlockHeight `json:"currentperiod"` 72 } 73 74 // RenterContract represents a contract formed by the renter. 75 RenterContract struct { 76 // Amount of contract funds that have been spent on downloads. 77 DownloadSpending types.Currency `json:"downloadspending"` 78 // Block height that the file contract ends on. 79 EndHeight types.BlockHeight `json:"endheight"` 80 // Fees paid in order to form the file contract. 81 Fees types.Currency `json:"fees"` 82 // Public key of the host the contract was formed with. 83 HostPublicKey types.SiaPublicKey `json:"hostpublickey"` 84 // ID of the file contract. 85 ID types.FileContractID `json:"id"` 86 // A signed transaction containing the most recent contract revision. 87 LastTransaction types.Transaction `json:"lasttransaction"` 88 // Address of the host the file contract was formed with. 89 NetAddress modules.NetAddress `json:"netaddress"` 90 // Remaining funds left for the renter to spend on uploads & downloads. 91 RenterFunds types.Currency `json:"renterfunds"` 92 // Size of the file contract, which is typically equal to the number of 93 // bytes that have been uploaded to the host. 94 Size uint64 `json:"size"` 95 // Block height that the file contract began on. 96 StartHeight types.BlockHeight `json:"startheight"` 97 // Amount of contract funds that have been spent on storage. 98 StorageSpending types.Currency `json:"storagespending"` 99 // DEPRECATED: This is the exact same value as StorageSpending, but it has 100 // incorrect capitalization. This was fixed in 1.3.2, but this field is kept 101 // to preserve backwards compatibility on clients who depend on the 102 // incorrect capitalization. This field will be removed in the future, so 103 // clients should switch to the StorageSpending field (above) with the 104 // correct lowercase name. 105 StorageSpendingDeprecated types.Currency `json:"StorageSpending"` 106 // Total cost to the wallet of forming the file contract. 107 TotalCost types.Currency `json:"totalcost"` 108 // Amount of contract funds that have been spent on uploads. 109 UploadSpending types.Currency `json:"uploadspending"` 110 // Signals if contract is good for uploading data 111 GoodForUpload bool `json:"goodforupload"` 112 // Signals if contract is good for a renewal 113 GoodForRenew bool `json:"goodforrenew"` 114 } 115 116 // RenterContracts contains the renter's contracts. 117 RenterContracts struct { 118 Contracts []RenterContract `json:"contracts"` 119 ActiveContracts []RenterContract `json:"activecontracts"` 120 InactiveContracts []RenterContract `json:"inactivecontracts"` 121 ExpiredContracts []RenterContract `json:"expiredcontracts"` 122 } 123 124 // RenterDownloadQueue contains the renter's download queue. 125 RenterDownloadQueue struct { 126 Downloads []DownloadInfo `json:"downloads"` 127 } 128 129 // RenterFile lists the file queried. 130 RenterFile struct { 131 File modules.FileInfo `json:"file"` 132 } 133 134 // RenterFiles lists the files known to the renter. 135 RenterFiles struct { 136 Files []modules.FileInfo `json:"files"` 137 } 138 139 // RenterLoad lists files that were loaded into the renter. 140 RenterLoad struct { 141 FilesAdded []string `json:"filesadded"` 142 } 143 144 // RenterPricesGET lists the data that is returned when a GET call is made 145 // to /renter/prices. 146 RenterPricesGET struct { 147 modules.RenterPriceEstimation 148 } 149 150 // RenterShareASCII contains an ASCII-encoded .sia file. 151 RenterShareASCII struct { 152 ASCIIsia string `json:"asciisia"` 153 } 154 155 // DownloadInfo contains all client-facing information of a file. 156 DownloadInfo struct { 157 Destination string `json:"destination"` // The destination of the download. 158 DestinationType string `json:"destinationtype"` // Can be "file", "memory buffer", or "http stream". 159 Filesize uint64 `json:"filesize"` // DEPRECATED. Same as 'Length'. 160 Length uint64 `json:"length"` // The length requested for the download. 161 Offset uint64 `json:"offset"` // The offset within the siafile requested for the download. 162 SiaPath string `json:"siapath"` // The siapath of the file used for the download. 163 164 Completed bool `json:"completed"` // Whether or not the download has completed. 165 EndTime time.Time `json:"endtime"` // The time when the download fully completed. 166 Error string `json:"error"` // Will be the empty string unless there was an error. 167 Received uint64 `json:"received"` // Amount of data confirmed and decoded. 168 StartTime time.Time `json:"starttime"` // The time when the download was started. 169 StartTimeUnix int64 `json:"starttimeunix"` // The time when the download was started in unix format. 170 TotalDataTransferred uint64 `json:"totaldatatransferred"` // The total amount of data transferred, including negotiation, overdrive etc. 171 } 172 ) 173 174 // renterHandlerGET handles the API call to /renter. 175 func (api *API) renterHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 176 settings := api.renter.Settings() 177 periodStart := api.renter.CurrentPeriod() 178 WriteJSON(w, RenterGET{ 179 Settings: settings, 180 FinancialMetrics: api.renter.PeriodSpending(), 181 CurrentPeriod: periodStart, 182 }) 183 } 184 185 // renterHandlerPOST handles the API call to set the Renter's settings. 186 func (api *API) renterHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 187 // Get the existing settings 188 settings := api.renter.Settings() 189 190 // Scan the allowance amount. (optional parameter) 191 if f := req.FormValue("funds"); f != "" { 192 funds, ok := scanAmount(f) 193 if !ok { 194 WriteError(w, Error{"unable to parse funds"}, http.StatusBadRequest) 195 return 196 } 197 settings.Allowance.Funds = funds 198 } 199 // Scan the number of hosts to use. (optional parameter) 200 if h := req.FormValue("hosts"); h != "" { 201 var hosts uint64 202 if _, err := fmt.Sscan(h, &hosts); err != nil { 203 WriteError(w, Error{"unable to parse hosts: " + err.Error()}, http.StatusBadRequest) 204 return 205 } else if hosts != 0 && hosts < requiredHosts { 206 WriteError(w, Error{fmt.Sprintf("insufficient number of hosts, need at least %v but have %v", recommendedHosts, hosts)}, http.StatusBadRequest) 207 } else { 208 settings.Allowance.Hosts = hosts 209 } 210 } else if settings.Allowance.Hosts == 0 { 211 // Sane defaults if host haven't been set before. 212 settings.Allowance.Hosts = recommendedHosts 213 } 214 // Scan the period. (optional parameter) 215 if p := req.FormValue("period"); p != "" { 216 var period types.BlockHeight 217 if _, err := fmt.Sscan(p, &period); err != nil { 218 WriteError(w, Error{"unable to parse period: " + err.Error()}, http.StatusBadRequest) 219 return 220 } 221 settings.Allowance.Period = types.BlockHeight(period) 222 } else if settings.Allowance.Period == 0 { 223 WriteError(w, Error{"period needs to be set if it hasn't been set before"}, http.StatusBadRequest) 224 return 225 } 226 // Scan the renew window. (optional parameter) 227 if rw := req.FormValue("renewwindow"); rw != "" { 228 var renewWindow types.BlockHeight 229 if _, err := fmt.Sscan(rw, &renewWindow); err != nil { 230 WriteError(w, Error{"unable to parse renewwindow: " + err.Error()}, http.StatusBadRequest) 231 return 232 } else if renewWindow != 0 && types.BlockHeight(renewWindow) < requiredRenewWindow { 233 WriteError(w, Error{fmt.Sprintf("renew window is too small, must be at least %v blocks but have %v blocks", requiredRenewWindow, renewWindow)}, http.StatusBadRequest) 234 return 235 } else { 236 settings.Allowance.RenewWindow = types.BlockHeight(renewWindow) 237 } 238 } else if settings.Allowance.RenewWindow == 0 { 239 // Sane defaults if renew window hasn't been set before. 240 settings.Allowance.RenewWindow = settings.Allowance.Period / 2 241 } 242 // Scan the download speed limit. (optional parameter) 243 if d := req.FormValue("maxdownloadspeed"); d != "" { 244 var downloadSpeed int64 245 if _, err := fmt.Sscan(d, &downloadSpeed); err != nil { 246 WriteError(w, Error{"unable to parse downloadspeed: " + err.Error()}, http.StatusBadRequest) 247 return 248 } 249 settings.MaxDownloadSpeed = downloadSpeed 250 } 251 // Scan the upload speed limit. (optional parameter) 252 if u := req.FormValue("maxuploadspeed"); u != "" { 253 var uploadSpeed int64 254 if _, err := fmt.Sscan(u, &uploadSpeed); err != nil { 255 WriteError(w, Error{"unable to parse uploadspeed: " + err.Error()}, http.StatusBadRequest) 256 return 257 } 258 settings.MaxUploadSpeed = uploadSpeed 259 } 260 // Scan the stream cache size. (optional parameter) 261 if dcs := req.FormValue("streamcachesize"); dcs != "" { 262 var streamCacheSize uint64 263 if _, err := fmt.Sscan(dcs, &streamCacheSize); err != nil { 264 WriteError(w, Error{"unable to parse streamcachesize: " + err.Error()}, http.StatusBadRequest) 265 return 266 } 267 settings.StreamCacheSize = streamCacheSize 268 } 269 // Set the settings in the renter. 270 err := api.renter.SetSettings(settings) 271 if err != nil { 272 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 273 return 274 } 275 WriteSuccess(w) 276 } 277 278 // renterContractsHandler handles the API call to request the Renter's 279 // contracts. 280 // 281 // Active contracts are contracts that the renter is actively using to store 282 // data and can upload, download, and renew 283 // 284 // Inactive contracts are contracts that are not currently being used by the 285 // renter because they are !goodForRenew, but have endheights that are in the 286 // future so could potentially become active again 287 // 288 // Expired contracts are contracts who's endheights are in the past 289 func (api *API) renterContractsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 290 // Parse flags 291 inactive, err := scanBool(req.FormValue("inactive")) 292 if err != nil { 293 return 294 } 295 expired, err := scanBool(req.FormValue("expired")) 296 if err != nil { 297 return 298 } 299 300 // Get current block height for reference 301 blockHeight := api.cs.Height() 302 303 // Get active contracts 304 contracts := []RenterContract{} 305 activeContracts := []RenterContract{} 306 inactiveContracts := []RenterContract{} 307 expiredContracts := []RenterContract{} 308 for _, c := range api.renter.Contracts() { 309 var size uint64 310 if len(c.Transaction.FileContractRevisions) != 0 { 311 size = c.Transaction.FileContractRevisions[0].NewFileSize 312 } 313 314 // Fetch host address 315 var netAddress modules.NetAddress 316 hdbe, exists := api.renter.Host(c.HostPublicKey) 317 if exists { 318 netAddress = hdbe.NetAddress 319 } 320 321 // Fetch utilities for contract 322 var goodForUpload bool 323 var goodForRenew bool 324 if utility, ok := api.renter.ContractUtility(c.HostPublicKey); ok { 325 goodForUpload = utility.GoodForUpload 326 goodForRenew = utility.GoodForRenew 327 } 328 contract := RenterContract{ 329 DownloadSpending: c.DownloadSpending, 330 EndHeight: c.EndHeight, 331 Fees: c.TxnFee.Add(c.SiafundFee).Add(c.ContractFee), 332 GoodForUpload: goodForUpload, 333 GoodForRenew: goodForRenew, 334 HostPublicKey: c.HostPublicKey, 335 ID: c.ID, 336 LastTransaction: c.Transaction, 337 NetAddress: netAddress, 338 RenterFunds: c.RenterFunds, 339 Size: size, 340 StartHeight: c.StartHeight, 341 StorageSpending: c.StorageSpending, 342 StorageSpendingDeprecated: c.StorageSpending, 343 TotalCost: c.TotalCost, 344 UploadSpending: c.UploadSpending, 345 } 346 if goodForRenew { 347 activeContracts = append(activeContracts, contract) 348 } else if inactive && !goodForRenew { 349 inactiveContracts = append(inactiveContracts, contract) 350 } 351 contracts = append(contracts, contract) 352 } 353 354 // Get expired contracts 355 if expired || inactive { 356 for _, c := range api.renter.OldContracts() { 357 var size uint64 358 if len(c.Transaction.FileContractRevisions) != 0 { 359 size = c.Transaction.FileContractRevisions[0].NewFileSize 360 } 361 362 // Fetch host address 363 var netAddress modules.NetAddress 364 hdbe, exists := api.renter.Host(c.HostPublicKey) 365 if exists { 366 netAddress = hdbe.NetAddress 367 } 368 369 // Fetch utilities for contract 370 var goodForUpload bool 371 var goodForRenew bool 372 if utility, ok := api.renter.ContractUtility(c.HostPublicKey); ok { 373 goodForUpload = utility.GoodForUpload 374 goodForRenew = utility.GoodForRenew 375 } 376 377 contract := RenterContract{ 378 DownloadSpending: c.DownloadSpending, 379 EndHeight: c.EndHeight, 380 Fees: c.TxnFee.Add(c.SiafundFee).Add(c.ContractFee), 381 GoodForUpload: goodForUpload, 382 GoodForRenew: goodForRenew, 383 HostPublicKey: c.HostPublicKey, 384 ID: c.ID, 385 LastTransaction: c.Transaction, 386 NetAddress: netAddress, 387 RenterFunds: c.RenterFunds, 388 Size: size, 389 StartHeight: c.StartHeight, 390 StorageSpending: c.StorageSpending, 391 StorageSpendingDeprecated: c.StorageSpending, 392 TotalCost: c.TotalCost, 393 UploadSpending: c.UploadSpending, 394 } 395 if expired && c.EndHeight < blockHeight { 396 expiredContracts = append(expiredContracts, contract) 397 } else if inactive && c.EndHeight >= blockHeight { 398 inactiveContracts = append(inactiveContracts, contract) 399 } 400 } 401 } 402 403 WriteJSON(w, RenterContracts{ 404 Contracts: contracts, 405 ActiveContracts: activeContracts, 406 InactiveContracts: inactiveContracts, 407 ExpiredContracts: expiredContracts, 408 }) 409 } 410 411 // renterClearDownloadsHandler handles the API call to request to clear the download queue. 412 func (api *API) renterClearDownloadsHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 413 var afterTime time.Time 414 beforeTime := types.EndOfTime 415 beforeStr, afterStr := req.FormValue("before"), req.FormValue("after") 416 if beforeStr != "" { 417 beforeInt, err := strconv.ParseInt(beforeStr, 10, 64) 418 if err != nil { 419 WriteError(w, Error{"parsing integer value for parameter `before` failed: " + err.Error()}, http.StatusBadRequest) 420 return 421 } 422 beforeTime = time.Unix(0, beforeInt) 423 } 424 if afterStr != "" { 425 afterInt, err := strconv.ParseInt(afterStr, 10, 64) 426 if err != nil { 427 WriteError(w, Error{"parsing integer value for parameter `after` failed: " + err.Error()}, http.StatusBadRequest) 428 return 429 } 430 afterTime = time.Unix(0, afterInt) 431 } 432 433 err := api.renter.ClearDownloadHistory(afterTime, beforeTime) 434 if err != nil { 435 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 436 return 437 } 438 WriteSuccess(w) 439 } 440 441 // renterDownloadsHandler handles the API call to request the download queue. 442 func (api *API) renterDownloadsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { 443 var downloads []DownloadInfo 444 for _, di := range api.renter.DownloadHistory() { 445 downloads = append(downloads, DownloadInfo{ 446 Destination: di.Destination, 447 DestinationType: di.DestinationType, 448 Filesize: di.Length, 449 Length: di.Length, 450 Offset: di.Offset, 451 SiaPath: di.SiaPath, 452 453 Completed: di.Completed, 454 EndTime: di.EndTime, 455 Error: di.Error, 456 Received: di.Received, 457 StartTime: di.StartTime, 458 StartTimeUnix: di.StartTimeUnix, 459 TotalDataTransferred: di.TotalDataTransferred, 460 }) 461 } 462 WriteJSON(w, RenterDownloadQueue{ 463 Downloads: downloads, 464 }) 465 } 466 467 // renterLoadHandler handles the API call to load a '.sia' file. 468 func (api *API) renterLoadHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 469 source := req.FormValue("source") 470 if !filepath.IsAbs(source) { 471 WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest) 472 return 473 } 474 475 files, err := api.renter.LoadSharedFiles(source) 476 if err != nil { 477 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 478 return 479 } 480 481 WriteJSON(w, RenterLoad{FilesAdded: files}) 482 } 483 484 // renterLoadAsciiHandler handles the API call to load a '.sia' file 485 // in ASCII form. 486 func (api *API) renterLoadASCIIHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 487 files, err := api.renter.LoadSharedFilesASCII(req.FormValue("asciisia")) 488 if err != nil { 489 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 490 return 491 } 492 493 WriteJSON(w, RenterLoad{FilesAdded: files}) 494 } 495 496 // renterRenameHandler handles the API call to rename a file entry in the 497 // renter. 498 func (api *API) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 499 err := api.renter.RenameFile(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("newsiapath")) 500 if err != nil { 501 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 502 return 503 } 504 505 WriteSuccess(w) 506 } 507 508 // renterFileHandler handles the API call to return specific file. 509 func (api *API) renterFileHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 510 file, err := api.renter.File(strings.TrimPrefix(ps.ByName("siapath"), "/")) 511 if err != nil { 512 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 513 return 514 } 515 WriteJSON(w, RenterFile{ 516 File: file, 517 }) 518 } 519 520 // renterFilesHandler handles the API call to list all of the files. 521 func (api *API) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 522 WriteJSON(w, RenterFiles{ 523 Files: api.renter.FileList(), 524 }) 525 } 526 527 // renterPricesHandler reports the expected costs of various actions given the 528 // renter settings and the set of available hosts. 529 func (api *API) renterPricesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 530 WriteJSON(w, RenterPricesGET{ 531 RenterPriceEstimation: api.renter.PriceEstimation(), 532 }) 533 } 534 535 // renterDeleteHandler handles the API call to delete a file entry from the 536 // renter. 537 func (api *API) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 538 err := api.renter.DeleteFile(strings.TrimPrefix(ps.ByName("siapath"), "/")) 539 if err != nil { 540 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 541 return 542 } 543 544 WriteSuccess(w) 545 } 546 547 // renterDownloadHandler handles the API call to download a file. 548 func (api *API) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 549 params, err := parseDownloadParameters(w, req, ps) 550 if err != nil { 551 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 552 return 553 } 554 if params.Async { 555 err = api.renter.DownloadAsync(params) 556 } else { 557 err = api.renter.Download(params) 558 } 559 if err != nil { 560 WriteError(w, Error{"download failed: " + err.Error()}, http.StatusInternalServerError) 561 return 562 } 563 if params.Httpwriter == nil { 564 // `httpresp=true` causes writes to w before this line is run, automatically 565 // adding `200 Status OK` code to response. Calling this results in a 566 // multiple calls to WriteHeaders() errors. 567 WriteSuccess(w) 568 return 569 } 570 } 571 572 // renterDownloadAsyncHandler handles the API call to download a file asynchronously. 573 func (api *API) renterDownloadAsyncHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 574 req.ParseForm() 575 req.Form.Set("async", "true") 576 api.renterDownloadHandler(w, req, ps) 577 } 578 579 // parseDownloadParameters parses the download parameters passed to the 580 // /renter/download endpoint. Validation of these parameters is done by the 581 // renter. 582 func parseDownloadParameters(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (modules.RenterDownloadParameters, error) { 583 destination := req.FormValue("destination") 584 585 // The offset and length in bytes. 586 offsetparam := req.FormValue("offset") 587 lengthparam := req.FormValue("length") 588 589 // Determines whether the response is written to response body. 590 httprespparam := req.FormValue("httpresp") 591 592 // Determines whether to return on completion of download or straight away. 593 // If httprespparam is present, this parameter is ignored. 594 asyncparam := req.FormValue("async") 595 596 // Parse the offset and length parameters. 597 var offset, length uint64 598 if len(offsetparam) > 0 { 599 _, err := fmt.Sscan(offsetparam, &offset) 600 if err != nil { 601 return modules.RenterDownloadParameters{}, build.ExtendErr("could not decode the offset as uint64: ", err) 602 } 603 } 604 if len(lengthparam) > 0 { 605 _, err := fmt.Sscan(lengthparam, &length) 606 if err != nil { 607 return modules.RenterDownloadParameters{}, build.ExtendErr("could not decode the offset as uint64: ", err) 608 } 609 } 610 611 // Parse the httpresp parameter. 612 httpresp, err := scanBool(httprespparam) 613 if err != nil { 614 return modules.RenterDownloadParameters{}, build.ExtendErr("httpresp parameter could not be parsed", err) 615 } 616 617 // Parse the async parameter. 618 async, err := scanBool(asyncparam) 619 if err != nil { 620 return modules.RenterDownloadParameters{}, build.ExtendErr("async parameter could not be parsed", err) 621 } 622 623 siapath := strings.TrimPrefix(ps.ByName("siapath"), "/") // Sia file name. 624 625 dp := modules.RenterDownloadParameters{ 626 Destination: destination, 627 Async: async, 628 Length: length, 629 Offset: offset, 630 SiaPath: siapath, 631 } 632 if httpresp { 633 dp.Httpwriter = w 634 } 635 636 return dp, nil 637 } 638 639 // renterShareHandler handles the API call to create a '.sia' file that 640 // shares a set of file. 641 func (api *API) renterShareHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 642 destination := req.FormValue("destination") 643 // Check that the destination path is absolute. 644 if !filepath.IsAbs(destination) { 645 WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest) 646 return 647 } 648 649 err := api.renter.ShareFiles(strings.Split(req.FormValue("siapaths"), ","), destination) 650 if err != nil { 651 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 652 return 653 } 654 655 WriteSuccess(w) 656 } 657 658 // renterShareAsciiHandler handles the API call to return a '.sia' file 659 // in ascii form. 660 func (api *API) renterShareASCIIHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 661 ascii, err := api.renter.ShareFilesASCII(strings.Split(req.FormValue("siapaths"), ",")) 662 if err != nil { 663 WriteError(w, Error{err.Error()}, http.StatusBadRequest) 664 return 665 } 666 WriteJSON(w, RenterShareASCII{ 667 ASCIIsia: ascii, 668 }) 669 } 670 671 // renterStreamHandler handles downloads from the /renter/stream endpoint 672 func (api *API) renterStreamHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 673 siaPath := strings.TrimPrefix(ps.ByName("siapath"), "/") 674 fileName, streamer, err := api.renter.Streamer(siaPath) 675 if err != nil { 676 WriteError(w, Error{fmt.Sprintf("failed to create download streamer: %v", err)}, 677 http.StatusInternalServerError) 678 return 679 } 680 http.ServeContent(w, req, fileName, time.Time{}, streamer) 681 } 682 683 // renterUploadHandler handles the API call to upload a file. 684 func (api *API) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { 685 source := req.FormValue("source") 686 if !filepath.IsAbs(source) { 687 WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest) 688 return 689 } 690 691 // Check whether the erasure coding parameters have been supplied. 692 var ec modules.ErasureCoder 693 if req.FormValue("datapieces") != "" || req.FormValue("paritypieces") != "" { 694 // Check that both values have been supplied. 695 if req.FormValue("datapieces") == "" || req.FormValue("paritypieces") == "" { 696 WriteError(w, Error{"must provide both the datapieces parameter and the paritypieces parameter if specifying erasure coding parameters"}, http.StatusBadRequest) 697 return 698 } 699 700 // Parse the erasure coding parameters. 701 var dataPieces, parityPieces int 702 _, err := fmt.Sscan(req.FormValue("datapieces"), &dataPieces) 703 if err != nil { 704 WriteError(w, Error{"unable to read parameter 'datapieces': " + err.Error()}, http.StatusBadRequest) 705 return 706 } 707 _, err = fmt.Sscan(req.FormValue("paritypieces"), &parityPieces) 708 if err != nil { 709 WriteError(w, Error{"unable to read parameter 'paritypieces': " + err.Error()}, http.StatusBadRequest) 710 return 711 } 712 713 // Verify that sane values for parityPieces and redundancy are being 714 // supplied. 715 if parityPieces < requiredParityPieces { 716 WriteError(w, Error{fmt.Sprintf("a minimum of %v parity pieces is required, but %v parity pieces requested", parityPieces, requiredParityPieces)}, http.StatusBadRequest) 717 return 718 } 719 redundancy := float64(dataPieces+parityPieces) / float64(dataPieces) 720 if float64(dataPieces+parityPieces)/float64(dataPieces) < requiredRedundancy { 721 WriteError(w, Error{fmt.Sprintf("a redundancy of %.2f is required, but redundancy of %.2f supplied", redundancy, requiredRedundancy)}, http.StatusBadRequest) 722 return 723 } 724 725 // Create the erasure coder. 726 ec, err = renter.NewRSCode(dataPieces, parityPieces) 727 if err != nil { 728 WriteError(w, Error{"unable to encode file using the provided parameters: " + err.Error()}, http.StatusBadRequest) 729 return 730 } 731 } 732 733 // Call the renter to upload the file. 734 err := api.renter.Upload(modules.FileUploadParams{ 735 Source: source, 736 SiaPath: strings.TrimPrefix(ps.ByName("siapath"), "/"), 737 ErasureCode: ec, 738 }) 739 if err != nil { 740 WriteError(w, Error{"upload failed: " + err.Error()}, http.StatusInternalServerError) 741 return 742 } 743 WriteSuccess(w) 744 }