228 lines
7.0 KiB
PHP
228 lines
7.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* @copyright Copyright (c) 2022, Varun Patil <radialapps@gmail.com>
|
|
* @author Varun Patil <radialapps@gmail.com>
|
|
* @license AGPL-3.0-or-later
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
namespace OCA\Memories\Command;
|
|
|
|
use OCA\Memories\Db\TimelineWrite;
|
|
use OCA\Memories\Service;
|
|
use OCP\Files\IRootFolder;
|
|
use OCP\IConfig;
|
|
use OCP\IGroupManager;
|
|
use OCP\IUser;
|
|
use OCP\IUserManager;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
|
|
class IndexOpts
|
|
{
|
|
public bool $force = false;
|
|
public bool $clear = false;
|
|
public ?string $user = null;
|
|
public ?string $folder = null;
|
|
public ?string $group = null;
|
|
public bool $skipCleanup = false;
|
|
|
|
public function __construct(InputInterface $input)
|
|
{
|
|
$this->force = (bool) $input->getOption('force');
|
|
$this->clear = (bool) $input->getOption('clear');
|
|
$this->user = $input->getOption('user');
|
|
$this->folder = $input->getOption('folder');
|
|
$this->skipCleanup = $input->getOption('skip-cleanup');
|
|
$this->group = $input->getOption('group');
|
|
}
|
|
}
|
|
|
|
class Index extends Command
|
|
{
|
|
/** @var int[][] */
|
|
protected array $sizes;
|
|
|
|
protected IUserManager $userManager;
|
|
protected IGroupManager $groupManager;
|
|
protected IRootFolder $rootFolder;
|
|
protected IConfig $config;
|
|
protected Service\Index $indexer;
|
|
protected TimelineWrite $timelineWrite;
|
|
|
|
// IO
|
|
private InputInterface $input;
|
|
private OutputInterface $output;
|
|
|
|
// Command options
|
|
private IndexOpts $opts;
|
|
|
|
public function __construct(
|
|
IRootFolder $rootFolder,
|
|
IUserManager $userManager,
|
|
IGroupManager $groupManager,
|
|
IConfig $config,
|
|
Service\Index $indexer,
|
|
TimelineWrite $timelineWrite
|
|
) {
|
|
parent::__construct();
|
|
|
|
$this->userManager = $userManager;
|
|
$this->groupManager = $groupManager;
|
|
$this->rootFolder = $rootFolder;
|
|
$this->config = $config;
|
|
$this->indexer = $indexer;
|
|
$this->timelineWrite = $timelineWrite;
|
|
}
|
|
|
|
protected function configure(): void
|
|
{
|
|
$this
|
|
->setName('memories:index')
|
|
->setDescription('Generate photo entries')
|
|
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Index only the specified user')
|
|
->addOption('folder', null, InputOption::VALUE_REQUIRED, 'Index only the specified folder (relative to the user\'s root)')
|
|
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force refresh of existing index entries')
|
|
->addOption('clear', null, InputOption::VALUE_NONE, 'Clear all existing index entries')
|
|
->addOption('skip-cleanup', null, InputOption::VALUE_NONE, 'Skip cleanup step')
|
|
->addOption('group', 'g', InputOption::VALUE_REQUIRED, 'Index only specified group')
|
|
;
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
/** @var \Symfony\Component\Console\Output\ConsoleOutputInterface $output */
|
|
$output = $output;
|
|
|
|
// Store input/output/opts for later use
|
|
$this->input = $input;
|
|
$this->output = $output;
|
|
$this->opts = new IndexOpts($input);
|
|
|
|
// Assign to indexer
|
|
$this->indexer->output = $output;
|
|
$this->indexer->section = $output->section();
|
|
|
|
try {
|
|
// Use static exiftool process
|
|
\OCA\Memories\Exif::ensureStaticExiftoolProc();
|
|
if (!Service\BinExt::testExiftool()) { // throws
|
|
throw new \Exception('exiftool could not be executed or test failed');
|
|
}
|
|
|
|
// Perform steps based on opts
|
|
$this->checkClear();
|
|
$this->checkForce();
|
|
|
|
// Run the indexer
|
|
$this->runIndex();
|
|
|
|
// Clean up the index
|
|
if (!$this->opts->skipCleanup) {
|
|
$this->indexer->cleanupStale();
|
|
}
|
|
|
|
return 0;
|
|
} catch (\Exception $e) {
|
|
$this->output->writeln("<error>{$e->getMessage()}</error>");
|
|
|
|
return 1;
|
|
} finally {
|
|
\OCA\Memories\Exif::closeStaticExiftoolProc();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check and act on the clear option if set.
|
|
*/
|
|
protected function checkClear(): void
|
|
{
|
|
if (!$this->opts->clear) {
|
|
return;
|
|
}
|
|
|
|
if ($this->input->isInteractive()) {
|
|
$this->output->write('Are you sure you want to clear the existing index? (y/N): ');
|
|
if ('y' !== trim(fgets(STDIN))) {
|
|
throw new \Exception('Aborting');
|
|
}
|
|
}
|
|
|
|
$this->timelineWrite->clear();
|
|
$this->output->writeln('Cleared existing index');
|
|
}
|
|
|
|
/**
|
|
* Check and act on the force option if set.
|
|
*/
|
|
protected function checkForce(): void
|
|
{
|
|
if (!$this->opts->force) {
|
|
return;
|
|
}
|
|
|
|
$this->output->writeln('Forcing refresh of existing index entries');
|
|
|
|
$this->timelineWrite->orphanAll();
|
|
}
|
|
|
|
/**
|
|
* Run the indexer.
|
|
*/
|
|
protected function runIndex(): void
|
|
{
|
|
$this->runForUsers(function (IUser $user) {
|
|
try {
|
|
$uid = $user->getUID();
|
|
$this->output->writeln("Indexing user {$uid}");
|
|
$this->indexer->indexUser($uid, $this->opts->folder);
|
|
} catch (\Exception $e) {
|
|
$this->output->writeln("<error>{$e->getMessage()}</error>");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Run function for all users (or selected user if set).
|
|
*
|
|
* @param mixed $closure
|
|
*/
|
|
private function runForUsers($closure)
|
|
{
|
|
if ($uid = $this->opts->user) {
|
|
if ($user = $this->userManager->get($uid)) {
|
|
$closure($user);
|
|
} else {
|
|
$this->output->writeln("<error>User {$uid} not found</error>");
|
|
}
|
|
} elseif ($gid = $this->opts->group) {
|
|
if ($group = $this->groupManager->get($gid)) {
|
|
foreach ($group->getUsers() as $user) {
|
|
$closure($user);
|
|
}
|
|
} else {
|
|
$this->output->writeln("<error>Group {$gid} not found</error>");
|
|
}
|
|
} else {
|
|
$this->userManager->callForSeenUsers(static fn (IUser $user) => $closure($user));
|
|
}
|
|
}
|
|
}
|