github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/cmd/spc/rentercmd.go (about) 1 package main 2 3 // TODO: If you run siac from a non-existent directory, the abs() function does 4 // not handle this very gracefully. 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "sort" 11 "strconv" 12 "strings" 13 "text/tabwriter" 14 "time" 15 16 "github.com/spf13/cobra" 17 "gitlab.com/NebulousLabs/errors" 18 19 "SiaPrime/modules" 20 "SiaPrime/node/api" 21 "SiaPrime/types" 22 ) 23 24 var ( 25 renterAllowanceCancelCmd = &cobra.Command{ 26 Use: "cancel", 27 Short: "Cancel the current allowance", 28 Long: "Cancel the current allowance, which controls how much money is spent on file contracts.", 29 Run: wrap(renterallowancecancelcmd), 30 } 31 32 renterAllowanceCmd = &cobra.Command{ 33 Use: "allowance", 34 Short: "View the current allowance", 35 Long: "View the current allowance, which controls how much money is spent on file contracts.", 36 Run: wrap(renterallowancecmd), 37 } 38 39 renterCmd = &cobra.Command{ 40 Use: "renter", 41 Short: "Perform renter actions", 42 Long: "Upload, download, rename, delete, load, or share files.", 43 Run: wrap(rentercmd), 44 } 45 46 renterContractsCmd = &cobra.Command{ 47 Use: "contracts", 48 Short: "View the Renter's contracts", 49 Long: "View the contracts that the Renter has formed with hosts.", 50 Run: wrap(rentercontractscmd), 51 } 52 53 renterContractsViewCmd = &cobra.Command{ 54 Use: "view [contract-id]", 55 Short: "View details of the specified contract", 56 Long: "View all details available of the specified contract.", 57 Run: wrap(rentercontractsviewcmd), 58 } 59 60 renterDownloadsCmd = &cobra.Command{ 61 Use: "downloads", 62 Short: "View the download queue", 63 Long: "View the list of files currently downloading.", 64 Run: wrap(renterdownloadscmd), 65 } 66 67 renterFilesDeleteCmd = &cobra.Command{ 68 Use: "delete [path]", 69 Aliases: []string{"rm"}, 70 Short: "Delete a file", 71 Long: "Delete a file. Does not delete the file on disk.", 72 Run: wrap(renterfilesdeletecmd), 73 } 74 75 renterFilesDownloadCmd = &cobra.Command{ 76 Use: "download [path] [destination]", 77 Short: "Download a file", 78 Long: "Download a previously-uploaded file to a specified destination.", 79 Run: wrap(renterfilesdownloadcmd), 80 } 81 82 renterFilesListCmd = &cobra.Command{ 83 Use: "list", 84 Aliases: []string{"ls"}, 85 Short: "List the status of all files", 86 Long: "List the status of all files known to the renter on the SiaPrime network.", 87 Run: wrap(renterfileslistcmd), 88 } 89 90 renterFilesRenameCmd = &cobra.Command{ 91 Use: "rename [path] [newpath]", 92 Aliases: []string{"mv"}, 93 Short: "Rename a file", 94 Long: "Rename a file.", 95 Run: wrap(renterfilesrenamecmd), 96 } 97 98 renterFilesUploadCmd = &cobra.Command{ 99 Use: "upload [source] [path]", 100 Short: "Upload a file", 101 Long: "Upload a file to [path] on the SiaPrime network.", 102 Run: wrap(renterfilesuploadcmd), 103 } 104 105 renterPricesCmd = &cobra.Command{ 106 Use: "prices [amount] [period] [hosts] [renew window]", 107 Short: "Display the price of storage and bandwidth", 108 Long: `Display the estimated prices of storing files, retrieving files, and creating a set of contracts. 109 110 An allowance can be provided for a more accurate estimate, if no allowance is provided the current set allowance will be used, 111 and if no allowance is set an allowance of 500SC, 12w period, 50 hosts, and 4w renew window will be used.`, 112 Run: renterpricescmd, 113 } 114 115 renterSetAllowanceCmd = &cobra.Command{ 116 Use: "setallowance [amount] [period] [hosts] [renew window]", 117 Short: "Set the allowance", 118 Long: `Set the amount of money that can be spent over a given period. 119 120 amount is given in currency units (SCP, KS, etc.) 121 122 period is given in either blocks (b), hours (h), days (d), or weeks (w). A 123 block is approximately 10 minutes, so one hour is six blocks, a day is 144 124 blocks, and a week is 1008 blocks. 125 126 The SiaPrime renter module spreads data across more than one SiaPrime server computer 127 or "host". The "hosts" parameter for the setallowance command determines 128 how many different hosts the renter will spread the data across. 129 130 Allowance can be automatically renewed periodically. If the current 131 blockheight + the renew window >= the end height the contract, 132 then the contract is renewed automatically. 133 134 Note that setting the allowance will cause siad to immediately begin forming 135 contracts! You should only set the allowance once you are fully synced and you 136 have a reasonable number (>30) of hosts in your hostdb.`, 137 Run: rentersetallowancecmd, 138 } 139 140 renterUploadsCmd = &cobra.Command{ 141 Use: "uploads", 142 Short: "View the upload queue", 143 Long: "View the list of files currently uploading.", 144 Run: wrap(renteruploadscmd), 145 } 146 ) 147 148 // abs returns the absolute representation of a path. 149 // TODO: bad things can happen if you run siac from a non-existent directory. 150 // Implement some checks to catch this problem. 151 func abs(path string) string { 152 abspath, err := filepath.Abs(path) 153 if err != nil { 154 return path 155 } 156 return abspath 157 } 158 159 // rentercmd displays the renter's financial metrics and lists the files it is 160 // tracking. 161 func rentercmd() { 162 rg, err := httpClient.RenterGet() 163 if err != nil { 164 die("Could not get renter info:", err) 165 } 166 fm := rg.FinancialMetrics 167 totalSpent := fm.ContractFees.Add(fm.UploadSpending). 168 Add(fm.DownloadSpending).Add(fm.StorageSpending) 169 170 fmt.Printf(`Renter Info: 171 Allowance:`) 172 173 if rg.Settings.Allowance.Funds.IsZero() { 174 fmt.Printf("\n No current allowance.\n") 175 } else { 176 fmt.Printf(` %v 177 Spent Funds: %v 178 Unspent Funds: %v 179 `, currencyUnits(rg.Settings.Allowance.Funds), 180 currencyUnits(totalSpent), currencyUnits(fm.Unspent)) 181 } 182 183 // also list files 184 renterfileslistcmd() 185 } 186 187 // renteruploadscmd is the handler for the command `siac renter uploads`. 188 // Lists files currently uploading. 189 func renteruploadscmd() { 190 rf, err := httpClient.RenterFilesGet() 191 if err != nil { 192 die("Could not get upload queue:", err) 193 } 194 195 // TODO: add a --history flag to the uploads command to mirror the --history 196 // flag in the downloads command. This hasn't been done yet because the 197 // call to /renter/files includes files that have been shared with you, 198 // not just files you've uploaded. 199 200 // Filter out files that have been uploaded. 201 var filteredFiles []modules.FileInfo 202 for _, fi := range rf.Files { 203 if !fi.Available { 204 filteredFiles = append(filteredFiles, fi) 205 } 206 } 207 if len(filteredFiles) == 0 { 208 fmt.Println("No files are uploading.") 209 return 210 } 211 fmt.Println("Uploading", len(filteredFiles), "files:") 212 for _, file := range filteredFiles { 213 fmt.Printf("%13s %s (uploading, %0.2f%%)\n", filesizeUnits(int64(file.Filesize)), file.SiaPath, file.UploadProgress) 214 } 215 } 216 217 // renterdownloadscmd is the handler for the command `siac renter downloads`. 218 // Lists files currently downloading, and optionally previously downloaded 219 // files if the -H or --history flag is specified. 220 func renterdownloadscmd() { 221 queue, err := httpClient.RenterDownloadsGet() 222 if err != nil { 223 die("Could not get download queue:", err) 224 } 225 // Filter out files that have been downloaded. 226 var downloading []api.DownloadInfo 227 for _, file := range queue.Downloads { 228 if !file.Completed { 229 downloading = append(downloading, file) 230 } 231 } 232 if len(downloading) == 0 { 233 fmt.Println("No files are downloading.") 234 } else { 235 fmt.Println("Downloading", len(downloading), "files:") 236 for _, file := range downloading { 237 fmt.Printf("%s: %5.1f%% %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), 100*float64(file.Received)/float64(file.Filesize), file.SiaPath, file.Destination) 238 } 239 } 240 if !renterShowHistory { 241 return 242 } 243 fmt.Println() 244 // Filter out files that are downloading. 245 var downloaded []api.DownloadInfo 246 for _, file := range queue.Downloads { 247 if file.Completed { 248 downloaded = append(downloaded, file) 249 } 250 } 251 if len(downloaded) == 0 { 252 fmt.Println("No files downloaded.") 253 } else { 254 fmt.Println("Downloaded", len(downloaded), "files:") 255 for _, file := range downloaded { 256 fmt.Printf("%s: %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), file.SiaPath, file.Destination) 257 } 258 } 259 } 260 261 // renterallowancecmd displays the current allowance. 262 func renterallowancecmd() { 263 rg, err := httpClient.RenterGet() 264 if err != nil { 265 die("Could not get allowance:", err) 266 } 267 allowance := rg.Settings.Allowance 268 269 // Show allowance info 270 fmt.Printf(`Allowance: 271 Amount: %v 272 Period: %v blocks 273 Renew Window: %v blocks 274 Hosts: %v 275 `, currencyUnits(allowance.Funds), allowance.Period, allowance.RenewWindow, allowance.Hosts) 276 277 // Show spending detail 278 fm := rg.FinancialMetrics 279 totalSpent := fm.ContractFees.Add(fm.UploadSpending). 280 Add(fm.DownloadSpending).Add(fm.StorageSpending) 281 // Calculate unspent allocated 282 unspentAllocated := types.ZeroCurrency 283 if fm.TotalAllocated.Cmp(totalSpent) >= 0 { 284 unspentAllocated = fm.TotalAllocated.Sub(totalSpent) 285 } 286 // Calculate unspent unallocated 287 unspentUnallocated := types.ZeroCurrency 288 if fm.Unspent.Cmp(unspentAllocated) >= 0 { 289 unspentUnallocated = fm.Unspent.Sub(unspentAllocated) 290 } 291 292 fmt.Printf(` 293 Spending: 294 Current Period Spending:`) 295 296 if rg.Settings.Allowance.Funds.IsZero() { 297 fmt.Printf("\n No current period spending.\n") 298 } else { 299 fmt.Printf(` 300 Spent Funds: %v 301 Storage: %v 302 Upload: %v 303 Download: %v 304 Fees: %v 305 Unspent Funds: %v 306 Allocated: %v 307 Unallocated: %v 308 `, currencyUnits(totalSpent), currencyUnits(fm.StorageSpending), 309 currencyUnits(fm.UploadSpending), currencyUnits(fm.DownloadSpending), 310 currencyUnits(fm.ContractFees), currencyUnits(fm.Unspent), 311 currencyUnits(unspentAllocated), currencyUnits(unspentUnallocated)) 312 } 313 314 fmt.Printf("\n Previous Spending:") 315 if fm.PreviousSpending.IsZero() && fm.WithheldFunds.IsZero() { 316 fmt.Printf("\n No previous spending.\n\n") 317 } else { 318 fmt.Printf(` %v 319 Withheld Funds: %v 320 Release Block: %v 321 322 `, currencyUnits(fm.PreviousSpending), currencyUnits(fm.WithheldFunds), fm.ReleaseBlock) 323 } 324 } 325 326 // renterallowancecancelcmd cancels the current allowance. 327 func renterallowancecancelcmd() { 328 fmt.Println(`Canceling your allowance will disable uploading new files, 329 repairing existing files, and renewing existing files. All files will cease 330 to be accessible after a short period of time.`) 331 again: 332 fmt.Print("Do you want to continue? [y/n] ") 333 var resp string 334 fmt.Scanln(&resp) 335 switch strings.ToLower(resp) { 336 case "y", "yes": 337 // continue below 338 case "n", "no": 339 return 340 default: 341 goto again 342 } 343 err := httpClient.RenterCancelAllowance() 344 if err != nil { 345 die("error canceling allowance:", err) 346 } 347 fmt.Println("Allowance canceled.") 348 } 349 350 // rentersetallowancecmd allows the user to set the allowance. 351 // the first two parameters, amount and period, are required. 352 // the second two parameters are optional: 353 // hosts integer number of hosts 354 // renewperiod how many blocks between renewals 355 func rentersetallowancecmd(cmd *cobra.Command, args []string) { 356 if len(args) < 2 || len(args) > 4 { 357 cmd.UsageFunc()(cmd) 358 os.Exit(exitCodeUsage) 359 } 360 hastings, err := parseCurrency(args[0]) 361 if err != nil { 362 die("Could not parse amount:", err) 363 } 364 blocks, err := parsePeriod(args[1]) 365 if err != nil { 366 die("Could not parse period:", err) 367 } 368 allowance := modules.Allowance{} 369 _, err = fmt.Sscan(hastings, &allowance.Funds) 370 if err != nil { 371 die("Could not parse amount:", err) 372 } 373 374 _, err = fmt.Sscan(blocks, &allowance.Period) 375 if err != nil { 376 die("Could not parse period:", err) 377 } 378 if len(args) > 2 { 379 hosts, err := strconv.Atoi(args[2]) 380 if err != nil { 381 die("Could not parse host count") 382 } 383 allowance.Hosts = uint64(hosts) 384 } 385 if len(args) > 3 { 386 renewWindow, err := parsePeriod(args[3]) 387 if err != nil { 388 die("Could not parse renew window") 389 } 390 _, err = fmt.Sscan(renewWindow, &allowance.RenewWindow) 391 if err != nil { 392 die("Could not parse renew window:", err) 393 } 394 } 395 err = httpClient.RenterPostAllowance(allowance) 396 if err != nil { 397 die("Could not set allowance:", err) 398 } 399 fmt.Println("Allowance updated.") 400 } 401 402 // byValue sorts contracts by their value in siacoins, high to low. If two 403 // contracts have the same value, they are sorted by their host's address. 404 type byValue []api.RenterContract 405 406 func (s byValue) Len() int { return len(s) } 407 func (s byValue) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 408 func (s byValue) Less(i, j int) bool { 409 cmp := s[i].RenterFunds.Cmp(s[j].RenterFunds) 410 if cmp == 0 { 411 return s[i].NetAddress < s[j].NetAddress 412 } 413 return cmp > 0 414 } 415 416 // rentercontractscmd is the handler for the comand `siac renter contracts`. 417 // It lists the Renter's contracts. 418 func rentercontractscmd() { 419 rc, err := httpClient.RenterInactiveContractsGet() 420 if err != nil { 421 die("Could not get contracts:", err) 422 } 423 424 fmt.Println("Active Contracts:") 425 if len(rc.ActiveContracts) == 0 { 426 fmt.Println(" No active contracts.") 427 } else { 428 // Display Active Contracts 429 sort.Sort(byValue(rc.ActiveContracts)) 430 var activeTotalStored uint64 431 var activeTotalRemaining, activeTotalSpent, activeTotalFees types.Currency 432 for _, c := range rc.ActiveContracts { 433 activeTotalStored += c.Size 434 activeTotalRemaining = activeTotalRemaining.Add(c.RenterFunds) 435 activeTotalSpent = activeTotalSpent.Add(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)) 436 activeTotalFees = activeTotalFees.Add(c.Fees) 437 } 438 fmt.Printf(` Number of Contracts: %v 439 Total stored: %s 440 Total Remaining: %v 441 Total Spent: %v 442 Total Fees: %v 443 444 `, len(rc.ActiveContracts), filesizeUnits(int64(activeTotalStored)), 445 currencyUnits(activeTotalRemaining), currencyUnits(activeTotalSpent), currencyUnits(activeTotalFees)) 446 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 447 fmt.Fprintln(w, " Host\tRemaining Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew") 448 for _, c := range rc.ActiveContracts { 449 address := c.NetAddress 450 if address == "" { 451 address = "Host Removed" 452 } 453 fmt.Fprintf(w, " %v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n", 454 address, 455 currencyUnits(c.RenterFunds), 456 currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)), 457 currencyUnits(c.Fees), 458 filesizeUnits(int64(c.Size)), 459 c.EndHeight, 460 c.ID, 461 c.GoodForUpload, 462 c.GoodForRenew) 463 } 464 w.Flush() 465 } 466 467 fmt.Println("\nInactive Contracts:") 468 if len(rc.InactiveContracts) == 0 { 469 fmt.Println(" No inactive contracts.") 470 } else { 471 // Display Inactive Contracts 472 sort.Sort(byValue(rc.InactiveContracts)) 473 var inactiveTotalStored uint64 474 var inactiveTotalRemaining, inactiveTotalSpent, inactiveTotalFees types.Currency 475 for _, c := range rc.InactiveContracts { 476 inactiveTotalStored += c.Size 477 inactiveTotalRemaining = inactiveTotalRemaining.Add(c.RenterFunds) 478 inactiveTotalSpent = inactiveTotalSpent.Add(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)) 479 inactiveTotalFees = inactiveTotalFees.Add(c.Fees) 480 } 481 482 fmt.Printf(` 483 Number of Contracts: %v 484 Total stored: %s 485 Total Remaining: %v 486 Total Spent: %v 487 Total Fees: %v 488 489 `, len(rc.InactiveContracts), filesizeUnits(int64(inactiveTotalStored)), currencyUnits(inactiveTotalRemaining), currencyUnits(inactiveTotalSpent), currencyUnits(inactiveTotalFees)) 490 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 491 fmt.Fprintln(w, " Host\tRemaining Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew") 492 for _, c := range rc.InactiveContracts { 493 address := c.NetAddress 494 if address == "" { 495 address = "Host Removed" 496 } 497 fmt.Fprintf(w, " %v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n", 498 address, 499 currencyUnits(c.RenterFunds), 500 currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)), 501 currencyUnits(c.Fees), 502 filesizeUnits(int64(c.Size)), 503 c.EndHeight, 504 c.ID, 505 c.GoodForUpload, 506 c.GoodForRenew) 507 } 508 w.Flush() 509 } 510 511 if renterAllContracts { 512 fmt.Println("\nExpired Contracts:") 513 rce, err := httpClient.RenterExpiredContractsGet() 514 if err != nil { 515 die("Could not get expired contracts:", err) 516 } 517 if len(rce.ExpiredContracts) == 0 { 518 fmt.Println(" No expired contracts.") 519 } else { 520 sort.Sort(byValue(rce.ExpiredContracts)) 521 var expiredTotalStored uint64 522 var expiredTotalWithheld, expiredTotalSpent, expiredTotalFees types.Currency 523 for _, c := range rce.ExpiredContracts { 524 expiredTotalStored += c.Size 525 expiredTotalWithheld = expiredTotalWithheld.Add(c.RenterFunds) 526 expiredTotalSpent = expiredTotalSpent.Add(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)) 527 expiredTotalFees = expiredTotalFees.Add(c.Fees) 528 } 529 fmt.Printf(` 530 Number of Contracts: %v 531 Total stored: %9s 532 Total Remaining: %v 533 Total Spent: %v 534 Total Fees: %v 535 536 `, len(rce.ExpiredContracts), filesizeUnits(int64(expiredTotalStored)), currencyUnits(expiredTotalWithheld), currencyUnits(expiredTotalSpent), currencyUnits(expiredTotalFees)) 537 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 538 fmt.Fprintln(w, " Host\tWithheld Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew") 539 for _, c := range rce.ExpiredContracts { 540 address := c.NetAddress 541 if address == "" { 542 address = "Host Removed" 543 } 544 fmt.Fprintf(w, " %v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n", 545 address, 546 currencyUnits(c.RenterFunds), 547 currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)), 548 currencyUnits(c.Fees), 549 filesizeUnits(int64(c.Size)), 550 c.EndHeight, 551 c.ID, 552 c.GoodForUpload, 553 c.GoodForRenew) 554 } 555 w.Flush() 556 } 557 } 558 } 559 560 // rentercontractsviewcmd is the handler for the command `siac renter contracts <id>`. 561 // It lists details of a specific contract. 562 func rentercontractsviewcmd(cid string) { 563 rc, err := httpClient.RenterInactiveContractsGet() 564 if err != nil { 565 die("Could not get contract details: ", err) 566 } 567 rce, err := httpClient.RenterExpiredContractsGet() 568 if err != nil { 569 die("Could not get expired contract details: ", err) 570 } 571 572 contracts := append(rc.ActiveContracts, rc.InactiveContracts...) 573 contracts = append(contracts, rce.ExpiredContracts...) 574 575 for _, rc := range contracts { 576 if rc.ID.String() == cid { 577 hostInfo, err := httpClient.HostDbHostsGet(rc.HostPublicKey) 578 if err != nil { 579 die("Could not fetch details of host: ", err) 580 } 581 fmt.Printf(` 582 Contract %v 583 Host: %v (Public Key: %v) 584 585 Start Height: %v 586 End Height: %v 587 588 Total cost: %v (Fees: %v) 589 Funds Allocated: %v 590 Upload Spending: %v 591 Storage Spending: %v 592 Download Spending: %v 593 Remaining Funds: %v 594 595 File Size: %v 596 `, rc.ID, rc.NetAddress, rc.HostPublicKey.String(), rc.StartHeight, rc.EndHeight, 597 currencyUnits(rc.TotalCost), 598 currencyUnits(rc.Fees), 599 currencyUnits(rc.TotalCost.Sub(rc.Fees)), 600 currencyUnits(rc.UploadSpending), 601 currencyUnits(rc.StorageSpending), 602 currencyUnits(rc.DownloadSpending), 603 currencyUnits(rc.RenterFunds), 604 filesizeUnits(int64(rc.Size))) 605 606 printScoreBreakdown(&hostInfo) 607 return 608 } 609 } 610 611 fmt.Println("Contract not found") 612 } 613 614 // renterfilesdeletecmd is the handler for the command `siac renter delete [path]`. 615 // Removes the specified path from the Sia network. 616 func renterfilesdeletecmd(path string) { 617 err := httpClient.RenterDeletePost(path) 618 if err != nil { 619 die("Could not delete file:", err) 620 } 621 fmt.Println("Deleted", path) 622 } 623 624 // renterfilesdownloadcmd is the handler for the comand `siac renter download [path] [destination]`. 625 // Downloads a path from the Sia network to the local specified destination. 626 func renterfilesdownloadcmd(path, destination string) { 627 destination = abs(destination) 628 629 // Queue the download. An error will be returned if the queueing failed, but 630 // the call will return before the download has completed. The call is made 631 // as an async call. 632 err := httpClient.RenterDownloadFullGet(path, destination, true) 633 if err != nil { 634 die("Download could not be started:", err) 635 } 636 637 // If the download is async, report success. 638 if renterDownloadAsync { 639 fmt.Printf("Queued Download '%s' to %s.\n", path, abs(destination)) 640 return 641 } 642 643 // If the download is blocking, display progress as the file downloads. 644 err = downloadprogress(path, destination) 645 if err != nil { 646 die("\nDownload could not be completed:", err) 647 } 648 fmt.Printf("\nDownloaded '%s' to '%s'.\n", path, abs(destination)) 649 } 650 651 // bandwidthUnit takes bps (bits per second) as an argument and converts 652 // them into a more human-readable string with a unit. 653 func bandwidthUnit(bps uint64) string { 654 units := []string{"Bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps", "Ebps", "Zbps", "Ybps"} 655 mag := uint64(1) 656 unit := "" 657 for _, unit = range units { 658 if bps < 1e3*mag { 659 break 660 } else if unit != units[len(units)-1] { 661 // don't want to perform this multiply on the last iter; that 662 // would give us 1.235 Ybps instead of 1235 Ybps 663 mag *= 1e3 664 } 665 666 } 667 return fmt.Sprintf("%.2f %s", float64(bps)/float64(mag), unit) 668 } 669 670 // downloadprogress will display the progress of the provided download to the 671 // user, and return an error when the download is finished. 672 func downloadprogress(siapath, destination string) error { 673 start := time.Now() 674 675 // helper type used for measurements. 676 type measurement struct { 677 progress uint64 678 time time.Time 679 } 680 681 // initialize measurementswith a first measurement of 0 progress. 682 measurements := []measurement{{ 683 progress: 0, 684 time: time.Now(), 685 }} 686 for range time.Tick(OutputRefreshRate) { 687 // Get the list of downloads. 688 queue, err := httpClient.RenterDownloadsGet() 689 if err != nil { 690 continue // benign 691 } 692 693 // Search for the download in the list of downloads. 694 var d api.DownloadInfo 695 found := false 696 for _, d = range queue.Downloads { 697 if d.SiaPath == siapath && d.Destination == destination { 698 found = true 699 break 700 } 701 } 702 // If the download has not appeared in the queue yet, either continue or 703 // give up. 704 if !found { 705 if time.Since(start) > RenterDownloadTimeout { 706 return errors.New("Unable to find download in queue") 707 } 708 continue 709 } 710 711 // Check whether the file has completed or otherwise errored out. 712 if d.Error != "" { 713 return errors.New(d.Error) 714 } 715 if d.Completed { 716 return nil 717 } 718 719 // Add the current progress to the measurements. 720 measurements = append(measurements, measurement{ 721 progress: d.Received, 722 time: time.Now(), 723 }) 724 725 // Shrink the measurements to only contain measurements from within the 726 // SpeedEstimationWindow. 727 for len(measurements) > 2 && measurements[len(measurements)-1].time.Sub(measurements[0].time) > SpeedEstimationWindow { 728 measurements = measurements[1:] 729 } 730 731 // Compute the progress and timespan between the first and last 732 // measurement to get the speed. 733 received := float64(measurements[len(measurements)-1].progress - measurements[0].progress) 734 timespan := measurements[len(measurements)-1].time.Sub(measurements[0].time) 735 speed := bandwidthUnit(uint64((received * 8) / timespan.Seconds())) 736 737 // Compuate the percentage of completion and time elapsed since the 738 // start of the download. 739 pct := 100 * float64(d.Received) / float64(d.Filesize) 740 elapsed := time.Since(d.StartTime) 741 elapsed -= elapsed % time.Second // round to nearest second 742 743 // Update the progress for the user. 744 fmt.Printf("\rDownloading... %5.1f%% of %v, %v elapsed, %s ", pct, filesizeUnits(int64(d.Filesize)), elapsed, speed) 745 } 746 747 // This code is unreachable, but the compiler requires this to be here. 748 return errors.New("ERROR: download progress reached code that should not be reachable") 749 } 750 751 // bySiaPath implements sort.Interface for [] modules.FileInfo based on the 752 // SiaPath field. 753 type bySiaPath []modules.FileInfo 754 755 func (s bySiaPath) Len() int { return len(s) } 756 func (s bySiaPath) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 757 func (s bySiaPath) Less(i, j int) bool { return s[i].SiaPath < s[j].SiaPath } 758 759 // renterfileslistcmd is the handler for the command `siac renter list`. 760 // Lists files known to the renter on the network. 761 func renterfileslistcmd() { 762 var rf api.RenterFiles 763 rf, err := httpClient.RenterFilesGet() 764 if err != nil { 765 die("Could not get file list:", err) 766 } 767 if len(rf.Files) == 0 { 768 fmt.Println("No files have been uploaded.") 769 return 770 } 771 fmt.Print("Tracking ", len(rf.Files), " files:") 772 var totalStored uint64 773 for _, file := range rf.Files { 774 totalStored += file.Filesize 775 } 776 fmt.Printf(" %9s\n", filesizeUnits(int64(totalStored))) 777 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 778 if renterListVerbose { 779 fmt.Fprintln(w, " File size\tAvailable\tUploaded\tProgress\tRedundancy\tRenewing\tOn Disk\tRecoverable\tSia path") 780 } 781 sort.Sort(bySiaPath(rf.Files)) 782 for _, file := range rf.Files { 783 fmt.Fprintf(w, " %9s", filesizeUnits(int64(file.Filesize))) 784 if renterListVerbose { 785 availableStr := yesNo(file.Available) 786 renewingStr := yesNo(file.Renewing) 787 redundancyStr := fmt.Sprintf("%.2f", file.Redundancy) 788 if file.Redundancy == -1 { 789 redundancyStr = "-" 790 } 791 uploadProgressStr := fmt.Sprintf("%.2f%%", file.UploadProgress) 792 if file.UploadProgress == -1 { 793 uploadProgressStr = "-" 794 } 795 onDiskStr := yesNo(file.OnDisk) 796 recoverableStr := yesNo(file.Recoverable) 797 fmt.Fprintf(w, "\t%s\t%9s\t%8s\t%10s\t%s\t%s\t%s", availableStr, filesizeUnits(int64(file.UploadedBytes)), uploadProgressStr, redundancyStr, renewingStr, onDiskStr, recoverableStr) 798 } 799 fmt.Fprintf(w, "\t%s", file.SiaPath) 800 if !renterListVerbose && !file.Available { 801 fmt.Fprintf(w, " (uploading, %0.2f%%)", file.UploadProgress) 802 } 803 fmt.Fprintln(w, "") 804 } 805 w.Flush() 806 } 807 808 // renterfilesrenamecmd is the handler for the command `siac renter rename [path] [newpath]`. 809 // Renames a file on the Sia network. 810 func renterfilesrenamecmd(path, newpath string) { 811 err := httpClient.RenterRenamePost(path, newpath) 812 if err != nil { 813 die("Could not rename file:", err) 814 } 815 fmt.Printf("Renamed %s to %s\n", path, newpath) 816 } 817 818 // renterfilesuploadcmd is the handler for the command `siac renter upload 819 // [source] [path]`. Uploads the [source] file to [path] on the Sia network. 820 // If [source] is a directory, all files inside it will be uploaded and named 821 // relative to [path]. 822 func renterfilesuploadcmd(source, path string) { 823 stat, err := os.Stat(source) 824 if err != nil { 825 die("Could not stat file or folder:", err) 826 } 827 828 if stat.IsDir() { 829 // folder 830 var files []string 831 err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 832 if err != nil { 833 fmt.Println("Warning: skipping file:", err) 834 return nil 835 } 836 if info.IsDir() { 837 return nil 838 } 839 files = append(files, path) 840 return nil 841 }) 842 if err != nil { 843 die("Could not read folder:", err) 844 } else if len(files) == 0 { 845 die("Nothing to upload.") 846 } 847 for _, file := range files { 848 fpath, _ := filepath.Rel(source, file) 849 fpath = filepath.Join(path, fpath) 850 fpath = filepath.ToSlash(fpath) 851 err = httpClient.RenterUploadDefaultPost(abs(file), fpath) 852 if err != nil { 853 die("Could not upload file:", err) 854 } 855 } 856 fmt.Printf("Uploaded %d files into '%s'.\n", len(files), path) 857 } else { 858 // single file 859 err = httpClient.RenterUploadDefaultPost(abs(source), path) 860 if err != nil { 861 die("Could not upload file:", err) 862 } 863 fmt.Printf("Uploaded '%s' as '%s'.\n", abs(source), path) 864 } 865 } 866 867 // renterpricescmd is the handler for the command `siac renter prices`, which 868 // displays the prices of various storage operations. The user can submit an 869 // allowance to have the estimate reflect those settings or the user can submit 870 // nothing 871 func renterpricescmd(cmd *cobra.Command, args []string) { 872 allowance := modules.Allowance{} 873 874 if len(args) != 0 && len(args) != 4 { 875 cmd.UsageFunc()(cmd) 876 os.Exit(exitCodeUsage) 877 } 878 if len(args) > 0 { 879 hastings, err := parseCurrency(args[0]) 880 if err != nil { 881 die("Could not parse amount:", err) 882 } 883 blocks, err := parsePeriod(args[1]) 884 if err != nil { 885 die("Could not parse period:", err) 886 } 887 _, err = fmt.Sscan(hastings, &allowance.Funds) 888 if err != nil { 889 die("Could not set allowance funds:", err) 890 } 891 892 _, err = fmt.Sscan(blocks, &allowance.Period) 893 if err != nil { 894 die("Could not set allowance period:", err) 895 } 896 hosts, err := strconv.Atoi(args[2]) 897 if err != nil { 898 die("Could not parse host count") 899 } 900 allowance.Hosts = uint64(hosts) 901 renewWindow, err := parsePeriod(args[3]) 902 if err != nil { 903 die("Could not parse renew window") 904 } 905 _, err = fmt.Sscan(renewWindow, &allowance.RenewWindow) 906 if err != nil { 907 die("Could not set allowance renew window:", err) 908 } 909 } 910 911 rpg, err := httpClient.RenterPricesGet(allowance) 912 if err != nil { 913 die("Could not read the renter prices:", err) 914 } 915 periodFactor := uint64(rpg.Allowance.Period / types.BlockHeight(4032)) 916 917 // Display Estimate 918 fmt.Println("Renter Prices (estimated):") 919 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 920 fmt.Fprintln(w, "\tFees for Creating a Set of Contracts:\t", currencyUnits(rpg.FormContracts)) 921 fmt.Fprintln(w, "\tDownload 1 TB:\t", currencyUnits(rpg.DownloadTerabyte)) 922 fmt.Fprintln(w, "\tStore 1 TB for 1 Month:\t", currencyUnits(rpg.StorageTerabyteMonth)) 923 fmt.Fprintln(w, "\tStore 1 TB for Allowance Period:\t", currencyUnits(rpg.StorageTerabyteMonth.Mul64(periodFactor))) 924 fmt.Fprintln(w, "\tUpload 1 TB:\t", currencyUnits(rpg.UploadTerabyte)) 925 w.Flush() 926 927 // Display allowance used for estimate 928 fmt.Println("\nAllowance used for estimate:") 929 fmt.Fprintln(w, "\tFunds:\t", currencyUnits(rpg.Allowance.Funds)) 930 fmt.Fprintln(w, "\tPeriod:\t", rpg.Allowance.Period) 931 fmt.Fprintln(w, "\tHosts:\t", rpg.Allowance.Hosts) 932 fmt.Fprintln(w, "\tRenew Window:\t", rpg.Allowance.RenewWindow) 933 w.Flush() 934 }