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  }