From 67f299c133882673e31aef26f399243af8dc5994 Mon Sep 17 00:00:00 2001 From: Florent Poinsaut Date: Wed, 19 Oct 2022 15:52:01 +0000 Subject: [PATCH] Add diff report Signed-off-by: Florent Poinsaut --- composer.json | 3 +- composer.lock | 45 +++++++++++- lib/Command/ListShares.php | 7 -- lib/Command/SendShares.php | 12 +++- lib/Service/ReportSender.php | 134 +++++++++++++++++++++++++++++++---- 5 files changed, 178 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 45376fd..f79cf8c 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "license": "MIT", "require": { "nikic/iter": "^2.2", - "symfony/serializer": "^5.4" + "symfony/serializer": "^5.4", + "swaggest/json-diff": "^3.9" }, "require-dev": { "phpunit/phpunit": "^9", diff --git a/composer.lock b/composer.lock index c9c367d..a0876f4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "280059a3e5c545fce72e8e3f92894660", + "content-hash": "2e26a4680f38483a82654971170dc65d", "packages": [ { "name": "nikic/iter", @@ -56,6 +56,49 @@ }, "time": "2021-08-02T15:04:32+00:00" }, + { + "name": "swaggest/json-diff", + "version": "v3.9.0", + "source": { + "type": "git", + "url": "https://github.com/swaggest/json-diff.git", + "reference": "ff3a7921e9f1aa096067eb541fcfd0e7611c558c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swaggest/json-diff/zipball/ff3a7921e9f1aa096067eb541fcfd0e7611c558c", + "reference": "ff3a7921e9f1aa096067eb541fcfd0e7611c558c", + "shasum": "" + }, + "require": { + "ext-json": "*" + }, + "require-dev": { + "phperf/phpunit": "4.8.37" + }, + "type": "library", + "autoload": { + "psr-4": { + "Swaggest\\JsonDiff\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Viacheslav Poturaev", + "email": "vearutop@gmail.com" + } + ], + "description": "JSON diff/rearrange/patch/pointer library for PHP", + "support": { + "issues": "https://github.com/swaggest/json-diff/issues", + "source": "https://github.com/swaggest/json-diff/tree/v3.9.0" + }, + "time": "2022-08-29T15:04:08+00:00" + }, { "name": "symfony/deprecation-contracts", "version": "v2.5.1", diff --git a/lib/Command/ListShares.php b/lib/Command/ListShares.php index eb9683c..6e94d60 100644 --- a/lib/Command/ListShares.php +++ b/lib/Command/ListShares.php @@ -37,13 +37,6 @@ public function configure() { $this->setName('sharing:list') ->setDescription('List who has access to shares by owner') - ->addOption( - 'output', - 'o', - InputOption::VALUE_OPTIONAL, - 'Output format (json or csv, default is json)', - 'json' - ) ->addOption( 'output', 'o', diff --git a/lib/Command/SendShares.php b/lib/Command/SendShares.php index b270c8d..5127f4f 100644 --- a/lib/Command/SendShares.php +++ b/lib/Command/SendShares.php @@ -57,6 +57,12 @@ public function configure() { $this->setName('sharing:send') ->setDescription('Send list who has access to shares by owner') + ->addOption( + 'diff', + 'd', + InputOption::VALUE_NONE, + 'Create a differential report in json format from the last available report' + ) ->addOption( 'recipients', 'r', @@ -75,6 +81,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->checkAllRequiredOptionsAreNotEmpty($input); [$user, $path, $token, $filter] = $this->getOptions($input); + $diff = $input->getOption('diff'); $recipients = $input->getOption('recipients'); $targetPath = $input->getOption('target-path'); @@ -91,9 +98,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $token ); + if ($diff) { + $this->reportSender->diff($recipient, $targetPath); + } + $this->reportSender->sendReport($recipient, $dateTime); } - return 0; } diff --git a/lib/Service/ReportSender.php b/lib/Service/ReportSender.php index dcd16d2..f9cfbd0 100644 --- a/lib/Service/ReportSender.php +++ b/lib/Service/ReportSender.php @@ -27,12 +27,21 @@ namespace OCA\ShareListing\Service; +use Icewind\SMB\Exception\NotFoundException; use iter; +use OC\Files\Search\SearchBinaryOperator; +use OC\Files\Search\SearchComparison; +use OC\Files\Search\SearchOrder; +use OC\Files\Search\SearchQuery; use OCA\ShareListing\Service\SharesList; use OCP\Defaults; use OCP\Files\FileInfo; use OCP\Files\Folder; +use OCP\Files\InvalidDirectoryException; use OCP\Files\IRootFolder; +use OCP\Files\Search\ISearchBinaryOperator; +use OCP\Files\Search\ISearchComparison; +use OCP\Files\Search\ISearchOrder; use OCP\IConfig; use OCP\IURLGenerator; use OCP\IUserManager; @@ -40,14 +49,18 @@ use OCP\Mail\IMailer; use OCP\Util; use Psr\Log\LoggerInterface; +use Swaggest\JsonDiff\JsonDiff; -class ReportSender { - public const ACTIVITY_LIMIT = 20; +class ReportSender +{ + protected const REPORT_NAME = ' - Shares report.'; /** @var string */ private $appName; /** @var Iconfig */ private $config; + /** @var ?array */ + protected $diffReport = null; private $mailer; private $userManager; @@ -115,22 +128,27 @@ public function createReport( $formats = ['json', 'csv']; $formatedDateTime = $dateTime->format('YmdHi'); foreach ($formats as $key => $format) { - $fileName = $formatedDateTime . ' - Shares report.' . $format; + $fileName = $formatedDateTime . self::REPORT_NAME . $format; if (!array_key_exists($fileName, $this->reports)) { if ($key === array_key_first($formats)) { $shares = iter\toArray($this->sharesList->getFormattedShares($userId, $filter, $path, $token)); } $reportFile = $folder->newFile($fileName); - $reportFile->putContent($this->sharesList->getSerializedShares($shares, $format)); - $this->reports[$reportFile->getName()] = $this->url->linkToRouteAbsolute( - 'files.View.showFile', - ['fileid' => $reportFile->getId()] - ); + $data = $this->sharesList->getSerializedShares($shares, $format); + $reportFile->putContent($data); + $this->reports[$reportFile->getName()] = [ + 'url' => $this->url->linkToRouteAbsolute( + 'files.View.showFile', + ['fileid' => $reportFile->getId()] + ), + 'data' => $data + ]; } } } - public function sendReport(string $recipient, \DateTimeImmutable $dateTime) { + public function sendReport(string $recipient, \DateTimeImmutable $dateTime) + { $defaultLanguage = $this->config->getSystemValue('default_language', 'en'); $userLanguages = $this->config->getUserValue($recipient, 'core', 'lang'); $language = (!empty($userLanguages)) ? $userLanguages : $defaultLanguage; @@ -146,12 +164,21 @@ public function sendReport(string $recipient, \DateTimeImmutable $dateTime) { $template->addHeader(); $template->addBodyText('You can find the list of shares reports generated on ' . $formatedDateTime . ':'); - foreach ($this->reports as $name => $url) { + foreach ($this->reports as $name => $value) { $template->addBodyListItem( - '' . $name . '', + '' . $name . '', '', '', - $name . ': ' . $url + $name . ': ' . $value['url'] + ); + } + if ($this->diffReport !== null) { + $template->addBodyText('You can also find the differential between this report and the previous one:'); + $template->addBodyListItem( + '' . $this->diffReport['fileName'] . '', + '', + '', + $this->diffReport['fileName'] . ': ' . $this->diffReport['url'] ); } @@ -170,7 +197,8 @@ public function sendReport(string $recipient, \DateTimeImmutable $dateTime) { } } - protected function getEmailAdressFromUserId(string $userId): ?string { + protected function getEmailAdressFromUserId(string $userId): ?string + { $user = $this->userManager->get($userId); if ($user === null) { $this->logger->warning( @@ -191,4 +219,84 @@ protected function getEmailAdressFromUserId(string $userId): ?string { return $email; } + + public function diff( + string $userId, + string $dir + ) { + $userFolder = $this->root->getUserFolder($userId); + + if ($userFolder->nodeExists($dir)) { + /** @var Folder $folder */ + $folder = $userFolder->get($dir); + if ($folder->getType() !== FileInfo::TYPE_FOLDER) { + throw new InvalidDirectoryException('Invalid directory, "' . $dir . '" not a folder'); + } + } else { + throw new InvalidDirectoryException('Invalid directory, "' . $dir . '" does not exist'); + } + + $search = $userFolder->search( + new SearchQuery( + new SearchBinaryOperator( + ISearchBinaryOperator::OPERATOR_OR, + [ + new SearchComparison( + ISearchComparison::COMPARE_LIKE, + 'name', + '%' . self::REPORT_NAME . 'json' + ) + ] + ), + 1, + 1, + [new SearchOrder(ISearchOrder::DIRECTION_DESCENDING, 'mtime')] + ) + ); + + if (empty($search)) { + throw new NotFoundException('No previous report found on this folder.'); + } + + $previousFile = $search[0]; + $previousFilename = $previousFile->getName(); + $previousDateTime = substr($previousFilename, 0, 12); + $previousContent = json_decode($previousFile->getContent()); + $previousContentWithId = []; + foreach ($previousContent as $value) { + $previousContentWithId[$value->id] = $value; + } + + $newFilename = array_keys($this->reports)[0]; + $newDateTime = substr($newFilename, 0, 12); + $newContent = json_decode(array_values($this->reports)[0]['data']); + $newContentWithId = []; + foreach ($newContent as $value) { + $newContentWithId[$value->id] = $value; + } + + $jsonDiff = new JsonDiff( + $previousContentWithId, + $newContentWithId, + JsonDiff::REARRANGE_ARRAYS + JsonDiff::COLLECT_MODIFIED_DIFF + ); + + $reportFilename = $previousDateTime . ' - ' . $newDateTime . ' - Shares report diff.json'; + $reportFile = $folder->newFile($reportFilename); + $res = [ + 'added' => $jsonDiff->getAdded(), + 'removed' => $jsonDiff->getRemoved(), + 'modified' => $jsonDiff->getModifiedDiff() + ]; + + $reportFile->putContent(json_encode($res, JSON_PRETTY_PRINT)); + + $this->diffReport = [ + 'url' => $this->url->linkToRouteAbsolute( + 'files.View.showFile', + ['fileid' => $reportFile->getId()] + ), + 'fileName' => $reportFilename + ]; + } }