name = $name; return $this; } /** * @task config */ public function getName() { return $this->name; } /** * Set the data to upload as a single raw blob. * * You can specify file data by calling this method with a single blob of * data, or by calling @{method:setPath} and providing a path to a file on * disk. * * @param bytes Blob of file data. * @task config */ public function setData($data) { $this->data = $data; return $this; } /** * @task config */ public function getData() { return $this->data; } /** * Set the data to upload by pointing to a file on disk. * * You can specify file data by calling this method with a path, or by * providing a blob of raw data to @{method:setData}. * * The path itself only provides data. If you want to name the file, you * should also call @{method:setName}. * * @param string Path on disk to a file containing data to upload. * @return this * @task config */ public function setPath($path) { $this->path = $path; return $this; } /** * @task config */ public function getPath() { return $this->path; } /** * @task config */ public function setViewPolicy($view_policy) { $this->viewPolicy = $view_policy; return $this; } /** * @task config */ public function getViewPolicy() { return $this->viewPolicy; } /** * Configure a file to be temporary instead of permanent. * * By default, files are retained indefinitely until explicitly deleted. If * you want to upload a temporary file instead, you can specify an epoch * timestamp. The file will be deleted after this time. * * @param int Epoch timestamp to retain the file until. * @return this * @task config */ public function setDeleteAfterEpoch($epoch) { $this->deleteAfterEpoch = $epoch; return $this; } /** * @task config */ public function getDeleteAfterEpoch() { return $this->deleteAfterEpoch; } /* -( Handling Upload Results )-------------------------------------------- */ /** * @task results */ public function getErrors() { return $this->errors; } /** * @task results */ public function getPHID() { return $this->phid; } /* -( Uploader API )------------------------------------------------------- */ /** * @task uploader */ public function willUpload() { $have_data = ($this->data !== null); $have_path = ($this->path !== null); if (!$have_data && !$have_path) { throw new Exception( pht( 'Specify setData() or setPath() when building a file data '. 'reference.')); } if ($have_data && $have_path) { throw new Exception( pht( 'Specify either setData() or setPath() when building a file data '. 'reference, but not both.')); } if ($have_path) { $path = $this->path; if (!Filesystem::pathExists($path)) { throw new Exception( pht( 'Unable to upload file: path "%s" does not exist.', $path)); } try { Filesystem::assertIsFile($path); } catch (FilesystemException $ex) { throw new Exception( pht( 'Unable to upload file: path "%s" is not a file.', $path)); } try { Filesystem::assertReadable($path); } catch (FilesystemException $ex) { throw new Exception( pht( 'Unable to upload file: path "%s" is not readable.', $path)); } $size = @filesize($path); if ($size === false) { throw new Exception( pht( 'Unable to upload file: failed to determine filesize of '. 'path "%s".', $path)); } $this->hash = $this->newFileHash($path); $this->size = $size; } else { $data = $this->data; $this->hash = $this->newDataHash($data); $this->size = strlen($data); } } /** * @task uploader */ public function didFail($error) { $this->errors[] = $error; return $this; } /** * @task uploader */ public function setPHID($phid) { $this->phid = $phid; return $this; } /** * @task uploader */ public function getByteSize() { if ($this->size === null) { throw new PhutilInvalidStateException('willUpload'); } return $this->size; } /** * @task uploader */ public function getContentHash() { if ($this->size === null) { throw new PhutilInvalidStateException('willUpload'); } return $this->hash; } /** * @task uploader */ public function didUpload() { if ($this->fileHandle) { @fclose($this->fileHandle); $this->fileHandle = null; } } /** * @task uploader */ public function readBytes($start, $end) { if ($this->size === null) { throw new PhutilInvalidStateException('willUpload'); } $len = ($end - $start); if ($this->data !== null) { return substr($this->data, $start, $len); } $path = $this->path; if ($this->fileHandle === null) { $f = @fopen($path, 'rb'); if (!$f) { throw new Exception( pht( 'Unable to upload file: failed to open path "%s" for reading.', $path)); } $this->fileHandle = $f; } $f = $this->fileHandle; $ok = @fseek($f, $start); if ($ok !== 0) { throw new Exception( pht( 'Unable to upload file: failed to fseek() to offset %d in file '. 'at path "%s".', $start, $path)); } $data = @fread($f, $len); if ($data === false) { throw new Exception( pht( 'Unable to upload file: failed to read %d bytes after offset %d '. 'from file at path "%s".', $len, $start, $path)); } return $data; } private function newFileHash($path) { $hash = hash_file('sha256', $path, $raw_output = false); if ($hash === false) { return null; } return $hash; } private function newDataHash($data) { $hash = hash('sha256', $data, $raw_output = false); if ($hash === false) { return null; } return $hash; } }