index: add background job (close #110)
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/579/head
parent
51c62cd3b2
commit
c3067dab91
|
@ -15,6 +15,9 @@ Note: this is a major release and may introduce breaking changes to your workflo
|
||||||
Make sure your temp directory is writable by the web server.
|
Make sure your temp directory is writable by the web server.
|
||||||
- **Breaking**: The `--cleanup` flag to `memories:index` has been removed and is no longer necessary.
|
- **Breaking**: The `--cleanup` flag to `memories:index` has been removed and is no longer necessary.
|
||||||
Folders having a `.nomedia` file will automatically be excluded from the timeline.
|
Folders having a `.nomedia` file will automatically be excluded from the timeline.
|
||||||
|
- **Feature**: Indexing will now build and check indices automatically in the backgroud.
|
||||||
|
Make sure Nextcloud cron is configured correctly. You can disable automatic indexing in the admin panel.
|
||||||
|
Note that files are still indexed immediately on upload.
|
||||||
- **Feature**: You can now choose which folders to index by default.
|
- **Feature**: You can now choose which folders to index by default.
|
||||||
This can be configured from the admin panel. The available options are:
|
This can be configured from the admin panel. The available options are:
|
||||||
- All media files (excluding folders with `.nomedia` files, default and recommended)
|
- All media files (excluding folders with `.nomedia` files, default and recommended)
|
||||||
|
|
|
@ -64,4 +64,7 @@ Memories is a *batteries-included* photo management solution for Nextcloud with
|
||||||
<step>OCA\Memories\Migration\Repair</step>
|
<step>OCA\Memories\Migration\Repair</step>
|
||||||
</install>
|
</install>
|
||||||
</repair-steps>
|
</repair-steps>
|
||||||
|
<background-jobs>
|
||||||
|
<job>OCA\Memories\Cron\IndexJob</job>
|
||||||
|
</background-jobs>
|
||||||
</info>
|
</info>
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\Memories\Cron;
|
||||||
|
|
||||||
|
use OCA\Memories\Service;
|
||||||
|
use OCA\Memories\Util;
|
||||||
|
use OCP\AppFramework\Utility\ITimeFactory;
|
||||||
|
use OCP\BackgroundJob\TimedJob;
|
||||||
|
use OCP\IUserManager;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
const MAX_RUN_TIME = 10; // seconds
|
||||||
|
const INTERVAL = 600; // seconds (don't set this too low)
|
||||||
|
|
||||||
|
class IndexJob extends TimedJob
|
||||||
|
{
|
||||||
|
private Service\Index $service;
|
||||||
|
private IUserManager $userManager;
|
||||||
|
private LoggerInterface $logger;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ITimeFactory $time,
|
||||||
|
Service\Index $service,
|
||||||
|
IUserManager $userManager,
|
||||||
|
LoggerInterface $logger
|
||||||
|
) {
|
||||||
|
parent::__construct($time);
|
||||||
|
$this->service = $service;
|
||||||
|
$this->userManager = $userManager;
|
||||||
|
$this->logger = $logger;
|
||||||
|
|
||||||
|
$this->setInterval(INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function run($arguments)
|
||||||
|
{
|
||||||
|
// Check if indexing is enabled
|
||||||
|
if ('0' === Util::getSystemConfig('memories.index.mode')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run for a maximum of 5 minutes
|
||||||
|
$startTime = microtime(true);
|
||||||
|
$this->service->continueCheck = function () use ($startTime) {
|
||||||
|
return (microtime(true) - $startTime) < MAX_RUN_TIME;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Index with static exiftool process
|
||||||
|
// This is sub-optimal: the process may not be required at all.
|
||||||
|
try {
|
||||||
|
\OCA\Memories\Exif::ensureStaticExiftoolProc();
|
||||||
|
$this->indexAllUsers();
|
||||||
|
} catch (Service\ProcessClosedException $e) {
|
||||||
|
$this->logger->warning('Memories: Indexing process closed before completion, will continue on next run.');
|
||||||
|
} finally {
|
||||||
|
\OCA\Memories\Exif::closeStaticExiftoolProc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index all users.
|
||||||
|
*
|
||||||
|
* @throws Service\ProcessClosedException if the process was closed before completion
|
||||||
|
*/
|
||||||
|
private function indexAllUsers(): void
|
||||||
|
{
|
||||||
|
$this->userManager->callForSeenUsers(function ($user) {
|
||||||
|
try {
|
||||||
|
$this->service->indexUser($user->getUID());
|
||||||
|
} catch (Service\ProcessClosedException $e) {
|
||||||
|
throw $e;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error('Indexing failed for user '.$user->getUID().': '.$e->getMessage());
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->error('[BUG] uncaught exception in memories: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,8 +40,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class Index
|
class Index
|
||||||
{
|
{
|
||||||
public ?OutputInterface $output;
|
public ?OutputInterface $output = null;
|
||||||
public ?ConsoleSectionOutput $section;
|
public ?ConsoleSectionOutput $section = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to check if the process should continue.
|
||||||
|
* This is called before every file is indexed.
|
||||||
|
*/
|
||||||
|
public ?\Closure $continueCheck = null;
|
||||||
|
|
||||||
protected IRootFolder $rootFolder;
|
protected IRootFolder $rootFolder;
|
||||||
protected TimelineWrite $timelineWrite;
|
protected TimelineWrite $timelineWrite;
|
||||||
|
@ -162,6 +168,7 @@ class Index
|
||||||
|
|
||||||
// Index files
|
// Index files
|
||||||
foreach ($fileIds as $fileId) {
|
foreach ($fileIds as $fileId) {
|
||||||
|
$this->ensureContinueOk();
|
||||||
$this->indexFile($chunk[$fileId]);
|
$this->indexFile($chunk[$fileId]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,8 +176,12 @@ class Index
|
||||||
// All folders
|
// All folders
|
||||||
$folders = array_filter($nodes, fn ($n) => $n instanceof Folder);
|
$folders = array_filter($nodes, fn ($n) => $n instanceof Folder);
|
||||||
foreach ($folders as $folder) {
|
foreach ($folders as $folder) {
|
||||||
|
$this->ensureContinueOk();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->indexFolder($folder);
|
$this->indexFolder($folder);
|
||||||
|
} catch (ProcessClosedException $e) {
|
||||||
|
throw $e;
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->logger->error('Failed to index folder {folder}: {error}', [
|
$this->logger->error('Failed to index folder {folder}: {error}', [
|
||||||
'folder' => $folder->getPath(),
|
'folder' => $folder->getPath(),
|
||||||
|
@ -280,4 +291,14 @@ class Index
|
||||||
$this->section->write($message);
|
$this->section->write($message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the process should go on.
|
||||||
|
*/
|
||||||
|
private function ensureContinueOk(): void
|
||||||
|
{
|
||||||
|
if (null !== $this->continueCheck && !($this->continueCheck)()) {
|
||||||
|
throw new ProcessClosedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace OCA\Memories\Service;
|
||||||
|
|
||||||
|
class ProcessClosedException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
Loading…
Reference in New Issue