Skip to content

Commit

Permalink
Add delayed file IO class.
Browse files Browse the repository at this point in the history
  • Loading branch information
Maikuolan committed Aug 24, 2019
1 parent 547a377 commit 2b104a5
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ found at:
- [2019.08.17; Maikuolan]: Added the ability to chain together multiple L10N
objects via L10N's fallback mechanism.

- [2019.08.23; Maikuolan]: Added a new class to the repository, "Delayed file
IO class".

=== Version/Release 2.0.0 ===
MAJOR RELEASE (BACKWARDS INCOMPATIBLE).

Expand Down
146 changes: 146 additions & 0 deletions src/DelayedIO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php
/**
* Delayed file IO class (last modified: 2019.08.23).
*
* This file is a part of the "common classes package", utilised by a number of
* packages and projects, including CIDRAM and phpMussel.
* Source: https://github.com/Maikuolan/Common
*
* License: GNU/GPLv2
* @see LICENSE.txt
*
* "COMMON CLASSES PACKAGE", as well as the earliest iteration and deployment
* of this class, COPYRIGHT 2019 and beyond by Caleb Mazalevskis (Maikuolan).
*/

namespace Maikuolan\Common;

class DelayedIO
{
/** Old data for the files being read/written. */
private $OldData = [];

/** New data for the files being read/written. */
private $NewData = [];

/** Whether the files should be locked when read/written. */
private $Locked = [];

/** Size of each data block to read per iteration (131072 = 128KB). */
const BLOCKSIZE = 131072;

/** How many seconds until an attempt to lock the handle should time-out. */
const LOCK_TIMEOUT = 5;

/**
* Read a file, or fetch from object memory if already read before.
*
* @param string $File The file to read.
* @param int $Lock Lock mask for when attempting to read from the file.
* @return string The file's content, or an empty string on failure.
*/
public function readFile(string $File = '', int $Lock = 0): string
{
if ($File === '') {
return '';
}
if (isset($this->NewData[$File])) {
return $this->NewData[$File];
}
if (!is_file($File) || !is_readable($File) || !($Size = filesize($File))) {
return '';
}
$Handle = fopen($File, 'rb');
if (!is_resource($Handle)) {
return '';
}
$Locked = false;
if ($Lock) {
$Time = time();
while (!$Locked) {
$Locked = flock($Handle, $Lock);
if (!$Locked && (time() - $Time) >= self::LOCK_TIMEOUT) {
break;
}
}
if (!$Locked) {
fclose($Handle);
return '';
}
}
$Iterations = ceil($Size / self::BLOCKSIZE) ?: 0;
$Data = '';
$Current = 0;
while ($Current < $Iterations) {
$Data .= fread($Handle, self::BLOCKSIZE);
$Current++;
}
if ($Locked) {
$Time = time();
$Unlocked = false;
while (!$Unlocked) {
$Unlocked = flock($Handle, LOCK_UN);
if (!$Unlocked && (time() - $Time) >= self::LOCK_TIMEOUT) {
break;
}
}
}
fclose($Handle);
$this->Locked[$File] = 0;
return $this->NewData[$File] = $this->OldData[$File] = $Data;
}

/**
* Prepare data to be written to a file.
*
* @param string $File The file/path to be written.
* @param string $Data The data to be written.
* @param int $Lock Lock mask for when attempting to write to the file.
* @return bool True if the file/path is writable; False if not writable.
*/
public function writeFile(string $File = '', string $Data = '', int $Lock = 0): bool
{
if (empty($File) || !is_writable($File)) {
return false;
}
$this->NewData[$File] = $Data;
$this->Locked[$File] = $Lock;
if (!isset($this->OldData[$File])) {
$this->OldData[$File] = '';
}
return true;
}

/**
* All pending modified files are written at object destruction.
*/
public function __destruct()
{
foreach ($this->NewData as $File => $NewData) {
if ($NewData === $this->OldData[$File]) {
continue;
}
$Handle = fopen($File, 'wb');
if (!is_resource($Handle)) {
continue;
}
if ($this->Locked[$File]) {
$Locked = false;
$Time = time();
while (!$Locked) {
$Locked = flock($Handle, $this->Locked[$File]);
if (!$Locked && (time() - $Time) >= self::LOCK_TIMEOUT) {
break;
}
}
if (!$Locked) {
fclose($Handle);
continue;
}
}
fwrite($Handle, $NewData);
fclose($Handle);
}
}

}

0 comments on commit 2b104a5

Please sign in to comment.