gitlab.com/jokerrs1/Sia@v1.3.2/node/api/renter.go (about)

     1  package api
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"path/filepath"
     7  	"sort"
     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  	}
   120  
   121  	// RenterDownloadQueue contains the renter's download queue.
   122  	RenterDownloadQueue struct {
   123  		Downloads []DownloadInfo `json:"downloads"`
   124  	}
   125  
   126  	// RenterFiles lists the files known to the renter.
   127  	RenterFiles struct {
   128  		Files []modules.FileInfo `json:"files"`
   129  	}
   130  
   131  	// RenterLoad lists files that were loaded into the renter.
   132  	RenterLoad struct {
   133  		FilesAdded []string `json:"filesadded"`
   134  	}
   135  
   136  	// RenterPricesGET lists the data that is returned when a GET call is made
   137  	// to /renter/prices.
   138  	RenterPricesGET struct {
   139  		modules.RenterPriceEstimation
   140  	}
   141  
   142  	// RenterShareASCII contains an ASCII-encoded .sia file.
   143  	RenterShareASCII struct {
   144  		ASCIIsia string `json:"asciisia"`
   145  	}
   146  
   147  	// DownloadInfo contains all client-facing information of a file.
   148  	DownloadInfo struct {
   149  		Destination     string `json:"destination"`     // The destination of the download.
   150  		DestinationType string `json:"destinationtype"` // Can be "file", "memory buffer", or "http stream".
   151  		Filesize        uint64 `json:"filesize"`        // DEPRECATED. Same as 'Length'.
   152  		Length          uint64 `json:"length"`          // The length requested for the download.
   153  		Offset          uint64 `json:"offset"`          // The offset within the siafile requested for the download.
   154  		SiaPath         string `json:"siapath"`         // The siapath of the file used for the download.
   155  
   156  		Completed            bool      `json:"completed"`            // Whether or not the download has completed.
   157  		EndTime              time.Time `json:"endtime"`              // The time when the download fully completed.
   158  		Error                string    `json:"error"`                // Will be the empty string unless there was an error.
   159  		Received             uint64    `json:"received"`             // Amount of data confirmed and decoded.
   160  		StartTime            time.Time `json:"starttime"`            // The time when the download was started.
   161  		TotalDataTransferred uint64    `json:"totaldatatransferred"` // The total amount of data transferred, including negotiation, overdrive etc.
   162  	}
   163  )
   164  
   165  // renterHandlerGET handles the API call to /renter.
   166  func (api *API) renterHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   167  	settings := api.renter.Settings()
   168  	periodStart := api.renter.CurrentPeriod()
   169  	WriteJSON(w, RenterGET{
   170  		Settings:         settings,
   171  		FinancialMetrics: api.renter.PeriodSpending(),
   172  		CurrentPeriod:    periodStart,
   173  	})
   174  }
   175  
   176  // renterHandlerPOST handles the API call to set the Renter's settings.
   177  func (api *API) renterHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   178  	// Get the existing settings
   179  	settings := api.renter.Settings()
   180  
   181  	// Scan the allowance amount. (optional parameter)
   182  	if f := req.FormValue("funds"); f != "" {
   183  		funds, ok := scanAmount(f)
   184  		if !ok {
   185  			WriteError(w, Error{"unable to parse funds"}, http.StatusBadRequest)
   186  			return
   187  		}
   188  		settings.Allowance.Funds = funds
   189  	}
   190  	// Scan the number of hosts to use. (optional parameter)
   191  	if h := req.FormValue("hosts"); h != "" {
   192  		var hosts uint64
   193  		if _, err := fmt.Sscan(h, &hosts); err != nil {
   194  			WriteError(w, Error{"unable to parse hosts: " + err.Error()}, http.StatusBadRequest)
   195  			return
   196  		} else if hosts != 0 && hosts < requiredHosts {
   197  			WriteError(w, Error{fmt.Sprintf("insufficient number of hosts, need at least %v but have %v", recommendedHosts, hosts)}, http.StatusBadRequest)
   198  		} else {
   199  			settings.Allowance.Hosts = hosts
   200  		}
   201  	} else if settings.Allowance.Hosts == 0 {
   202  		// Sane defaults if host haven't been set before.
   203  		settings.Allowance.Hosts = recommendedHosts
   204  	}
   205  	// Scan the period. (optional parameter)
   206  	if p := req.FormValue("period"); p != "" {
   207  		var period types.BlockHeight
   208  		if _, err := fmt.Sscan(p, &period); err != nil {
   209  			WriteError(w, Error{"unable to parse period: " + err.Error()}, http.StatusBadRequest)
   210  			return
   211  		}
   212  		settings.Allowance.Period = types.BlockHeight(period)
   213  	} else if settings.Allowance.Period == 0 {
   214  		WriteError(w, Error{"period needs to be set if it hasn't been set before"}, http.StatusBadRequest)
   215  		return
   216  	}
   217  	// Scan the renew window. (optional parameter)
   218  	if rw := req.FormValue("renewwindow"); rw != "" {
   219  		var renewWindow types.BlockHeight
   220  		if _, err := fmt.Sscan(rw, &renewWindow); err != nil {
   221  			WriteError(w, Error{"unable to parse renewwindow: " + err.Error()}, http.StatusBadRequest)
   222  			return
   223  		} else if renewWindow != 0 && types.BlockHeight(renewWindow) < requiredRenewWindow {
   224  			WriteError(w, Error{fmt.Sprintf("renew window is too small, must be at least %v blocks but have %v blocks", requiredRenewWindow, renewWindow)}, http.StatusBadRequest)
   225  			return
   226  		} else {
   227  			settings.Allowance.RenewWindow = types.BlockHeight(renewWindow)
   228  		}
   229  	} else if settings.Allowance.RenewWindow == 0 {
   230  		// Sane defaults if renew window hasn't been set before.
   231  		settings.Allowance.RenewWindow = settings.Allowance.Period / 2
   232  	}
   233  	// Scan the download speed limit. (optional parameter)
   234  	if d := req.FormValue("maxdownloadspeed"); d != "" {
   235  		var downloadSpeed int64
   236  		if _, err := fmt.Sscan(d, &downloadSpeed); err != nil {
   237  			WriteError(w, Error{"unable to parse downloadspeed: " + err.Error()}, http.StatusBadRequest)
   238  			return
   239  		}
   240  		settings.MaxDownloadSpeed = downloadSpeed
   241  	}
   242  	// Scan the upload speed limit. (optional parameter)
   243  	if u := req.FormValue("maxuploadspeed"); u != "" {
   244  		var uploadSpeed int64
   245  		if _, err := fmt.Sscan(u, &uploadSpeed); err != nil {
   246  			WriteError(w, Error{"unable to parse uploadspeed: " + err.Error()}, http.StatusBadRequest)
   247  			return
   248  		}
   249  		settings.MaxUploadSpeed = uploadSpeed
   250  	}
   251  	// Set the settings in the renter.
   252  	err := api.renter.SetSettings(settings)
   253  	if err != nil {
   254  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   255  		return
   256  	}
   257  	WriteSuccess(w)
   258  }
   259  
   260  // renterContractsHandler handles the API call to request the Renter's contracts.
   261  func (api *API) renterContractsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   262  	contracts := []RenterContract{}
   263  	for _, c := range api.renter.Contracts() {
   264  		var size uint64
   265  		if len(c.Transaction.FileContractRevisions) != 0 {
   266  			size = c.Transaction.FileContractRevisions[0].NewFileSize
   267  		}
   268  
   269  		// Fetch host address
   270  		var netAddress modules.NetAddress
   271  		hdbe, exists := api.renter.Host(c.HostPublicKey)
   272  		if exists {
   273  			netAddress = hdbe.NetAddress
   274  		}
   275  
   276  		// Fetch utilities for contract
   277  		var goodForUpload bool
   278  		var goodForRenew bool
   279  		if utility, ok := api.renter.ContractUtility(c.ID); ok {
   280  			goodForUpload = utility.GoodForUpload
   281  			goodForRenew = utility.GoodForRenew
   282  		}
   283  
   284  		contracts = append(contracts, RenterContract{
   285  			DownloadSpending:          c.DownloadSpending,
   286  			EndHeight:                 c.EndHeight,
   287  			Fees:                      c.TxnFee.Add(c.SiafundFee).Add(c.ContractFee),
   288  			GoodForUpload:             goodForUpload,
   289  			GoodForRenew:              goodForRenew,
   290  			HostPublicKey:             c.HostPublicKey,
   291  			ID:                        c.ID,
   292  			LastTransaction:           c.Transaction,
   293  			NetAddress:                netAddress,
   294  			RenterFunds:               c.RenterFunds,
   295  			Size:                      size,
   296  			StartHeight:               c.StartHeight,
   297  			StorageSpending:           c.StorageSpending,
   298  			StorageSpendingDeprecated: c.StorageSpending,
   299  			TotalCost:                 c.TotalCost,
   300  			UploadSpending:            c.UploadSpending,
   301  		})
   302  	}
   303  	WriteJSON(w, RenterContracts{
   304  		Contracts: contracts,
   305  	})
   306  }
   307  
   308  // renterDownloadsHandler handles the API call to request the download queue.
   309  func (api *API) renterDownloadsHandler(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
   310  	var downloads []DownloadInfo
   311  	for _, di := range api.renter.DownloadHistory() {
   312  		downloads = append(downloads, DownloadInfo{
   313  			Destination:     di.Destination,
   314  			DestinationType: di.DestinationType,
   315  			Filesize:        di.Length,
   316  			Length:          di.Length,
   317  			Offset:          di.Offset,
   318  			SiaPath:         di.SiaPath,
   319  
   320  			Completed:            di.Completed,
   321  			EndTime:              di.EndTime,
   322  			Error:                di.Error,
   323  			Received:             di.Received,
   324  			StartTime:            di.StartTime,
   325  			TotalDataTransferred: di.TotalDataTransferred,
   326  		})
   327  	}
   328  	// sort the downloads by newest first
   329  	sort.Slice(downloads, func(i, j int) bool { return downloads[i].StartTime.After(downloads[j].StartTime) })
   330  	WriteJSON(w, RenterDownloadQueue{
   331  		Downloads: downloads,
   332  	})
   333  }
   334  
   335  // renterLoadHandler handles the API call to load a '.sia' file.
   336  func (api *API) renterLoadHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   337  	source := req.FormValue("source")
   338  	if !filepath.IsAbs(source) {
   339  		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
   340  		return
   341  	}
   342  
   343  	files, err := api.renter.LoadSharedFiles(source)
   344  	if err != nil {
   345  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   346  		return
   347  	}
   348  
   349  	WriteJSON(w, RenterLoad{FilesAdded: files})
   350  }
   351  
   352  // renterLoadAsciiHandler handles the API call to load a '.sia' file
   353  // in ASCII form.
   354  func (api *API) renterLoadASCIIHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   355  	files, err := api.renter.LoadSharedFilesASCII(req.FormValue("asciisia"))
   356  	if err != nil {
   357  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   358  		return
   359  	}
   360  
   361  	WriteJSON(w, RenterLoad{FilesAdded: files})
   362  }
   363  
   364  // renterRenameHandler handles the API call to rename a file entry in the
   365  // renter.
   366  func (api *API) renterRenameHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   367  	err := api.renter.RenameFile(strings.TrimPrefix(ps.ByName("siapath"), "/"), req.FormValue("newsiapath"))
   368  	if err != nil {
   369  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   370  		return
   371  	}
   372  
   373  	WriteSuccess(w)
   374  }
   375  
   376  // renterFilesHandler handles the API call to list all of the files.
   377  func (api *API) renterFilesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   378  	WriteJSON(w, RenterFiles{
   379  		Files: api.renter.FileList(),
   380  	})
   381  }
   382  
   383  // renterPricesHandler reports the expected costs of various actions given the
   384  // renter settings and the set of available hosts.
   385  func (api *API) renterPricesHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
   386  	WriteJSON(w, RenterPricesGET{
   387  		RenterPriceEstimation: api.renter.PriceEstimation(),
   388  	})
   389  }
   390  
   391  // renterDeleteHandler handles the API call to delete a file entry from the
   392  // renter.
   393  func (api *API) renterDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   394  	err := api.renter.DeleteFile(strings.TrimPrefix(ps.ByName("siapath"), "/"))
   395  	if err != nil {
   396  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   397  		return
   398  	}
   399  
   400  	WriteSuccess(w)
   401  }
   402  
   403  // renterDownloadHandler handles the API call to download a file.
   404  func (api *API) renterDownloadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   405  	params, err := parseDownloadParameters(w, req, ps)
   406  	if err != nil {
   407  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   408  		return
   409  	}
   410  
   411  	if params.Async { // Create goroutine if `async` param set.
   412  		// check for errors for 5 seconds to catch validation errors (no file with
   413  		// that path, invalid parameters, insufficient hosts, etc)
   414  		errchan := make(chan error)
   415  		go func() {
   416  			errchan <- api.renter.Download(params)
   417  		}()
   418  		select {
   419  		case err = <-errchan:
   420  			if err != nil {
   421  				WriteError(w, Error{"download failed: " + err.Error()}, http.StatusInternalServerError)
   422  				return
   423  			}
   424  		case <-time.After(time.Millisecond * 100):
   425  		}
   426  	} else {
   427  		err := api.renter.Download(params)
   428  		if err != nil {
   429  			WriteError(w, Error{"download failed: " + err.Error()}, http.StatusInternalServerError)
   430  			return
   431  		}
   432  	}
   433  
   434  	if params.Httpwriter == nil {
   435  		// `httpresp=true` causes writes to w before this line is run, automatically
   436  		// adding `200 Status OK` code to response. Calling this results in a
   437  		// multiple calls to WriteHeaders() errors.
   438  		WriteSuccess(w)
   439  		return
   440  	}
   441  }
   442  
   443  // renterDownloadAsyncHandler handles the API call to download a file asynchronously.
   444  func (api *API) renterDownloadAsyncHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   445  	req.ParseForm()
   446  	req.Form.Set("async", "true")
   447  	api.renterDownloadHandler(w, req, ps)
   448  }
   449  
   450  // parseDownloadParameters parses the download parameters passed to the
   451  // /renter/download endpoint. Validation of these parameters is done by the
   452  // renter.
   453  func parseDownloadParameters(w http.ResponseWriter, req *http.Request, ps httprouter.Params) (modules.RenterDownloadParameters, error) {
   454  	destination := req.FormValue("destination")
   455  
   456  	// The offset and length in bytes.
   457  	offsetparam := req.FormValue("offset")
   458  	lengthparam := req.FormValue("length")
   459  
   460  	// Determines whether the response is written to response body.
   461  	httprespparam := req.FormValue("httpresp")
   462  
   463  	// Determines whether to return on completion of download or straight away.
   464  	// If httprespparam is present, this parameter is ignored.
   465  	asyncparam := req.FormValue("async")
   466  
   467  	// Parse the offset and length parameters.
   468  	var offset, length uint64
   469  	if len(offsetparam) > 0 {
   470  		_, err := fmt.Sscan(offsetparam, &offset)
   471  		if err != nil {
   472  			return modules.RenterDownloadParameters{}, build.ExtendErr("could not decode the offset as uint64: ", err)
   473  		}
   474  	}
   475  	if len(lengthparam) > 0 {
   476  		_, err := fmt.Sscan(lengthparam, &length)
   477  		if err != nil {
   478  			return modules.RenterDownloadParameters{}, build.ExtendErr("could not decode the offset as uint64: ", err)
   479  		}
   480  	}
   481  
   482  	// Parse the httpresp parameter.
   483  	httpresp, err := scanBool(httprespparam)
   484  	if err != nil {
   485  		return modules.RenterDownloadParameters{}, build.ExtendErr("httpresp parameter could not be parsed", err)
   486  	}
   487  
   488  	// Parse the async parameter.
   489  	async, err := scanBool(asyncparam)
   490  	if err != nil {
   491  		return modules.RenterDownloadParameters{}, build.ExtendErr("async parameter could not be parsed", err)
   492  	}
   493  
   494  	siapath := strings.TrimPrefix(ps.ByName("siapath"), "/") // Sia file name.
   495  
   496  	dp := modules.RenterDownloadParameters{
   497  		Destination: destination,
   498  		Async:       async,
   499  		Length:      length,
   500  		Offset:      offset,
   501  		SiaPath:     siapath,
   502  	}
   503  	if httpresp {
   504  		dp.Httpwriter = w
   505  	}
   506  
   507  	return dp, nil
   508  }
   509  
   510  // renterShareHandler handles the API call to create a '.sia' file that
   511  // shares a set of file.
   512  func (api *API) renterShareHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   513  	destination := req.FormValue("destination")
   514  	// Check that the destination path is absolute.
   515  	if !filepath.IsAbs(destination) {
   516  		WriteError(w, Error{"destination must be an absolute path"}, http.StatusBadRequest)
   517  		return
   518  	}
   519  
   520  	err := api.renter.ShareFiles(strings.Split(req.FormValue("siapaths"), ","), destination)
   521  	if err != nil {
   522  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   523  		return
   524  	}
   525  
   526  	WriteSuccess(w)
   527  }
   528  
   529  // renterShareAsciiHandler handles the API call to return a '.sia' file
   530  // in ascii form.
   531  func (api *API) renterShareASCIIHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   532  	ascii, err := api.renter.ShareFilesASCII(strings.Split(req.FormValue("siapaths"), ","))
   533  	if err != nil {
   534  		WriteError(w, Error{err.Error()}, http.StatusBadRequest)
   535  		return
   536  	}
   537  	WriteJSON(w, RenterShareASCII{
   538  		ASCIIsia: ascii,
   539  	})
   540  }
   541  
   542  // renterUploadHandler handles the API call to upload a file.
   543  func (api *API) renterUploadHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
   544  	source := req.FormValue("source")
   545  	if !filepath.IsAbs(source) {
   546  		WriteError(w, Error{"source must be an absolute path"}, http.StatusBadRequest)
   547  		return
   548  	}
   549  
   550  	// Check whether the erasure coding parameters have been supplied.
   551  	var ec modules.ErasureCoder
   552  	if req.FormValue("datapieces") != "" || req.FormValue("paritypieces") != "" {
   553  		// Check that both values have been supplied.
   554  		if req.FormValue("datapieces") == "" || req.FormValue("paritypieces") == "" {
   555  			WriteError(w, Error{"must provide both the datapieces paramaeter and the paritypieces parameter if specifying erasure coding parameters"}, http.StatusBadRequest)
   556  			return
   557  		}
   558  
   559  		// Parse the erasure coding parameters.
   560  		var dataPieces, parityPieces int
   561  		_, err := fmt.Sscan(req.FormValue("datapieces"), &dataPieces)
   562  		if err != nil {
   563  			WriteError(w, Error{"unable to read parameter 'datapieces': " + err.Error()}, http.StatusBadRequest)
   564  			return
   565  		}
   566  		_, err = fmt.Sscan(req.FormValue("paritypieces"), &parityPieces)
   567  		if err != nil {
   568  			WriteError(w, Error{"unable to read parameter 'paritypieces': " + err.Error()}, http.StatusBadRequest)
   569  			return
   570  		}
   571  
   572  		// Verify that sane values for parityPieces and redundancy are being
   573  		// supplied.
   574  		if parityPieces < requiredParityPieces {
   575  			WriteError(w, Error{fmt.Sprintf("a minimum of %v parity pieces is required, but %v parity pieces requested", parityPieces, requiredParityPieces)}, http.StatusBadRequest)
   576  			return
   577  		}
   578  		redundancy := float64(dataPieces+parityPieces) / float64(dataPieces)
   579  		if float64(dataPieces+parityPieces)/float64(dataPieces) < requiredRedundancy {
   580  			WriteError(w, Error{fmt.Sprintf("a redundancy of %.2f is required, but redundancy of %.2f supplied", redundancy, requiredRedundancy)}, http.StatusBadRequest)
   581  			return
   582  		}
   583  
   584  		// Create the erasure coder.
   585  		ec, err = renter.NewRSCode(dataPieces, parityPieces)
   586  		if err != nil {
   587  			WriteError(w, Error{"unable to encode file using the provided parameters: " + err.Error()}, http.StatusBadRequest)
   588  			return
   589  		}
   590  	}
   591  
   592  	// Call the renter to upload the file.
   593  	err := api.renter.Upload(modules.FileUploadParams{
   594  		Source:      source,
   595  		SiaPath:     strings.TrimPrefix(ps.ByName("siapath"), "/"),
   596  		ErasureCode: ec,
   597  	})
   598  	if err != nil {
   599  		WriteError(w, Error{"upload failed: " + err.Error()}, http.StatusInternalServerError)
   600  		return
   601  	}
   602  	WriteSuccess(w)
   603  }