diff --git a/src/IO/File.php b/src/IO/File.php index d5511a92..10b3304b 100644 --- a/src/IO/File.php +++ b/src/IO/File.php @@ -1,167 +1,26 @@ */ class File { - /** - * Open + Read flag. - */ - public const OPEN_READ = 'rb'; - - /** - * Open + Write flag. - */ - public const OPEN_WRITE = 'cb'; - - /** - * Open + Read + Write flag. - */ - public const OPEN_READ_WRITE = 'rb+'; - - /** - * Create (truncates!) + Write flag. - */ - public const CREATE_WRITE = 'wb'; - - /** - * Create (truncates!) + Read + Write flag. - */ - public const CREATE_READ_WRITE = 'wb+'; - - /** - * Open + Write flag. - */ - public const OPEN_CREATE_WRITE = 'xb'; - - /** - * Open or Create + Read + Write flag. - */ - public const OPEN_CREATE_READ_WRITE = 'xb+'; - - /** - * Resource/stream container. - * @var resource - */ - private $resource; - - /** - * Filename. - * @var string - */ - public $name = ''; - - /** - * Real, fixed path. - * @var string - */ - public $path = ''; - - /** - * Filesize in bytes. - * @var int - */ - public $size = 0; - - /** - * ID of file owner. - * @var int - */ - public $owner = 0; - - /** - * ID of file's owning group. - * @var int - */ - public $group = 0; - - /** - * Last time this file has been accessed. - * @var int - */ - public $accessTime = 0; - - /** - * Last time this file has been modified. - * @var int - */ - public $modifiedTime = 0; - - /** - * Current stream position. - * @var int - */ - public $position = 0; - - /** - * Fixes path and opens resource. - * @param string $path - * @param string $mode - * @throws FileDoesNotExistException - * @throws IOException - */ - public function __construct(string $path, string $mode) + public static function open(string $filename): FileStream { - $path = Directory::fixSlashes($path); - $this->path = realpath($path); - $this->name = basename($this->path); - - try { - $this->resource = fopen($path, $mode, false); - } catch (ErrorException $e) { - throw new FileDoesNotExistException($e->getMessage()); - } - - if (!is_resource($this->resource)) { - throw new IOException('Failed to create resource.'); - } - - $this->updateMetaData(); - } - - /** - * Updates the meta data of the resource. - */ - private function updateMetaData(): void - { - $meta = fstat($this->resource); - $this->size = intval($meta['size']); - $this->owner = intval($meta['uid']); - $this->group = intval($meta['gid']); - $this->accessTime = intval($meta['atime']); - $this->modifiedTime = intval($meta['mtime']); - } - - /** - * Updates the position variable. - */ - private function updatePosition(): void - { - $this->position = ftell($this->resource); - } - - /** - * Calls the close method. - */ - public function __destruct() - { - $this->close(); + return new FileStream($filename, FileStream::MODE_READ_WRITE, true); } /** * Creates an instance of a temporary file. * @param string $prefix - * @return File + * @return FileStream */ - public static function temp(string $prefix = 'Misuzu'): File + public static function temp(string $prefix = 'Misuzu'): FileStream { - return new static(tempnam(sys_get_temp_dir(), $prefix)); + return static::open(tempnam(sys_get_temp_dir(), $prefix)); } /** @@ -190,145 +49,4 @@ class File unlink($path); } - - /** - * Closes the resource context. - */ - public function close(): void - { - if (is_resource($this->resource)) { - fclose($this->resource); - } - } - - /** - * Moves the position to 0. - */ - public function start(): void - { - rewind($this->resource); - $this->updatePosition(); - } - - /** - * Moves the position to the end of the file. - */ - public function end(): void - { - fseek($this->resource, 0, SEEK_END); - $this->updatePosition(); - } - - /** - * Sets the position. - * @param int $offset - * @param bool $relative - * @return int - */ - public function position(int $offset, bool $relative = false): int - { - fseek($this->resource, $offset, $relative ? SEEK_CUR : SEEK_SET); - $this->updatePosition(); - return $this->position; - } - - /** - * Checks if the current position is the end of the file. - * @return bool - */ - public function atEOF(): bool - { - return feof($this->resource); - } - - /** - * Tries to find the position of a string in the context. - * @param string $string - * @return int - */ - public function find(string $string): int - { - while ($this->position < $this->size) { - $find = strpos($this->read(8192), $string); - - if ($find !== false) { - return $find + $this->position; - } - - $this->position($this->position + 8192); - } - - return -1; - } - - /** - * Locks the file and reads from it. - * @param int $length - * @throws IOException - * @return string - */ - public function read(int $length = null): string - { - if ($length === null) { - $length = $this->size; - $this->start(); - } - - flock($this->resource, LOCK_SH); - $data = fread($this->resource, $length); - flock($this->resource, LOCK_UN); - - if ($data === false) { - throw new IOException('Read failed.'); - } - - $this->updateMetaData(); - - return $data; - } - - /** - * Gets the character at the current position. - * @return string - */ - public function char(): string - { - return fgetc($this->resource); - } - - /** - * Locks the file, writes to the stream and flushes to file. - * @param string $data - * @param int $length - * @throws IOException - */ - public function write(string $data, int $length = 0): void - { - if ($length > 0) { - $length = strlen($data); - } - - flock($this->resource, LOCK_EX); - $write = fwrite($this->resource, $data, $length); - $flush = fflush($this->resource); - flock($this->resource, LOCK_UN); - - if ($write === false || $flush === false) { - throw new IOException('Write failed.'); - } - - $this->updateMetaData(); - $this->updatePosition(); - } - - /** - * The same as write except it moves to the end of the file first. - * @param string $data - * @param int $length - */ - public function append(string $data, int $length = 0): void - { - $this->end(); - $this->write($data, $length); - } } diff --git a/src/IO/FileStream.php b/src/IO/FileStream.php new file mode 100644 index 00000000..da3645e6 --- /dev/null +++ b/src/IO/FileStream.php @@ -0,0 +1,252 @@ +isLocked = $lock; + $this->filePath = $path; + $this->fileMode = $mode; + + try { + $this->fileHandle = fopen($this->filePath, static::constructFileMode($this->fileMode)); + } catch (ErrorException $ex) { + throw new FileDoesNotExistException($ex->getMessage()); + } + + $this->ensureHandleActive(); + + if ($this->isLocked) { + flock($this->fileHandle, LOCK_EX | LOCK_NB); + } + } + + public function __destruct() + { + if (!is_resource($this->fileHandle)) { + return; + } + + $this->close(); + } + + protected static function constructFileMode(int $mode): string + { + $mode_read = ($mode & static::MODE_READ) > 0; + $mode_write = ($mode & static::MODE_WRITE) > 0; + $mode_truncate = ($mode & static::MODE_TRUNCATE_RAW) > 0; + $mode_append = ($mode & static::MODE_APPEND_RAW) > 0; + + // why would you ever + if ($mode_append && $mode_truncate) { + throw new IOException("Can't append and truncate at the same time."); + } + + if (($mode_append || $mode_truncate) && !$mode_write) { + throw new IOException("Can't append or truncate without write privileges."); + } + + $mode_string = ''; + + if ($mode_append) { + $mode_string = 'a'; + } elseif ($mode_truncate) { + $mode_string = 'w'; + } elseif ($mode_write) { + $mode_string = 'c'; + } + + $mode_string .= 'b'; + + if ($mode_read) { + if (strlen($mode_string) < 2) { + $mode_string = 'r' . $mode_string; + } else { + $mode_string .= '+'; + } + } + + // should be at least two characters because of the b flag + if (strlen($mode_string) < 2) { + throw IOException('Failed to construct mode???'); + } + + return $mode_string; + } + + public function getResource(): resource + { + $this->ensureHandleActive(); + return $this->fileHandle; + } + + protected function ensureHandleActive(): void + { + if (!is_resource($this->fileHandle)) { + throw new IOException("No active file handle."); + } + } + + protected function ensureCanRead(): void + { + if (!$this->getCanRead()) { + throw new IOException('This stream cannot perform read operations.'); + } + } + + protected function ensureCanWrite(): void + { + if (!$this->getCanWrite()) { + throw new IOException('This stream cannot perform write operations.'); + } + } + + protected function ensureCanSeek(): void + { + if (!$this->getCanSeek()) { + throw new IOException('This stream cannot perform seek operations.'); + } + } + + protected function getCanRead(): bool + { + return ($this->fileMode & static::MODE_READ) > 0 && is_readable($this->filePath); + } + + protected function getCanSeek(): bool + { + return ($this->fileMode & static::MODE_APPEND_RAW) == 0 && $this->getCanRead(); + } + + protected function getCanTimeout(): bool + { + return false; + } + + protected function getCanWrite(): bool + { + return ($this->fileMode & static::MODE_WRITE) > 0 && is_writable($this->filePath); + } + + protected function getLength(): int + { + $this->ensureHandleActive(); + return fstat($this->fileHandle)['size']; + } + + protected function getPosition(): int + { + $this->ensureHandleActive(); + return ftell($this->fileHandle); + } + + protected function getReadTimeout(): int + { + return -1; + } + + protected function getWriteTimeout(): int + { + return -1; + } + + public function flush(): void + { + $this->ensureHandleActive(); + fflush($this->fileHandle); + } + + public function close(): void + { + $this->ensureHandleActive(); + + if ($this->isLocked) { + flock($this->fileHandle, LOCK_UN | LOCK_NB); + } + + fclose($this->fileHandle); + } + + public function read(int $length): string + { + $this->ensureHandleActive(); + $this->ensureCanRead(); + + $read = fread($this->fileHandle, $length); + + if ($read === false) { + throw new IOException('Read failed.'); + } + + return $read; + } + + public function readChar(): int + { + $this->ensureHandleActive(); + return ord(fgetc($this->fileHandle)); + } + + public function write(string $data): int + { + $this->ensureHandleActive(); + $this->ensureCanWrite(); + + $write = fwrite($this->fileHandle, $data); + + if ($write === false) { + throw new IOException('Write failed.'); + } + + return $write; + } + + public function writeChar(int $char): void + { + $this->ensureHandleActive(); + $this->write(chr($char), 0, 1); + } + + public function seek(int $offset, int $origin): void + { + $this->ensureHandleActive(); + $this->ensureCanSeek(); + + switch ($origin) { + case Stream::ORIGIN_BEGIN: + $origin = SEEK_SET; + break; + + case Stream::ORIGIN_END: + $origin = SEEK_END; + break; + + case Stream::ORIGIN_CURRENT: + $origin = SEEK_CUR; + break; + + default: + throw new IOException('Invalid seek origin.'); + } + + if (fseek($this->fileHandle, $offset, $origin) !== 0) { + throw new IOException('Seek operation failed.'); + } + } +} diff --git a/src/IO/Stream.php b/src/IO/Stream.php new file mode 100644 index 00000000..c64ee76c --- /dev/null +++ b/src/IO/Stream.php @@ -0,0 +1,40 @@ +{$name}(); + } + + // todo: specialised exception type + throw new IOException('Invalid property.'); + } + + abstract protected function getCanRead(): bool; + abstract protected function getCanSeek(): bool; + abstract protected function getCanTimeout(): bool; + abstract protected function getCanWrite(): bool; + abstract protected function getLength(): int; + abstract protected function getPosition(): int; + abstract protected function getReadTimeout(): int; + abstract protected function getWriteTimeout(): int; + + abstract public function flush(): void; + abstract public function close(): void; + abstract public function seek(int $offset, int $origin): void; + + abstract public function read(int $length): string; + abstract public function readChar(): int; + + abstract public function write(string $data): int; + abstract public function writeChar(int $char): void; +} diff --git a/src/Zalgo.php b/src/Zalgo.php index 7604a4b5..db686288 100644 --- a/src/Zalgo.php +++ b/src/Zalgo.php @@ -40,9 +40,9 @@ class Zalgo public const ZALGO_MODE_MAX = 3; private const ZALGO_MODES = [ - ZALGO_MODE_MINI, - ZALGO_MODE_NORMAL, - ZALGO_MODE_MAX, + self::ZALGO_MODE_MINI, + self::ZALGO_MODE_NORMAL, + self::ZALGO_MODE_MAX, ]; public const ZALGO_DIR_UP = 0x1; @@ -105,7 +105,7 @@ class Zalgo } $str .= $char; - + switch ($mode) { case self::ZALGO_MODE_MINI: $num_up = mt_rand(0, 8); @@ -129,9 +129,11 @@ class Zalgo if ($going_up) { $str .= self::getZalgo(self::ZALGO_CHARS_UP, $num_up); } + if ($going_mid) { $str .= self::getZalgo(self::ZALGO_CHARS_MIDDLE, $num_mid); } + if ($going_down) { $str .= self::getZalgo(self::ZALGO_CHARS_DOWN, $num_down); } diff --git a/tests/FileSystemTest.php b/tests/FileSystemTest.php index 31a2e756..e4d3b177 100644 --- a/tests/FileSystemTest.php +++ b/tests/FileSystemTest.php @@ -4,6 +4,7 @@ namespace MisuzuTests; use PHPUnit\Framework\TestCase; use Misuzu\IO\Directory; use Misuzu\IO\File; +use Misuzu\IO\FileStream; define('WORKING_DIR', sys_get_temp_dir() . '/MisuzuFileSystemTest' . time()); @@ -34,48 +35,48 @@ class FileSystemTest extends TestCase public function testCreateFile() { - $file = new File(WORKING_DIR . '/file', File::CREATE_READ_WRITE); - $this->assertInstanceOf(File::class, $file); + $file = File::open(WORKING_DIR . '/file'); + $this->assertInstanceOf(FileStream::class, $file); $file->close(); } public function testWriteFile() { - $file = new File(WORKING_DIR . '/file', File::OPEN_WRITE); - $this->assertInstanceOf(File::class, $file); + $file = new FileStream(WORKING_DIR . '/file', FileStream::MODE_TRUNCATE, false); + $this->assertInstanceOf(FileStream::class, $file); $file->write('misuzu'); - $this->assertEquals(6, $file->size); + $this->assertEquals(6, $file->length); $file->close(); } public function testAppendFile() { - $file = new File(WORKING_DIR . '/file', File::OPEN_WRITE); - $this->assertInstanceOf(File::class, $file); + $file = new FileStream(WORKING_DIR . '/file', FileStream::MODE_APPEND); + $this->assertInstanceOf(FileStream::class, $file); - $file->append(' test'); - $this->assertEquals(11, $file->size); + $file->write(' test'); + $this->assertEquals(11, $file->length); $file->close(); } public function testPosition() { - $file = new File(WORKING_DIR . '/file', File::OPEN_READ); - $this->assertInstanceOf(File::class, $file); + $file = new FileStream(WORKING_DIR . '/file', FileStream::MODE_READ); + $this->assertInstanceOf(FileStream::class, $file); - $file->start(); + $file->seek(0, FileStream::ORIGIN_BEGIN); $this->assertEquals(0, $file->position); - $file->end(); - $this->assertEquals($file->size, $file->position); + $file->seek(0, FileStream::ORIGIN_END); + $this->assertEquals($file->length, $file->position); - $file->position(4); + $file->seek(4, FileStream::ORIGIN_BEGIN); $this->assertEquals(4, $file->position); - $file->position(4, true); + $file->seek(4, FileStream::ORIGIN_CURRENT); $this->assertEquals(8, $file->position); $file->close(); @@ -83,34 +84,24 @@ class FileSystemTest extends TestCase public function testRead() { - $file = new File(WORKING_DIR . '/file', File::OPEN_READ); - $this->assertInstanceOf(File::class, $file); + $file = new FileStream(WORKING_DIR . '/file', FileStream::MODE_READ); + $this->assertInstanceOf(FileStream::class, $file); - $this->assertEquals('misuzu test', $file->read()); + $this->assertEquals('misuzu test', $file->read($file->length)); - $file->position(7); - $this->assertEquals('test', $file->read()); - - $file->close(); - } - - public function testFind() - { - $file = new File(WORKING_DIR . '/file', File::OPEN_READ); - $this->assertInstanceOf(File::class, $file); - - $this->assertEquals(7, $file->find('test')); + $file->seek(7, FileStream::ORIGIN_BEGIN); + $this->assertEquals('test', $file->read(4)); $file->close(); } public function testChar() { - $file = new File(WORKING_DIR . '/file', File::OPEN_READ); - $this->assertInstanceOf(File::class, $file); + $file = new FileStream(WORKING_DIR . '/file', FileStream::MODE_READ); + $this->assertInstanceOf(FileStream::class, $file); - $file->position(3); - $this->assertEquals('s', $file->char()); + $file->seek(3, FileStream::ORIGIN_BEGIN); + $this->assertEquals(ord('u'), $file->readChar()); $file->close(); } diff --git a/tests/ZalgoTest.php b/tests/ZalgoTest.php index 8e07fd1d..c3eff2d6 100644 --- a/tests/ZalgoTest.php +++ b/tests/ZalgoTest.php @@ -10,8 +10,19 @@ class ZalgoTest extends TestCase public function testStrip() { - $this->assertEquals(TEST_STRING, Zalgo::strip(Zalgo::run(TEST_STRING, Zalgo::ZALGO_MODE_MINI))); - $this->assertEquals(TEST_STRING, Zalgo::strip(Zalgo::run(TEST_STRING, Zalgo::ZALGO_MODE_NORMAL))); - $this->assertEquals(TEST_STRING, Zalgo::strip(Zalgo::run(TEST_STRING, Zalgo::ZALGO_MODE_MAX))); + $this->assertEquals( + static::TEST_STRING, + Zalgo::strip(Zalgo::run(static::TEST_STRING, Zalgo::ZALGO_MODE_MINI)) + ); + + $this->assertEquals( + static::TEST_STRING, + Zalgo::strip(Zalgo::run(static::TEST_STRING, Zalgo::ZALGO_MODE_NORMAL)) + ); + + $this->assertEquals( + static::TEST_STRING, + Zalgo::strip(Zalgo::run(static::TEST_STRING, Zalgo::ZALGO_MODE_MAX)) + ); } }