metadataRequestService->getMetadataFromFileId($node->getId()); } catch (FilesMetadataNotFoundException) { $metadata = new FilesMetadata($node->getId()); } // if $process is LIVE, we enforce LIVE // if $process is NAMED, we go NAMED // else BACKGROUND if ((self::PROCESS_LIVE & $process) !== 0) { $event = new MetadataLiveEvent($node, $metadata); } elseif ((self::PROCESS_NAMED & $process) !== 0) { $event = new MetadataNamedEvent($node, $metadata, $namedEvent); } else { $event = new MetadataBackgroundEvent($node, $metadata); } $this->eventDispatcher->dispatchTyped($event); $this->saveMetadata($event->getMetadata()); // if requested, we add a new job for next cron to refresh metadata out of main thread // if $process was set to LIVE+BACKGROUND, we run background process directly if ($event instanceof MetadataLiveEvent && $event->isRunAsBackgroundJobRequested()) { if ((self::PROCESS_BACKGROUND & $process) !== 0) { return $this->refreshMetadata($node, self::PROCESS_BACKGROUND); } $this->jobList->add(UpdateSingleMetadata::class, [$node->getOwner()?->getUID(), $node->getId()]); } return $metadata; } /** * @param int $fileId file id * @param boolean $generate Generate if metadata does not exists * * @inheritDoc * @return IFilesMetadata * @throws FilesMetadataNotFoundException if not found * @since 28.0.0 */ public function getMetadata(int $fileId, bool $generate = false): IFilesMetadata { try { return $this->metadataRequestService->getMetadataFromFileId($fileId); } catch (FilesMetadataNotFoundException $ex) { if ($generate) { return new FilesMetadata($fileId); } throw $ex; } } /** * returns metadata of multiple file ids * * @param int[] $fileIds file ids * * @return array File ID is the array key, files without metadata are not returned in the array * @psalm-return array * @since 28.0.0 */ public function getMetadataForFiles(array $fileIds): array { return $this->metadataRequestService->getMetadataFromFileIds($fileIds); } /** * @param IFilesMetadata $filesMetadata metadata * * @inheritDoc * @throws FilesMetadataException if metadata seems malformed * @since 28.0.0 */ public function saveMetadata(IFilesMetadata $filesMetadata): void { if ($filesMetadata->getFileId() === 0 || !$filesMetadata->updated()) { return; } $json = json_encode($filesMetadata->jsonSerialize()); if (strlen($json) > self::JSON_MAXSIZE) { $this->logger->debug('huge metadata content detected: ' . $json); throw new FilesMetadataException('json cannot exceed ' . self::JSON_MAXSIZE . ' characters long; fileId: ' . $filesMetadata->getFileId() . '; size: ' . strlen($json)); } try { if ($filesMetadata->getSyncToken() === '') { $this->metadataRequestService->store($filesMetadata); } else { $this->metadataRequestService->updateMetadata($filesMetadata); } } catch (DBException $e) { // most of the logged exception are the result of race condition // between 2 simultaneous process trying to create/update metadata $this->logger->warning('issue while saveMetadata', ['exception' => $e, 'metadata' => $filesMetadata]); return; } // update indexes foreach ($filesMetadata->getIndexes() as $index) { try { $this->indexRequestService->updateIndex($filesMetadata, $index); } catch (DBException $e) { $this->logger->warning('issue while updateIndex', ['exception' => $e]); } } // update metadata types list $current = $this->getKnownMetadata(); $current->import($filesMetadata->jsonSerialize(true)); $this->appConfig->setValueArray('core', self::CONFIG_KEY, $current->jsonSerialize(), lazy: true); } /** * @param int $fileId file id * * @inheritDoc * @since 28.0.0 */ public function deleteMetadata(int $fileId): void { try { $this->metadataRequestService->dropMetadata($fileId); } catch (Exception $e) { $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]); } try { $this->indexRequestService->dropIndex($fileId); } catch (Exception $e) { $this->logger->warning('issue while deleteMetadata', ['exception' => $e, 'fileId' => $fileId]); } } /** * @param IQueryBuilder $qb * @param string $fileTableAlias alias of the table that contains data about files * @param string $fileIdField alias of the field that contains file ids * * @inheritDoc * @return IMetadataQuery * @see IMetadataQuery * @since 28.0.0 */ public function getMetadataQuery( IQueryBuilder $qb, string $fileTableAlias, string $fileIdField, ): IMetadataQuery { return new MetadataQuery($qb, $this, $fileTableAlias, $fileIdField); } /** * @inheritDoc * @return IFilesMetadata * @since 28.0.0 */ public function getKnownMetadata(): IFilesMetadata { if ($this->all !== null) { return $this->all; } $this->all = new FilesMetadata(); try { $this->all->import($this->appConfig->getValueArray('core', self::CONFIG_KEY, lazy: true)); } catch (JsonException) { $this->logger->warning('issue while reading stored list of metadata. Advised to run ./occ files:scan --all --generate-metadata'); } return $this->all; } /** * @param string $key metadata key * @param string $type metadata type * @param bool $indexed TRUE if metadata can be search * @param int $editPermission remote edit permission via Webdav PROPPATCH * * @inheritDoc * @since 28.0.0 * @see IMetadataValueWrapper::TYPE_INT * @see IMetadataValueWrapper::TYPE_FLOAT * @see IMetadataValueWrapper::TYPE_BOOL * @see IMetadataValueWrapper::TYPE_ARRAY * @see IMetadataValueWrapper::TYPE_STRING_LIST * @see IMetadataValueWrapper::TYPE_INT_LIST * @see IMetadataValueWrapper::TYPE_STRING * @see IMetadataValueWrapper::EDIT_FORBIDDEN * @see IMetadataValueWrapper::EDIT_REQ_OWNERSHIP * @see IMetadataValueWrapper::EDIT_REQ_WRITE_PERMISSION * @see IMetadataValueWrapper::EDIT_REQ_READ_PERMISSION */ public function initMetadata( string $key, string $type, bool $indexed = false, int $editPermission = IMetadataValueWrapper::EDIT_FORBIDDEN, ): void { $current = $this->getKnownMetadata(); try { if ($current->getType($key) === $type && $indexed === $current->isIndex($key) && $editPermission === $current->getEditPermission($key)) { return; // if key exists, with same type and indexed, we do nothing. } } catch (FilesMetadataNotFoundException) { // if value does not exist, we keep on the writing of course } $current->import([$key => ['type' => $type, 'indexed' => $indexed, 'editPermission' => $editPermission]]); $this->appConfig->setValueArray('core', self::CONFIG_KEY, $current->jsonSerialize(), lazy: true); $this->all = $current; } /** * load listeners * * @param IEventDispatcher $eventDispatcher */ public static function loadListeners(IEventDispatcher $eventDispatcher): void { $eventDispatcher->addServiceListener(NodeWrittenEvent::class, MetadataUpdate::class); $eventDispatcher->addServiceListener(CacheEntryRemovedEvent::class, MetadataDelete::class); } }