Rename locations to places

Signed-off-by: Louis Chemineau <louis@chmn.me>
This commit is contained in:
Louis Chemineau 2023-02-23 18:58:31 +01:00 committed by John Molakvoæ
parent 0234a47791
commit eac254ed8f
No known key found for this signature in database
GPG Key ID: 60C25B8C072916CF
88 changed files with 225 additions and 225 deletions

View File

@ -32,7 +32,7 @@
<commands>
<command>OCA\Photos\Command\UpdateReverseGeocodingFilesCommand</command>
<command>OCA\Photos\Command\MapMediaToLocationCommand</command>
<command>OCA\Photos\Command\MapMediaToPlaceCommand</command>
</commands>
<sabre>
@ -46,6 +46,6 @@
</sabre>
<background-jobs>
<job>OCA\Photos\Jobs\AutomaticLocationMapperJob</job>
<job>OCA\Photos\Jobs\AutomaticPlaceMapperJob</job>
</background-jobs>
</info>

View File

@ -46,7 +46,7 @@ return [
'path' => '',
]
],
['name' => 'page#index', 'url' => '/locations/{path}', 'verb' => 'GET', 'postfix' => 'locations',
['name' => 'page#index', 'url' => '/places/{path}', 'verb' => 'GET', 'postfix' => 'places',
'requirements' => [
'path' => '.*',
],

View File

@ -128,21 +128,21 @@ describe('Manage albums', () => {
cy.contains('Save').click()
})
it('Edit an album\'s location', () => {
it('Edit an album\'s place', () => {
cy.get('[aria-label="Open actions menu"]').click()
cy.contains('Edit album details').click()
cy.get('form [name="location"]').clear().type('New location')
cy.get('form [name="place"]').clear().type('New place')
cy.contains('Save').click()
cy.contains('New location')
cy.contains('New place')
cy.reload()
cy.contains('New location')
cy.contains('New place')
cy.get('[aria-label="Open actions menu"]').click()
cy.contains('Edit album details').click()
cy.get('form [name="location"]').clear()
cy.get('form [name="place"]').clear()
cy.contains('Save').click()
})
})

View File

@ -20,7 +20,7 @@
*
*/
import { uploadTestMedia } from './photosUtils'
import { navigateToLocation, runOccCommand } from './locationsUtils'
import { navigateToPlace, runOccCommand } from './placesUtils'
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
Cypress.on('uncaught:exception', (err) => {
@ -30,27 +30,27 @@ Cypress.on('uncaught:exception', (err) => {
}
})
describe('Manage locations', () => {
describe('Manage places', () => {
before(function() {
cy.createRandomUser()
.then((user) => {
uploadTestMedia(user)
runOccCommand(`photos:map-media-to-location --user ${user.userId}`)
runOccCommand(`photos:map-media-to-place --user ${user.userId}`)
cy.login(user)
cy.visit('/apps/photos')
})
})
beforeEach(() => {
cy.visit('apps/photos/locations')
cy.visit('apps/photos/places')
})
it('Check that we detect some locations out of the existing files', () => {
it('Check that we detect some places out of the existing files', () => {
cy.get('ul.collections__list li').should('have.length', 4)
})
it('Navigate to location and check that it contains some files', () => {
navigateToLocation('Lauris')
it('Navigate to place and check that it contains some files', () => {
navigateToPlace('Lauris')
cy.get('[data-test="media"]').should('have.length', 1)
})
})

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -32,7 +32,7 @@ use OCA\Photos\Listener\NodeDeletedListener;
use OCA\Photos\Listener\TagListener;
use OCA\Photos\Listener\GroupUserRemovedListener;
use OCA\Photos\Listener\GroupDeletedListener;
use OCA\Photos\Listener\LocationManagerEventListener;
use OCA\Photos\Listener\PlaceManagerEventListener;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
@ -83,7 +83,7 @@ class Application extends App implements IBootstrap {
$context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class);
// Priority of -1 to be triggered after event listeners populating metadata.
$context->registerEventListener(NodeWrittenEvent::class, LocationManagerEventListener::class, -1);
$context->registerEventListener(NodeWrittenEvent::class, PlaceManagerEventListener::class, -1);
$context->registerEventListener(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);

View File

@ -28,16 +28,16 @@ use OCP\IConfig;
use OCP\IUserManager;
use OCP\Files\IRootFolder;
use OCP\Files\Folder;
use OCA\Photos\Service\MediaLocationManager;
use OCA\Photos\Service\MediaPlaceManager;
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 MapMediaToLocationCommand extends Command {
class MapMediaToPlaceCommand extends Command {
public function __construct(
private IRootFolder $rootFolder,
private MediaLocationManager $mediaLocationManager,
private MediaPlaceManager $mediaPlaceManager,
private IConfig $config,
private IUserManager $userManager,
) {
@ -48,7 +48,7 @@ class MapMediaToLocationCommand extends Command {
* Configure the command
*/
protected function configure(): void {
$this->setName('photos:map-media-to-location')
$this->setName('photos:map-media-to-place')
->setDescription('Reverse geocode media coordinates.')
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Limit the mapping to a user.', null);
}
@ -107,7 +107,7 @@ class MapMediaToLocationCommand extends Command {
continue;
}
$this->mediaLocationManager->setLocationForFile($node->getId());
$this->mediaPlaceManager->setPlaceForFile($node->getId());
$count++;
}

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
*
*/
namespace OCA\Photos\DB\Location;
namespace OCA\Photos\DB\Place;
use OCA\Photos\DB\PhotosFile;
class LocationFile extends PhotosFile {
class PlaceFile extends PhotosFile {
public function __construct(
int $fileId,
string $name,
@ -35,7 +35,7 @@ class LocationFile extends PhotosFile {
int $size,
int $mtime,
string $etag,
private string $location,
private string $place,
) {
parent::__construct(
$fileId,
@ -47,7 +47,7 @@ class LocationFile extends PhotosFile {
);
}
public function getLocation(): string {
return $this->location;
public function getPlace(): string {
return $this->place;
}
}

View File

@ -23,12 +23,12 @@ declare(strict_types=1);
*
*/
namespace OCA\Photos\DB\Location;
namespace OCA\Photos\DB\Place;
class LocationInfo {
class PlaceInfo {
public function __construct(
private string $userId,
private string $location
private string $place
) {
}
@ -36,7 +36,7 @@ class LocationInfo {
return $this->userId;
}
public function getLocation(): string {
return $this->location;
public function getPlace(): string {
return $this->place;
}
}

View File

@ -23,7 +23,7 @@ declare(strict_types=1);
*
*/
namespace OCA\Photos\DB\Location;
namespace OCA\Photos\DB\Place;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use OCP\DB\QueryBuilder\IQueryBuilder;
@ -32,8 +32,8 @@ use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
class LocationMapper {
public const METADATA_TYPE = 'photos_location';
class PlaceMapper {
public const METADATA_TYPE = 'photos_place';
public function __construct(
private IDBConnection $connection,
@ -42,8 +42,8 @@ class LocationMapper {
) {
}
/** @return LocationInfo[] */
public function findLocationsForUser(string $userId): array {
/** @return PlaceInfo[] */
public function findPlacesForUser(string $userId): array {
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
@ -62,11 +62,11 @@ class LocationMapper {
->executeQuery()
->fetchAll();
return array_map(fn ($row) => new LocationInfo($userId, $row['metadata']), $rows);
return array_map(fn ($row) => new PlaceInfo($userId, $row['metadata']), $rows);
}
/** @return LocationInfo */
public function findLocationForUser(string $userId, string $location): LocationInfo {
/** @return PlaceInfo */
public function findPlaceForUser(string $userId, string $place): PlaceInfo {
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
@ -82,7 +82,7 @@ class LocationMapper {
->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_TYPE)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($location)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($place)))
->executeQuery()
->fetchAll();
@ -90,11 +90,11 @@ class LocationMapper {
throw new NotFoundException();
}
return new LocationInfo($userId, $rows[0]['metadata']);
return new PlaceInfo($userId, $rows[0]['metadata']);
}
/** @return LocationFile[] */
public function findFilesForUserAndLocation(string $userId, string $location) {
/** @return PlaceFile[] */
public function findFilesForUserAndPlace(string $userId, string $place) {
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
@ -110,12 +110,12 @@ class LocationMapper {
->where($qb->expr()->eq('file.storage', $qb->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('file.mimepart', $qb->createNamedParameter($mimepart, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_TYPE)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($location)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($place)))
->executeQuery()
->fetchAll();
return array_map(
fn ($row) => new LocationFile(
fn ($row) => new PlaceFile(
(int)$row['fileid'],
$row['name'],
$this->mimeTypeLoader->getMimetypeById($row['mimetype']),
@ -128,7 +128,7 @@ class LocationMapper {
);
}
public function findFileForUserAndLocation(string $userId, string $location, string $fileId, string $fileName): LocationFile {
public function findFileForUserAndPlace(string $userId, string $place, string $fileId, string $fileName): PlaceFile {
$storageId = $this->rootFolder
->getUserFolder($userId)
->getMountPoint()
@ -146,7 +146,7 @@ class LocationMapper {
->andWhere($qb->expr()->eq('file.fileid', $qb->createNamedParameter($fileId)))
->andWhere($qb->expr()->eq('file.name', $qb->createNamedParameter($fileName)))
->andWhere($qb->expr()->eq('meta.group_name', $qb->createNamedParameter(self::METADATA_TYPE)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($location)))
->andWhere($qb->expr()->eq('meta.metadata', $qb->createNamedParameter($place)))
->executeQuery()
->fetchAll();
@ -154,7 +154,7 @@ class LocationMapper {
throw new NotFoundException();
}
return new LocationFile(
return new PlaceFile(
(int)$rows[0]['fileid'],
$rows[0]['name'],
$this->mimeTypeLoader->getMimetypeById($rows[0]['mimetype']),
@ -165,27 +165,27 @@ class LocationMapper {
);
}
public function setLocationForFile(string $location, int $fileId): void {
public function setPlaceForFile(string $place, int $fileId): void {
try {
$query = $this->connection->getQueryBuilder();
$query->insert('file_metadata')
->values([
"id" => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
"group_name" => $query->createNamedParameter(self::METADATA_TYPE),
"metadata" => $query->createNamedParameter($location),
"metadata" => $query->createNamedParameter($place),
])
->executeStatement();
} catch (\Exception $ex) {
if ($ex->getPrevious() instanceof UniqueConstraintViolationException) {
$this->updateLocationForFile($location, $fileId);
$this->updatePlaceForFile($place, $fileId);
}
}
}
public function updateLocationForFile(string $location, int $fileId): void {
public function updatePlaceForFile(string $place, int $fileId): void {
$query = $this->connection->getQueryBuilder();
$query->update('file_metadata')
->set("metadata", $query->createNamedParameter($location))
->set("metadata", $query->createNamedParameter($place))
->where($query->expr()->eq('id', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('group_name', $query->createNamedParameter(self::METADATA_TYPE)))
->executeStatement();

View File

@ -26,7 +26,7 @@ declare(strict_types=1);
namespace OCA\Photos\Jobs;
use OCA\Photos\AppInfo\Application;
use OCA\Photos\Service\MediaLocationManager;
use OCA\Photos\Service\MediaPlaceManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\TimedJob;
use OCP\Files\Folder;
@ -34,30 +34,30 @@ use OCP\Files\IRootFolder;
use OCP\IConfig;
use OCP\IUserManager;
class AutomaticLocationMapperJob extends TimedJob {
class AutomaticPlaceMapperJob extends TimedJob {
public function __construct(
ITimeFactory $time,
private IConfig $config,
private IRootFolder $rootFolder,
private IUserManager $userManager,
private MediaLocationManager $mediaLocationManager,
private MediaPlaceManager $mediaPlaceManager,
) {
parent::__construct($time);
$this->mediaLocationManager = $mediaLocationManager;
$this->mediaPlaceManager = $mediaPlaceManager;
$this->setTimeSensitivity(\OCP\BackgroundJob\IJob::TIME_INSENSITIVE);
$this->setInterval(24 * 3600);
}
protected function run($argument) {
$locationMappingDone = $this->config->getAppValue(Application::APP_ID, 'lastLocationMappingDone', 'false');
$placeMappingDone = $this->config->getAppValue(Application::APP_ID, 'lastPlaceMappingDone', 'false');
if ($locationMappingDone === 'true') {
if ($placeMappingDone === 'true') {
return;
}
$users = $this->userManager->search('');
$lastMappedUser = $this->config->getAppValue(Application::APP_ID, 'lastLocationMappedUser', '');
$lastMappedUser = $this->config->getAppValue(Application::APP_ID, 'lastPlaceMappedUser', '');
if ($lastMappedUser === '') {
$lastMappedUser = $users[array_key_first($users)]->getUID();
@ -80,10 +80,10 @@ class AutomaticLocationMapperJob extends TimedJob {
}
$this->scanFilesForUser($user->getUID());
$this->config->setAppValue(Application::APP_ID, 'lastLocationMappedUser', $user->getUID());
$this->config->setAppValue(Application::APP_ID, 'lastPlaceMappedUser', $user->getUID());
}
$this->config->setAppValue(Application::APP_ID, 'lastLocationMappingDone', 'true');
$this->config->setAppValue(Application::APP_ID, 'lastPlaceMappingDone', 'true');
}
private function scanFilesForUser(string $userId): void {
@ -108,7 +108,7 @@ class AutomaticLocationMapperJob extends TimedJob {
continue;
}
$this->mediaLocationManager->setLocationForFile($node->getId());
$this->mediaPlaceManager->setPlaceForFile($node->getId());
}
}
}

View File

@ -25,24 +25,24 @@ declare(strict_types=1);
namespace OCA\Photos\Jobs;
use OCA\Photos\Service\MediaLocationManager;
use OCA\Photos\Service\MediaPlaceManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\QueuedJob;
class MapMediaToLocationJob extends QueuedJob {
private MediaLocationManager $mediaLocationManager;
class MapMediaToPlaceJob extends QueuedJob {
private MediaPlaceManager $mediaPlaceManager;
public function __construct(
ITimeFactory $time,
MediaLocationManager $mediaLocationManager
MediaPlaceManager $mediaPlaceManager
) {
parent::__construct($time);
$this->mediaLocationManager = $mediaLocationManager;
$this->mediaPlaceManager = $mediaPlaceManager;
}
protected function run($argument) {
[$fileId] = $argument;
$this->mediaLocationManager->setLocationForFile($fileId);
$this->mediaPlaceManager->setPlaceForFile($fileId);
}
}

View File

@ -25,8 +25,8 @@ declare(strict_types=1);
namespace OCA\Photos\Listener;
use OCA\Photos\Jobs\MapMediaToLocationJob;
use OCA\Photos\Service\MediaLocationManager;
use OCA\Photos\Jobs\MapMediaToPlaceJob;
use OCA\Photos\Service\MediaPlaceManager;
use OCP\BackgroundJob\IJobList;
use OCP\IConfig;
use OCP\EventDispatcher\Event;
@ -34,11 +34,11 @@ use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\NodeWrittenEvent;
/**
* Listener to create, update or remove location info from the database.
* Listener to add place info from the database.
*/
class LocationManagerEventListener implements IEventListener {
class PlaceManagerEventListener implements IEventListener {
public function __construct(
private MediaLocationManager $mediaLocationManager,
private MediaPlaceManager $mediaPlaceManager,
private IConfig $config,
private IJobList $jobList,
) {
@ -60,7 +60,7 @@ class LocationManagerEventListener implements IEventListener {
$fileId = $event->getNode()->getId();
$this->jobList->add(MapMediaToLocationJob::class, [$fileId]);
$this->jobList->add(MapMediaToPlaceJob::class, [$fileId]);
}
}

View File

@ -24,10 +24,10 @@ declare(strict_types=1);
namespace OCA\Photos\Sabre;
use OCA\Photos\Album\AlbumMapper;
use OCA\Photos\DB\Location\LocationMapper;
use OCA\Photos\DB\Place\PlaceMapper;
use OCA\Photos\Sabre\Album\AlbumsHome;
use OCA\Photos\Sabre\Album\SharedAlbumsHome;
use OCA\Photos\Sabre\Location\LocationsHome;
use OCA\Photos\Sabre\Place\PlacesHome;
use OCA\Photos\Service\ReverseGeoCoderService;
use OCA\Photos\Service\UserConfigService;
use OCP\Files\IRootFolder;
@ -41,7 +41,7 @@ class PhotosHome implements ICollection {
public function __construct(
private array $principalInfo,
private AlbumMapper $albumMapper,
private LocationMapper $locationMapper,
private PlaceMapper $placeMapper,
private ReverseGeoCoderService $reverseGeoCoderService,
private string $userId,
private IRootFolder $rootFolder,
@ -87,8 +87,8 @@ class PhotosHome implements ICollection {
return new AlbumsHome($this->principalInfo, $this->albumMapper, $this->userId, $this->rootFolder, $this->userConfigService);
case SharedAlbumsHome::NAME:
return new SharedAlbumsHome($this->principalInfo, $this->albumMapper, $this->userId, $this->rootFolder, $this->userManager, $this->groupManager, $this->userConfigService);
case LocationsHome::NAME:
return new LocationsHome($this->userId, $this->rootFolder, $this->reverseGeoCoderService, $this->locationMapper);
case PlacesHome::NAME:
return new PlacesHome($this->userId, $this->rootFolder, $this->reverseGeoCoderService, $this->placeMapper);
}
throw new NotFound();
@ -101,12 +101,12 @@ class PhotosHome implements ICollection {
return [
new AlbumsHome($this->principalInfo, $this->albumMapper, $this->userId, $this->rootFolder, $this->userConfigService),
new SharedAlbumsHome($this->principalInfo, $this->albumMapper, $this->userId, $this->rootFolder, $this->userManager, $this->groupManager, $this->userConfigService),
new LocationsHome($this->userId, $this->rootFolder, $this->reverseGeoCoderService, $this->locationMapper),
new PlacesHome($this->userId, $this->rootFolder, $this->reverseGeoCoderService, $this->placeMapper),
];
}
public function childExists($name): bool {
return $name === AlbumsHome::NAME || $name === SharedAlbumsHome::NAME || $name === LocationsHome::NAME;
return $name === AlbumsHome::NAME || $name === SharedAlbumsHome::NAME || $name === PlacesHome::NAME;
}
public function getLastModified(): int {

View File

@ -23,10 +23,10 @@ declare(strict_types=1);
*
*/
namespace OCA\Photos\Sabre\Location;
namespace OCA\Photos\Sabre\Place;
use OCA\Photos\DB\Location\LocationFile;
use OCA\Photos\DB\Location\LocationInfo;
use OCA\Photos\DB\Place\PlaceFile;
use OCA\Photos\DB\Place\PlaceInfo;
use OCA\Photos\Sabre\CollectionPhoto;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
@ -36,10 +36,10 @@ use OCP\Files\NotFoundException;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\IFile;
class LocationPhoto extends CollectionPhoto implements IFile {
class PlacePhoto extends CollectionPhoto implements IFile {
public function __construct(
private LocationInfo $locationInfo,
LocationFile $file,
private PlaceInfo $placeInfo,
PlaceFile $file,
private IRootFolder $rootFolder,
Folder $userFolder
) {
@ -50,12 +50,12 @@ class LocationPhoto extends CollectionPhoto implements IFile {
* @return void
*/
public function delete() {
throw new Forbidden('Cannot remove from a location');
throw new Forbidden('Cannot remove from a place');
}
private function getNode(): Node {
$nodes = $this->rootFolder
->getUserFolder($this->locationInfo->getUserId())
->getUserFolder($this->placeInfo->getUserId())
->getById($this->file->getFileId());
$node = current($nodes);

View File

@ -23,11 +23,11 @@ declare(strict_types=1);
*
*/
namespace OCA\Photos\Sabre\Location;
namespace OCA\Photos\Sabre\Place;
use OCA\Photos\DB\Location\LocationFile;
use OCA\Photos\DB\Location\LocationInfo;
use OCA\Photos\DB\Location\LocationMapper;
use OCA\Photos\DB\Place\PlaceFile;
use OCA\Photos\DB\Place\PlaceInfo;
use OCA\Photos\DB\Place\PlaceMapper;
use OCA\Photos\Service\ReverseGeoCoderService;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
@ -35,14 +35,14 @@ use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
class LocationRoot implements ICollection {
/** @var LocationFile[]|null */
class PlaceRoot implements ICollection {
/** @var PlaceFile[]|null */
protected ?array $children = null;
public function __construct(
protected LocationMapper $locationMapper,
protected PlaceMapper $placeMapper,
protected ReverseGeoCoderService $reverseGeoCoderService,
protected LocationInfo $locationInfo,
protected PlaceInfo $placeInfo,
protected string $userId,
protected IRootFolder $rootFolder,
) {
@ -52,18 +52,18 @@ class LocationRoot implements ICollection {
* @return never
*/
public function delete() {
throw new Forbidden('Not allowed to delete a location collection');
throw new Forbidden('Not allowed to delete a place collection');
}
public function getName(): string {
return $this->locationInfo->getLocation();
return $this->placeInfo->getPlace();
}
/**
* @return never
*/
public function setName($name) {
throw new Forbidden('Cannot change the location collection name');
throw new Forbidden('Cannot change the place collection name');
}
/**
@ -72,7 +72,7 @@ class LocationRoot implements ICollection {
* @return never
*/
public function createFile($name, $data = null) {
throw new Forbidden('Cannot create a file in a location collection');
throw new Forbidden('Cannot create a file in a place collection');
}
/**
@ -83,24 +83,24 @@ class LocationRoot implements ICollection {
}
/**
* @return LocationPhoto[]
* @return PlacePhoto[]
*/
public function getChildren(): array {
if ($this->children === null) {
$this->children = array_map(
fn (LocationFile $file) => new LocationPhoto($this->locationInfo, $file, $this->rootFolder, $this->rootFolder->getUserFolder($this->userId)),
$this->locationMapper->findFilesForUserAndLocation($this->locationInfo->getUserId(), $this->locationInfo->getLocation())
fn (PlaceFile $file) => new PlacePhoto($this->placeInfo, $file, $this->rootFolder, $this->rootFolder->getUserFolder($this->userId)),
$this->placeMapper->findFilesForUserAndPlace($this->placeInfo->getUserId(), $this->placeInfo->getPlace())
);
}
return $this->children;
}
public function getChild($name): LocationPhoto {
public function getChild($name): PlacePhoto {
try {
[$fileId, $fileName] = explode('-', $name, 2);
$locationFile = $this->locationMapper->findFileForUserAndLocation($this->locationInfo->getUserId(), $this->locationInfo->getLocation(), $fileId, $fileName);
return new LocationPhoto($this->locationInfo, $locationFile, $this->rootFolder, $this->rootFolder->getUserFolder($this->userId));
$placeFile = $this->placeMapper->findFileForUserAndPlace($this->placeInfo->getUserId(), $this->placeInfo->getPlace(), $fileId, $fileName);
return new PlacePhoto($this->placeInfo, $placeFile, $this->rootFolder, $this->rootFolder->getUserFolder($this->userId));
} catch (NotFoundException $ex) {
throw new NotFound("File $name not found", 0, $ex);
}
@ -122,7 +122,7 @@ class LocationRoot implements ICollection {
public function getFirstPhoto(): int {
$children = $this->getChildren();
if (count($children) === 0) {
throw new \Exception('No children found for location');
throw new \Exception('No children found for place');
}
return $children[0]->getFileId();
@ -132,7 +132,7 @@ class LocationRoot implements ICollection {
* @return int[]
*/
public function getFileIds(): array {
return array_map(function (LocationPhoto $file) {
return array_map(function (PlacePhoto $file) {
return $file->getFileId();
}, $this->getChildren());
}

View File

@ -23,22 +23,22 @@ declare(strict_types=1);
*
*/
namespace OCA\Photos\Sabre\Location;
namespace OCA\Photos\Sabre\Place;
use OCP\Files\IRootFolder;
use OCA\Photos\DB\Location\LocationInfo;
use OCA\Photos\DB\Location\LocationMapper;
use OCA\Photos\DB\Place\PlaceInfo;
use OCA\Photos\DB\Place\PlaceMapper;
use OCA\Photos\Service\ReverseGeoCoderService;
use OCP\Files\NotFoundException;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\ICollection;
class LocationsHome implements ICollection {
public const NAME = 'locations';
class PlacesHome implements ICollection {
public const NAME = 'places';
/**
* @var LocationRoot[]
* @var PlaceRoot[]
*/
protected ?array $children = null;
@ -46,7 +46,7 @@ class LocationsHome implements ICollection {
protected string $userId,
protected IRootFolder $rootFolder,
protected ReverseGeoCoderService $reverseGeoCoderService,
protected LocationMapper $locationMapper,
protected PlaceMapper $placeMapper,
) {
}
@ -76,23 +76,23 @@ class LocationsHome implements ICollection {
throw new Forbidden('Not allowed to create folder in this folder');
}
public function getChild($name): LocationRoot {
public function getChild($name): PlaceRoot {
try {
$locationInfo = $this->locationMapper->findLocationForUser($this->userId, $name);
return new LocationRoot($this->locationMapper, $this->reverseGeoCoderService, $locationInfo, $this->userId, $this->rootFolder);
$placeInfo = $this->placeMapper->findPlaceForUser($this->userId, $name);
return new PlaceRoot($this->placeMapper, $this->reverseGeoCoderService, $placeInfo, $this->userId, $this->rootFolder);
} catch (NotFoundException $ex) {
throw new NotFound("Location $name does not exist", 0, $ex);
throw new NotFound("Place $name does not exist", 0, $ex);
}
}
/**
* @return LocationRoot[]
* @return PlaceRoot[]
*/
public function getChildren(): array {
if ($this->children === null) {
$this->children = array_map(
fn (LocationInfo $locationInfo) => new LocationRoot($this->locationMapper, $this->reverseGeoCoderService, $locationInfo, $this->userId, $this->rootFolder),
$this->locationMapper->findLocationsForUser($this->userId)
fn (PlaceInfo $placeInfo) => new PlaceRoot($this->placeMapper, $this->reverseGeoCoderService, $placeInfo, $this->userId, $this->rootFolder),
$this->placeMapper->findPlacesForUser($this->userId)
);
}

View File

@ -28,8 +28,8 @@ use OCA\DAV\Connector\Sabre\FilesPlugin;
use OCA\Photos\Album\AlbumMapper;
use OCA\Photos\Sabre\Album\AlbumPhoto;
use OCA\Photos\Sabre\Album\AlbumRoot;
use OCA\Photos\Sabre\Location\LocationPhoto;
use OCA\Photos\Sabre\Location\LocationRoot;
use OCA\Photos\Sabre\Place\PlacePhoto;
use OCA\Photos\Sabre\Place\PlaceRoot;
use OCP\IConfig;
use OCP\IPreview;
use OCP\Files\NotFoundException;
@ -45,7 +45,7 @@ class PropFindPlugin extends ServerPlugin {
public const FILE_NAME_PROPERTYNAME = '{http://nextcloud.org/ns}file-name';
public const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
public const DATE_RANGE_PROPERTYNAME = '{http://nextcloud.org/ns}dateRange';
public const LOCATION_PROPERTYNAME = '{http://nextcloud.org/ns}location';
public const LOCATION_PROPERTYNAME = '{http://nextcloud.org/ns}place';
public const LAST_PHOTO_PROPERTYNAME = '{http://nextcloud.org/ns}last-photo';
public const NBITEMS_PROPERTYNAME = '{http://nextcloud.org/ns}nbItems';
public const COLLABORATORS_PROPERTYNAME = '{http://nextcloud.org/ns}collaborators';
@ -93,7 +93,7 @@ class PropFindPlugin extends ServerPlugin {
}
public function propFind(PropFind $propFind, INode $node): void {
if ($node instanceof AlbumPhoto || $node instanceof LocationPhoto) {
if ($node instanceof AlbumPhoto || $node instanceof PlacePhoto) {
// Checking if the node is truly available and ignoring if not
// Should be pre-emptively handled by the NodeDeletedEvent
try {
@ -147,7 +147,7 @@ class PropFindPlugin extends ServerPlugin {
}
}
if ($node instanceof LocationRoot) {
if ($node instanceof PlaceRoot) {
$propFind->handle(self::LAST_PHOTO_PROPERTYNAME, fn () => $node->getFirstPhoto());
$propFind->handle(self::NBITEMS_PROPERTYNAME, fn () => count($node->getChildren()));
@ -167,8 +167,8 @@ class PropFindPlugin extends ServerPlugin {
public function handleUpdateProperties($path, PropPatch $propPatch): void {
$node = $this->tree->getNodeForPath($path);
if ($node instanceof AlbumRoot) {
$propPatch->handle(self::LOCATION_PROPERTYNAME, function ($location) use ($node) {
$this->albumMapper->setLocation($node->getAlbum()->getAlbum()->getId(), $location);
$propPatch->handle(self::LOCATION_PROPERTYNAME, function ($place) use ($node) {
$this->albumMapper->setLocation($node->getAlbum()->getAlbum()->getId(), $place);
return true;
});
$propPatch->handle(self::COLLABORATORS_PROPERTYNAME, function ($collaborators) use ($node) {

View File

@ -24,7 +24,7 @@ declare(strict_types=1);
namespace OCA\Photos\Sabre;
use OCA\Photos\Album\AlbumMapper;
use OCA\Photos\DB\Location\LocationMapper;
use OCA\Photos\DB\Place\PlaceMapper;
use OCA\Photos\Service\ReverseGeoCoderService;
use OCA\Photos\Service\UserConfigService;
use OCP\Files\IRootFolder;
@ -37,7 +37,7 @@ use OCP\IGroupManager;
class RootCollection extends AbstractPrincipalCollection {
public function __construct(
private AlbumMapper $albumMapper,
private LocationMapper $locationMapper,
private PlaceMapper $placeMapper,
private ReverseGeoCoderService $reverseGeoCoderService,
private IUserSession $userSession,
private IRootFolder $rootFolder,
@ -64,7 +64,7 @@ class RootCollection extends AbstractPrincipalCollection {
if (is_null($user) || $name !== $user->getUID()) {
throw new \Sabre\DAV\Exception\Forbidden();
}
return new PhotosHome($principalInfo, $this->albumMapper, $this->locationMapper, $this->reverseGeoCoderService, $name, $this->rootFolder, $this->userManager, $this->groupManager, $this->userConfigService);
return new PhotosHome($principalInfo, $this->albumMapper, $this->placeMapper, $this->reverseGeoCoderService, $name, $this->rootFolder, $this->userManager, $this->groupManager, $this->userConfigService);
}
public function getName(): string {

View File

@ -26,37 +26,37 @@ declare(strict_types=1);
namespace OCA\Photos\Service;
use OC\Metadata\IMetadataManager;
use OCA\Photos\DB\Location\LocationMapper;
use OCA\Photos\DB\Place\PlaceMapper;
class MediaLocationManager {
class MediaPlaceManager {
public function __construct(
private IMetadataManager $metadataManager,
private ReverseGeoCoderService $rgcService,
private LocationMapper $locationMapper,
private PlaceMapper $placeMapper,
) {
}
public function setLocationForFile(int $fileId): void {
$location = $this->getLocationForFile($fileId);
public function setPlaceForFile(int $fileId): void {
$place = $this->getPlaceForFile($fileId);
if ($location === null) {
if ($place === null) {
return;
}
$this->locationMapper->setLocationForFile($location, $fileId);
$this->placeMapper->setPlaceForFile($place, $fileId);
}
public function updateLocationForFile(int $fileId): void {
$location = $this->getLocationForFile($fileId);
public function updatePlaceForFile(int $fileId): void {
$place = $this->getPlaceForFile($fileId);
if ($location === null) {
if ($place === null) {
return;
}
$this->locationMapper->updateLocationForFile($location, $fileId);
$this->placeMapper->updatePlaceForFile($place, $fileId);
}
private function getLocationForFile(int $fileId): ?string {
private function getPlaceForFile(int $fileId): ?string {
$gpsMetadata = $this->metadataManager->fetchMetadataFor('gps', [$fileId])[$fileId];
$metadata = $gpsMetadata->getMetadata();
@ -71,6 +71,6 @@ class MediaLocationManager {
return null;
}
return $this->rgcService->getLocationForCoordinates($latitude, $longitude);
return $this->rgcService->getPlaceForCoordinates($latitude, $longitude);
}
}

View File

@ -54,13 +54,13 @@ class ReverseGeoCoderService {
}
}
public function getLocationForCoordinates(float $latitude, float $longitude): string {
public function getPlaceForCoordinates(float $latitude, float $longitude): string {
$this->loadKdTree();
$result = $this->fsSearcher->search(new Point([$latitude, $longitude]), 1);
return $this->getLocationNameForLocationId($result[0]->getId());
return $this->getPlaceNameForPlaceId($result[0]->getId());
}
private function getLocationNameForLocationId(int $locationId): string {
private function getPlaceNameForPlaceId(int $placeId): string {
if ($this->citiesMapping === null) {
$this->downloadCities1000();
$cities1000 = $this->loadCities1000();
@ -70,7 +70,7 @@ class ReverseGeoCoderService {
}
}
return $this->citiesMapping[$locationId];
return $this->citiesMapping[$placeId];
}
private function downloadCities1000(bool $force = false): void {
@ -89,7 +89,7 @@ class ReverseGeoCoderService {
$zip = new \ZipArchive;
$res = $zip->open($cities1000ZipTmpFileName);
if ($res !== true) {
throw new \Exception("Fail to unzip location file: $res", $res);
throw new \Exception("Fail to unzip place file: $res", $res);
}
$cities1000TxtSteam = $zip->getStream('cities1000.txt');

View File

@ -64,7 +64,7 @@
:title="t('photos', 'Tags')">
<Tag slot="icon" :size="20" />
</NcAppNavigationItem>
<NcAppNavigationItem :to="{name: 'locations'}" :title="t('photos', 'Places')">
<NcAppNavigationItem :to="{name: 'places'}" :title="t('photos', 'Places')">
<MapMarker slot="icon" :size="20" />
</NcAppNavigationItem>
<NcAppNavigationItem v-if="showLocationMenuEntry"

View File

@ -74,8 +74,8 @@ export default {
}
this.errorFetchingCollection = error
logger.error('[PublicLocationContent] Error fetching location', { error })
showError(this.t('photos', 'Failed to fetch location.'))
logger.error('[PublicCollectionContent] Error fetching collection', { error })
showError(this.t('photos', 'Failed to fetch collection.'))
} finally {
this.loadingCollection = false
}
@ -112,8 +112,8 @@ export default {
this.errorFetchingCollectionFiles = error
showError(this.t('photos', 'Failed to fetch locations list.'))
logger.error('[PublicLocationContent] Error fetching location files', { error })
showError(this.t('photos', 'Failed to fetch collections list.'))
logger.error('[PublicCollectionContent] Error fetching collection files', { error })
} finally {
this.loadingCollectionFiles = false
this.semaphore.release(semaphoreSymbol)

View File

@ -36,8 +36,8 @@ const AlbumContent = () => import('../views/AlbumContent')
const SharedAlbums = () => import('../views/SharedAlbums')
const SharedAlbumContent = () => import('../views/SharedAlbumContent')
const PublicAlbumContent = () => import('../views/PublicAlbumContent')
const Locations = () => import('../views/Locations')
const LocationContent = () => import('../views/LocationContent')
const Places = () => import('../views/Places')
const PlaceContent = () => import('../views/PlaceContent')
const Tags = () => import('../views/Tags')
const TagContent = () => import('../views/TagContent')
const Timeline = () => import('../views/Timeline')
@ -172,16 +172,16 @@ const router = new Router({
},
},
{
path: '/locations',
component: Locations,
name: 'locations',
path: '/places',
component: Places,
name: 'places',
},
{
path: '/locations/:locationName*',
component: LocationContent,
name: 'locations',
path: '/places/:placeName*',
component: PlaceContent,
name: 'places',
props: route => ({
locationName: route.params.locationName,
placeName: route.params.placeName,
}),
},
{

View File

@ -30,8 +30,8 @@ import { genFileInfo } from '../utils/fileUtils.js'
/**
* @typedef {object} Collection
* @property {string} basename - The name of the collection (ex: "Athens").
* @property {string} filename - The filename of the collection (ex: "/photos/admin/locations/Athens").
* @property {string} source - The full source of the collection (ex: "https://nextcloud_server1.test/remote.php/dav//photos/admin/locations/Athens").
* @property {string} filename - The filename of the collection (ex: "/photos/admin/places/Athens").
* @property {string} source - The full source of the collection (ex: "https://nextcloud_server1.test/remote.php/dav//photos/admin/places/Athens").
* @property {number} nbItems - The number of item in the collection.
* @property {number} lastPhoto - The file id for the cover of the collection.
*/
@ -40,8 +40,8 @@ import { genFileInfo } from '../utils/fileUtils.js'
* @typedef {object} CollectionFile
* @property {string} fileid - The id of the file.
* @property {string} basename - The name of the file (ex: "790-IMG_20180906_085724.jpg").
* @property {string} filename - The file name of the file (ex: "/photos/admin/locations/Athens/790-IMG_20180906_085724.jpg").
* @property {string} source - The full source of the collection (ex: "https://nextcloud_server1.test/remote.php/dav//photos/admin/locations/Athens/790-IMG_20180906_085724.jpg").
* @property {string} filename - The file name of the file (ex: "/photos/admin/places/Athens/790-IMG_20180906_085724.jpg").
* @property {string} source - The full source of the collection (ex: "https://nextcloud_server1.test/remote.php/dav//photos/admin/places/Athens/790-IMG_20180906_085724.jpg").
* @property {object} fileMetadataSizeParsed - The metadata of the file.
* @property {number} fileMetadataSizeParsed.width - The width of the file.
* @property {number} fileMetadataSizeParsed.height - The height of the file.

View File

@ -27,7 +27,7 @@ import files from './files.js'
import albums from './albums.js'
import sharedAlbums from './sharedAlbums.js'
import collections from './collections.js'
import locations from './locations.js'
import places from './places.js'
import faces from './faces.js'
import folders from './folders.js'
import systemtags from './systemtags.js'
@ -44,7 +44,7 @@ export default new Store({
systemtags,
publicAlbums: collectionStoreFactory('publicAlbum'),
collections,
locations,
places,
},
strict: process.env.NODE_ENV !== 'production',

View File

@ -22,12 +22,12 @@
import { getCurrentUser } from '@nextcloud/auth'
const locationsPrefix = `/photos/${getCurrentUser()?.uid}/locations/`
const placesPrefix = `/photos/${getCurrentUser()?.uid}/places/`
const getters = {
locations: (_, __, ___, rootGetters) => rootGetters.collectionsWithPrefix(locationsPrefix),
getLocation: (_, __, rootState) => locationName => rootState.collections.collections[`${locationsPrefix}${locationName}`] || null,
getLocationFiles: (_, __, rootState) => locationName => rootState.collections.collectionsFiles[`${locationsPrefix}${locationName}`] || [],
places: (_, __, ___, rootGetters) => rootGetters.collectionsWithPrefix(placesPrefix),
getPlace: (_, __, rootState) => placeName => rootState.collections.collections[`${placesPrefix}${placeName}`] || null,
getPlaceFiles: (_, __, rootState) => placeName => rootState.collections.collectionsFiles[`${placesPrefix}${placeName}`] || [],
}
export default { getters }

View File

@ -22,30 +22,30 @@
<template>
<div>
<CollectionContent ref="collectionContent"
:collection="location"
:collection-file-ids="locationFileIds"
:collection="place"
:collection-file-ids="placeFileIds"
:semaphore="semaphore"
:loading="loadingCollection || loadingCollectionFiles"
:error="errorFetchingCollection || errorFetchingCollectionFiles">
<!-- Header -->
<HeaderNavigation v-if="location !== null"
<HeaderNavigation v-if="place !== null"
key="navigation"
slot="header"
:loading="loadingCollection || loadingCollectionFiles"
:params="{ locationName }"
:path="'/' + locationName"
:title="location.basename"
@refresh="fetchLocationFiles" />
:params="{ placeName }"
:path="'/' + placeName"
:title="place.basename"
@refresh="fetchPlaceFiles" />
<!-- No content -->
<NcEmptyContent slot="empty-content"
:title="t('photos', 'This location does not have any photos or videos yet!')"
class="location__empty">
:title="t('photos', 'This place does not have any photos or videos yet!')"
class="place__empty">
<ImagePlus slot="icon" />
<NcButton slot="action"
type="primary"
:aria-label="t('photos', 'Add photos to this location')"
:aria-label="t('photos', 'Add photos to this place')"
@click="showAddPhotosModal = true">
<Plus slot="icon" />
{{ t('photos', "Add") }}
@ -68,7 +68,7 @@ import HeaderNavigation from '../components/HeaderNavigation.vue'
import { getCurrentUser } from '@nextcloud/auth'
export default {
name: 'LocationContent',
name: 'PlaceContent',
components: {
Plus,
ImagePlus,
@ -84,7 +84,7 @@ export default {
],
props: {
locationName: {
placeName: {
type: String,
default: '/',
},
@ -96,45 +96,45 @@ export default {
loadingCollection: false,
errorFetchingCollection: null,
loadingCount: 0,
loadingAddFilesToLocation: false,
loadingAddFilesToPlace: false,
}
},
computed: {
/**
* @return {import('../services/collectionFetcher.js').Collection} The location information for the current locationName.
* @return {import('../services/collectionFetcher.js').Collection} The place information for the current placeName.
*/
location() {
return this.$store.getters.getLocation(this.locationName)
place() {
return this.$store.getters.getPlace(this.placeName)
},
/**
* @return {string} The location's filename based on its name. Useful to fetch the location information and content.
* @return {string} The place's filename based on its name. Useful to fetch the place information and content.
*/
locationFileName() {
return `/photos/${getCurrentUser()?.uid}/locations/${this.locationName}`
placeFileName() {
return `/photos/${getCurrentUser()?.uid}/places/${this.placeName}`
},
/**
* @return {string[]} The list of files for the current locationName.
* @return {string[]} The list of files for the current placeName.
*/
locationFileIds() {
return this.$store.getters.getLocationFiles(this.locationName)
placeFileIds() {
return this.$store.getters.getPlaceFiles(this.placeName)
},
},
async beforeMount() {
await this.fetchLocation()
await this.fetchLocationFiles()
await this.fetchPlace()
await this.fetchPlaceFiles()
},
methods: {
async fetchLocation() {
this.fetchCollection(this.locationFileName)
async fetchPlace() {
this.fetchCollection(this.placeFileName)
},
async fetchLocationFiles() {
this.fetchCollectionFiles(this.locationFileName)
async fetchPlaceFiles() {
this.fetchCollectionFiles(this.placeFileName)
},
t: translate,
@ -142,7 +142,7 @@ export default {
}
</script>
<style lang="scss" scoped>
.location {
.place {
display: flex;
flex-direction: column;
@ -156,7 +156,7 @@ export default {
text-overflow: ellipsis;
}
&__location {
&__place {
margin-left: -4px;
display: flex;
color: var(--color-text-lighter);

View File

@ -21,32 +21,32 @@
-->
<template>
<div>
<CollectionsList :collections="locations"
<CollectionsList :collections="places"
:loading="loadingCollections"
:error="errorFetchingCollections"
class="locations-list">
class="places-list">
<HeaderNavigation key="navigation"
slot="header"
:loading="loadingCollections"
:title="t('photos', 'Places')"
:root-title="t('photos', 'Places')"
@refresh="fetchLocations" />
@refresh="fetchPlaces" />
<CollectionCover :key="collection.basename"
slot-scope="{collection}"
:link="`/locations/${collection.basename}`"
:alt-img="t('photos', 'Cover photo for location {locationName}', { locationName: collection.basename })"
:link="`/places/${collection.basename}`"
:alt-img="t('photos', 'Cover photo for place {placeName}', { placeName: collection.basename })"
:cover-url="collection.lastPhoto | coverUrl">
<h2 class="location__name">
<h2 class="place__name">
{{ collection.basename }}
</h2>
<div slot="subtitle" class="location__details">
<div slot="subtitle" class="place__details">
{{ n('photos', '%n item', '%n photos and videos', collection.nbItems,) }}
</div>
</CollectionCover>
<NcEmptyContent slot="empty-collections-list" :title="t('photos', 'There is no location yet!')">
<NcEmptyContent slot="empty-collections-list" :title="t('photos', 'There is no place yet!')">
<FolderMultipleImage slot="icon" />
</NcEmptyContent>
</CollectionsList>
@ -68,7 +68,7 @@ import CollectionCover from '../components/Collection/CollectionCover.vue'
import HeaderNavigation from '../components/HeaderNavigation.vue'
export default {
name: 'Locations',
name: 'Places',
components: {
FolderMultipleImage,
NcEmptyContent,
@ -79,7 +79,7 @@ export default {
filters: {
/**
* @param {string} fileId The location's last photo.
* @param {string} fileId The place's last photo.
*/
coverUrl(fileId) {
if (fileId === -1) {
@ -95,20 +95,20 @@ export default {
],
computed: {
/**
* @return {{location: Object<string, import('../services/collectionFetcher').Collection>}}
* @return {{place: Object<string, import('../services/collectionFetcher').Collection>}}
*/
locations() {
return this.$store.getters.locations
places() {
return this.$store.getters.places
},
},
async beforeMount() {
this.fetchLocations()
this.fetchPlaces()
},
methods: {
fetchLocations() {
this.fetchCollections(`/photos/${getCurrentUser()?.uid}/locations`)
fetchPlaces() {
this.fetchCollections(`/photos/${getCurrentUser()?.uid}/places`)
},
t: translate,
@ -117,11 +117,11 @@ export default {
}
</script>
<style lang="scss" scoped>
.locations-list {
.places-list {
display: flex;
flex-direction: column;
.location__name {
.place__name {
font-weight: normal;
overflow: hidden;
white-space: nowrap;