github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/cmd/siac/rentercmd.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "sort" 8 "strconv" 9 "text/tabwriter" 10 "time" 11 12 "github.com/spf13/cobra" 13 14 "github.com/Synthesix/Sia/modules" 15 "github.com/Synthesix/Sia/node/api" 16 ) 17 18 var ( 19 renterAllowanceCancelCmd = &cobra.Command{ 20 Use: "cancel", 21 Short: "Cancel the current allowance", 22 Long: "Cancel the current allowance, which controls how much money is spent on file contracts.", 23 Run: wrap(renterallowancecancelcmd), 24 } 25 26 renterAllowanceCmd = &cobra.Command{ 27 Use: "allowance", 28 Short: "View the current allowance", 29 Long: "View the current allowance, which controls how much money is spent on file contracts.", 30 Run: wrap(renterallowancecmd), 31 } 32 33 renterCmd = &cobra.Command{ 34 Use: "renter", 35 Short: "Perform renter actions", 36 Long: "Upload, download, rename, delete, load, or share files.", 37 Run: wrap(rentercmd), 38 } 39 40 renterContractsCmd = &cobra.Command{ 41 Use: "contracts", 42 Short: "View the Renter's contracts", 43 Long: "View the contracts that the Renter has formed with hosts.", 44 Run: wrap(rentercontractscmd), 45 } 46 47 renterContractsViewCmd = &cobra.Command{ 48 Use: "view [contract-id]", 49 Short: "View details of the specified contract", 50 Long: "View all details available of the specified contract.", 51 Run: wrap(rentercontractsviewcmd), 52 } 53 54 renterDownloadsCmd = &cobra.Command{ 55 Use: "downloads", 56 Short: "View the download queue", 57 Long: "View the list of files currently downloading.", 58 Run: wrap(renterdownloadscmd), 59 } 60 61 renterFilesDeleteCmd = &cobra.Command{ 62 Use: "delete [path]", 63 Aliases: []string{"rm"}, 64 Short: "Delete a file", 65 Long: "Delete a file. Does not delete the file on disk.", 66 Run: wrap(renterfilesdeletecmd), 67 } 68 69 renterFilesDownloadCmd = &cobra.Command{ 70 Use: "download [path] [destination]", 71 Short: "Download a file", 72 Long: "Download a previously-uploaded file to a specified destination.", 73 Run: wrap(renterfilesdownloadcmd), 74 } 75 76 renterFilesListCmd = &cobra.Command{ 77 Use: "list", 78 Aliases: []string{"ls"}, 79 Short: "List the status of all files", 80 Long: "List the status of all files known to the renter on the Sia network.", 81 Run: wrap(renterfileslistcmd), 82 } 83 84 renterFilesRenameCmd = &cobra.Command{ 85 Use: "rename [path] [newpath]", 86 Aliases: []string{"mv"}, 87 Short: "Rename a file", 88 Long: "Rename a file.", 89 Run: wrap(renterfilesrenamecmd), 90 } 91 92 renterFilesUploadCmd = &cobra.Command{ 93 Use: "upload [source] [path]", 94 Short: "Upload a file", 95 Long: "Upload a file to [path] on the Sia network.", 96 Run: wrap(renterfilesuploadcmd), 97 } 98 99 renterPricesCmd = &cobra.Command{ 100 Use: "prices", 101 Short: "Display the price of storage and bandwidth", 102 Long: "Display the estimated prices of storing files, retrieving files, and creating a set of contracts", 103 Run: wrap(renterpricescmd), 104 } 105 106 renterSetAllowanceCmd = &cobra.Command{ 107 Use: "setallowance [amount] [period] [hosts] [renew window]", 108 Short: "Set the allowance", 109 Long: `Set the amount of money that can be spent over a given period. 110 111 amount is given in currency units (SC, KS, etc.) 112 113 period is given in either blocks (b), hours (h), days (d), or weeks (w). A 114 block is approximately 10 minutes, so one hour is six blocks, a day is 144 115 blocks, and a week is 1008 blocks. 116 117 The Sia renter module spreads data across more than one Sia server computer 118 or "host". The "hosts" parameter for the setallowance command determines 119 how many different hosts the renter will spread the data across. 120 121 Allowance can be automatically renewed periodically. If the current 122 blockheight + the renew window >= the end height the contract, 123 then the contract is renewed automatically. 124 125 Note that setting the allowance will cause siad to immediately begin forming 126 contracts! You should only set the allowance once you are fully synced and you 127 have a reasonable number (>30) of hosts in your hostdb.`, 128 Run: rentersetallowancecmd, 129 } 130 131 renterUploadsCmd = &cobra.Command{ 132 Use: "uploads", 133 Short: "View the upload queue", 134 Long: "View the list of files currently uploading.", 135 Run: wrap(renteruploadscmd), 136 } 137 ) 138 139 // abs returns the absolute representation of a path. 140 // TODO: bad things can happen if you run siac from a non-existent directory. 141 // Implement some checks to catch this problem. 142 func abs(path string) string { 143 abspath, err := filepath.Abs(path) 144 if err != nil { 145 return path 146 } 147 return abspath 148 } 149 150 // rentercmd displays the renter's financial metrics and lists the files it is 151 // tracking. 152 func rentercmd() { 153 var rg api.RenterGET 154 err := getAPI("/renter", &rg) 155 if err != nil { 156 die("Could not get renter info:", err) 157 } 158 fm := rg.FinancialMetrics 159 fmt.Printf(`Renter info: 160 Storage Spending: %v 161 Upload Spending: %v 162 Download Spending: %v 163 Unspent Funds: %v 164 Total Allocated: %v 165 166 `, currencyUnits(fm.StorageSpending), currencyUnits(fm.UploadSpending), 167 currencyUnits(fm.DownloadSpending), currencyUnits(fm.Unspent), 168 currencyUnits(fm.ContractSpending)) 169 170 // also list files 171 renterfileslistcmd() 172 } 173 174 // renteruploadscmd is the handler for the command `siac renter uploads`. 175 // Lists files currently uploading. 176 func renteruploadscmd() { 177 var rf api.RenterFiles 178 err := getAPI("/renter/files", &rf) 179 if err != nil { 180 die("Could not get upload queue:", err) 181 } 182 183 // TODO: add a --history flag to the uploads command to mirror the --history 184 // flag in the downloads command. This hasn't been done yet because the 185 // call to /renter/files includes files that have been shared with you, 186 // not just files you've uploaded. 187 188 // Filter out files that have been uploaded. 189 var filteredFiles []modules.FileInfo 190 for _, fi := range rf.Files { 191 if !fi.Available { 192 filteredFiles = append(filteredFiles, fi) 193 } 194 } 195 if len(filteredFiles) == 0 { 196 fmt.Println("No files are uploading.") 197 return 198 } 199 fmt.Println("Uploading", len(filteredFiles), "files:") 200 for _, file := range filteredFiles { 201 fmt.Printf("%13s %s (uploading, %0.2f%%)\n", filesizeUnits(int64(file.Filesize)), file.SiaPath, file.UploadProgress) 202 } 203 } 204 205 // renterdownloadscmd is the handler for the command `siac renter downloads`. 206 // Lists files currently downloading, and optionally previously downloaded 207 // files if the -H or --history flag is specified. 208 func renterdownloadscmd() { 209 var queue api.RenterDownloadQueue 210 err := getAPI("/renter/downloads", &queue) 211 if err != nil { 212 die("Could not get download queue:", err) 213 } 214 // Filter out files that have been downloaded. 215 var downloading []api.DownloadInfo 216 for _, file := range queue.Downloads { 217 if file.Received != file.Filesize { 218 downloading = append(downloading, file) 219 } 220 } 221 if len(downloading) == 0 { 222 fmt.Println("No files are downloading.") 223 } else { 224 fmt.Println("Downloading", len(downloading), "files:") 225 for _, file := range downloading { 226 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) 227 } 228 } 229 if !renterShowHistory { 230 return 231 } 232 fmt.Println() 233 // Filter out files that are downloading. 234 var downloaded []api.DownloadInfo 235 for _, file := range queue.Downloads { 236 if file.Received == file.Filesize { 237 downloaded = append(downloaded, file) 238 } 239 } 240 if len(downloaded) == 0 { 241 fmt.Println("No files downloaded.") 242 } else { 243 fmt.Println("Downloaded", len(downloaded), "files:") 244 for _, file := range downloaded { 245 fmt.Printf("%s: %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), file.SiaPath, file.Destination) 246 } 247 } 248 } 249 250 // renterallowancecmd displays the current allowance. 251 func renterallowancecmd() { 252 var rg api.RenterGET 253 err := getAPI("/renter", &rg) 254 if err != nil { 255 die("Could not get allowance:", err) 256 } 257 allowance := rg.Settings.Allowance 258 259 // convert to SC 260 fmt.Printf(`Allowance: 261 Amount: %v 262 Period: %v blocks 263 `, currencyUnits(allowance.Funds), allowance.Period) 264 } 265 266 // renterallowancecancelcmd cancels the current allowance. 267 func renterallowancecancelcmd() { 268 err := post("/renter", "hosts=0&funds=0&period=0&renewwindow=0") 269 if err != nil { 270 die("error canceling allowance:", err) 271 } 272 fmt.Println("Allowance canceled.") 273 } 274 275 // rentersetallowancecmd allows the user to set the allowance. 276 // the first two parameters, amount and period, are required. 277 // the second two parameters are optional: 278 // hosts integer number of hosts 279 // renewperiod how many blocks between renewals 280 func rentersetallowancecmd(cmd *cobra.Command, args []string) { 281 if len(args) < 2 || len(args) > 4 { 282 cmd.UsageFunc()(cmd) 283 os.Exit(exitCodeUsage) 284 } 285 hastings, err := parseCurrency(args[0]) 286 if err != nil { 287 die("Could not parse amount:", err) 288 } 289 blocks, err := parsePeriod(args[1]) 290 if err != nil { 291 die("Could not parse period") 292 } 293 queryString := fmt.Sprintf("funds=%s&period=%s", hastings, blocks) 294 if len(args) > 2 { 295 _, err = strconv.Atoi(args[2]) 296 if err != nil { 297 die("Could not parse host count") 298 } 299 queryString += fmt.Sprintf("&hosts=%s", args[2]) 300 } 301 if len(args) > 3 { 302 renewWindow, err := parsePeriod(args[3]) 303 if err != nil { 304 die("Could not parse renew window") 305 } 306 queryString += fmt.Sprintf("&renewwindow=%s", renewWindow) 307 } 308 err = post("/renter", queryString) 309 if err != nil { 310 die("Could not set allowance:", err) 311 } 312 fmt.Println("Allowance updated.") 313 } 314 315 // byValue sorts contracts by their value in siacoins, high to low. If two 316 // contracts have the same value, they are sorted by their host's address. 317 type byValue []api.RenterContract 318 319 func (s byValue) Len() int { return len(s) } 320 func (s byValue) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 321 func (s byValue) Less(i, j int) bool { 322 cmp := s[i].RenterFunds.Cmp(s[j].RenterFunds) 323 if cmp == 0 { 324 return s[i].NetAddress < s[j].NetAddress 325 } 326 return cmp > 0 327 } 328 329 // rentercontractscmd is the handler for the comand `siac renter contracts`. 330 // It lists the Renter's contracts. 331 func rentercontractscmd() { 332 var rc api.RenterContracts 333 err := getAPI("/renter/contracts", &rc) 334 if err != nil { 335 die("Could not get contracts:", err) 336 } 337 if len(rc.Contracts) == 0 { 338 fmt.Println("No contracts have been formed.") 339 return 340 } 341 sort.Sort(byValue(rc.Contracts)) 342 fmt.Println("Contracts:") 343 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 344 fmt.Fprintln(w, "Host\tRemaining Funds\tSpent Funds\tSpent Fees\tData\tEnd Height\tID\tGoodForUpload\tGoodForRenew") 345 for _, c := range rc.Contracts { 346 fmt.Fprintf(w, "%v\t%8s\t%8s\t%8s\t%v\t%v\t%v\t%v\t%v\n", 347 c.NetAddress, 348 currencyUnits(c.RenterFunds), 349 currencyUnits(c.TotalCost.Sub(c.RenterFunds).Sub(c.Fees)), 350 currencyUnits(c.Fees), 351 filesizeUnits(int64(c.Size)), 352 c.EndHeight, 353 c.ID, 354 c.GoodForUpload, 355 c.GoodForRenew) 356 } 357 w.Flush() 358 } 359 360 // rentercontractsviewcmd is the handler for the command `siac renter contracts <id>`. 361 // It lists details of a specific contract. 362 func rentercontractsviewcmd(cid string) { 363 var rc api.RenterContracts 364 err := getAPI("/renter/contracts", &rc) 365 if err != nil { 366 die("Could not get contract details: ", err) 367 } 368 369 for _, rc := range rc.Contracts { 370 if rc.ID.String() == cid { 371 var hostInfo api.HostdbHostsGET 372 err = getAPI("/hostdb/hosts/"+rc.HostPublicKey.String(), &hostInfo) 373 if err != nil { 374 die("Could not fetch details of host: ", err) 375 } 376 fmt.Printf(` 377 Contract %v 378 Host: %v (Public Key: %v) 379 380 Start Height: %v 381 End Height: %v 382 383 Total cost: %v (Fees: %v) 384 Funds Allocated: %v 385 Upload Spending: %v 386 Storage Spending: %v 387 Download Spending: %v 388 Remaining Funds: %v 389 390 File Size: %v 391 `, rc.ID, rc.NetAddress, rc.HostPublicKey.String(), rc.StartHeight, rc.EndHeight, 392 currencyUnits(rc.TotalCost), 393 currencyUnits(rc.Fees), 394 currencyUnits(rc.TotalCost.Sub(rc.Fees)), 395 currencyUnits(rc.UploadSpending), 396 currencyUnits(rc.StorageSpending), 397 currencyUnits(rc.DownloadSpending), 398 currencyUnits(rc.RenterFunds), 399 filesizeUnits(int64(rc.Size))) 400 401 printScoreBreakdown(&hostInfo) 402 return 403 } 404 } 405 406 fmt.Println("Contract not found") 407 } 408 409 // renterfilesdeletecmd is the handler for the command `siac renter delete [path]`. 410 // Removes the specified path from the Sia network. 411 func renterfilesdeletecmd(path string) { 412 err := post("/renter/delete/"+path, "") 413 if err != nil { 414 die("Could not delete file:", err) 415 } 416 fmt.Println("Deleted", path) 417 } 418 419 // renterfilesdownloadcmd is the handler for the comand `siac renter download [path] [destination]`. 420 // Downloads a path from the Sia network to the local specified destination. 421 func renterfilesdownloadcmd(path, destination string) { 422 destination = abs(destination) 423 done := make(chan struct{}) 424 go downloadprogress(done, path) 425 426 err := get("/renter/download/" + path + "?destination=" + destination) 427 close(done) 428 if err != nil { 429 die("Could not download file:", err) 430 } 431 fmt.Printf("\nDownloaded '%s' to %s.\n", path, abs(destination)) 432 } 433 434 func downloadprogress(done chan struct{}, siapath string) { 435 time.Sleep(time.Second) // give download time to initialize 436 for { 437 select { 438 case <-done: 439 return 440 441 case <-time.Tick(time.Second): 442 // get download progress of file 443 var queue api.RenterDownloadQueue 444 err := getAPI("/renter/downloads", &queue) 445 if err != nil { 446 continue // benign 447 } 448 var d api.DownloadInfo 449 for _, d = range queue.Downloads { 450 if d.SiaPath == siapath { 451 break 452 } 453 } 454 if d.Filesize == 0 { 455 continue // file hasn't appeared in queue yet 456 } 457 pct := 100 * float64(d.Received) / float64(d.Filesize) 458 elapsed := time.Since(d.StartTime) 459 elapsed -= elapsed % time.Second // round to nearest second 460 mbps := (float64(d.Received*8) / 1e6) / time.Since(d.StartTime).Seconds() 461 fmt.Printf("\rDownloading... %5.1f%% of %v, %v elapsed, %.2f Mbps ", pct, filesizeUnits(int64(d.Filesize)), elapsed, mbps) 462 } 463 } 464 465 } 466 467 // bySiaPath implements sort.Interface for [] modules.FileInfo based on the 468 // SiaPath field. 469 type bySiaPath []modules.FileInfo 470 471 func (s bySiaPath) Len() int { return len(s) } 472 func (s bySiaPath) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 473 func (s bySiaPath) Less(i, j int) bool { return s[i].SiaPath < s[j].SiaPath } 474 475 // renterfileslistcmd is the handler for the command `siac renter list`. 476 // Lists files known to the renter on the network. 477 func renterfileslistcmd() { 478 var rf api.RenterFiles 479 err := getAPI("/renter/files", &rf) 480 if err != nil { 481 die("Could not get file list:", err) 482 } 483 if len(rf.Files) == 0 { 484 fmt.Println("No files have been uploaded.") 485 return 486 } 487 fmt.Println("Tracking", len(rf.Files), "files:") 488 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 489 if renterListVerbose { 490 fmt.Fprintln(w, "File size\tAvailable\tUploaded\tProgress\tRedundancy\tRenewing\tSia path") 491 } 492 sort.Sort(bySiaPath(rf.Files)) 493 for _, file := range rf.Files { 494 fmt.Fprintf(w, "%9s", filesizeUnits(int64(file.Filesize))) 495 if renterListVerbose { 496 availableStr := yesNo(file.Available) 497 renewingStr := yesNo(file.Renewing) 498 redundancyStr := fmt.Sprintf("%.2f", file.Redundancy) 499 if file.Redundancy == -1 { 500 redundancyStr = "-" 501 } 502 uploadProgressStr := fmt.Sprintf("%.2f%%", file.UploadProgress) 503 if file.UploadProgress == -1 { 504 uploadProgressStr = "-" 505 } 506 fmt.Fprintf(w, "\t%s\t%9s\t%8s\t%10s\t%s", availableStr, filesizeUnits(int64(file.UploadedBytes)), uploadProgressStr, redundancyStr, renewingStr) 507 } 508 fmt.Fprintf(w, "\t%s", file.SiaPath) 509 if !renterListVerbose && !file.Available { 510 fmt.Fprintf(w, " (uploading, %0.2f%%)", file.UploadProgress) 511 } 512 fmt.Fprintln(w, "") 513 } 514 w.Flush() 515 } 516 517 // renterfilesrenamecmd is the handler for the command `siac renter rename [path] [newpath]`. 518 // Renames a file on the Sia network. 519 func renterfilesrenamecmd(path, newpath string) { 520 err := post("/renter/rename/"+path, "newsiapath="+newpath) 521 if err != nil { 522 die("Could not rename file:", err) 523 } 524 fmt.Printf("Renamed %s to %s\n", path, newpath) 525 } 526 527 // renterfilesuploadcmd is the handler for the command `siac renter upload 528 // [source] [path]`. Uploads the [source] file to [path] on the Sia network. 529 // If [source] is a directory, all files inside it will be uploaded and named 530 // relative to [path]. 531 func renterfilesuploadcmd(source, path string) { 532 stat, err := os.Stat(source) 533 if err != nil { 534 die("Could not stat file or folder:", err) 535 } 536 537 if stat.IsDir() { 538 // folder 539 var files []string 540 err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 541 if err != nil { 542 fmt.Println("Warning: skipping file:", err) 543 return nil 544 } 545 if info.IsDir() { 546 return nil 547 } 548 files = append(files, path) 549 return nil 550 }) 551 if err != nil { 552 die("Could not read folder:", err) 553 } else if len(files) == 0 { 554 die("Nothing to upload.") 555 } 556 for _, file := range files { 557 fpath, _ := filepath.Rel(source, file) 558 fpath = filepath.Join(path, fpath) 559 fpath = filepath.ToSlash(fpath) 560 err = post("/renter/upload/"+fpath, "source="+abs(file)) 561 if err != nil { 562 die("Could not upload file:", err) 563 } 564 } 565 fmt.Printf("Uploaded %d files into '%s'.\n", len(files), path) 566 } else { 567 // single file 568 err = post("/renter/upload/"+path, "source="+abs(source)) 569 if err != nil { 570 die("Could not upload file:", err) 571 } 572 fmt.Printf("Uploaded '%s' as %s.\n", abs(source), path) 573 } 574 } 575 576 // renterpricescmd is the handler for the command `siac renter prices`, which 577 // displays the prices of various storage operations. 578 func renterpricescmd() { 579 var rpg api.RenterPricesGET 580 err := getAPI("/renter/prices", &rpg) 581 if err != nil { 582 die("Could not read the renter prices:", err) 583 } 584 585 fmt.Println("Renter Prices (estimated):") 586 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 587 fmt.Fprintln(w, "\tFees for Creating a Set of Contracts:\t", currencyUnits(rpg.FormContracts)) 588 fmt.Fprintln(w, "\tDownload 1 TB:\t", currencyUnits(rpg.DownloadTerabyte)) 589 fmt.Fprintln(w, "\tStore 1 TB for 1 Month:\t", currencyUnits(rpg.StorageTerabyteMonth)) 590 fmt.Fprintln(w, "\tUpload 1 TB:\t", currencyUnits(rpg.UploadTerabyte)) 591 w.Flush() 592 }