github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/siac/rentercmd.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "math" 6 "os" 7 "path/filepath" 8 "sort" 9 "text/tabwriter" 10 11 "github.com/spf13/cobra" 12 13 "github.com/NebulousLabs/Sia/api" 14 "github.com/NebulousLabs/Sia/modules" 15 ) 16 17 var ( 18 renterCmd = &cobra.Command{ 19 Use: "renter", 20 Short: "Perform renter actions", 21 Long: "Upload, download, rename, delete, load, or share files.", 22 Run: wrap(rentercmd), 23 } 24 25 renterUploadsCmd = &cobra.Command{ 26 Use: "uploads", 27 Short: "View the upload queue", 28 Long: "View the list of files currently uploading.", 29 Run: wrap(renteruploadscmd), 30 } 31 32 renterDownloadsCmd = &cobra.Command{ 33 Use: "downloads", 34 Short: "View the download queue", 35 Long: "View the list of files currently downloading.", 36 Run: wrap(renterdownloadscmd), 37 } 38 39 renterAllowanceCmd = &cobra.Command{ 40 Use: "allowance", 41 Short: "View the current allowance", 42 Long: "View the current allowance, which controls how much money is spent on file contracts.", 43 Run: wrap(renterallowancecmd), 44 } 45 renterSetAllowanceCmd = &cobra.Command{ 46 Use: "setallowance [amount] [period]", 47 Short: "Set the allowance", 48 Long: `Set the amount of money that can be spent over a given period. 49 amount is given in currency units (SC, KS, etc.) 50 period is given in weeks; 1 week is roughly 1000 blocks 51 52 Note that setting the allowance will cause siad to immediately begin forming 53 contracts! You should only set the allowance once you are fully synced and you 54 have a reasonable number (>30) of hosts in your hostdb.`, 55 Run: wrap(rentersetallowancecmd), 56 } 57 58 renterContractsCmd = &cobra.Command{ 59 Use: "contracts", 60 Short: "View the Renter's contracts", 61 Long: "View the contracts that the Renter has formed with hosts.", 62 Run: wrap(rentercontractscmd), 63 } 64 65 renterFilesDeleteCmd = &cobra.Command{ 66 Use: "delete [path]", 67 Aliases: []string{"rm"}, 68 Short: "Delete a file", 69 Long: "Delete a file. Does not delete the file on disk.", 70 Run: wrap(renterfilesdeletecmd), 71 } 72 73 renterFilesDownloadCmd = &cobra.Command{ 74 Use: "download [path] [destination]", 75 Short: "Download a file", 76 Long: "Download a previously-uploaded file to a specified destination.", 77 Run: wrap(renterfilesdownloadcmd), 78 } 79 80 renterFilesListCmd = &cobra.Command{ 81 Use: "list", 82 Aliases: []string{"ls"}, 83 Short: "List the status of all files", 84 Long: "List the status of all files known to the renter on the Sia network.", 85 Run: wrap(renterfileslistcmd), 86 } 87 88 renterFilesLoadCmd = &cobra.Command{ 89 Use: "load [source]", 90 Short: "Load a .sia file", 91 Long: "Load a .sia file, adding the file entries contained within.", 92 Run: wrap(renterfilesloadcmd), 93 } 94 95 renterFilesLoadASCIICmd = &cobra.Command{ 96 Use: "loadascii [ascii]", 97 Short: "Load an ASCII-encoded .sia file", 98 Long: "Load an ASCII-encoded .sia file.", 99 Run: wrap(renterfilesloadasciicmd), 100 } 101 102 renterFilesRenameCmd = &cobra.Command{ 103 Use: "rename [path] [newpath]", 104 Aliases: []string{"mv"}, 105 Short: "Rename a file", 106 Long: "Rename a file.", 107 Run: wrap(renterfilesrenamecmd), 108 } 109 110 renterFilesShareCmd = &cobra.Command{ 111 Use: "share [path] [destination]", 112 Short: "Export a file to a .sia for sharing", 113 Long: "Export a file to a .sia for sharing.", 114 Run: wrap(renterfilessharecmd), 115 } 116 117 renterFilesShareASCIICmd = &cobra.Command{ 118 Use: "shareascii [path]", 119 Short: "Prints an ASCII-encoded .sia file for sharing", 120 Long: "Prints an ASCII-encoded .sia file for sharing, but does not save the .sia file to disk.", 121 Run: wrap(renterfilesshareasciicmd), 122 } 123 124 renterFilesUploadCmd = &cobra.Command{ 125 Use: "upload [source] [path]", 126 Short: "Upload a file", 127 Long: "Upload a file to [path] on the Sia network.", 128 Run: wrap(renterfilesuploadcmd), 129 } 130 ) 131 132 // abs returns the absolute representation of a path. 133 // TODO: bad things can happen if you run siac from a non-existent directory. 134 // Implement some checks to catch this problem. 135 func abs(path string) string { 136 abspath, err := filepath.Abs(path) 137 if err != nil { 138 return path 139 } 140 return abspath 141 } 142 143 // rentercmd displays the renter's financial metrics and lists the files it is 144 // tracking. 145 func rentercmd() { 146 var rg api.RenterGET 147 err := getAPI("/renter", &rg) 148 if err != nil { 149 die("Could not get renter info:", err) 150 } 151 fm := rg.FinancialMetrics 152 unspent := fm.ContractSpending.Sub(fm.DownloadSpending).Sub(fm.StorageSpending).Sub(fm.UploadSpending) 153 fmt.Printf(`Renter info: 154 Storage Spending: %v 155 Upload Spending: %v 156 Download Spending: %v 157 Unspent Funds: %v 158 Total Allocated: %v 159 160 `, currencyUnits(fm.StorageSpending), currencyUnits(fm.UploadSpending), 161 currencyUnits(fm.DownloadSpending), currencyUnits(unspent), 162 currencyUnits(fm.ContractSpending)) 163 164 // also list files 165 renterfileslistcmd() 166 } 167 168 // renteruploadscmd is the handler for the command `siac renter uploads`. 169 // Lists files currently uploading. 170 func renteruploadscmd() { 171 var rf api.RenterFiles 172 err := getAPI("/renter/files", &rf) 173 if err != nil { 174 die("Could not get upload queue:", err) 175 } 176 177 // TODO: add a --history flag to the uploads command to mirror the --history 178 // flag in the downloads command. This hasn't been done yet because the 179 // call to /renter/files includes files that have been shared with you, 180 // not just files you've uploaded. 181 182 // Filter out files that have been uploaded. 183 var filteredFiles []modules.FileInfo 184 for _, fi := range rf.Files { 185 if !fi.Available { 186 filteredFiles = append(filteredFiles, fi) 187 } 188 } 189 if len(filteredFiles) == 0 { 190 fmt.Println("No files are uploading.") 191 return 192 } 193 fmt.Println("Uploading", len(filteredFiles), "files:") 194 for _, file := range filteredFiles { 195 fmt.Printf("%13s %s (uploading, %0.2f%%)\n", filesizeUnits(int64(file.Filesize)), file.SiaPath, file.UploadProgress) 196 } 197 } 198 199 // renterdownloadscmd is the handler for the command `siac renter downloads`. 200 // Lists files currently downloading, and optionally previously downloaded 201 // files if the -H or --history flag is specified. 202 func renterdownloadscmd() { 203 var queue api.RenterDownloadQueue 204 err := getAPI("/renter/downloads", &queue) 205 if err != nil { 206 die("Could not get download queue:", err) 207 } 208 // Filter out files that have been downloaded. 209 var downloading []modules.DownloadInfo 210 for _, file := range queue.Downloads { 211 if file.Received != file.Filesize { 212 downloading = append(downloading, file) 213 } 214 } 215 if len(downloading) == 0 { 216 fmt.Println("No files are downloading.") 217 } else { 218 fmt.Println("Downloading", len(downloading), "files:") 219 for _, file := range downloading { 220 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) 221 } 222 } 223 if !renterShowHistory { 224 return 225 } 226 fmt.Println() 227 // Filter out files that are downloading. 228 var downloaded []modules.DownloadInfo 229 for _, file := range queue.Downloads { 230 if file.Received == file.Filesize { 231 downloaded = append(downloaded, file) 232 } 233 } 234 if len(downloaded) == 0 { 235 fmt.Println("No files downloaded.") 236 } else { 237 fmt.Println("Downloaded", len(downloaded), "files:") 238 for _, file := range downloaded { 239 fmt.Printf("%s: %s -> %s\n", file.StartTime.Format("Jan 02 03:04 PM"), file.SiaPath, file.Destination) 240 } 241 } 242 } 243 244 // renterallowancecmd displays the current allowance. 245 func renterallowancecmd() { 246 var allowance modules.Allowance 247 err := getAPI("/renter/allowance", &allowance) 248 if err != nil { 249 die("Could not get allowance:", err) 250 } 251 252 // convert to SC 253 fmt.Printf(`Allowance: 254 Amount: %v 255 Period: %v blocks 256 `, currencyUnits(allowance.Funds), allowance.Period) 257 } 258 259 // rentersetallowancecmd allows the user to set the allowance. 260 func rentersetallowancecmd(amount, period string) { 261 hastings, err := parseCurrency(amount) 262 if err != nil { 263 die("Could not parse amount:", err) 264 } 265 blocks, err := parsePeriod(period) 266 if err != nil { 267 die("Could not parse period") 268 } 269 err = post("/renter/allowance", fmt.Sprintf("funds=%s&period=%s", hastings, blocks)) 270 if err != nil { 271 die("Could not set allowance:", err) 272 } 273 fmt.Println("Allowance updated.") 274 } 275 276 // byHeight sorts contracts by their expiration, high to low. If two contracts 277 // expire at the same height, they are sorted by their host's address. 278 type byHeight []modules.RenterContract 279 280 func (s byHeight) Len() int { return len(s) } 281 func (s byHeight) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 282 func (s byHeight) Less(i, j int) bool { 283 hi, hj := s[i].LastRevision.NewWindowStart, s[j].LastRevision.NewWindowStart 284 if hi == hj { 285 return s[i].NetAddress < s[j].NetAddress 286 } 287 return hi > hj 288 } 289 290 // rentercontractscmd is the handler for the comand `siac renter contracts`. 291 // It lists the Renter's contracts. 292 func rentercontractscmd() { 293 var rc api.RenterContracts 294 err := getAPI("/renter/contracts", &rc) 295 if err != nil { 296 die("Could not get contracts:", err) 297 } 298 if len(rc.Contracts) == 0 { 299 fmt.Println("No contracts have been formed.") 300 return 301 } 302 sort.Sort(byHeight(rc.Contracts)) 303 fmt.Println("Contracts:") 304 w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0) 305 fmt.Fprintln(w, "Host\tValue\tData\tEnd Height\tID") 306 for _, c := range rc.Contracts { 307 value := currencyUnits(c.LastRevision.NewValidProofOutputs[0].Value) 308 data := filesizeUnits(int64(modules.SectorSize) * int64(len(c.MerkleRoots))) 309 fmt.Fprintf(w, "%v\t%8s\t%v\t%v\t%v\n", c.NetAddress, value, data, c.LastRevision.NewWindowStart, c.ID) 310 } 311 w.Flush() 312 } 313 314 // renterfilesdeletecmd is the handler for the command `siac renter delete [path]`. 315 // Removes the specified path from the Sia network. 316 func renterfilesdeletecmd(path string) { 317 err := post("/renter/delete/"+path, "") 318 if err != nil { 319 die("Could not delete file:", err) 320 } 321 fmt.Println("Deleted", path) 322 } 323 324 // renterfilesdownloadcmd is the handler for the comand `siac renter download [path] [destination]`. 325 // Downloads a path from the Sia network to the local specified destination. 326 func renterfilesdownloadcmd(path, destination string) { 327 err := get("/renter/download/" + path + "?destination=" + abs(destination)) 328 if err != nil { 329 die("Could not download file:", err) 330 } 331 fmt.Printf("Downloaded '%s' to %s.\n", path, abs(destination)) 332 } 333 334 // bySiaPath implements sort.Interface for [] modules.FileInfo based on the 335 // SiaPath field. 336 type bySiaPath []modules.FileInfo 337 338 func (s bySiaPath) Len() int { return len(s) } 339 func (s bySiaPath) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 340 func (s bySiaPath) Less(i, j int) bool { return s[i].SiaPath < s[j].SiaPath } 341 342 // renterfileslistcmd is the handler for the command `siac renter list`. 343 // Lists files known to the renter on the network. 344 func renterfileslistcmd() { 345 var rf api.RenterFiles 346 err := getAPI("/renter/files", &rf) 347 if err != nil { 348 die("Could not get file list:", err) 349 } 350 if len(rf.Files) == 0 { 351 fmt.Println("No files have been uploaded.") 352 return 353 } 354 fmt.Println("Tracking", len(rf.Files), "files:") 355 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) 356 if renterListVerbose { 357 fmt.Fprintln(w, "File size\tAvailable\tProgress\tRedundancy\tRenewing\tSia path") 358 } 359 sort.Sort(bySiaPath(rf.Files)) 360 for _, file := range rf.Files { 361 fmt.Fprintf(w, "%9s", filesizeUnits(int64(file.Filesize))) 362 if renterListVerbose { 363 availableStr := yesNo(file.Available) 364 renewingStr := yesNo(file.Renewing) 365 redundancyStr := fmt.Sprintf("%.2f", file.Redundancy) 366 if math.IsNaN(file.Redundancy) { 367 redundancyStr = "-" 368 } 369 uploadProgressStr := fmt.Sprintf("%.2f%%", file.UploadProgress) 370 if math.IsNaN(file.UploadProgress) { 371 uploadProgressStr = "-" 372 } 373 fmt.Fprintf(w, "\t%s\t%8s\t%10s\t%s", availableStr, uploadProgressStr, redundancyStr, renewingStr) 374 } 375 fmt.Fprintf(w, "\t%s", file.SiaPath) 376 if !renterListVerbose && !file.Available { 377 fmt.Fprintf(w, " (uploading, %0.2f%%)", file.UploadProgress) 378 } 379 fmt.Fprintln(w, "") 380 } 381 w.Flush() 382 } 383 384 // renterfilesloadcmd is the handler for the command `siac renter load [source]`. 385 // Loads a .sia file, adding the file entries contained within. 386 func renterfilesloadcmd(source string) { 387 var info api.RenterLoad 388 err := postResp("/renter/load", "source="+abs(source), &info) 389 if err != nil { 390 die("Could not load file:", err) 391 } 392 fmt.Printf("Loaded %d file(s):\n", len(info.FilesAdded)) 393 for _, file := range info.FilesAdded { 394 fmt.Printf("\t%s\n", file) 395 } 396 } 397 398 // renterfilesloadasciicmd is the handler for the command `siac renter loadascii [ascii]`. 399 // Load an ASCII-encoded .sia file. 400 func renterfilesloadasciicmd(ascii string) { 401 var info api.RenterLoad 402 err := postResp("/renter/loadascii", "asciisia="+ascii, &info) 403 if err != nil { 404 die("Could not load file:", err) 405 } 406 fmt.Printf("Loaded %d file(s):\n", len(info.FilesAdded)) 407 for _, file := range info.FilesAdded { 408 fmt.Printf("\t%s\n", file) 409 } 410 } 411 412 // renterfilesrenamecmd is the handler for the command `siac renter rename [path] [newpath]`. 413 // Renames a file on the Sia network. 414 func renterfilesrenamecmd(path, newpath string) { 415 err := post("/renter/rename/"+path, "newsiapath="+newpath) 416 if err != nil { 417 die("Could not rename file:", err) 418 } 419 fmt.Printf("Renamed %s to %s\n", path, newpath) 420 } 421 422 // renterfilessharecmd is the handler for the command `siac renter share [path] [destination]`. 423 // Export a file to a .sia for sharing. 424 func renterfilessharecmd(path, destination string) { 425 err := get(fmt.Sprintf("/renter/share?siapaths=%s&destination=%s", path, abs(destination))) 426 if err != nil { 427 die("Could not share file:", err) 428 } 429 fmt.Printf("Exported %s to %s\n", path, abs(destination)) 430 } 431 432 // renterfilesshareasciicmd is the handler for the command `siac renter shareascii [path]`. 433 // Prints an ascii-encoded sia file. 434 func renterfilesshareasciicmd(path string) { 435 var data api.RenterShareASCII 436 err := getAPI("/renter/shareascii?siapaths="+path, &data) 437 if err != nil { 438 die("Could not share file:", err) 439 } 440 fmt.Println(data.ASCIIsia) 441 } 442 443 // renterfilesuploadcmd is the handler for the command `siac renter upload [source] [path]`. 444 // Uploads the [source] file to [path] on the Sia network. 445 func renterfilesuploadcmd(source, path string) { 446 err := post("/renter/upload/"+path, "source="+abs(source)) 447 if err != nil { 448 die("Could not upload file:", err) 449 } 450 fmt.Printf("Uploaded '%s' as %s.\n", abs(source), path) 451 }