Skip to content

Commit

Permalink
Add commands and listeners to generate location data of files:
Browse files Browse the repository at this point in the history
The location data is stored inside `oc_files_metadata`.

## Added commands

- `occ photos:update-1000-cities` to update the cities1000 file.
- `occ photos:map-media-to-location`to map picture coordinates to a location

## Architecture

- `ReverseGeoCoderService` download the necessary files and build the `KDTree`
- `UpdateReverseGeocodingFilesCommand` command to allow to manually create the needed reverse geocoding files
- `MediaLocationManager` to manager the location mappings
- `MapMediaToLocationCommand` command to manually trigger location data mapping. Useful for pre-existing pictures.
- `LocationManagerNodeEventListener` to react to node, user and share events.
- `MapMediaToLocationJob` to reduce the load in event listeners

```php
                                          ┌─────────────────────┐
                            ┌────────────►│MapMediaToLocationJob│
                            │             └─────────┬───────────┘
                            │                       │
   ┌────────────────────────┴───────┐               │
   │LocationManagerNodeEventListener├──┐            ▼
   └────────────────────────────────┘  │  ┌────────────────────┐     ┌──────────────┐
                                       ├─►│MediaLocationManager├────►│LocationMapper│
   ┌─────────────────────────┐         │  └─────────┬──────────┘     └──────────────┘
   │MapMediaToLocationCommand├─────────┘            │
   └─────────────────────────┘                      │
                                                    ▼
   ┌──────────────────────────────────┐   ┌──────────────────────┐
   │UpdateReverseGeocodingFilesCommand├──►│ReverseGeoCoderService│
   └──────────────────────────────────┘   └──────────────────────┘
```

Signed-off-by: Louis Chemineau <louis@chmn.me>
  • Loading branch information
artonge committed Feb 22, 2023
1 parent efa2bf5 commit 5db737a
Show file tree
Hide file tree
Showing 20 changed files with 1,100 additions and 78 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
- name: Checkout app
uses: actions/checkout@v3

- name: Install server dependencies
run: composer install

- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@v1.1
id: versions
Expand All @@ -36,7 +39,7 @@ jobs:
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"

- name: Install dependencies & build app
- name: Install node dependencies & build app
run: |
npm ci
TESTING=true npm run build --if-present
Expand Down
2 changes: 1 addition & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

require_once './vendor/autoload.php';
require_once __DIR__ . '/vendor/autoload.php';

use Nextcloud\CodingStandard\Config;

Expand Down
21 changes: 15 additions & 6 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
<?xml version="1.0"?>
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>photos</id>
<name>Photos</name>
<summary>Your memories under your control</summary>
<description>Your memories under your control</description>
<version>2.2.0</version>
<licence>agpl</licence>
<author mail="skjnldsv@protonmail.com">John Molakvoæ</author>
<author mail="skjnldsv@protonmail.com">John Molakvoæ</author>
<namespace>Photos</namespace>
<category>multimedia</category>
<types>
<dav />
<authentication />
</types>

<website>https://github.com/nextcloud/photos</website>
<bugs>https://github.com/nextcloud/photos/issues</bugs>
<website>https://github.com/nextcloud/photos</website>
<bugs>https://github.com/nextcloud/photos/issues</bugs>
<repository>https://github.com/nextcloud/photos.git</repository>
<default_enable />
<dependencies>
Expand All @@ -30,6 +30,11 @@
</navigation>
</navigations>

<commands>
<command>OCA\Photos\Command\UpdateReverseGeocodingFilesCommand</command>
<command>OCA\Photos\Command\MapMediaToLocationCommand</command>
</commands>

<sabre>
<collections>
<collection>OCA\Photos\Sabre\RootCollection</collection>
Expand All @@ -39,4 +44,8 @@
<plugin>OCA\Photos\Sabre\Album\PropFindPlugin</plugin>
</plugins>
</sabre>
</info>

<background-jobs>
<job>OCA\Photos\Jobs\AutomaticLocationMapperJob</job>
</background-jobs>
</info>
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@
"vimeo/psalm": "^4.22",
"sabre/dav": "^4.2.1",
"nextcloud/ocp": "dev-master"
},
"require": {
"hexogen/kdtree": "^0.2.5"
}
}
66 changes: 64 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 11 additions & 52 deletions lib/Album/AlbumFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,11 @@

namespace OCA\Photos\Album;

use OC\Metadata\FileMetadata;
use OCA\Photos\DB\PhotosFile;

class AlbumFile {
private int $fileId;
private string $name;
private string $mimeType;
private int $size;
private int $mtime;
private string $etag;
class AlbumFile extends PhotosFile {
private int $added;
private string $owner;
/** @var array<string, FileMetadata> */
private array $metaData = [];

public function __construct(
int $fileId,
Expand All @@ -47,52 +39,19 @@ public function __construct(
int $added,
string $owner
) {
$this->fileId = $fileId;
$this->name = $name;
$this->mimeType = $mimeType;
$this->size = $size;
$this->mtime = $mtime;
$this->etag = $etag;
parent::__construct(
$fileId,
$name,
$mimeType,
$size,
$mtime,
$etag
);

$this->added = $added;
$this->owner = $owner;
}

public function getFileId(): int {
return $this->fileId;
}

public function getName(): string {
return $this->name;
}

public function getMimeType(): string {
return $this->mimeType;
}

public function getSize(): int {
return $this->size;
}

public function getMTime(): int {
return $this->mtime;
}

public function getEtag(): string {
return $this->etag;
}

public function setMetadata(string $key, FileMetadata $value): void {
$this->metaData[$key] = $value;
}

public function hasMetadata(string $key): bool {
return isset($this->metaData[$key]);
}

public function getMetadata(string $key): FileMetadata {
return $this->metaData[$key];
}

public function getAdded(): int {
return $this->added;
}
Expand Down
8 changes: 8 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
use OCA\Photos\Listener\TagListener;
use OCA\Photos\Listener\GroupUserRemovedListener;
use OCA\Photos\Listener\GroupDeletedListener;
use OCA\Photos\Listener\LocationManagerEventListener;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
Expand All @@ -40,6 +41,10 @@
use OCP\SystemTag\MapperEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Files\Events\Node\NodeWrittenEvent;
use OCP\User\Events\UserDeletedEvent;

require_once __DIR__ . '/../../vendor/autoload.php';

class Application extends App implements IBootstrap {
public const APP_ID = 'photos';
Expand Down Expand Up @@ -78,6 +83,9 @@ public function register(IRegistrationContext $context): void {

$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(SabrePluginAuthInitEvent::class, SabrePluginAuthInitListener::class);

$context->registerEventListener(MapperEvent::EVENT_ASSIGN, TagListener::class);
Expand Down
116 changes: 116 additions & 0 deletions lib/Command/MapMediaToLocationCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);
/**
* @copyright Copyright (c) 2022 Louis Chemineau <louis@chmn.me>
*
* @author Louis Chemineau <louis@chmn.me>
*
* @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\Photos\Command;

use OCP\IConfig;
use OCP\IUserManager;
use OCP\Files\IRootFolder;
use OCP\Files\Folder;
use OCA\Photos\Service\MediaLocationManager;
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 {
public function __construct(
private IRootFolder $rootFolder,
private MediaLocationManager $mediaLocationManager,
private IConfig $config,
private IUserManager $userManager,
) {
parent::__construct();
}

/**
* Configure the command
*/
protected function configure(): void {
$this->setName('photos:map-media-to-location')
->setDescription('Reverse geocode media coordinates.')
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'Limit the mapping to a user.', null);
}

/**
* Execute the command
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
if (!$this->config->getSystemValueBool('enable_file_metadata', true)) {
throw new \Exception('File metadata is not enabled.');
}

$userId = $input->getOption('user');
if ($userId === null) {
$this->scanForAllUsers($output);
} else {
$this->scanFilesForUser($userId, $output);
}

return 0;
}

private function scanForAllUsers(OutputInterface $output): void {
$users = $this->userManager->search('');

$output->writeln("Scanning all users:");
foreach ($users as $user) {
$this->scanFilesForUser($user->getUID(), $output);
}
}

private function scanFilesForUser(string $userId, OutputInterface $output): void {
$userFolder = $this->rootFolder->getUserFolder($userId);
$output->write(" - Scanning files for $userId");
$startTime = time();
$count = $this->scanFolder($userFolder);
$timeElapse = time() - $startTime;
$output->writeln(" - $count files, $timeElapse sec");
}

private function scanFolder(Folder $folder): int {
$count = 0;

// Do not scan share and other moveable mounts.
if ($folder->getMountPoint() instanceof \OC\Files\Mount\MoveableMount) {
return $count;
}

foreach ($folder->getDirectoryListing() as $node) {
if ($node instanceof Folder) {
$count += $this->scanFolder($node);
continue;
}

if (!str_starts_with($node->getMimeType(), 'image')) {
continue;
}

$this->mediaLocationManager->setLocationForFile($node->getId());
$count++;
}

return $count;
}
}
Loading

0 comments on commit 5db737a

Please sign in to comment.