OpenXE/classes/Components/Filesystem/FilesystemSyncCache.php

447 lines
10 KiB
PHP
Raw Normal View History

2021-05-21 08:49:41 +02:00
<?php
namespace Xentral\Components\Filesystem;
use Xentral\Components\Database\Database;
use Xentral\Components\Filesystem\Exception\FileNotFoundException;
use Xentral\Components\Filesystem\Exception\FilesystemException;
use Xentral\Components\Filesystem\Exception\InvalidArgumentException;
/**
* @todo Datenbank anlegen
*/
final class FilesystemSyncCache implements FilesystemInterface
{
/** @var Database $db */
private $db;
/** @var FilesystemInterface $fs */
private $fs;
/** @var int $syncId */
private $syncId;
/**
* @param Database $database
* @param FilesystemInterface $filesystem
* @param int $syncId
*/
public function __construct(Database $database, FilesystemInterface $filesystem, $syncId)
{
if ((int)$syncId <= 0) {
throw new InvalidArgumentException(sprintf('Sync-ID "%s" is invalid.', $syncId));
}
$this->db = $database;
$this->fs = $filesystem;
$this->syncId = (int)$syncId;
}
/**
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listChanges($directory = '', $recursive = false)
{
$result = $this->fs->listContents($directory, $recursive);
$location = PathUtil::normalizePath($directory);
$cache = $this->readCache($location, $recursive);
foreach ($result as $item) {
$path = $item->getPath();
// Ignore directories; only files are nessesary for syncing
if ($item->isDir()) {
continue;
}
// Defaults
$item->set('missing', false);
$item->set('modified', null);
if (!array_key_exists($path, $cache)) {
$item->set('missing', true);
continue;
}
if ((int)$cache[$path]['size'] !== (int)$item->getSize()) {
$item->set('modified', true);
continue;
}
if ((int)$cache[$path]['timestamp'] !== (int)$item->getTimestamp()) {
$item->set('modified', true);
continue;
}
$item->set('modified', false);
}
return $result;
}
/**
* Lists deleted files
*
* Lists files that are present in cache but does not exist on the filesystem any more.
*
* @param string $directory
* @param bool $recursive
*
* @return array|PathInfo[]
*/
public function listDeleted($directory = '', $recursive = false)
{
$paths = $this->fs->listPaths($directory, $recursive);
$location = PathUtil::normalizePath($directory);
$cache = $this->readCache($location, $recursive);
foreach ($cache as $path => $cacheItem) {
if (in_array($path, $paths, true)) {
unset($cache[$path]);
}
}
$result = [];
foreach ($cache as $cacheItem) {
$pathinfo = PathUtil::pathinfo($cacheItem['path']);
$result[] = new PathInfo(array_merge($cacheItem, $pathinfo));
}
return $result;
}
/**
* @inheritdoc
*/
public function has($path)
{
return $this->fs->has($path);
}
/**
* @inheritdoc
*/
public function getInfo($path)
{
return $this->fs->getInfo($path);
}
/**
* @inheritdoc
*/
public function getType($path)
{
return $this->fs->getType($path);
}
/**
* @inheritdoc
*/
public function getSize($path)
{
return $this->fs->getSize($path);
}
/**
* @inheritdoc
*/
public function getTimestamp($path)
{
return $this->fs->getTimestamp($path);
}
/**
* @inheritdoc
*/
public function getMimetype($path)
{
return $this->fs->getMimetype($path);
}
/**
* @inheritdoc
*/
public function listContents($directory = '', $recursive = false)
{
return $this->fs->listContents($directory, $recursive);
}
/**
* @inheritdoc
*/
public function listDirs($directory = '', $recursive = false)
{
return $this->fs->listDirs($directory, $recursive);
}
/**
* @inheritdoc
*/
public function listFiles($directory = '', $recursive = false)
{
return $this->fs->listFiles($directory, $recursive);
}
/**
* @inheritdoc
*/
public function listPaths($directory = '', $recursive = false)
{
return $this->fs->listPaths($directory, $recursive);
}
/**
* @inheritdoc
*/
public function read($path)
{
$result = $this->fs->read($path);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function readStream($path)
{
$result = $this->fs->readStream($path);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function write($path, $contents, array $config = [])
{
$result = $this->fs->write($path, $contents, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function writeStream($path, $resource, array $config = [])
{
$result = $this->fs->writeStream($path, $resource, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function put($path, $contents, array $config = [])
{
$result = $this->fs->put($path, $contents, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function putStream($path, $resource, array $config = [])
{
$result = $this->fs->putStream($path, $resource, $config);
$this->updateCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function delete($path)
{
$result = $this->fs->delete($path);
$this->deleteCachePath($path);
return $result;
}
/**
* Deletes a single file, but without Exception if path does not exist.
*
* @param string $path
*
* @return bool
*/
public function softDelete($path)
{
$result = false;
try {
$this->deleteCachePath($path);
$result = $this->fs->delete($path);
} catch (FileNotFoundException $e) {
// nope - its soft
}
return $result;
}
/**
* @inheritdoc
*/
public function deleteDir($dirname)
{
$result = $this->fs->deleteDir($dirname);
$this->deleteCacheDir($dirname);
return $result;
}
/**
* @inheritdoc
*/
public function createDir($dirname, array $config = [])
{
return $this->fs->createDir($dirname, $config);
}
/**
* @inheritdoc
*/
public function rename($path, $newpath)
{
$result = $this->fs->rename($path, $newpath);
$this->deleteCachePath($path);
return $result;
}
/**
* @inheritdoc
*/
public function copy($path, $newpath)
{
$result = $this->fs->copy($path, $newpath);
$this->updateCachePath($newpath);
return $result;
}
/**
* @inheritdoc
*/
public function getAdapter()
{
return $this->fs->getAdapter();
}
/**
* @param string $path
* @param bool $recursive
*
* @return array
*/
private function readCache($path, $recursive = false)
{
$this->ensureDependencies();
$path = $this->normalizePath($path);
return $this->db->fetchAssoc(
'SELECT f.path, f.dirname, f.type, f.size, f.updated_at AS timestamp ' .
'FROM sync_files AS f ' .
'WHERE f.sync_id = :sync_id AND f.dirname LIKE :path_prefix',
[
'sync_id' => $this->syncId,
'path_prefix' => $recursive === true ? $path . '%' : $path,
]
);
}
/**
* @param string $path
*
* @return void
*/
private function updateCachePath($path)
{
$this->ensureDependencies();
$info = $this->fs->getInfo($path);
$this->db->perform(
'REPLACE INTO sync_files (sync_id, `path`, dirname, type, size, updated_at) ' .
'VALUES (:sync_id, :path, :dirname, :type, :size, :updated_at)',
[
'sync_id' => $this->syncId,
'path' => $info->getPath(),
'dirname' => $info->getDir(),
'type' => $info->getType(),
'size' => (int)$info->getSize(),
'updated_at' => (int)$info->getTimestamp(),
]
);
}
/**
* @param string $path
*
* @return int Deleted row count
*/
private function deleteCachePath($path)
{
$this->ensureDependencies();
$path = $this->normalizePath($path);
return $this->db->fetchAffected(
'DELETE FROM sync_files WHERE sync_id = :sync_id AND `path` = :path',
['sync_id' => $this->syncId, 'path' => $path]
);
}
/**
* @param string $dirname
*
* @return int Deleted row count
*/
private function deleteCacheDir($dirname)
{
$this->ensureDependencies();
$dirname = $this->normalizePath($dirname);
return $this->db->fetchAffected(
'DELETE FROM sync_files WHERE sync_id = :sync_id AND `dirname` LIKE :dirname',
['sync_id' => $this->syncId, 'dirname' => $dirname . '%']
);
}
/**
* @param string $path
*
* @return string
*/
private function normalizePath($path)
{
return PathUtil::normalizePath($path);
}
/**
* @throws FilesystemException
*
* @return void
*/
private function ensureDependencies()
{
if ($this->db === null || $this->syncId === null) {
throw new FilesystemException('Can not continue. Required dependencies are missing.');
}
}
}