Tab -> Space
parent
f0c0e03f2c
commit
af38c24198
|
@ -35,8 +35,8 @@
|
||||||
<nextcloud min-version="22" max-version="24"/>
|
<nextcloud min-version="22" max-version="24"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<commands>
|
<commands>
|
||||||
<command>OCA\Memories\Command\Index</command>
|
<command>OCA\Memories\Command\Index</command>
|
||||||
</commands>
|
</commands>
|
||||||
<navigations>
|
<navigations>
|
||||||
<navigation>
|
<navigation>
|
||||||
<name>Memories</name>
|
<name>Memories</name>
|
||||||
|
|
|
@ -4,13 +4,13 @@ return [
|
||||||
// Days and folder API
|
// Days and folder API
|
||||||
['name' => 'page#main', 'url' => '/', 'verb' => 'GET'],
|
['name' => 'page#main', 'url' => '/', 'verb' => 'GET'],
|
||||||
['name' => 'page#folder', 'url' => '/folders/{path}', 'verb' => 'GET',
|
['name' => 'page#folder', 'url' => '/folders/{path}', 'verb' => 'GET',
|
||||||
'requirements' => [
|
'requirements' => [
|
||||||
'path' => '.*',
|
'path' => '.*',
|
||||||
],
|
],
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'path' => '',
|
'path' => '',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
// API
|
// API
|
||||||
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
|
['name' => 'api#days', 'url' => '/api/days', 'verb' => 'GET'],
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.icon-folder.icon-dark {
|
.icon-folder.icon-dark {
|
||||||
@include icon-color('folder', 'filetypes', $color-black, 1, true);
|
@include icon-color('folder', 'filetypes', $color-black, 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include icon-black-white('yourmemories', 'memories', 1);
|
@include icon-black-white('yourmemories', 'memories', 1);
|
|
@ -37,40 +37,40 @@ use OCP\Files\Events\Node\NodeDeletedEvent;
|
||||||
use OCP\Files\Events\Node\NodeTouchedEvent;
|
use OCP\Files\Events\Node\NodeTouchedEvent;
|
||||||
|
|
||||||
class Application extends App implements IBootstrap {
|
class Application extends App implements IBootstrap {
|
||||||
public const APPNAME = 'memories';
|
public const APPNAME = 'memories';
|
||||||
|
|
||||||
public const IMAGE_MIMES = [
|
public const IMAGE_MIMES = [
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/heic',
|
'image/heic',
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/tiff',
|
'image/tiff',
|
||||||
// 'image/gif', // too rarely used for photos
|
// 'image/gif', // too rarely used for photos
|
||||||
// 'image/x-xbitmap', // too rarely used for photos
|
// 'image/x-xbitmap', // too rarely used for photos
|
||||||
// 'image/bmp', // too rarely used for photos
|
// 'image/bmp', // too rarely used for photos
|
||||||
// 'image/svg+xml', // too rarely used for photos
|
// 'image/svg+xml', // too rarely used for photos
|
||||||
];
|
];
|
||||||
|
|
||||||
public const VIDEO_MIMES = [
|
public const VIDEO_MIMES = [
|
||||||
'video/mpeg',
|
'video/mpeg',
|
||||||
// 'video/ogg', // too rarely used for photos
|
// 'video/ogg', // too rarely used for photos
|
||||||
// 'video/webm', // too rarely used for photos
|
// 'video/webm', // too rarely used for photos
|
||||||
'video/mp4',
|
'video/mp4',
|
||||||
// 'video/x-m4v', // too rarely used for photos
|
// 'video/x-m4v', // too rarely used for photos
|
||||||
'video/quicktime',
|
'video/quicktime',
|
||||||
'video/x-matroska',
|
'video/x-matroska',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
parent::__construct(self::APPNAME);
|
parent::__construct(self::APPNAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function register(IRegistrationContext $context): void {
|
public function register(IRegistrationContext $context): void {
|
||||||
$context->registerEventListener(NodeWrittenEvent::class, PostWriteListener::class);
|
$context->registerEventListener(NodeWrittenEvent::class, PostWriteListener::class);
|
||||||
$context->registerEventListener(NodeTouchedEvent::class, PostWriteListener::class);
|
$context->registerEventListener(NodeTouchedEvent::class, PostWriteListener::class);
|
||||||
$context->registerEventListener(NodeDeletedEvent::class, PostDeleteListener::class);
|
$context->registerEventListener(NodeDeletedEvent::class, PostDeleteListener::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function boot(IBootContext $context): void {
|
public function boot(IBootContext $context): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -46,149 +46,149 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
class Index extends Command {
|
class Index extends Command {
|
||||||
|
|
||||||
/** @var ?GlobalStoragesService */
|
/** @var ?GlobalStoragesService */
|
||||||
protected $globalService;
|
protected $globalService;
|
||||||
|
|
||||||
/** @var int[][] */
|
/** @var int[][] */
|
||||||
protected array $sizes;
|
protected array $sizes;
|
||||||
|
|
||||||
protected IUserManager $userManager;
|
protected IUserManager $userManager;
|
||||||
protected IRootFolder $rootFolder;
|
protected IRootFolder $rootFolder;
|
||||||
protected IPreview $previewGenerator;
|
protected IPreview $previewGenerator;
|
||||||
protected IConfig $config;
|
protected IConfig $config;
|
||||||
protected OutputInterface $output;
|
protected OutputInterface $output;
|
||||||
protected IManager $encryptionManager;
|
protected IManager $encryptionManager;
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
protected TimelineWrite $timelineWrite;
|
protected TimelineWrite $timelineWrite;
|
||||||
|
|
||||||
// Stats
|
// Stats
|
||||||
private int $nProcessed = 0;
|
private int $nProcessed = 0;
|
||||||
private int $nSkipped = 0;
|
private int $nSkipped = 0;
|
||||||
private int $nInvalid = 0;
|
private int $nInvalid = 0;
|
||||||
|
|
||||||
public function __construct(IRootFolder $rootFolder,
|
public function __construct(IRootFolder $rootFolder,
|
||||||
IUserManager $userManager,
|
IUserManager $userManager,
|
||||||
IPreview $previewGenerator,
|
IPreview $previewGenerator,
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
IManager $encryptionManager,
|
IManager $encryptionManager,
|
||||||
IDBConnection $connection,
|
IDBConnection $connection,
|
||||||
ContainerInterface $container) {
|
ContainerInterface $container) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->userManager = $userManager;
|
$this->userManager = $userManager;
|
||||||
$this->rootFolder = $rootFolder;
|
$this->rootFolder = $rootFolder;
|
||||||
$this->previewGenerator = $previewGenerator;
|
$this->previewGenerator = $previewGenerator;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->encryptionManager = $encryptionManager;
|
$this->encryptionManager = $encryptionManager;
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->timelineWrite = new TimelineWrite($this->connection);
|
$this->timelineWrite = new TimelineWrite($this->connection);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->globalService = $container->get(GlobalStoragesService::class);
|
$this->globalService = $container->get(GlobalStoragesService::class);
|
||||||
} catch (ContainerExceptionInterface $e) {
|
} catch (ContainerExceptionInterface $e) {
|
||||||
$this->globalService = null;
|
$this->globalService = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Make sure exiftool is available */
|
/** Make sure exiftool is available */
|
||||||
private function testExif() {
|
private function testExif() {
|
||||||
$testfile = dirname(__FILE__). '/../../exiftest.jpg';
|
$testfile = dirname(__FILE__). '/../../exiftest.jpg';
|
||||||
$stream = fopen($testfile, 'rb');
|
$stream = fopen($testfile, 'rb');
|
||||||
if (!$stream) {
|
if (!$stream) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$exif = \OCA\Memories\Exif::getExifFromStream($stream);
|
$exif = \OCA\Memories\Exif::getExifFromStream($stream);
|
||||||
fclose($stream);
|
fclose($stream);
|
||||||
|
|
||||||
if (!$exif || $exif["DateTimeOriginal"] !== "2004:08:31 19:52:58") {
|
if (!$exif || $exif["DateTimeOriginal"] !== "2004:08:31 19:52:58") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configure(): void {
|
protected function configure(): void {
|
||||||
$this
|
$this
|
||||||
->setName('memories:index')
|
->setName('memories:index')
|
||||||
->setDescription('Generate entries');
|
->setDescription('Generate entries');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
// Refuse to run without exiftool
|
// Refuse to run without exiftool
|
||||||
\OCA\Memories\Exif::ensureStaticExiftoolProc();
|
\OCA\Memories\Exif::ensureStaticExiftoolProc();
|
||||||
if (!$this->testExif()) {
|
if (!$this->testExif()) {
|
||||||
error_log('FATAL: exiftool could not be found or test failed');
|
error_log('FATAL: exiftool could not be found or test failed');
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time measurement
|
// Time measurement
|
||||||
$startTime = microtime(true);
|
$startTime = microtime(true);
|
||||||
|
|
||||||
if ($this->encryptionManager->isEnabled()) {
|
if ($this->encryptionManager->isEnabled()) {
|
||||||
$output->writeln('Encryption is enabled. Aborted.');
|
$output->writeln('Encryption is enabled. Aborted.');
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
$this->output = $output;
|
$this->output = $output;
|
||||||
|
|
||||||
$this->userManager->callForSeenUsers(function (IUser $user) {
|
$this->userManager->callForSeenUsers(function (IUser $user) {
|
||||||
$this->generateUserEntries($user);
|
$this->generateUserEntries($user);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close the exiftool process
|
// Close the exiftool process
|
||||||
\OCA\Memories\Exif::closeStaticExiftoolProc();
|
\OCA\Memories\Exif::closeStaticExiftoolProc();
|
||||||
|
|
||||||
// Show some stats
|
// Show some stats
|
||||||
$endTime = microtime(true);
|
$endTime = microtime(true);
|
||||||
$execTime = intval(($endTime - $startTime)*1000)/1000 ;
|
$execTime = intval(($endTime - $startTime)*1000)/1000 ;
|
||||||
$nTotal = $this->nInvalid + $this->nSkipped + $this->nProcessed;
|
$nTotal = $this->nInvalid + $this->nSkipped + $this->nProcessed;
|
||||||
$this->output->writeln("==========================================");
|
$this->output->writeln("==========================================");
|
||||||
$this->output->writeln("Checked $nTotal files in $execTime sec");
|
$this->output->writeln("Checked $nTotal files in $execTime sec");
|
||||||
$this->output->writeln($this->nInvalid . " not valid media items");
|
$this->output->writeln($this->nInvalid . " not valid media items");
|
||||||
$this->output->writeln($this->nSkipped . " skipped because unmodified");
|
$this->output->writeln($this->nSkipped . " skipped because unmodified");
|
||||||
$this->output->writeln($this->nProcessed . " (re-)processed");
|
$this->output->writeln($this->nProcessed . " (re-)processed");
|
||||||
$this->output->writeln("==========================================");
|
$this->output->writeln("==========================================");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateUserEntries(IUser &$user): void {
|
private function generateUserEntries(IUser &$user): void {
|
||||||
\OC_Util::tearDownFS();
|
\OC_Util::tearDownFS();
|
||||||
\OC_Util::setupFS($user->getUID());
|
\OC_Util::setupFS($user->getUID());
|
||||||
|
|
||||||
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
||||||
$this->parseFolder($userFolder);
|
$this->parseFolder($userFolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseFolder(Folder &$folder): void {
|
private function parseFolder(Folder &$folder): void {
|
||||||
try {
|
try {
|
||||||
$folderPath = $folder->getPath();
|
$folderPath = $folder->getPath();
|
||||||
$this->output->writeln('Scanning folder ' . $folderPath);
|
$this->output->writeln('Scanning folder ' . $folderPath);
|
||||||
|
|
||||||
$nodes = $folder->getDirectoryListing();
|
$nodes = $folder->getDirectoryListing();
|
||||||
|
|
||||||
foreach ($nodes as &$node) {
|
foreach ($nodes as &$node) {
|
||||||
if ($node instanceof Folder) {
|
if ($node instanceof Folder) {
|
||||||
$this->parseFolder($node);
|
$this->parseFolder($node);
|
||||||
} elseif ($node instanceof File) {
|
} elseif ($node instanceof File) {
|
||||||
$this->parseFile($node);
|
$this->parseFile($node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (StorageNotAvailableException $e) {
|
} catch (StorageNotAvailableException $e) {
|
||||||
$this->output->writeln(sprintf('<error>Storage for folder folder %s is not available: %s</error>',
|
$this->output->writeln(sprintf('<error>Storage for folder folder %s is not available: %s</error>',
|
||||||
$folder->getPath(),
|
$folder->getPath(),
|
||||||
$e->getHint()
|
$e->getHint()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function parseFile(File &$file): void {
|
private function parseFile(File &$file): void {
|
||||||
$res = $this->timelineWrite->processFile($file);
|
$res = $this->timelineWrite->processFile($file);
|
||||||
if ($res === 2) {
|
if ($res === 2) {
|
||||||
$this->nProcessed++;
|
$this->nProcessed++;
|
||||||
} else if ($res === 1) {
|
} else if ($res === 1) {
|
||||||
$this->nSkipped++;
|
$this->nSkipped++;
|
||||||
} else {
|
} else {
|
||||||
$this->nInvalid++;
|
$this->nInvalid++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -41,172 +41,172 @@ use OCP\Files\FileInfo;
|
||||||
use OCP\Files\Search\ISearchComparison;
|
use OCP\Files\Search\ISearchComparison;
|
||||||
|
|
||||||
class ApiController extends Controller {
|
class ApiController extends Controller {
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
private IUserSession $userSession;
|
private IUserSession $userSession;
|
||||||
private IDBConnection $connection;
|
private IDBConnection $connection;
|
||||||
private IRootFolder $rootFolder;
|
private IRootFolder $rootFolder;
|
||||||
private TimelineQuery $timelineQuery;
|
private TimelineQuery $timelineQuery;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
IUserSession $userSession,
|
IUserSession $userSession,
|
||||||
IDBConnection $connection,
|
IDBConnection $connection,
|
||||||
IRootFolder $rootFolder) {
|
IRootFolder $rootFolder) {
|
||||||
|
|
||||||
parent::__construct(Application::APPNAME, $request);
|
parent::__construct(Application::APPNAME, $request);
|
||||||
|
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->userSession = $userSession;
|
$this->userSession = $userSession;
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->timelineQuery = new TimelineQuery($this->connection);
|
$this->timelineQuery = new TimelineQuery($this->connection);
|
||||||
$this->rootFolder = $rootFolder;
|
$this->rootFolder = $rootFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* @return JSONResponse
|
* @return JSONResponse
|
||||||
*/
|
*/
|
||||||
public function days(): JSONResponse {
|
public function days(): JSONResponse {
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (is_null($user)) {
|
if (is_null($user)) {
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
$list = $this->timelineQuery->getDays($this->config, $user->getUID());
|
$list = $this->timelineQuery->getDays($this->config, $user->getUID());
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* @return JSONResponse
|
* @return JSONResponse
|
||||||
*/
|
*/
|
||||||
public function day(string $id): JSONResponse {
|
public function day(string $id): JSONResponse {
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (is_null($user) || !is_numeric($id)) {
|
if (is_null($user) || !is_numeric($id)) {
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
$list = $this->timelineQuery->getDay($this->config, $user->getUID(), intval($id));
|
$list = $this->timelineQuery->getDay($this->config, $user->getUID(), intval($id));
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if folder is allowed and get it if yes
|
* Check if folder is allowed and get it if yes
|
||||||
*/
|
*/
|
||||||
private function getAllowedFolder(int $folder, $user) {
|
private function getAllowedFolder(int $folder, $user) {
|
||||||
// Get root if folder not specified
|
// Get root if folder not specified
|
||||||
$root = $this->rootFolder->getUserFolder($user->getUID());
|
$root = $this->rootFolder->getUserFolder($user->getUID());
|
||||||
if ($folder === 0) {
|
if ($folder === 0) {
|
||||||
$folder = $root->getId();
|
$folder = $root->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check access to folder
|
// Check access to folder
|
||||||
$nodes = $root->getById($folder);
|
$nodes = $root->getById($folder);
|
||||||
if (empty($nodes)) {
|
if (empty($nodes)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check it is a folder
|
// Check it is a folder
|
||||||
$node = $nodes[0];
|
$node = $nodes[0];
|
||||||
if (!$node instanceof \OCP\Files\Folder) {
|
if (!$node instanceof \OCP\Files\Folder) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* @return JSONResponse
|
* @return JSONResponse
|
||||||
*/
|
*/
|
||||||
public function folder(string $folder): JSONResponse {
|
public function folder(string $folder): JSONResponse {
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (is_null($user) || !is_numeric($folder)) {
|
if (is_null($user) || !is_numeric($folder)) {
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check permissions
|
// Check permissions
|
||||||
$node = $this->getAllowedFolder(intval($folder), $user);
|
$node = $this->getAllowedFolder(intval($folder), $user);
|
||||||
if (is_null($node)) {
|
if (is_null($node)) {
|
||||||
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get response from db
|
// Get response from db
|
||||||
$list = $this->timelineQuery->getDaysFolder($node->getId());
|
$list = $this->timelineQuery->getDaysFolder($node->getId());
|
||||||
|
|
||||||
// Get subdirectories
|
// Get subdirectories
|
||||||
$sub = $node->search(new SearchQuery(
|
$sub = $node->search(new SearchQuery(
|
||||||
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', FileInfo::MIMETYPE_FOLDER),
|
new SearchComparison(ISearchComparison::COMPARE_EQUAL, 'mimetype', FileInfo::MIMETYPE_FOLDER),
|
||||||
0, 0, [], $user));
|
0, 0, [], $user));
|
||||||
$sub = array_filter($sub, function ($item) use ($node) {
|
$sub = array_filter($sub, function ($item) use ($node) {
|
||||||
return $item->getParent()->getId() === $node->getId();
|
return $item->getParent()->getId() === $node->getId();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort by name
|
// Sort by name
|
||||||
usort($sub, function($a, $b) {
|
usort($sub, function($a, $b) {
|
||||||
return strnatcmp($a->getName(), $b->getName());
|
return strnatcmp($a->getName(), $b->getName());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Map sub to JSON array
|
// Map sub to JSON array
|
||||||
$subdirArray = [
|
$subdirArray = [
|
||||||
"dayid" => -0.1,
|
"dayid" => -0.1,
|
||||||
"detail" => array_map(function ($node) {
|
"detail" => array_map(function ($node) {
|
||||||
return [
|
return [
|
||||||
"fileid" => $node->getId(),
|
"fileid" => $node->getId(),
|
||||||
"name" => $node->getName(),
|
"name" => $node->getName(),
|
||||||
"is_folder" => 1,
|
"is_folder" => 1,
|
||||||
"path" => $node->getPath(),
|
"path" => $node->getPath(),
|
||||||
];
|
];
|
||||||
}, $sub, []),
|
}, $sub, []),
|
||||||
];
|
];
|
||||||
$subdirArray["count"] = count($subdirArray["detail"]);
|
$subdirArray["count"] = count($subdirArray["detail"]);
|
||||||
array_unshift($list, $subdirArray);
|
array_unshift($list, $subdirArray);
|
||||||
|
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* @return JSONResponse
|
* @return JSONResponse
|
||||||
*/
|
*/
|
||||||
public function folderDay(string $folder, string $dayId): JSONResponse {
|
public function folderDay(string $folder, string $dayId): JSONResponse {
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (is_null($user) || !is_numeric($folder) || !is_numeric($dayId)) {
|
if (is_null($user) || !is_numeric($folder) || !is_numeric($dayId)) {
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
$node = $this->getAllowedFolder(intval($folder), $user);
|
$node = $this->getAllowedFolder(intval($folder), $user);
|
||||||
if ($node === NULL) {
|
if ($node === NULL) {
|
||||||
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
return new JSONResponse([], Http::STATUS_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
$list = $this->timelineQuery->getDayFolder($node->getId(), intval($dayId));
|
$list = $this->timelineQuery->getDayFolder($node->getId(), intval($dayId));
|
||||||
return new JSONResponse($list, Http::STATUS_OK);
|
return new JSONResponse($list, Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*
|
*
|
||||||
* update preferences (user setting)
|
* update preferences (user setting)
|
||||||
*
|
*
|
||||||
* @param string key the identifier to change
|
* @param string key the identifier to change
|
||||||
* @param string value the value to set
|
* @param string value the value to set
|
||||||
*
|
*
|
||||||
* @return JSONResponse an empty JSONResponse with respective http status code
|
* @return JSONResponse an empty JSONResponse with respective http status code
|
||||||
*/
|
*/
|
||||||
public function setUserConfig(string $key, string $value): JSONResponse {
|
public function setUserConfig(string $key, string $value): JSONResponse {
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (is_null($user)) {
|
if (is_null($user)) {
|
||||||
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED);
|
||||||
}
|
}
|
||||||
|
|
||||||
$userId = $user->getUid();
|
$userId = $user->getUid();
|
||||||
$this->config->setUserValue($userId, Application::APPNAME, $key, $value);
|
$this->config->setUserValue($userId, Application::APPNAME, $key, $value);
|
||||||
return new JSONResponse([], Http::STATUS_OK);
|
return new JSONResponse([], Http::STATUS_OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,60 +13,60 @@ use OCP\IUserSession;
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
|
|
||||||
class PageController extends Controller {
|
class PageController extends Controller {
|
||||||
protected string $userId;
|
protected string $userId;
|
||||||
protected $appName;
|
protected $appName;
|
||||||
protected IEventDispatcher $eventDispatcher;
|
protected IEventDispatcher $eventDispatcher;
|
||||||
private IInitialState $initialState;
|
private IInitialState $initialState;
|
||||||
private IUserSession $userSession;
|
private IUserSession $userSession;
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $AppName,
|
string $AppName,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
string $UserId,
|
string $UserId,
|
||||||
IEventDispatcher $eventDispatcher,
|
IEventDispatcher $eventDispatcher,
|
||||||
IInitialState $initialState,
|
IInitialState $initialState,
|
||||||
IUserSession $userSession,
|
IUserSession $userSession,
|
||||||
IConfig $config) {
|
IConfig $config) {
|
||||||
|
|
||||||
parent::__construct($AppName, $request);
|
parent::__construct($AppName, $request);
|
||||||
$this->userId = $UserId;
|
$this->userId = $UserId;
|
||||||
$this->appName = $AppName;
|
$this->appName = $AppName;
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
$this->initialState = $initialState;
|
$this->initialState = $initialState;
|
||||||
$this->userSession = $userSession;
|
$this->userSession = $userSession;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function main() {
|
public function main() {
|
||||||
$user = $this->userSession->getUser();
|
$user = $this->userSession->getUser();
|
||||||
if (is_null($user)) {
|
if (is_null($user)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Util::addScript($this->appName, 'memories-main');
|
Util::addScript($this->appName, 'memories-main');
|
||||||
Util::addStyle($this->appName, 'custom-icons');
|
Util::addStyle($this->appName, 'custom-icons');
|
||||||
|
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||||
|
|
||||||
|
|
||||||
$timelinePath = \OCA\Memories\Util::getPhotosPath($this->config, $user->getUid());
|
$timelinePath = \OCA\Memories\Util::getPhotosPath($this->config, $user->getUid());
|
||||||
$this->initialState->provideInitialState('timelinePath', $timelinePath);
|
$this->initialState->provideInitialState('timelinePath', $timelinePath);
|
||||||
|
|
||||||
$response = new TemplateResponse($this->appName, 'main');
|
$response = new TemplateResponse($this->appName, 'main');
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
*/
|
*/
|
||||||
public function folder() {
|
public function folder() {
|
||||||
return $this->main();
|
return $this->main();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,11 @@ use OCP\IConfig;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
class TimelineQuery {
|
class TimelineQuery {
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
|
||||||
public function __construct(IDBConnection $connection) {
|
public function __construct(IDBConnection $connection) {
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the days response
|
* Process the days response
|
||||||
|
@ -104,7 +104,7 @@ class TimelineQuery {
|
||||||
ORDER BY `*PREFIX*memories`.`datetaken` DESC';
|
ORDER BY `*PREFIX*memories`.`datetaken` DESC';
|
||||||
|
|
||||||
$path = "files" . Exif::getPhotosPath($config, $user) . "%";
|
$path = "files" . Exif::getPhotosPath($config, $user) . "%";
|
||||||
$rows = $this->connection->executeQuery($sql, [$path, $user, $dayId], [
|
$rows = $this->connection->executeQuery($sql, [$path, $user, $dayId], [
|
||||||
\PDO::PARAM_STR, \PDO::PARAM_STR, \PDO::PARAM_INT,
|
\PDO::PARAM_STR, \PDO::PARAM_STR, \PDO::PARAM_INT,
|
||||||
])->fetchAll();
|
])->fetchAll();
|
||||||
return $this->processDay($rows);
|
return $this->processDay($rows);
|
||||||
|
@ -126,7 +126,7 @@ class TimelineQuery {
|
||||||
AND (`*PREFIX*filecache`.`parent`=? OR `*PREFIX*filecache`.`fileid`=?)
|
AND (`*PREFIX*filecache`.`parent`=? OR `*PREFIX*filecache`.`fileid`=?)
|
||||||
WHERE `*PREFIX*memories`.`dayid`=?
|
WHERE `*PREFIX*memories`.`dayid`=?
|
||||||
ORDER BY `*PREFIX*memories`.`datetaken` DESC';
|
ORDER BY `*PREFIX*memories`.`datetaken` DESC';
|
||||||
$rows = $this->connection->executeQuery($sql, [$folderId, $folderId, $dayId], [
|
$rows = $this->connection->executeQuery($sql, [$folderId, $folderId, $dayId], [
|
||||||
\PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_INT,
|
\PDO::PARAM_INT, \PDO::PARAM_INT, \PDO::PARAM_INT,
|
||||||
])->fetchAll();
|
])->fetchAll();
|
||||||
return $this->processDay($rows);
|
return $this->processDay($rows);
|
||||||
|
|
|
@ -9,11 +9,11 @@ use OCP\Files\File;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
class TimelineWrite {
|
class TimelineWrite {
|
||||||
protected IDBConnection $connection;
|
protected IDBConnection $connection;
|
||||||
|
|
||||||
public function __construct(IDBConnection $connection) {
|
public function __construct(IDBConnection $connection) {
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a file to insert Exif data into the database
|
* Process a file to insert Exif data into the database
|
||||||
|
|
|
@ -34,20 +34,20 @@ use OCP\IDBConnection;
|
||||||
class PostDeleteListener implements IEventListener {
|
class PostDeleteListener implements IEventListener {
|
||||||
private TimelineWrite $util;
|
private TimelineWrite $util;
|
||||||
|
|
||||||
public function __construct(IDBConnection $connection) {
|
public function __construct(IDBConnection $connection) {
|
||||||
$this->util = new TimelineWrite($connection);
|
$this->util = new TimelineWrite($connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Event $event): void {
|
public function handle(Event $event): void {
|
||||||
if (!($event instanceof NodeDeletedEvent)) {
|
if (!($event instanceof NodeDeletedEvent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$node = $event->getNode();
|
$node = $event->getNode();
|
||||||
if ($node instanceof Folder) {
|
if ($node instanceof Folder) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->util->deleteFile($node);
|
$this->util->deleteFile($node);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,23 +36,23 @@ use OCP\IUserManager;
|
||||||
class PostWriteListener implements IEventListener {
|
class PostWriteListener implements IEventListener {
|
||||||
private TimelineWrite $util;
|
private TimelineWrite $util;
|
||||||
|
|
||||||
public function __construct(IDBConnection $connection,
|
public function __construct(IDBConnection $connection,
|
||||||
IUserManager $userManager) {
|
IUserManager $userManager) {
|
||||||
$this->userManager = $userManager;
|
$this->userManager = $userManager;
|
||||||
$this->util = new TimelineWrite($connection);
|
$this->util = new TimelineWrite($connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Event $event): void {
|
public function handle(Event $event): void {
|
||||||
if (!($event instanceof NodeWrittenEvent) &&
|
if (!($event instanceof NodeWrittenEvent) &&
|
||||||
!($event instanceof NodeTouchedEvent)) {
|
!($event instanceof NodeTouchedEvent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$node = $event->getNode();
|
$node = $event->getNode();
|
||||||
if ($node instanceof Folder) {
|
if ($node instanceof Folder) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->util->processFile($node);
|
$this->util->processFile($node);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -58,19 +58,19 @@ class Version000000Date20220812163631 extends SimpleMigrationStep {
|
||||||
'notnull' => false,
|
'notnull' => false,
|
||||||
]);
|
]);
|
||||||
$table->addColumn('fileid', Types::BIGINT, [
|
$table->addColumn('fileid', Types::BIGINT, [
|
||||||
'notnull' => true,
|
'notnull' => true,
|
||||||
'length' => 20,
|
'length' => 20,
|
||||||
]);
|
]);
|
||||||
$table->addColumn('dayid', Types::INTEGER, [
|
$table->addColumn('dayid', Types::INTEGER, [
|
||||||
'notnull' => true,
|
'notnull' => true,
|
||||||
]);
|
]);
|
||||||
$table->addColumn('isvideo', Types::BOOLEAN, [
|
$table->addColumn('isvideo', Types::BOOLEAN, [
|
||||||
'notnull' => false,
|
'notnull' => false,
|
||||||
'default' => false
|
'default' => false
|
||||||
]);
|
]);
|
||||||
$table->addColumn('mtime', Types::INTEGER, [
|
$table->addColumn('mtime', Types::INTEGER, [
|
||||||
'notnull' => true,
|
'notnull' => true,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$table->setPrimaryKey(['id']);
|
$table->setPrimaryKey(['id']);
|
||||||
$table->addIndex(['uid'], 'memories_uid_index');
|
$table->addIndex(['uid'], 'memories_uid_index');
|
||||||
|
|
118
src/App.vue
118
src/App.vue
|
@ -1,43 +1,43 @@
|
||||||
<template>
|
<template>
|
||||||
<NcContent app-name="memories">
|
<NcContent app-name="memories">
|
||||||
<NcAppNavigation>
|
<NcAppNavigation>
|
||||||
<template id="app-memories-navigation" #list>
|
<template id="app-memories-navigation" #list>
|
||||||
<NcAppNavigationItem :to="{name: 'timeline'}"
|
<NcAppNavigationItem :to="{name: 'timeline'}"
|
||||||
:title="t('timeline', 'Timeline')"
|
:title="t('timeline', 'Timeline')"
|
||||||
icon="icon-yourmemories"
|
icon="icon-yourmemories"
|
||||||
exact>
|
exact>
|
||||||
</NcAppNavigationItem>
|
</NcAppNavigationItem>
|
||||||
<NcAppNavigationItem :to="{name: 'folders'}"
|
<NcAppNavigationItem :to="{name: 'folders'}"
|
||||||
:title="t('folders', 'Folders')"
|
:title="t('folders', 'Folders')"
|
||||||
icon="icon-files-dark">
|
icon="icon-files-dark">
|
||||||
</NcAppNavigationItem>
|
</NcAppNavigationItem>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<NcAppNavigationSettings :title="t('memories', 'Settings')">
|
<NcAppNavigationSettings :title="t('memories', 'Settings')">
|
||||||
<Settings />
|
<Settings />
|
||||||
</NcAppNavigationSettings>
|
</NcAppNavigationSettings>
|
||||||
</template>
|
</template>
|
||||||
</NcAppNavigation>
|
</NcAppNavigation>
|
||||||
|
|
||||||
<NcAppContent>
|
<NcAppContent>
|
||||||
<div class="outer">
|
<div class="outer">
|
||||||
<router-view />
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</NcAppContent>
|
</NcAppContent>
|
||||||
</NcContent>
|
</NcContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.outer {
|
.outer {
|
||||||
padding: 0 0 0 44px;
|
padding: 0 0 0 44px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.outer {
|
.outer {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -48,35 +48,35 @@ import Timeline from './components/Timeline.vue'
|
||||||
import Settings from './components/Settings.vue'
|
import Settings from './components/Settings.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
NcContent,
|
NcContent,
|
||||||
NcAppContent,
|
NcAppContent,
|
||||||
NcAppNavigation,
|
NcAppNavigation,
|
||||||
NcAppNavigationItem,
|
NcAppNavigationItem,
|
||||||
NcAppNavigationSettings,
|
NcAppNavigationSettings,
|
||||||
|
|
||||||
Timeline,
|
Timeline,
|
||||||
Settings,
|
Settings,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: false,
|
loading: false,
|
||||||
show: true,
|
show: true,
|
||||||
starred: false,
|
starred: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.show = false
|
this.show = false
|
||||||
console.debug(arguments)
|
console.debug(arguments)
|
||||||
},
|
},
|
||||||
newButtonAction() {
|
newButtonAction() {
|
||||||
console.debug(arguments)
|
console.debug(arguments)
|
||||||
},
|
},
|
||||||
log() {
|
log() {
|
||||||
console.debug(arguments)
|
console.debug(arguments)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<label for="timeline-path">{{ t('memories', 'Timeline Path') }}</label>
|
<label for="timeline-path">{{ t('memories', 'Timeline Path') }}</label>
|
||||||
<input id="timeline-path"
|
<input id="timeline-path"
|
||||||
v-model="timelinePath"
|
v-model="timelinePath"
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<button @click="updateAll()">
|
<button @click="updateAll()">
|
||||||
{{ t('memories', 'Update') }}
|
{{ t('memories', 'Update') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -43,10 +43,10 @@ input[type=text] {
|
||||||
import UserConfig from '../mixins/UserConfig'
|
import UserConfig from '../mixins/UserConfig'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
mixins: [
|
mixins: [
|
||||||
UserConfig,
|
UserConfig,
|
||||||
],
|
],
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async updateAll() {
|
async updateAll() {
|
||||||
|
|
|
@ -176,16 +176,16 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
$route(from, to) {
|
$route(from, to) {
|
||||||
console.log('route changed', from, to)
|
console.log('route changed', from, to)
|
||||||
this.resetState();
|
this.resetState();
|
||||||
this.fetchDays();
|
this.fetchDays();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.resetState();
|
this.resetState();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
/** Reset all state */
|
/** Reset all state */
|
||||||
|
|
24
src/main.js
24
src/main.js
|
@ -29,10 +29,10 @@ import router from './router'
|
||||||
|
|
||||||
// Adding translations to the whole app
|
// Adding translations to the whole app
|
||||||
Vue.mixin({
|
Vue.mixin({
|
||||||
methods: {
|
methods: {
|
||||||
t,
|
t,
|
||||||
n,
|
n,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
Vue.use(VueVirtualScroller)
|
Vue.use(VueVirtualScroller)
|
||||||
|
@ -42,15 +42,15 @@ Vue.use(VueVirtualScroller)
|
||||||
// original scripts are loaded from
|
// original scripts are loaded from
|
||||||
// https://github.com/nextcloud/server/blob/5bf3d1bb384da56adbf205752be8f840aac3b0c5/lib/private/legacy/template.php#L120-L122
|
// https://github.com/nextcloud/server/blob/5bf3d1bb384da56adbf205752be8f840aac3b0c5/lib/private/legacy/template.php#L120-L122
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
if (!window.OCA.Files) {
|
if (!window.OCA.Files) {
|
||||||
window.OCA.Files = {}
|
window.OCA.Files = {}
|
||||||
}
|
}
|
||||||
// register unused client for the sidebar to have access to its parser methods
|
// register unused client for the sidebar to have access to its parser methods
|
||||||
Object.assign(window.OCA.Files, { App: { fileList: { filesClient: OC.Files.getClient() } } }, window.OCA.Files)
|
Object.assign(window.OCA.Files, { App: { fileList: { filesClient: OC.Files.getClient() } } }, window.OCA.Files)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default new Vue({
|
export default new Vue({
|
||||||
el: '#content',
|
el: '#content',
|
||||||
router,
|
router,
|
||||||
render: h => h(App),
|
render: h => h(App),
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,21 +4,21 @@ import { genFileInfo } from './FileUtils'
|
||||||
import client from './DavClient';
|
import client from './DavClient';
|
||||||
|
|
||||||
const props = `
|
const props = `
|
||||||
<oc:fileid />
|
<oc:fileid />
|
||||||
<oc:permissions />
|
<oc:permissions />
|
||||||
<d:getlastmodified />
|
<d:getlastmodified />
|
||||||
<d:getetag />
|
<d:getetag />
|
||||||
<d:getcontenttype />
|
<d:getcontenttype />
|
||||||
<d:getcontentlength />
|
<d:getcontentlength />
|
||||||
<nc:has-preview />
|
<nc:has-preview />
|
||||||
<oc:favorite />
|
<oc:favorite />
|
||||||
<d:resourcetype />`;
|
<d:resourcetype />`;
|
||||||
|
|
||||||
const IMAGE_MIME_TYPES = [
|
const IMAGE_MIME_TYPES = [
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/tiff',
|
'image/tiff',
|
||||||
'image/heic',
|
'image/heic',
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function getFiles(fileIds) {
|
export async function getFiles(fileIds) {
|
||||||
|
@ -34,102 +34,102 @@ export async function getFiles(fileIds) {
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: 'SEARCH',
|
method: 'SEARCH',
|
||||||
headers: {
|
headers: {
|
||||||
'content-Type': 'text/xml',
|
'content-Type': 'text/xml',
|
||||||
},
|
},
|
||||||
data: `<?xml version="1.0" encoding="UTF-8"?>
|
data: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<d:searchrequest xmlns:d="DAV:"
|
<d:searchrequest xmlns:d="DAV:"
|
||||||
xmlns:oc="http://owncloud.org/ns"
|
xmlns:oc="http://owncloud.org/ns"
|
||||||
xmlns:nc="http://nextcloud.org/ns"
|
xmlns:nc="http://nextcloud.org/ns"
|
||||||
xmlns:ns="https://github.com/icewind1991/SearchDAV/ns"
|
xmlns:ns="https://github.com/icewind1991/SearchDAV/ns"
|
||||||
xmlns:ocs="http://open-collaboration-services.org/ns">
|
xmlns:ocs="http://open-collaboration-services.org/ns">
|
||||||
<d:basicsearch>
|
<d:basicsearch>
|
||||||
<d:select>
|
<d:select>
|
||||||
<d:prop>
|
<d:prop>
|
||||||
${props}
|
${props}
|
||||||
</d:prop>
|
</d:prop>
|
||||||
</d:select>
|
</d:select>
|
||||||
<d:from>
|
<d:from>
|
||||||
<d:scope>
|
<d:scope>
|
||||||
<d:href>${prefixPath}</d:href>
|
<d:href>${prefixPath}</d:href>
|
||||||
<d:depth>0</d:depth>
|
<d:depth>0</d:depth>
|
||||||
</d:scope>
|
</d:scope>
|
||||||
</d:from>
|
</d:from>
|
||||||
<d:where>
|
<d:where>
|
||||||
<d:or>
|
<d:or>
|
||||||
${filter}
|
${filter}
|
||||||
</d:or>
|
</d:or>
|
||||||
</d:where>
|
</d:where>
|
||||||
</d:basicsearch>
|
</d:basicsearch>
|
||||||
</d:searchrequest>`,
|
</d:searchrequest>`,
|
||||||
deep: true,
|
deep: true,
|
||||||
details: true,
|
details: true,
|
||||||
responseType: 'text',
|
responseType: 'text',
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await client.getDirectoryContents('', options);
|
let response = await client.getDirectoryContents('', options);
|
||||||
return response.data
|
return response.data
|
||||||
.map(data => genFileInfo(data))
|
.map(data => genFileInfo(data))
|
||||||
.map(data => Object.assign({}, data, { filename: data.filename.replace(prefixPath, '') }));
|
.map(data => Object.assign({}, data, { filename: data.filename.replace(prefixPath, '') }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFolderPreviewFileIds(folderPath, limit) {
|
export async function getFolderPreviewFileIds(folderPath, limit) {
|
||||||
const prefixPath = `/files/${getCurrentUser().uid}`;
|
const prefixPath = `/files/${getCurrentUser().uid}`;
|
||||||
|
|
||||||
const filter = IMAGE_MIME_TYPES.map(mime => `
|
const filter = IMAGE_MIME_TYPES.map(mime => `
|
||||||
<d:like>
|
<d:like>
|
||||||
<d:prop>
|
<d:prop>
|
||||||
<d:getcontenttype/>
|
<d:getcontenttype/>
|
||||||
</d:prop>
|
</d:prop>
|
||||||
<d:literal>${mime}</d:literal>
|
<d:literal>${mime}</d:literal>
|
||||||
</d:like>
|
</d:like>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: 'SEARCH',
|
method: 'SEARCH',
|
||||||
headers: {
|
headers: {
|
||||||
'content-Type': 'text/xml',
|
'content-Type': 'text/xml',
|
||||||
},
|
},
|
||||||
data: `<?xml version="1.0" encoding="UTF-8"?>
|
data: `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<d:searchrequest xmlns:d="DAV:"
|
<d:searchrequest xmlns:d="DAV:"
|
||||||
xmlns:oc="http://owncloud.org/ns"
|
xmlns:oc="http://owncloud.org/ns"
|
||||||
xmlns:nc="http://nextcloud.org/ns"
|
xmlns:nc="http://nextcloud.org/ns"
|
||||||
xmlns:ns="https://github.com/icewind1991/SearchDAV/ns"
|
xmlns:ns="https://github.com/icewind1991/SearchDAV/ns"
|
||||||
xmlns:ocs="http://open-collaboration-services.org/ns">
|
xmlns:ocs="http://open-collaboration-services.org/ns">
|
||||||
<d:basicsearch>
|
<d:basicsearch>
|
||||||
<d:select>
|
<d:select>
|
||||||
<d:prop>
|
<d:prop>
|
||||||
${props}
|
${props}
|
||||||
</d:prop>
|
</d:prop>
|
||||||
</d:select>
|
</d:select>
|
||||||
<d:from>
|
<d:from>
|
||||||
<d:scope>
|
<d:scope>
|
||||||
<d:href>${prefixPath}/${folderPath}</d:href>
|
<d:href>${prefixPath}/${folderPath}</d:href>
|
||||||
<d:depth>0</d:depth>
|
<d:depth>0</d:depth>
|
||||||
</d:scope>
|
</d:scope>
|
||||||
</d:from>
|
</d:from>
|
||||||
<d:where>
|
<d:where>
|
||||||
<d:or>
|
<d:or>
|
||||||
${filter}
|
${filter}
|
||||||
</d:or>
|
</d:or>
|
||||||
</d:where>
|
</d:where>
|
||||||
<d:limit>
|
<d:limit>
|
||||||
<d:nresults>${limit}</d:nresults>
|
<d:nresults>${limit}</d:nresults>
|
||||||
</d:limit>
|
</d:limit>
|
||||||
</d:basicsearch>
|
</d:basicsearch>
|
||||||
</d:searchrequest>`,
|
</d:searchrequest>`,
|
||||||
deep: true,
|
deep: true,
|
||||||
details: true,
|
details: true,
|
||||||
responseType: 'text',
|
responseType: 'text',
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await client.getDirectoryContents('', options);
|
let response = await client.getDirectoryContents('', options);
|
||||||
return response.data
|
return response.data
|
||||||
.map(data => genFileInfo(data))
|
.map(data => genFileInfo(data))
|
||||||
.map(data => Object.assign({}, data, {
|
.map(data => Object.assign({}, data, {
|
||||||
filename: data.filename.replace(prefixPath, '')
|
filename: data.filename.replace(prefixPath, '')
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteFile(path) {
|
export async function deleteFile(path) {
|
||||||
|
@ -143,30 +143,30 @@ export async function deleteFile(path) {
|
||||||
* @param {string[]} fileNames - The file's names
|
* @param {string[]} fileNames - The file's names
|
||||||
*/
|
*/
|
||||||
export async function downloadFiles(fileNames) {
|
export async function downloadFiles(fileNames) {
|
||||||
const randomToken = Math.random().toString(36).substring(2)
|
const randomToken = Math.random().toString(36).substring(2)
|
||||||
|
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
params.append('files', JSON.stringify(fileNames))
|
params.append('files', JSON.stringify(fileNames))
|
||||||
params.append('downloadStartSecret', randomToken)
|
params.append('downloadStartSecret', randomToken)
|
||||||
|
|
||||||
const downloadURL = generateUrl(`/apps/files/ajax/download.php?${params}`)
|
const downloadURL = generateUrl(`/apps/files/ajax/download.php?${params}`)
|
||||||
|
|
||||||
window.location = `${downloadURL}downloadStartSecret=${randomToken}`
|
window.location = `${downloadURL}downloadStartSecret=${randomToken}`
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const waitForCookieInterval = setInterval(
|
const waitForCookieInterval = setInterval(
|
||||||
() => {
|
() => {
|
||||||
const cookieIsSet = document.cookie
|
const cookieIsSet = document.cookie
|
||||||
.split(';')
|
.split(';')
|
||||||
.map(cookie => cookie.split('='))
|
.map(cookie => cookie.split('='))
|
||||||
.findIndex(([cookieName, cookieValue]) => cookieName === 'ocDownloadStarted' && cookieValue === randomToken)
|
.findIndex(([cookieName, cookieValue]) => cookieName === 'ocDownloadStarted' && cookieValue === randomToken)
|
||||||
|
|
||||||
if (cookieIsSet) {
|
if (cookieIsSet) {
|
||||||
clearInterval(waitForCookieInterval)
|
clearInterval(waitForCookieInterval)
|
||||||
resolve(true)
|
resolve(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
50
|
50
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const isNumber = function(num) {
|
const isNumber = function(num) {
|
||||||
if (!num) {
|
if (!num) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return Number(num).toString() === num.toString()
|
return Number(num).toString() === num.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isNumber }
|
export { isNumber }
|
Loading…
Reference in New Issue