Exclude mounts in nomedia

Signed-off-by: Varun Patil <varunpatil@ucla.edu>
pull/579/head
Varun Patil 2023-04-14 15:26:20 -07:00
parent 8ec21747f3
commit 3ada3d6510
3 changed files with 82 additions and 14 deletions

View File

@ -23,12 +23,17 @@ declare(strict_types=1);
namespace OCA\Memories\Db; namespace OCA\Memories\Db;
use OC\Files\Search\SearchComparison;
use OC\Files\Search\SearchQuery;
use OCA\Memories\Exceptions; use OCA\Memories\Exceptions;
use OCA\Memories\Exif; use OCA\Memories\Exif;
use OCA\Memories\Util; use OCA\Memories\Util;
use OCP\Files\File; use OCP\Files\File;
use OCP\Files\Folder; use OCP\Files\Folder;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\Files\Search\ISearchComparison;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IConfig; use OCP\IConfig;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserManager; use OCP\IUserManager;
@ -42,19 +47,22 @@ class FsManager
protected IRootFolder $rootFolder; protected IRootFolder $rootFolder;
protected AlbumsQuery $albumsQuery; protected AlbumsQuery $albumsQuery;
protected IRequest $request; protected IRequest $request;
protected ICache $nomediaCache;
public function __construct( public function __construct(
IConfig $config, IConfig $config,
IUserSession $userSession, IUserSession $userSession,
IRootFolder $rootFolder, IRootFolder $rootFolder,
AlbumsQuery $albumsQuery, AlbumsQuery $albumsQuery,
IRequest $request IRequest $request,
ICacheFactory $cacheFactory
) { ) {
$this->config = $config; $this->config = $config;
$this->userSession = $userSession; $this->userSession = $userSession;
$this->rootFolder = $rootFolder; $this->rootFolder = $rootFolder;
$this->albumsQuery = $albumsQuery; $this->albumsQuery = $albumsQuery;
$this->request = $request; $this->request = $request;
$this->nomediaCache = $cacheFactory->createLocal('memories:nomedia');
} }
/** Get the TimelineRoot object relevant to the request */ /** Get the TimelineRoot object relevant to the request */
@ -98,16 +106,30 @@ class FsManager
$folder = $userFolder->get(Exif::removeExtraSlash($folderPath)); $folder = $userFolder->get(Exif::removeExtraSlash($folderPath));
$root->addFolder($folder); $root->addFolder($folder);
} else { } else {
// Get timeline paths
$paths = Exif::getTimelinePaths($uid); $paths = Exif::getTimelinePaths($uid);
if ($path = $this->request->getParam('timelinePath', null)) { if ($path = $this->request->getParam('timelinePath', null)) {
$paths = [Exif::removeExtraSlash($path)]; $paths = [Exif::removeExtraSlash($path)];
} }
// Combined etag, for cache invalidation.
// This is cheaper and more sensible than the root etag.
// The only time this breaks down is if the user places a .nomedia
// outside the timeline path; rely on expiration for that.
$cEtag = $uid;
// Multiple timeline path support // Multiple timeline path support
foreach ($paths as $path) { foreach ($paths as $path) {
$root->addFolder($userFolder->get($path)); $node = $userFolder->get($path);
$root->addFolder($node);
$cEtag .= $node->getEtag();
} }
// Add shares or external stores inside the current folders
$root->addMountPoints(); $root->addMountPoints();
// Exclude .nomedia folders
$root->excludePaths($this->getNoMediaFolders($userFolder, md5($cEtag)));
} }
} catch (\OCP\Files\NotFoundException $e) { } catch (\OCP\Files\NotFoundException $e) {
$msg = $e->getMessage(); $msg = $e->getMessage();
@ -118,6 +140,27 @@ class FsManager
return $root; return $root;
} }
/**
* Get list of folders with .nomedia file.
*
* @param Folder $root root folder
* @param string $key etag for cache key
*/
public function getNoMediaFolders(Folder $root, string $key): array
{
if (null !== ($paths = $this->nomediaCache->get($key))) {
return $paths;
}
$comp = new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'name', '.nomedia');
$search = $root->search(new SearchQuery($comp, 0, 0, [], Util::getUser()));
$paths = array_map(fn (File $node) => \dirname($node->getPath()), $search);
$this->nomediaCache->set($key, $paths, 60 * 60); // 1 hour
return $paths;
}
/** /**
* Get a file with ID for the current user. * Get a file with ID for the current user.
* *

View File

@ -154,6 +154,11 @@ trait TimelineQueryDays
$root = $this->root(); $root = $this->root();
} }
// Never join against an empty root
if (!$root || $root->isEmpty()) {
throw new \InvalidArgumentException('Timeline root is empty');
}
// Join with memories // Join with memories
$baseOp = $query->expr()->eq('f.fileid', 'm.fileid'); $baseOp = $query->expr()->eq('f.fileid', 'm.fileid');
if ($root->isEmpty()) { if ($root->isEmpty()) {

View File

@ -14,6 +14,8 @@ class TimelineRoot
/** Initialize */ /** Initialize */
public function __construct() public function __construct()
{ {
$this->folders = [];
$this->folderPaths = [];
} }
/** /**
@ -31,35 +33,42 @@ class TimelineRoot
*/ */
public function addFolder(FileInfo $info) public function addFolder(FileInfo $info)
{ {
$folderPath = $info->getPath(); $path = $info->getPath();
if (FileInfo::MIMETYPE_FOLDER !== $info->getMimetype()) { if (FileInfo::MIMETYPE_FOLDER !== $info->getMimetype()) {
throw new \Exception("Not a folder: {$folderPath}"); throw new \Exception("Not a folder: {$path}");
} }
if (!$info->isReadable()) { if (!$info->isReadable()) {
throw new \Exception("Folder not readable: {$folderPath}"); throw new \Exception("Folder not readable: {$path}");
} }
// Add top level folder // Add top level folder
$id = $info->getId(); $this->setFolder($info->getId(), $info, $path);
$this->folders[$id] = $info;
$this->folderPaths[$id] = $folderPath;
} }
// Add mountpoints recursively // Add mountpoints recursively
public function addMountPoints() public function addMountPoints()
{ {
$mp = []; $folders = [];
foreach ($this->folderPaths as $id => $folderPath) { foreach ($this->folderPaths as $id => $folderPath) {
$mounts = \OC\Files\Filesystem::getMountManager()->findIn($folderPath); $mounts = \OC\Files\Filesystem::getMountManager()->findIn($folderPath);
foreach ($mounts as &$mount) { foreach ($mounts as $mount) {
$id = $mount->getStorageRootId(); $this->setFolder($mount->getStorageRootId(), null, $mount->getMountPoint());
$path = $mount->getMountPoint(); }
$mp[$id] = $path; }
$this->folderPaths += $folders;
}
public function excludePaths(array $paths)
{
foreach ($paths as $path) {
foreach ($this->folderPaths as $id => $folderPath) {
if (str_starts_with($folderPath, $path)) {
unset($this->folderPaths[$id], $this->folders[$id]);
}
} }
} }
$this->folderPaths += $mp;
} }
public function getFolderPath(int $id) public function getFolderPath(int $id)
@ -86,4 +95,15 @@ class TimelineRoot
{ {
return empty($this->folderPaths); return empty($this->folderPaths);
} }
private function setFolder(int $id, ?FileInfo $fileInfo, ?string $path)
{
if (null !== $path) {
$this->folderPaths[$id] = $path;
}
if (null !== $fileInfo) {
$this->folders[$id] = $fileInfo;
}
}
} }