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  }