%PDF- %PDF-
| Direktori : /www/varak.net/nextcloud.varak.net/apps_old/apps/circles/lib/Service/ |
| Current File : //www/varak.net/nextcloud.varak.net/apps_old/apps/circles/lib/Service/MaintenanceService.php |
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Circles\Service;
use Exception;
use OC\User\NoUserException;
use OCA\Circles\Db\AccountsRequest;
use OCA\Circles\Db\CircleRequest;
use OCA\Circles\Db\EventWrapperRequest;
use OCA\Circles\Db\MemberRequest;
use OCA\Circles\Db\ShareWrapperRequest;
use OCA\Circles\Exceptions\InitiatorNotFoundException;
use OCA\Circles\Exceptions\MaintenanceException;
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\IFederatedUser;
use OCA\Circles\Model\Circle;
use OCA\Circles\Model\Member;
use OCA\Circles\Model\Probes\CircleProbe;
use OCA\Circles\Model\ShareWrapper;
use OCA\Circles\Tools\Model\SimpleDataStore;
use OCA\Circles\Tools\Traits\TNCLogger;
use OCP\IGroupManager;
use OCP\IUserManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Class MaintenanceService
*
* @package OCA\Circles\Service
*/
class MaintenanceService {
use TNCLogger;
public const TIMEOUT = 18000;
public static $DELAY =
[
1 => 60, // every minute
2 => 300, // every 5 minutes
3 => 3600, // every hour
4 => 75400, // every day
5 => 432000 // evey week
];
private ?OutputInterface $output = null;
public function __construct(
private IUserManager $userManager,
private IGroupManager $groupManager,
private CircleRequest $circleRequest,
private AccountsRequest $accountRequest,
private MemberRequest $memberRequest,
private ShareWrapperRequest $shareWrapperRequest,
private EventWrapperRequest $eventWrapperRequest,
private SyncService $syncService,
private FederatedUserService $federatedUserService,
private ShareWrapperService $shareWrapperService,
private MembershipService $membershipService,
private EventWrapperService $eventWrapperService,
private CircleService $circleService,
private ConfigService $configService,
private LoggerInterface $logger,
) {
}
/**
* @param OutputInterface $output
*/
public function setOccOutput(OutputInterface $output): void {
$this->output = $output;
}
/**
* level=1 -> run every minute
* level=2 -> run every 5 minutes
* level=3 -> run every hour
* level=4 -> run every day
* level=5 -> run every week
*
* @param int $level
*
* @throws MaintenanceException
*/
public function runMaintenance(int $level, bool $forceRefresh = false): void {
$this->federatedUserService->bypassCurrentUserCondition(true);
$this->lockMaintenanceRun();
$this->debug('running maintenance (' . $level . ')');
switch ($level) {
case 1:
$this->runMaintenance1($forceRefresh);
break;
case 2:
$this->runMaintenance2($forceRefresh);
break;
case 3:
$this->runMaintenance3($forceRefresh);
break;
case 4:
$this->runMaintenance4($forceRefresh);
break;
case 5:
$this->runMaintenance5($forceRefresh);
break;
}
$this->configService->setAppValue(ConfigService::MAINTENANCE_RUN, '0');
}
/**
* @throws MaintenanceException
*/
private function lockMaintenanceRun(): void {
$run = $this->configService->getAppValueInt(ConfigService::MAINTENANCE_RUN);
if ($run > time() - self::TIMEOUT) {
throw new MaintenanceException('maintenance already running');
}
$this->configService->setAppValue(ConfigService::MAINTENANCE_RUN, (string)time());
}
/**
* every minute
*/
private function runMaintenance1(bool $forceRefresh = false): void {
try {
$this->output('Remove circles with no owner');
$this->removeCirclesWithNoOwner();
} catch (Exception $e) {
}
}
/**
* every 10 minutes
*/
private function runMaintenance2(bool $forceRefresh = false): void {
try {
$this->output('Remove members with no circles');
$this->removeMembersWithNoCircles();
} catch (Exception $e) {
}
try {
$this->output('Retry failed FederatedEvents (asap)');
$this->eventWrapperService->retry(EventWrapperService::RETRY_ASAP);
} catch (Exception $e) {
}
}
/**
* every hour
*/
private function runMaintenance3(bool $forceRefresh = false): void {
try {
$this->output('Retry failed FederatedEvents (hourly)');
$this->eventWrapperService->retry(EventWrapperService::RETRY_HOURLY);
} catch (Exception $e) {
}
}
/**
* every day
*/
private function runMaintenance4(bool $forceRefresh = false): void {
try {
$this->output('Retry failed FederatedEvents (daily)');
$this->eventWrapperService->retry(EventWrapperService::RETRY_DAILY);
} catch (Exception $e) {
}
try {
// TODO: waiting for confirmation of a good migration before cleaning orphan shares
if ($this->configService->getAppValueBool(ConfigService::MIGRATION_22_CONFIRMED)) {
$this->output('Remove deprecated shares');
$this->removeDeprecatedShares();
}
} catch (Exception $e) {
}
try {
$this->output('Synchronizing local entities');
$this->syncService->sync();
} catch (Exception $e) {
}
try {
$this->output('Delete old and terminated FederatedEvents');
$this->eventWrapperRequest->deleteOldEntries(false);
} catch (Exception $e) {
$this->logger->warning('issue while deleting old events', ['exception' => $e]);
}
}
/**
* every week
*/
private function runMaintenance5(bool $forceRefresh = false): void {
try {
$this->output('Update memberships');
$this->updateAllMemberships();
} catch (Exception $e) {
}
try {
$this->output('refresh members\' display name');
$this->refreshDisplayName($forceRefresh);
} catch (Exception $e) {
}
try {
// Can be removed in NC27.
$this->output('Remove orphan shares');
$this->removeOrphanShares();
} catch (Exception $e) {
}
try {
// Can be removed in NC27.
$this->output('fix sub-circle display name');
$this->fixSubCirclesDisplayName();
} catch (Exception $e) {
}
}
/**
* @throws InitiatorNotFoundException
* @throws RequestBuilderException
*/
private function removeCirclesWithNoOwner(): void {
$probe = new CircleProbe();
$probe->includeSystemCircles()
->includeSingleCircles()
->includePersonalCircles();
$circles = $this->circleService->getCircles($probe);
foreach ($circles as $circle) {
if (!$circle->hasOwner()) {
$this->circleRequest->delete($circle);
}
}
}
/**
*
*/
private function removeMembersWithNoCircles(): void {
// $members = $this->membersRequest->forceGetAllMembers();
//
// foreach ($members as $member) {
// try {
// $this->circlesRequest->forceGetCircle($member->getCircleId());
// } catch (CircleDoesNotExistException $e) {
// $this->membersRequest->removeMember($member);
// }
// }
}
private function removeOrphanShares(): void {
$this->shareWrapperRequest->removeOrphanShares();
}
/**
* @throws RequestBuilderException
*/
private function removeDeprecatedShares(): void {
$probe = new CircleProbe();
$probe->includePersonalCircles()
->includeSingleCircles()
->includeSystemCircles();
$circles = array_map(
function (Circle $circle) {
return $circle->getSingleId();
}, $this->circleRequest->getCircles(null, $probe)
);
$shares = array_unique(
array_map(
function (ShareWrapper $share) {
return $share->getSharedWith();
}, $this->shareWrapperRequest->getShares()
)
);
foreach ($shares as $share) {
if (!in_array($share, $circles)) {
$this->shareWrapperService->deleteAllSharesToCircle($share);
}
}
}
/**
* @throws InitiatorNotFoundException
* @throws RequestBuilderException
*/
private function updateAllMemberships(): void {
$probe = new CircleProbe();
$probe->includeSystemCircles()
->includeSingleCircles()
->includePersonalCircles();
foreach ($this->circleService->getCircles($probe) as $circle) {
$this->membershipService->manageMemberships($circle->getSingleId());
}
}
/**
* @throws RequestBuilderException
* @throws InitiatorNotFoundException
*/
private function refreshDisplayName(bool $forceRefresh = false): void {
$circleFilter = new Circle();
$circleFilter->setConfig(Circle::CFG_SINGLE);
$probe = new CircleProbe();
$probe->includeSingleCircles()
->setFilterCircle($circleFilter)
->mustBeOwner();
$circles = $this->circleService->getCircles($probe);
foreach ($circles as $circle) {
$owner = $circle->getOwner();
if (!$forceRefresh && $owner->getDisplayUpdate() > (time() - 691200)) {
continue; // ignore update done in the last 8 days.
}
$this->updateDisplayName($owner);
}
}
/**
* @param IFederatedUser $federatedUser
*
* @return string
* @throws NoUserException
*/
public function updateDisplayName(IFederatedUser $federatedUser): string {
if ($federatedUser->getUserType() !== Member::TYPE_USER) {
return '';
}
$user = $this->userManager->get($federatedUser->getUserId());
if ($user === null) {
throw new NoUserException();
}
$displayName = $user->getDisplayName();
if ($displayName !== '') {
$this->memberRequest->updateDisplayName($federatedUser->getSingleId(), $displayName);
$this->circleRequest->updateDisplayName($federatedUser->getSingleId(), $displayName);
}
return $displayName;
}
/**
* @throws RequestBuilderException
* @throws InitiatorNotFoundException
*/
private function fixSubCirclesDisplayName(): void {
$probe = new CircleProbe();
$probe->includeSingleCircles();
$circles = $this->circleService->getCircles($probe);
foreach ($circles as $circle) {
$this->memberRequest->updateDisplayName($circle->getSingleId(), $circle->getDisplayName());
}
}
/**
* should only be called from a BackgroundJob
*
* @param bool $heavy - set to true to run heavy maintenance process.
*/
public function runMaintenances(bool $heavy = false): void {
$last = new SimpleDataStore();
$last->json($this->configService->getAppValue(ConfigService::MAINTENANCE_UPDATE));
$maxLevel = ($heavy) ? 5 : 3;
for ($i = $maxLevel; $i > 0; $i--) {
if ($this->canRunLevel($i, $last)) {
try {
$this->runMaintenance($i);
} catch (MaintenanceException $e) {
continue;
}
$last->sInt((string)$i, time());
}
}
$this->configService->setAppValue(ConfigService::MAINTENANCE_UPDATE, json_encode($last));
}
/**
* @param int $level
* @param SimpleDataStore $last
*
* @return bool
*/
private function canRunLevel(int $level, SimpleDataStore $last): bool {
$now = time();
$timeLastRun = $last->gInt((string)$level);
if ($timeLastRun === 0) {
return true;
}
return ($timeLastRun + self::$DELAY[$level] < $now);
}
/**
* @param string $message
*/
private function output(string $message): void {
$this->output?->writeln('- ' . $message);
}
}