place-setup: recalculate places
Signed-off-by: Varun Patil <varunpatil@ucla.edu>pull/579/head
parent
7a733d3d11
commit
526559b672
|
@ -88,10 +88,16 @@ class PlacesSetup extends Command
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download the planet database
|
// Download the planet database
|
||||||
$this->output->writeln('Downloading planet database');
|
|
||||||
$datafile = $this->places->downloadPlanet();
|
$datafile = $this->places->downloadPlanet();
|
||||||
|
|
||||||
// Import the planet database
|
// Import the planet database
|
||||||
$this->places->importPlanet($datafile);
|
$this->places->importPlanet($datafile);
|
||||||
|
|
||||||
|
// Recalculate all places
|
||||||
|
$this->places->recalculateAll();
|
||||||
|
|
||||||
|
$this->output->writeln('Done');
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -205,11 +205,9 @@ class OtherController extends GenericApiController
|
||||||
header('Content-Length: 0');
|
header('Content-Length: 0');
|
||||||
|
|
||||||
$places = \OC::$server->get(\OCA\Memories\Service\Places::class);
|
$places = \OC::$server->get(\OCA\Memories\Service\Places::class);
|
||||||
|
|
||||||
echo "Downloading planet file...\n";
|
|
||||||
flush();
|
|
||||||
$datafile = $places->downloadPlanet();
|
$datafile = $places->downloadPlanet();
|
||||||
$places->importPlanet($datafile);
|
$places->importPlanet($datafile);
|
||||||
|
$places->recalculateAll();
|
||||||
|
|
||||||
echo "Done.\n";
|
echo "Done.\n";
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
|
@ -12,21 +12,78 @@ trait TimelineWriteOrphans
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark all files in the table as orphaned.
|
* Mark all or some files in the table as (un)orphaned.
|
||||||
|
*
|
||||||
|
* @param bool $value True to mark as orphaned, false to mark as un-orphaned
|
||||||
|
* @param int[] $fileIds List of file IDs to mark, or empty to mark all files
|
||||||
|
* @param bool $onlyMain Only mark the main file, not the live photo
|
||||||
*
|
*
|
||||||
* @return int Number of rows affected
|
* @return int Number of rows affected
|
||||||
*/
|
*/
|
||||||
public function orphanAll(): int
|
public function orphanAll(bool $value = true, ?array $fileIds = null, bool $onlyMain = false): int
|
||||||
{
|
{
|
||||||
$do = function (string $table) {
|
$do = function (string $table) use ($value, $fileIds) {
|
||||||
$query = $this->connection->getQueryBuilder();
|
$query = $this->connection->getQueryBuilder();
|
||||||
$query->update($table)
|
$query->update($table)
|
||||||
->set('orphan', $query->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))
|
->set('orphan', $query->createNamedParameter($value, IQueryBuilder::PARAM_BOOL))
|
||||||
;
|
;
|
||||||
|
|
||||||
|
if ($fileIds) {
|
||||||
|
$query->where($query->expr()->in('fileid', $query->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY)));
|
||||||
|
}
|
||||||
|
|
||||||
return $query->executeStatement();
|
return $query->executeStatement();
|
||||||
};
|
};
|
||||||
|
|
||||||
return $do('memories') + $do('memories_livephoto');
|
$count = $do('memories');
|
||||||
|
|
||||||
|
if ($onlyMain) {
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $count + $do('memories_livephoto');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orphan and run an update on all files.
|
||||||
|
*
|
||||||
|
* @param array $fields list of fields to select
|
||||||
|
* @param int $txnSize number of rows to process in a single transaction
|
||||||
|
* @param \Closure $callback will be passed each row
|
||||||
|
*/
|
||||||
|
public function orphanAndRun(array $fields, int $txnSize, \Closure $callback)
|
||||||
|
{
|
||||||
|
// Orphan all files. This means if we are interrupted,
|
||||||
|
// it will lead to a re-index of the whole library!
|
||||||
|
$this->orphanAll(true, null, true);
|
||||||
|
|
||||||
|
while (\count($orphans = $this->getSomeOrphans($txnSize, $fields))) {
|
||||||
|
$this->connection->beginTransaction();
|
||||||
|
|
||||||
|
foreach ($orphans as $row) {
|
||||||
|
$callback($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all files as not orphaned.
|
||||||
|
$fileIds = array_map(fn ($row) => $row['fileid'], $orphans);
|
||||||
|
$this->orphanAll(false, $fileIds, true);
|
||||||
|
|
||||||
|
$this->connection->commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of orphaned files.
|
||||||
|
*/
|
||||||
|
protected function getSomeOrphans(int $count, array $fields): array
|
||||||
|
{
|
||||||
|
$query = $this->connection->getQueryBuilder();
|
||||||
|
$query->select(...$fields)
|
||||||
|
->from('memories')
|
||||||
|
->where($query->expr()->eq('orphan', $query->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)))
|
||||||
|
->setMaxResults($count)
|
||||||
|
;
|
||||||
|
|
||||||
|
return $query->executeQuery()->fetchAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,53 +15,6 @@ trait TimelineWritePlaces
|
||||||
{
|
{
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
|
||||||
/**
|
|
||||||
* Process the location part of exif data.
|
|
||||||
*
|
|
||||||
* Also update the exif data with the tzid from location (LocationTZID)
|
|
||||||
* Performs an in-place update of the exif data.
|
|
||||||
*
|
|
||||||
* @param int $fileId The file ID
|
|
||||||
* @param array $exif The exif data (will change)
|
|
||||||
* @param array|bool $prevRow The previous row of data
|
|
||||||
*
|
|
||||||
* @return array Update values
|
|
||||||
*/
|
|
||||||
protected function processExifLocation(int $fileId, array &$exif, $prevRow): array
|
|
||||||
{
|
|
||||||
// Store location data
|
|
||||||
[$lat, $lon] = self::readCoord($exif);
|
|
||||||
$oldLat = $prevRow ? (float) $prevRow['lat'] : null;
|
|
||||||
$oldLon = $prevRow ? (float) $prevRow['lon'] : null;
|
|
||||||
$mapCluster = $prevRow ? (int) $prevRow['mapcluster'] : -1;
|
|
||||||
$osmIds = [];
|
|
||||||
|
|
||||||
if ($lat || $lon || $oldLat || $oldLon) {
|
|
||||||
try {
|
|
||||||
$mapCluster = $this->mapGetCluster($mapCluster, $lat, $lon, $oldLat, $oldLon);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$logger = \OC::$server->get(LoggerInterface::class);
|
|
||||||
$logger->log(3, 'Error updating map cluster data: '.$e->getMessage(), ['app' => 'memories']);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$osmIds = $this->updatePlacesData($fileId, $lat, $lon);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$logger = \OC::$server->get(LoggerInterface::class);
|
|
||||||
$logger->log(3, 'Error updating places data: '.$e->getMessage(), ['app' => 'memories']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NULL if invalid
|
|
||||||
$mapCluster = $mapCluster <= 0 ? null : $mapCluster;
|
|
||||||
|
|
||||||
// Set tzid from location if not present
|
|
||||||
$this->setTzidFromLocation($exif, $osmIds);
|
|
||||||
|
|
||||||
// Return update values
|
|
||||||
return [$lat, $lon, $mapCluster, $osmIds];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add places data for a file.
|
* Add places data for a file.
|
||||||
*
|
*
|
||||||
|
@ -71,7 +24,7 @@ trait TimelineWritePlaces
|
||||||
*
|
*
|
||||||
* @return array The list of osm_id of the places
|
* @return array The list of osm_id of the places
|
||||||
*/
|
*/
|
||||||
protected function updatePlacesData(int $fileId, $lat, $lon): array
|
public function updatePlacesData(int $fileId, $lat, $lon): array
|
||||||
{
|
{
|
||||||
// Get GIS type
|
// Get GIS type
|
||||||
$gisType = \OCA\Memories\Util::placesGISType();
|
$gisType = \OCA\Memories\Util::placesGISType();
|
||||||
|
@ -140,6 +93,53 @@ trait TimelineWritePlaces
|
||||||
return array_map(fn ($row) => $row['osm_id'], $rows);
|
return array_map(fn ($row) => $row['osm_id'], $rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the location part of exif data.
|
||||||
|
*
|
||||||
|
* Also update the exif data with the tzid from location (LocationTZID)
|
||||||
|
* Performs an in-place update of the exif data.
|
||||||
|
*
|
||||||
|
* @param int $fileId The file ID
|
||||||
|
* @param array $exif The exif data (will change)
|
||||||
|
* @param array|bool $prevRow The previous row of data
|
||||||
|
*
|
||||||
|
* @return array Update values
|
||||||
|
*/
|
||||||
|
protected function processExifLocation(int $fileId, array &$exif, $prevRow): array
|
||||||
|
{
|
||||||
|
// Store location data
|
||||||
|
[$lat, $lon] = self::readCoord($exif);
|
||||||
|
$oldLat = $prevRow ? (float) $prevRow['lat'] : null;
|
||||||
|
$oldLon = $prevRow ? (float) $prevRow['lon'] : null;
|
||||||
|
$mapCluster = $prevRow ? (int) $prevRow['mapcluster'] : -1;
|
||||||
|
$osmIds = [];
|
||||||
|
|
||||||
|
if ($lat || $lon || $oldLat || $oldLon) {
|
||||||
|
try {
|
||||||
|
$mapCluster = $this->mapGetCluster($mapCluster, $lat, $lon, $oldLat, $oldLon);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$logger = \OC::$server->get(LoggerInterface::class);
|
||||||
|
$logger->log(3, 'Error updating map cluster data: '.$e->getMessage(), ['app' => 'memories']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$osmIds = $this->updatePlacesData($fileId, $lat, $lon);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$logger = \OC::$server->get(LoggerInterface::class);
|
||||||
|
$logger->log(3, 'Error updating places data: '.$e->getMessage(), ['app' => 'memories']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NULL if invalid
|
||||||
|
$mapCluster = $mapCluster <= 0 ? null : $mapCluster;
|
||||||
|
|
||||||
|
// Set tzid from location if not present
|
||||||
|
$this->setTzidFromLocation($exif, $osmIds);
|
||||||
|
|
||||||
|
// Return update values
|
||||||
|
return [$lat, $lon, $mapCluster, $osmIds];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set timezone offset from location if not present.
|
* Set timezone offset from location if not present.
|
||||||
*
|
*
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace OCA\Memories\Service;
|
namespace OCA\Memories\Service;
|
||||||
|
|
||||||
|
use OCA\Memories\Db\TimelineWrite;
|
||||||
use OCP\IConfig;
|
use OCP\IConfig;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
@ -18,13 +19,16 @@ class Places
|
||||||
|
|
||||||
protected IConfig $config;
|
protected IConfig $config;
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
protected TimelineWrite $timelineWrite;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
IDBConnection $connection
|
IDBConnection $connection,
|
||||||
|
TimelineWrite $timelineWrite
|
||||||
) {
|
) {
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
|
$this->timelineWrite = $timelineWrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,6 +93,9 @@ class Places
|
||||||
*/
|
*/
|
||||||
public function downloadPlanet(): string
|
public function downloadPlanet(): string
|
||||||
{
|
{
|
||||||
|
echo "Download planet data to temporary file...\n";
|
||||||
|
flush();
|
||||||
|
|
||||||
$filename = sys_get_temp_dir().'/planet_coarse_boundaries.zip';
|
$filename = sys_get_temp_dir().'/planet_coarse_boundaries.zip';
|
||||||
unlink($filename);
|
unlink($filename);
|
||||||
|
|
||||||
|
@ -134,6 +141,7 @@ class Places
|
||||||
public function importPlanet(string $datafile): void
|
public function importPlanet(string $datafile): void
|
||||||
{
|
{
|
||||||
echo "Inserting planet data into database...\n";
|
echo "Inserting planet data into database...\n";
|
||||||
|
flush();
|
||||||
|
|
||||||
// Detect the GIS type
|
// Detect the GIS type
|
||||||
$gis = $this->detectGisType();
|
$gis = $this->detectGisType();
|
||||||
|
@ -317,16 +325,47 @@ class Places
|
||||||
// Mark success
|
// Mark success
|
||||||
echo "Planet database imported successfully!\n";
|
echo "Planet database imported successfully!\n";
|
||||||
echo "You should re-index your library now.\n";
|
echo "You should re-index your library now.\n";
|
||||||
|
flush();
|
||||||
$this->config->setSystemValue('memories.gis_type', $gis);
|
$this->config->setSystemValue('memories.gis_type', $gis);
|
||||||
|
|
||||||
// Delete data file
|
// Delete data file
|
||||||
unlink($datafile);
|
unlink($datafile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recalculate all places for all users.
|
||||||
|
*/
|
||||||
|
public function recalculateAll()
|
||||||
|
{
|
||||||
|
echo "Recalculating places for all files (do not interrupt this process)...\n";
|
||||||
|
flush();
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
$this->timelineWrite->orphanAndRun(['fileid', 'lat', 'lon'], 20, function (array $row) use (&$count) {
|
||||||
|
++$count;
|
||||||
|
|
||||||
|
// Only proceed if we have a valid location
|
||||||
|
$fileid = $row['fileid'];
|
||||||
|
$lat = (float) $row['lat'];
|
||||||
|
$lon = (float) $row['lon'];
|
||||||
|
|
||||||
|
// Update places
|
||||||
|
if ($lat || $lon) {
|
||||||
|
$this->timelineWrite->updatePlacesData($fileid, $lat, $lon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print every 500 files
|
||||||
|
if (0 === $count % 500) {
|
||||||
|
echo "Updated places data for {$count} files\n";
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create database tables and indices.
|
* Create database tables and indices.
|
||||||
*/
|
*/
|
||||||
private function setupDatabase(): void
|
protected function setupDatabase(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Get Gis type
|
// Get Gis type
|
||||||
|
|
|
@ -697,16 +697,24 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
placesSetup(event: Event) {
|
placesSetup(event: Event) {
|
||||||
|
const warnSetup = this.t(
|
||||||
|
"memories",
|
||||||
|
"Looks like the database is already setup. Are you sure you want to redownload planet data?"
|
||||||
|
);
|
||||||
|
const warnLong = this.t(
|
||||||
|
"memories",
|
||||||
|
"You are about to download the planet database. This may take a while."
|
||||||
|
);
|
||||||
|
const warnReindex = this.t(
|
||||||
|
"memories",
|
||||||
|
"This may also cause all photos to be re-indexed!"
|
||||||
|
);
|
||||||
const msg =
|
const msg =
|
||||||
"Looks like the database is already setup. Are you sure you want to drop the table and redownload OSM data?";
|
(this.status.gis_count ? warnSetup : warnLong) + " " + warnReindex;
|
||||||
if (this.status.gis_count && !confirm(msg)) {
|
if (!confirm(msg)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
alert(
|
|
||||||
"Please wait for the download and insertion to complete. This may take a while."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue