index: allow specifying user and folder (fix #184)
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/563/head
parent
2c3181b497
commit
70c2b0d11d
|
@ -6,6 +6,7 @@ This file is manually updated. Please file an issue if something is missing.
|
||||||
|
|
||||||
- **Feature**: Use GPS location data for timezone calculation.
|
- **Feature**: Use GPS location data for timezone calculation.
|
||||||
Many cameras do not store the timezone in EXIF data. This feature allows Memories to use the GPS location data to calculate the timezone. To take advantage of this, you will need to run `occ memories:places-setup` followed by `occ memories:index --clear` (or `occ memories:index -f`) to reindex your photos.
|
Many cameras do not store the timezone in EXIF data. This feature allows Memories to use the GPS location data to calculate the timezone. To take advantage of this, you will need to run `occ memories:places-setup` followed by `occ memories:index --clear` (or `occ memories:index -f`) to reindex your photos.
|
||||||
|
- **Feature**: You can now specify the user and/or folder to index when running `occ memories:index`.
|
||||||
|
|
||||||
## v4.12.4 + v4.12.5 (2023-03-23)
|
## v4.12.4 + v4.12.5 (2023-03-23)
|
||||||
|
|
||||||
|
|
|
@ -47,12 +47,16 @@ class IndexOpts
|
||||||
public bool $refresh = false;
|
public bool $refresh = false;
|
||||||
public bool $clear = false;
|
public bool $clear = false;
|
||||||
public bool $cleanup = false;
|
public bool $cleanup = false;
|
||||||
|
public ?string $user = null;
|
||||||
|
public ?string $folder = null;
|
||||||
|
|
||||||
public function __construct(InputInterface $input)
|
public function __construct(InputInterface $input)
|
||||||
{
|
{
|
||||||
$this->refresh = (bool) $input->getOption('refresh');
|
$this->refresh = (bool) $input->getOption('refresh');
|
||||||
$this->clear = (bool) $input->getOption('clear');
|
$this->clear = (bool) $input->getOption('clear');
|
||||||
$this->cleanup = (bool) $input->getOption('cleanup');
|
$this->cleanup = (bool) $input->getOption('cleanup');
|
||||||
|
$this->user = $input->getOption('user');
|
||||||
|
$this->folder = $input->getOption('folder');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +85,9 @@ class Index extends Command
|
||||||
// Helper for the progress bar
|
// Helper for the progress bar
|
||||||
private ConsoleSectionOutput $outputSection;
|
private ConsoleSectionOutput $outputSection;
|
||||||
|
|
||||||
|
// Command options
|
||||||
|
private IndexOpts $opts;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IRootFolder $rootFolder,
|
IRootFolder $rootFolder,
|
||||||
IUserManager $userManager,
|
IUserManager $userManager,
|
||||||
|
@ -107,24 +114,11 @@ class Index extends Command
|
||||||
$this
|
$this
|
||||||
->setName('memories:index')
|
->setName('memories:index')
|
||||||
->setDescription('Generate photo entries')
|
->setDescription('Generate photo entries')
|
||||||
->addOption(
|
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Index only the specified user')
|
||||||
'refresh',
|
->addOption('folder', null, InputOption::VALUE_REQUIRED, 'Index only the specified folder')
|
||||||
'f',
|
->addOption('refresh', 'f', InputOption::VALUE_NONE, 'Refresh existing entries')
|
||||||
InputOption::VALUE_NONE,
|
->addOption('clear', null, InputOption::VALUE_NONE, 'Clear existing index before creating a new one (slow)')
|
||||||
'Refresh existing entries'
|
->addOption('cleanup', null, InputOption::VALUE_NONE, 'Remove orphaned entries from index (e.g. from .nomedia files)')
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
'clear',
|
|
||||||
null,
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Clear existing index before creating a new one (slow)'
|
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
'cleanup',
|
|
||||||
null,
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Remove orphaned entries from index (e.g. from .nomedia files)'
|
|
||||||
)
|
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +132,7 @@ class Index extends Command
|
||||||
$output->writeln("\nMIME Type support:");
|
$output->writeln("\nMIME Type support:");
|
||||||
$mimes = array_merge(Application::IMAGE_MIMES, Application::VIDEO_MIMES);
|
$mimes = array_merge(Application::IMAGE_MIMES, Application::VIDEO_MIMES);
|
||||||
$someUnsupported = false;
|
$someUnsupported = false;
|
||||||
foreach ($mimes as &$mimeType) {
|
foreach ($mimes as $mimeType) {
|
||||||
if ($this->preview->isMimeSupported($mimeType)) {
|
if ($this->preview->isMimeSupported($mimeType)) {
|
||||||
$output->writeln(" {$mimeType}: supported");
|
$output->writeln(" {$mimeType}: supported");
|
||||||
} else {
|
} else {
|
||||||
|
@ -155,10 +149,10 @@ class Index extends Command
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get options and arguments
|
// Get options and arguments
|
||||||
$opts = new IndexOpts($input);
|
$this->opts = new IndexOpts($input);
|
||||||
|
|
||||||
// Clear index if asked for this
|
// Clear index if asked for this
|
||||||
if ($opts->clear && $input->isInteractive()) {
|
if ($this->opts->clear && $input->isInteractive()) {
|
||||||
$output->write('Are you sure you want to clear the existing index? (y/N): ');
|
$output->write('Are you sure you want to clear the existing index? (y/N): ');
|
||||||
$answer = trim(fgets(STDIN));
|
$answer = trim(fgets(STDIN));
|
||||||
if ('y' !== $answer) {
|
if ('y' !== $answer) {
|
||||||
|
@ -167,14 +161,21 @@ class Index extends Command
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($opts->clear) {
|
if ($this->opts->clear) {
|
||||||
$this->timelineWrite->clear();
|
$this->timelineWrite->clear();
|
||||||
$output->writeln('Cleared existing index');
|
$output->writeln('Cleared existing index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect incompatible options
|
||||||
|
if ($this->opts->cleanup && ($this->opts->user || $this->opts->folder)) {
|
||||||
|
$output->writeln('<error>Cannot use --cleanup with --user or --folder</error>');
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Orphan all entries so we can delete them later
|
// Orphan all entries so we can delete them later
|
||||||
// Refresh works similarly, with a different flag on the process call
|
// Refresh works similarly, with a different flag on the process call
|
||||||
if ($opts->cleanup || $opts->refresh) {
|
if ($this->opts->cleanup || $this->opts->refresh) {
|
||||||
$output->write('Marking all entries for refresh / cleanup ... ');
|
$output->write('Marking all entries for refresh / cleanup ... ');
|
||||||
$count = $this->timelineWrite->orphanAll();
|
$count = $this->timelineWrite->orphanAll();
|
||||||
$output->writeln("{$count} marked");
|
$output->writeln("{$count} marked");
|
||||||
|
@ -184,9 +185,9 @@ class Index extends Command
|
||||||
try {
|
try {
|
||||||
\OCA\Memories\Exif::ensureStaticExiftoolProc();
|
\OCA\Memories\Exif::ensureStaticExiftoolProc();
|
||||||
|
|
||||||
return $this->executeWithOpts($output, $opts);
|
return $this->executeNow($output);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
error_log('FATAL: '.$e->getMessage());
|
$this->output->writeln("<error>{$e->getMessage()}</error>");
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -194,7 +195,7 @@ class Index extends Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function executeWithOpts(OutputInterface $output, IndexOpts &$opts): int
|
protected function executeNow(OutputInterface $output): int
|
||||||
{
|
{
|
||||||
// Refuse to run without exiftool
|
// Refuse to run without exiftool
|
||||||
if (!$this->testExif()) {
|
if (!$this->testExif()) {
|
||||||
|
@ -215,13 +216,21 @@ class Index extends Command
|
||||||
}
|
}
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
|
|
||||||
// Call indexing for each user
|
// Call indexing for specified or each user
|
||||||
$this->userManager->callForSeenUsers(function (IUser &$user) use (&$opts) {
|
if ($uid = $this->opts->user) {
|
||||||
$this->generateUserEntries($user, $opts);
|
if ($user = $this->userManager->get($uid)) {
|
||||||
});
|
$this->indexOneUser($user);
|
||||||
|
} else {
|
||||||
|
throw new \Exception("User {$uid} not found");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->userManager->callForSeenUsers(function (IUser $user) {
|
||||||
|
$this->indexOneUser($user);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Clear orphans if asked for this
|
// Clear orphans if asked for this
|
||||||
if ($opts->cleanup || $opts->refresh) {
|
if (($this->opts->cleanup || $this->opts->refresh) && !($this->opts->user || $this->opts->folder)) {
|
||||||
$output->write('Deleting orphaned entries ... ');
|
$output->write('Deleting orphaned entries ... ');
|
||||||
$count = $this->timelineWrite->removeOrphans();
|
$count = $this->timelineWrite->removeOrphans();
|
||||||
$output->writeln("{$count} deleted");
|
$output->writeln("{$count} deleted");
|
||||||
|
@ -278,22 +287,39 @@ class Index extends Command
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateUserEntries(IUser &$user, IndexOpts &$opts): void
|
private function indexOneUser(IUser $user): void
|
||||||
{
|
{
|
||||||
\OC_Util::tearDownFS();
|
\OC_Util::tearDownFS();
|
||||||
\OC_Util::setupFS($user->getUID());
|
\OC_Util::setupFS($user->getUID());
|
||||||
|
|
||||||
$uid = $user->getUID();
|
$uid = $user->getUID();
|
||||||
$userFolder = $this->rootFolder->getUserFolder($uid);
|
$folder = $this->rootFolder->getUserFolder($uid);
|
||||||
|
|
||||||
|
if ($path = $this->opts->folder) {
|
||||||
|
try {
|
||||||
|
$folder = $folder->get($path);
|
||||||
|
} catch (\OCP\Files\NotFoundException $e) {
|
||||||
|
$this->output->writeln("<error>Folder {$path} not found for user {$uid}</error>");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$folder instanceof Folder) {
|
||||||
|
$this->output->writeln("<error>Path {$path} is not a folder for user {$uid}</error>");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->outputSection = $this->output->section();
|
$this->outputSection = $this->output->section();
|
||||||
++$this->nUser;
|
++$this->nUser;
|
||||||
|
|
||||||
$this->outputSection->overwrite("Scanning files for {$uid}");
|
$this->outputSection->overwrite("Scanning files for {$uid}");
|
||||||
$this->parseFolder($userFolder, $opts);
|
$this->indexFolder($folder);
|
||||||
$this->outputSection->overwrite("Scanned all files for {$uid}");
|
$this->outputSection->overwrite("Scanned all files for {$uid}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseFolder(Folder &$folder, IndexOpts &$opts): void
|
private function indexFolder(Folder $folder): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Respect the '.nomedia' file. If present don't traverse the folder
|
// Respect the '.nomedia' file. If present don't traverse the folder
|
||||||
|
@ -305,15 +331,15 @@ class Index extends Command
|
||||||
|
|
||||||
$nodes = $folder->getDirectoryListing();
|
$nodes = $folder->getDirectoryListing();
|
||||||
|
|
||||||
foreach ($nodes as $i => &$node) {
|
foreach ($nodes as $i => $node) {
|
||||||
if ($node instanceof Folder) {
|
if ($node instanceof Folder) {
|
||||||
$this->parseFolder($node, $opts);
|
$this->indexFolder($node);
|
||||||
} elseif ($node instanceof File) {
|
} elseif ($node instanceof File) {
|
||||||
$path = $node->getPath();
|
$path = $node->getPath();
|
||||||
$path = \strlen($path) > 80 ? '...'.substr($path, -77) : $path;
|
$path = \strlen($path) > 80 ? '...'.substr($path, -77) : $path;
|
||||||
|
|
||||||
$this->outputSection->overwrite("Scanning {$path}");
|
$this->outputSection->overwrite("Scanning {$path}");
|
||||||
$this->parseFile($node, $opts);
|
$this->indexFile($node);
|
||||||
$this->tempManager->clean();
|
$this->tempManager->clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,7 +352,7 @@ class Index extends Command
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseFile(File &$file, IndexOpts &$opts): void
|
private function indexFile(File $file): void
|
||||||
{
|
{
|
||||||
// Process the file
|
// Process the file
|
||||||
$res = 1;
|
$res = 1;
|
||||||
|
@ -335,14 +361,14 @@ class Index extends Command
|
||||||
// If refreshing the index, force reprocessing
|
// If refreshing the index, force reprocessing
|
||||||
// when the file is still an orphan. this way, the
|
// when the file is still an orphan. this way, the
|
||||||
// files are reprocessed exactly once
|
// files are reprocessed exactly once
|
||||||
$force = $opts->refresh ? 2 : 0;
|
$force = $this->opts->refresh ? 2 : 0;
|
||||||
|
|
||||||
// (re-)process the file
|
// (re-)process the file
|
||||||
$res = $this->timelineWrite->processFile($file, $force);
|
$res = $this->timelineWrite->processFile($file, $force);
|
||||||
|
|
||||||
// If the file was processed successfully,
|
// If the file was processed successfully,
|
||||||
// remove it from the orphan list
|
// remove it from the orphan list
|
||||||
if ($opts->cleanup || $opts->refresh) {
|
if ($this->opts->cleanup || $this->opts->refresh) {
|
||||||
$this->timelineWrite->unorphan($file);
|
$this->timelineWrite->unorphan($file);
|
||||||
}
|
}
|
||||||
} catch (\Error $e) {
|
} catch (\Error $e) {
|
||||||
|
|
Loading…
Reference in New Issue