Skip to content
Sara Golemon edited this page Mar 24, 2018 · 16 revisions

High Level Goals

  • Object based streams
  • User wrapper/filter/transport via Interfaces
    • Make wrapper/transport separation clear
  • "Better" contexts
  • Filter attachment "not stupid"
  • Support for async/concurrency

Sell it Goals

  • async
  • objects allow for cloning and immutability
  • filtering working properly with added features
  • error handling fixes

Proposed interfaces

interface Stream {
  /* e.g. new TCP('tcp://localhost:80'); */
  public function __construct(string $uri, string $mode, ?Stream\Context $ctx = null);

  public function getName(): string;
  public function getUri(): string; // "opened path"

  public function isOpen(): bool;
  public function read(int $maxbytes = 0): string;
  public function write(string $data): int; /* bytes written */
  public function flush(): bool;
  public function close(): void;

  public function seek(int $pos, int $whence = Stream::SEEK_SET): bool;
  public function isSeekable(): bool;
  public function stat(): Stream\StatBuf;
  public function setOption(int $option, $value): bool;
  public function getStream(): Stream; /* For proxy transports */
}

namespace Stream {

const SEEK_SET = 0;
const SEEK_CUR = 1;
const SEEK_END = 2;

function Open(string $uri, string $mode,
              int $options, ?Stream\Context $ctx = null): Stream;

interface Transport {
  // TBD: connect/accept/listen/bind/sendto/recvfrom/oob/etc...
}

namespace Transport {
function register(string $scheme,
                  (function (string $uri, ?Stream\Context $ctx): Stream) $factory): bool;
function unregister(string $scheme): bool;
function exists(string $scheme): bool;
} // namespace Transport

namespace Wrapper {
function register(string $scheme,
                  (function (string $uri, string $mode, ?\Stream\Context $ctx = null): Stream\Transport)): bool;
function unregister(string $scheme): bool;
function exists(string $scheme): bool;
function getWrapper(string $scheme): Stream\Wrapper;
}  // namespace Wrapper

interface Wrapper {
  public function getName(): string;

  public function open(string $uri, string $mode, ?Stream\Context $ctx = null): Stream;
  public function opendir(string $uri, int $flags = 0, ?Stream\Context $ctx = null): Stream;
  public function stat(string $uri, int $flags = 0, ?Stream\Context $ctx = null): Stream\StatBuf;

  public function unlink(string $uri): bool;
  public function move(string $from, string $to): bool;
  public function copy(string $from, string $to): bool;
  public function mkdir(string $uri, bool $recursive = true, int $mode = 0644): bool;
  public function rmdir(string $uri): bool;

  public function touch(string $uri): bool;
  public function chmod(string $uri, int $mode): bool;
  public function chusr(string $uri, string $user): bool;
  public function chgrp(string $uri, string $group): bool;
}

namespace Context {
const READ_FILTER = 'read_filter';
const WRITE_FILTER = 'write_filter';
const NOTIFIER = 'notifier';
}  // namespace Context

interface Filter {
  // This method can't be named "filter" because of PHP4 constructors. *sigh*
  public function doFilter(string $str): string;
}

interface NotificationListener {
  // Provisional API based on current notifiers; Subject to change.
  public function notify(int $code, int $sev, string $msg, int $msg_code, int $bytes_xferred, int $bytes_max): void;
}

trait TContext {
  public function getOption(string $scheme, string $option): mixed {
    $opts = $this->getSchemeOptions($sceme);
    if (!array_key_exists($option, $opts)) {
      throw new NotFoundException("Unknown option $opt for scheme $scheme");
    }
    retur $opts[$option];
  }
  public function getSchemeOptions(string $scheme): array<mixed> {
    $opts = $this->getAllOptions();
    if (!array_key_exists($scheme, $opts)) {
      throw new NotFoundException("Unknown scheme $scheme");
    }
    return $opts[$scheme];
  }
  public function getAllOptions(): array<string, array<string, mixed>> {
    return [];
  }

  public function getParam(string $param): mixed {
    $params = $this->getAllParams();
    if (!array_key_exists($param, $params)) {
      throw new NotFoundException("Unknown context param $param");
    }
    return $params[$param];
  }

  public function getAllParams(): array<string, mixed> {
    return [];
  }
}

interface Context {
  // Options apply to specific wrappers/transports.
  // Example options: [ 'http' => [ 'user_agent' => 'Mozilla' ] ]
  public function getOption(string $scheme, string $option): mixed;
  public function getSchemeOptions(string $scheme): array<string, mixed>;
  public function getAllOptions(): array<string, array<string, mixed>>;

  // Params apply to all streams equally.
  // Example params: [ Context::READ_FILTER => Stream\Filter ]
  //               : [ Context::WRITE_FILTER => Stream\Filter ]
  //               : [ Context::NOTIFIER => Stream\NotificationListener ]
  public function getParam(string $param): mixed;
  public function getAllParams(): array<string, mixed>;
}

interface StatBuf {}

interface StatBuf\Sizable {
  public function getSize(): int;
}

interface StatBuf\File {
  public function getCTime(): int; // Epochs? DateTime?
  public function getMTime(): int;
  public function getATime(): int;
}

interface StatBuf\PosixFile extends StatBuf\File {
  public function getMode(): int;
  public function getDevice(): int;
  public function getInode(): int;
  public function getNLinks(): int;
  public function getUid(): int;
  public function getGid(): int;
  public function getRdev(): int;
  public function getBlockSize(): int;
  public function getBlocks(): int;
}

interface StatBuf\NTFSFile extends StatBuf\File {
  // TBD
}

// Base exception for all Streams ops.
interface Exception extends \Exception {}

// Provisional exception specializations.
interface NotFoundException extends Exception {} // File not found, wrapper unknown, etc...
interface ConnectionResetException extends Exception {} // Disconnected.
interface AccessException extends Exception {} // File exists, but you can't have it.
interface StorageException extends Exception {} // Disk full, OOM, etc...
interface LockException extends Exception {} // Unable to acquire lock, or resource currently locked.

} // namespace Stream
Clone this wiki locally