2020-04-28 11:58:00 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
2024-09-30 11:44:11 +03:00
|
|
|
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
2020-04-28 11:58:00 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
namespace OCA\EndToEndEncryption;
|
|
|
|
|
|
|
|
use OC\User\NoUserException;
|
|
|
|
use OCA\EndToEndEncryption\Exceptions\MetaDataExistsException;
|
|
|
|
use OCA\EndToEndEncryption\Exceptions\MissingMetaDataException;
|
|
|
|
use OCP\Files\IAppData;
|
|
|
|
use OCP\Files\IRootFolder;
|
|
|
|
use OCP\Files\NotFoundException;
|
|
|
|
use OCP\Files\NotPermittedException;
|
2020-08-10 15:29:56 +03:00
|
|
|
use OCP\Files\SimpleFS\ISimpleFile;
|
2023-03-20 21:37:14 +03:00
|
|
|
use OCP\Files\SimpleFS\ISimpleFolder;
|
2020-04-28 11:58:00 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class MetaDataStorage
|
|
|
|
*
|
|
|
|
* @package OCA\EndToEndEncryption
|
|
|
|
*/
|
|
|
|
class MetaDataStorage implements IMetaDataStorage {
|
2022-04-08 00:02:30 +03:00
|
|
|
private IAppData $appData;
|
|
|
|
private IRootFolder $rootFolder;
|
|
|
|
private string $metaDataRoot = '/meta-data';
|
|
|
|
private string $metaDataFileName = 'meta.data';
|
|
|
|
private string $intermediateMetaDataFileName = 'intermediate.meta.data';
|
2023-06-01 17:30:55 +03:00
|
|
|
private string $metaDataSignatureFileName = 'meta.data.signature';
|
|
|
|
private string $intermediateMetaDataSignatureFileName = 'intermediate.meta.data.signature';
|
|
|
|
private string $metaDataCounterFileName = 'meta.data.counter';
|
|
|
|
private string $intermediateMetaDataCounterFileName = 'intermediate.meta.data.counter';
|
2020-04-28 11:58:00 +03:00
|
|
|
|
|
|
|
public function __construct(IAppData $appData,
|
2023-12-26 22:15:44 +03:00
|
|
|
IRootFolder $rootFolder) {
|
2020-04-28 11:58:00 +03:00
|
|
|
$this->appData = $appData;
|
|
|
|
$this->rootFolder = $rootFolder;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2020-07-09 11:01:52 +03:00
|
|
|
public function getMetaData(string $userId, int $id): string {
|
2020-04-28 11:58:00 +03:00
|
|
|
$this->verifyFolderStructure();
|
2020-07-09 11:01:52 +03:00
|
|
|
$this->verifyOwner($userId, $id);
|
2020-04-28 11:58:00 +03:00
|
|
|
|
2020-08-10 15:29:56 +03:00
|
|
|
$legacyFile = $this->getLegacyFile($userId, $id);
|
|
|
|
if ($legacyFile !== null) {
|
|
|
|
return $legacyFile->getContent();
|
|
|
|
}
|
|
|
|
|
2020-04-28 11:58:00 +03:00
|
|
|
$folderName = $this->getFolderNameForFileId($id);
|
|
|
|
$folder = $this->appData->getFolder($folderName);
|
|
|
|
|
|
|
|
return $folder
|
|
|
|
->getFile($this->metaDataFileName)
|
|
|
|
->getContent();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2023-07-03 14:15:35 +03:00
|
|
|
public function setMetaDataIntoIntermediateFile(string $userId, int $id, string $metaData, string $token, string $signature): void {
|
2020-04-28 11:58:00 +03:00
|
|
|
$this->verifyFolderStructure();
|
2020-07-09 11:01:52 +03:00
|
|
|
$this->verifyOwner($userId, $id);
|
2020-04-28 11:58:00 +03:00
|
|
|
|
2020-08-10 15:29:56 +03:00
|
|
|
$legacyFile = $this->getLegacyFile($userId, $id);
|
|
|
|
if ($legacyFile !== null) {
|
|
|
|
throw new MetaDataExistsException('Legacy Meta-data file already exists');
|
|
|
|
}
|
|
|
|
|
2020-04-28 11:58:00 +03:00
|
|
|
$folderName = $this->getFolderNameForFileId($id);
|
|
|
|
try {
|
|
|
|
$dir = $this->appData->getFolder($folderName);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
$dir = $this->appData->newFolder($folderName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do not override metadata-file
|
|
|
|
if ($dir->fileExists($this->metaDataFileName)) {
|
|
|
|
throw new MetaDataExistsException('Meta-data file already exists');
|
|
|
|
}
|
|
|
|
|
2020-04-28 19:58:26 +03:00
|
|
|
if ($dir->fileExists($this->intermediateMetaDataFileName)) {
|
|
|
|
throw new MetaDataExistsException('Intermediate meta-data file already exists');
|
|
|
|
}
|
|
|
|
|
|
|
|
$dir->newFile($this->intermediateMetaDataFileName)
|
2020-04-28 11:58:00 +03:00
|
|
|
->putContent($metaData);
|
2023-03-20 21:37:14 +03:00
|
|
|
|
2023-07-03 14:15:35 +03:00
|
|
|
$dir->newFile($this->intermediateMetaDataSignatureFileName)
|
|
|
|
->putContent($signature);
|
|
|
|
|
2023-03-20 21:37:14 +03:00
|
|
|
$this->getTokenFolder($token)->newFile("$id", '');
|
2020-04-28 11:58:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2023-07-03 14:15:35 +03:00
|
|
|
public function updateMetaDataIntoIntermediateFile(string $userId, int $id, string $fileKey, string $token, string $signature = ''): void {
|
2020-04-28 11:58:00 +03:00
|
|
|
// ToDo check signature for race condition
|
|
|
|
$this->verifyFolderStructure();
|
2020-07-09 11:01:52 +03:00
|
|
|
$this->verifyOwner($userId, $id);
|
2020-04-28 11:58:00 +03:00
|
|
|
|
2020-08-10 15:29:56 +03:00
|
|
|
$legacyFile = $this->getLegacyFile($userId, $id);
|
2020-04-28 11:58:00 +03:00
|
|
|
$folderName = $this->getFolderNameForFileId($id);
|
|
|
|
try {
|
|
|
|
$dir = $this->appData->getFolder($folderName);
|
|
|
|
} catch (NotFoundException $ex) {
|
2020-08-10 15:29:56 +03:00
|
|
|
// No folder and no legacy
|
|
|
|
if ($legacyFile === null) {
|
|
|
|
throw new MissingMetaDataException('Meta-data file missing');
|
|
|
|
}
|
|
|
|
|
|
|
|
$dir = $this->appData->newFolder($folderName);
|
2020-04-28 11:58:00 +03:00
|
|
|
}
|
|
|
|
|
2020-08-10 15:29:56 +03:00
|
|
|
if ($legacyFile === null && !$dir->fileExists($this->metaDataFileName)) {
|
2020-04-28 11:58:00 +03:00
|
|
|
throw new MissingMetaDataException('Meta-data file missing');
|
|
|
|
}
|
|
|
|
|
2020-04-28 19:58:26 +03:00
|
|
|
try {
|
|
|
|
$intermediateMetaDataFile = $dir->getFile($this->intermediateMetaDataFileName);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
$intermediateMetaDataFile = $dir->newFile($this->intermediateMetaDataFileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
$intermediateMetaDataFile
|
2020-04-28 11:58:00 +03:00
|
|
|
->putContent($fileKey);
|
2023-03-20 21:37:14 +03:00
|
|
|
|
2023-06-01 17:30:55 +03:00
|
|
|
// Signature can be empty when deleting the metadata, or during filedrop upload.
|
|
|
|
if ($signature !== '') {
|
|
|
|
$this->writeSignature($dir, $this->intermediateMetaDataSignatureFileName, $signature);
|
|
|
|
}
|
|
|
|
|
2023-03-20 21:37:14 +03:00
|
|
|
$this->getTokenFolder($token)->newFile("$id", '');
|
2020-04-28 11:58:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2020-07-09 11:01:52 +03:00
|
|
|
public function deleteMetaData(string $userId, int $id): void {
|
2020-04-28 11:58:00 +03:00
|
|
|
$this->verifyFolderStructure();
|
2020-07-09 11:01:52 +03:00
|
|
|
$this->verifyOwner($userId, $id);
|
2020-04-28 11:58:00 +03:00
|
|
|
|
|
|
|
$folderName = $this->getFolderNameForFileId($id);
|
|
|
|
try {
|
|
|
|
$dir = $this->appData->getFolder($folderName);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$dir->delete();
|
2020-08-10 15:29:56 +03:00
|
|
|
$this->cleanupLegacyFile($userId, $id);
|
2020-04-28 11:58:00 +03:00
|
|
|
}
|
|
|
|
|
2020-04-28 19:58:26 +03:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2020-07-09 11:01:52 +03:00
|
|
|
public function saveIntermediateFile(string $userId, int $id): void {
|
2020-04-28 19:58:26 +03:00
|
|
|
$this->verifyFolderStructure();
|
2020-07-09 11:01:52 +03:00
|
|
|
$this->verifyOwner($userId, $id);
|
2020-04-28 19:58:26 +03:00
|
|
|
|
|
|
|
$folderName = $this->getFolderNameForFileId($id);
|
|
|
|
try {
|
|
|
|
$dir = $this->appData->getFolder($folderName);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
throw new MissingMetaDataException('Intermediate meta-data file missing');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$dir->fileExists($this->intermediateMetaDataFileName)) {
|
|
|
|
throw new MissingMetaDataException('Intermediate meta-data file missing');
|
|
|
|
}
|
|
|
|
|
|
|
|
$intermediateMetaDataFile = $dir->getFile($this->intermediateMetaDataFileName);
|
2020-07-23 17:52:38 +03:00
|
|
|
// If the intermediate file is empty, delete the metadata file
|
|
|
|
if ($intermediateMetaDataFile->getContent() === '{}') {
|
|
|
|
$dir->delete();
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
$finalFile = $dir->getFile($this->metaDataFileName);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
$finalFile = $dir->newFile($this->metaDataFileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
$finalFile->putContent($intermediateMetaDataFile->getContent());
|
|
|
|
// After successfully saving, automatically delete the intermediate file
|
|
|
|
$intermediateMetaDataFile->delete();
|
2023-06-01 17:30:55 +03:00
|
|
|
|
|
|
|
if ($dir->fileExists($this->intermediateMetaDataSignatureFileName)) {
|
|
|
|
$intermediateMetaDataSignature = $dir->getFile($this->intermediateMetaDataSignatureFileName);
|
|
|
|
$this->writeSignature($dir, $this->metaDataSignatureFileName, $intermediateMetaDataSignature->getContent());
|
|
|
|
$intermediateMetaDataSignature->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->saveCounter($id);
|
2020-04-28 19:58:26 +03:00
|
|
|
}
|
2020-08-10 15:29:56 +03:00
|
|
|
|
|
|
|
$this->cleanupLegacyFile($userId, $id);
|
2020-04-28 19:58:26 +03:00
|
|
|
}
|
|
|
|
|
2023-07-03 14:15:35 +03:00
|
|
|
private function writeSignature(ISimpleFolder $dir, string $filename, string $signature): void {
|
|
|
|
try {
|
|
|
|
$signatureFile = $dir->getFile($filename);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
$signatureFile = $dir->newFile($filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
$signatureFile->putContent($signature);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function readSignature(int $id): string {
|
|
|
|
$folderName = $this->getFolderNameForFileId($id);
|
|
|
|
$dir = $this->appData->getFolder($folderName);
|
2024-01-23 19:41:51 +03:00
|
|
|
|
|
|
|
try {
|
|
|
|
return $dir->getFile($this->metaDataSignatureFileName)->getContent();
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
$metadata = $dir->getFile($this->metaDataFileName)->getContent();
|
|
|
|
$decodedMetadata = json_decode($metadata, true);
|
|
|
|
|
2024-08-26 16:52:54 +03:00
|
|
|
if ($decodedMetadata['metadata']['version'] === '1.2') {
|
|
|
|
return '';
|
2024-01-23 19:41:51 +03:00
|
|
|
}
|
|
|
|
|
2024-03-26 14:32:58 +03:00
|
|
|
if ($decodedMetadata['metadata']['version'] === 1.2) {
|
2024-08-26 16:52:54 +03:00
|
|
|
return '';
|
2024-03-26 14:32:58 +03:00
|
|
|
}
|
|
|
|
|
2024-03-27 11:17:13 +03:00
|
|
|
if ($decodedMetadata['metadata']['version'] === 1) {
|
2024-08-26 16:52:54 +03:00
|
|
|
return '';
|
2024-03-27 11:17:13 +03:00
|
|
|
}
|
|
|
|
|
2024-01-23 19:41:51 +03:00
|
|
|
throw $ex;
|
|
|
|
}
|
2023-07-03 14:15:35 +03:00
|
|
|
}
|
|
|
|
|
2020-04-28 19:58:26 +03:00
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
2020-07-09 11:01:52 +03:00
|
|
|
public function deleteIntermediateFile(string $userId, int $id): void {
|
2020-04-28 19:58:26 +03:00
|
|
|
$this->verifyFolderStructure();
|
2020-07-09 11:01:52 +03:00
|
|
|
$this->verifyOwner($userId, $id);
|
2020-04-28 19:58:26 +03:00
|
|
|
|
|
|
|
$folderName = $this->getFolderNameForFileId($id);
|
|
|
|
try {
|
|
|
|
$dir = $this->appData->getFolder($folderName);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-06-01 17:30:55 +03:00
|
|
|
if ($dir->fileExists($this->intermediateMetaDataFileName)) {
|
|
|
|
$dir->getFile($this->intermediateMetaDataFileName)
|
|
|
|
->delete();
|
2020-04-28 19:58:26 +03:00
|
|
|
}
|
|
|
|
|
2023-06-01 17:30:55 +03:00
|
|
|
if ($dir->fileExists($this->intermediateMetaDataCounterFileName)) {
|
|
|
|
$dir->getFile($this->intermediateMetaDataCounterFileName)
|
|
|
|
->delete();
|
|
|
|
}
|
2020-04-28 19:58:26 +03:00
|
|
|
}
|
|
|
|
|
2020-04-28 11:58:00 +03:00
|
|
|
private function getFolderNameForFileId(int $id): string {
|
|
|
|
return $this->metaDataRoot . '/' . $id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Verifies that user has access to file-id
|
|
|
|
*
|
|
|
|
* @throws NotFoundException
|
|
|
|
*/
|
2020-07-09 11:01:52 +03:00
|
|
|
protected function verifyOwner(string $userId, int $id): void {
|
2020-04-28 11:58:00 +03:00
|
|
|
try {
|
2020-07-09 11:01:52 +03:00
|
|
|
$userFolder = $this->rootFolder->getUserFolder($userId);
|
2024-08-26 16:52:54 +03:00
|
|
|
} catch (NoUserException|NotPermittedException $ex) {
|
2020-07-09 11:01:52 +03:00
|
|
|
throw new NotFoundException('No user-root for '. $userId);
|
2020-04-28 11:58:00 +03:00
|
|
|
}
|
|
|
|
|
2020-07-09 11:01:52 +03:00
|
|
|
$ownerNodes = $userFolder->getById($id);
|
2020-04-28 11:58:00 +03:00
|
|
|
if (!isset($ownerNodes[0])) {
|
|
|
|
throw new NotFoundException('No file for owner with ID ' . $id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws NotFoundException
|
|
|
|
* @throws NotPermittedException
|
|
|
|
*/
|
|
|
|
protected function verifyFolderStructure(): void {
|
|
|
|
$appDataRoot = $this->appData->getFolder('/');
|
|
|
|
if (!$appDataRoot->fileExists($this->metaDataRoot)) {
|
|
|
|
$this->appData->newFolder($this->metaDataRoot);
|
|
|
|
}
|
|
|
|
}
|
2020-08-10 15:29:56 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws NotPermittedException
|
|
|
|
*/
|
|
|
|
protected function getLegacyFile(string $userId, int $id): ?ISimpleFile {
|
|
|
|
try {
|
|
|
|
$legacyOwnerPath = $this->getLegacyOwnerPath($userId, $id);
|
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
// Just return if file does not exist for user
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$legacyFolder = $this->appData->getFolder($this->metaDataRoot . '/' . $legacyOwnerPath);
|
|
|
|
return $legacyFolder->getFile($this->metaDataFileName);
|
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
// Just return if no legacy file exits
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @throws NotPermittedException
|
|
|
|
*/
|
|
|
|
protected function cleanupLegacyFile(string $userId, int $id): void {
|
|
|
|
try {
|
|
|
|
$legacyOwnerPath = $this->getLegacyOwnerPath($userId, $id);
|
|
|
|
} catch (NotFoundException $e) {
|
|
|
|
// Just return if file does not exist for user
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
$legacyFolder = $this->appData->getFolder($this->metaDataRoot . '/' . $legacyOwnerPath);
|
|
|
|
$legacyFolder->delete();
|
2024-08-26 16:52:54 +03:00
|
|
|
} catch (NotFoundException|NotPermittedException $e) {
|
2020-08-10 15:29:56 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get path to the file for the file-owner.
|
|
|
|
* This is needed for the old way of storing metadata-files.
|
|
|
|
*
|
|
|
|
* @throws NotFoundException
|
|
|
|
* @throws NotPermittedException
|
|
|
|
*/
|
|
|
|
protected function getLegacyOwnerPath(string $userId, int $id):string {
|
|
|
|
try {
|
|
|
|
$userFolder = $this->rootFolder->getUserFolder($userId);
|
|
|
|
} catch (NoUserException $ex) {
|
|
|
|
throw new NotFoundException('No user-root for '. $userId);
|
|
|
|
}
|
|
|
|
|
|
|
|
$ownerNodes = $userFolder->getById($id);
|
|
|
|
if (!isset($ownerNodes[0])) {
|
|
|
|
throw new NotFoundException('No file for owner with ID ' . $id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $ownerNodes[0]->getPath();
|
|
|
|
}
|
2023-03-20 21:37:14 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function getTouchedFolders(string $token): array {
|
|
|
|
return array_map(
|
|
|
|
fn (ISimpleFile $file) => (int)$file->getName(),
|
|
|
|
$this->getTokenFolder($token)->getDirectoryListing()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function clearTouchedFolders(string $token): void {
|
|
|
|
$this->getTokenFolder($token)->delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
// To ease the wrap-up process during unlocking,
|
|
|
|
// we keep track of every folder for which metadata was updated.
|
|
|
|
// For that we create a file named /tokens/$token/$folderId.
|
|
|
|
private function getTokenFolder(string $token): ISimpleFolder {
|
|
|
|
try {
|
|
|
|
return $this->appData->getFolder("/tokens/$token");
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
return $this->appData->newFolder("/tokens/$token");
|
|
|
|
}
|
|
|
|
}
|
2023-06-01 17:30:55 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function getCounter(int $id): int {
|
|
|
|
try {
|
|
|
|
$metadataFolder = $this->appData->getFolder($this->getFolderNameForFileId($id));
|
|
|
|
$counterFile = $metadataFolder->getFile($this->metaDataCounterFileName);
|
|
|
|
return (int)$counterFile->getContent();
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
|
|
|
*/
|
|
|
|
public function saveIntermediateCounter(int $id, int $counter): void {
|
|
|
|
$metadataFolder = $this->appData->getFolder($this->getFolderNameForFileId($id));
|
2024-01-16 19:28:03 +03:00
|
|
|
$metadataFolder->newFile($this->intermediateMetaDataCounterFileName)->putContent((string)$counter);
|
2023-06-01 17:30:55 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the latest received counter from the intermediate file.
|
|
|
|
*/
|
|
|
|
private function saveCounter(int $id): void {
|
|
|
|
$metadataFolder = $this->appData->getFolder($this->getFolderNameForFileId($id));
|
|
|
|
if (!$metadataFolder->fileExists($this->intermediateMetaDataCounterFileName)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$intermediateCounterFile = $metadataFolder->getFile($this->intermediateMetaDataCounterFileName);
|
|
|
|
|
|
|
|
try {
|
|
|
|
$counterFile = $metadataFolder->getFile($this->metaDataCounterFileName);
|
|
|
|
} catch (NotFoundException $ex) {
|
|
|
|
$counterFile = $metadataFolder->newFile($this->metaDataCounterFileName);
|
|
|
|
}
|
|
|
|
|
|
|
|
$counterFile->putContent($intermediateCounterFile->getContent());
|
|
|
|
$intermediateCounterFile->delete();
|
|
|
|
}
|
2020-04-28 11:58:00 +03:00
|
|
|
}
|