Merge pull request #56 from nextcloud/fix_bg

Improve backgroundjob
This commit is contained in:
Roeland Jago Douma 2018-03-05 21:50:13 +01:00 коммит произвёл GitHub
Родитель 0d6f9c2f71 9bd88768dd
Коммит 3d08a44647
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 125 добавлений и 341 удалений

Просмотреть файл

@ -8,18 +8,15 @@
namespace OCA\Files_Antivirus;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use OCA\Files_Antivirus\Scanner\ScannerFactory;
use OC\Files\Filesystem;
use OCP\Files\File;
use OCP\Files\IMimeTypeLoader;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
class BackgroundScanner {
@ -28,9 +25,6 @@ class BackgroundScanner {
/** @var IRootFolder */
protected $rootFolder;
/** @var Folder[] */
protected $userFolders;
/** @var ScannerFactory */
private $scannerFactory;
@ -40,12 +34,6 @@ class BackgroundScanner {
/** @var AppConfig */
private $appConfig;
/** @var string */
protected $currentFilesystemUser;
/** @var IUserSession */
protected $userSession;
/** @var ILogger */
protected $logger;
@ -65,7 +53,6 @@ class BackgroundScanner {
* @param IL10N $l10n
* @param AppConfig $appConfig
* @param IRootFolder $rootFolder
* @param IUserSession $userSession
* @param ILogger $logger
* @param IUserManager $userManager
* @param IDBConnection $db
@ -75,7 +62,6 @@ class BackgroundScanner {
IL10N $l10n,
AppConfig $appConfig,
IRootFolder $rootFolder,
IUserSession $userSession,
ILogger $logger,
IUserManager $userManager,
IDBConnection $db,
@ -85,7 +71,6 @@ class BackgroundScanner {
$this->scannerFactory = $scannerFactory;
$this->l10n = $l10n;
$this->appConfig = $appConfig;
$this->userSession = $userSession;
$this->logger = $logger;
$this->userManager = $userManager;
$this->db = $db;
@ -121,22 +106,12 @@ class BackgroundScanner {
$this->logger->error( __METHOD__ . ', exception: ' . $e->getMessage(), ['app' => 'files_antivirus']);
}
}
$this->tearDownFilesystem();
}
protected function getFilesForScan(){
$dirMimeTypeId = $this->mimeTypeLoader->getId('httpd/unix-directory');
$qb = $this->db->getQueryBuilder();
if ($this->db->getDatabasePlatform() instanceof MySqlPlatform) {
$concatFunction = $qb->createFunction(
"CONCAT('/', mnt.user_id, '/')"
);
} else {
$concatFunction = $qb->createFunction(
"'/' || " . $qb->getColumnName('mnt.user_id') . " || '/'"
);
}
$sizeLimit = (int)$this->appConfig->getAvMaxFileSize();
if ( $sizeLimit === -1 ){
@ -144,7 +119,7 @@ class BackgroundScanner {
} else {
$sizeLimitExpr = $qb->expr()->andX(
$qb->expr()->neq('fc.size', $qb->expr()->literal('0')),
$qb->expr()->lt('fc.size', $qb->expr()->literal((string) $sizeLimit))
$qb->expr()->lt('fc.size', $qb->createNamedParameter($sizeLimit))
);
}
@ -156,8 +131,7 @@ class BackgroundScanner {
'mounts',
'mnt',
$qb->expr()->andX(
$qb->expr()->eq('fc.storage', 'mnt.storage_id'),
$qb->expr()->eq('mnt.mount_point', $concatFunction)
$qb->expr()->eq('fc.storage', 'mnt.storage_id')
)
)
->where(
@ -181,56 +155,24 @@ class BackgroundScanner {
* @param IUser $owner
* @param int $fileId
*/
protected function scanOneFile($owner, $fileId){
$this->initFilesystemForUser($owner);
$view = Filesystem::getView();
$path = $view->getPath($fileId);
if (!is_null($path)) {
$item = new Item($this->l10n, $view, $path, $fileId);
$scanner = $this->scannerFactory->getScanner();
$status = $scanner->scan($item);
$status->dispatch($item, true);
protected function scanOneFile(IUser $owner, $fileId){
$userFolder = $this->rootFolder->getUserFolder($owner->getUID());
$files = $userFolder->getById($fileId);
if (count($files) === 0) {
return;
}
}
/**
* @param \OCP\IUser $user
* @return \OCP\Files\Folder
*/
protected function getUserFolder(IUser $user) {
if (!isset($this->userFolders[$user->getUID()])) {
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
$this->userFolders[$user->getUID()] = $userFolder;
/** @var File $file */
$file = array_pop($files);
if (!($file instanceof File)) {
return;
}
return $this->userFolders[$user->getUID()];
}
/**
* @param IUser $user
*/
protected function initFilesystemForUser(IUser $user) {
if ($this->currentFilesystemUser !== $user->getUID()) {
if ($this->currentFilesystemUser !== '') {
$this->tearDownFilesystem();
}
Filesystem::init($user->getUID(), '/' . $user->getUID() . '/files');
$this->userSession->setUser($user);
$this->currentFilesystemUser = $user->getUID();
Filesystem::initMountPoints($user->getUID());
}
}
/**
*
*/
protected function tearDownFilesystem(){
$this->userSession->setUser(null);
\OC_Util::tearDownFS();
}
/**
* @deprecated since v8.0.0
*/
public static function check(){
$item = new Item($this->l10n, $file);
$scanner = $this->scannerFactory->getScanner();
$status = $scanner->scan($item);
$status->dispatch($item, true);
}
}

Просмотреть файл

@ -10,17 +10,26 @@ namespace OCA\Files_Antivirus\Db;
use OCP\AppFramework\Db\Entity;
class Item extends Entity{
/**
* Class Item
*
* @package OCA\Files_Antivirus\Db
*
* @method int getFileid()
* @method setFileid(int $id)
* @method int getCheckTime()
* @method setCheckTime(int $time)
*/
class Item extends Entity {
/**
* fileid that was scanned
* @var int
*/
protected $fileid;
/**
* Timestamp of the check
* @var int
*/
protected $checkTime;
}

Просмотреть файл

@ -8,6 +8,8 @@
namespace OCA\Files_Antivirus\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\Mapper;
use OCP\IDBConnection;
@ -15,4 +17,31 @@ class ItemMapper extends Mapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'files_antivirus', Item::class);
}
/**
* Find rule by id
* @param int $fileid
* @return Rule
* @throws DoesNotExistException
*/
public function findByFileId($fileid){
$sql = 'SELECT * FROM ' . $this->getTableName() .' WHERE fileid = ?';
return $this->findEntity($sql, [$fileid]);
}
public function delete(Entity $entity) {
if (!($entity instanceof Item)) {
throw new \InvalidArgumentException();
}
$qb = $this->db->getQueryBuilder();
$qb->delete('files_antivirus')
->where(
$qb->expr()->eq('fileid', $qb->createNamedParameter($entity->getFileid()))
);
$qb->execute();
return $entity;
}
}

Просмотреть файл

@ -31,8 +31,8 @@ class RuleMapper extends Mapper {
* @return Rule
*/
public function find($id){
$sql = 'SELECT * FROM *PREFIX*files_avir_status WHERE id = ?';
return $this->findEntity($sql, [$id]);
$sql = 'SELECT * FROM *PREFIX*files_avir_status WHERE id = ?';
return $this->findEntity($sql, [$id]);
}
/**
@ -40,7 +40,7 @@ class RuleMapper extends Mapper {
*/
public function findAll(){
$sql = 'SELECT * FROM `*PREFIX*files_avir_status`';
return $this->findEntities($sql);
return $this->findEntities($sql);
}
/**

Просмотреть файл

@ -8,80 +8,50 @@
namespace OCA\Files_Antivirus;
use OC\Files\View;
use OCA\Files_Antivirus\Activity\Provider;
use OCA\Files_Antivirus\AppInfo\Application;
use OCA\Files_Antivirus\Db\ItemMapper;
use OCP\Activity\IManager as ActivityManager;
use OCP\App;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Files\File;
use OCP\IL10N;
use OCP\ILogger;
class Item implements IScannable{
/**
* Scanned fileid (optional)
* @var int
*/
protected $id;
/**
* File view
* @var \OC\Files\View
*/
protected $view;
/**
* Path relative to the view
* @var string
*/
protected $path;
/**
* file handle, user to read from the file
* @var resource
*/
protected $fileHandle;
/**
* Portion size
* @var int
*/
protected $chunkSize;
/**
* Is filesize match the size conditions
* @var bool
*/
protected $isValidSize;
/**
* @var IL10N
*/
/** @var IL10N */
private $l10n;
public function __construct(IL10N $l10n, View $view, $path, $id = null) {
/** @var AppConfig */
private $config;
/** @var ActivityManager */
private $activityManager;
/** @var ItemMapper */
private $itemMapper;
/** @var ILogger */
private $logger;
/** @var File */
private $file;
public function __construct(IL10N $l10n, File $file) {
$this->l10n = $l10n;
if (!is_object($view)){
$this->logError('Can\'t init filesystem view.', $id, $path);
throw new \RuntimeException();
}
if(!$view->file_exists($path)) {
$this->logError('File does not exist.', $id, $path);
throw new \RuntimeException();
}
$this->file = $file;
$this->id = $id;
if (is_null($id)){
$this->id = $view->getFileInfo($path)->getId();
}
$this->view = $view;
$this->path = $path;
$this->isValidSize = $view->filesize($path) > 0;
$application = new AppInfo\Application();
$config = $application->getContainer()->query(AppConfig::class);
$this->chunkSize = $config->getAvChunkSize();
$this->config = $application->getContainer()->query(AppConfig::class);
$this->activityManager = \OC::$server->getActivityManager();
$this->itemMapper = $application->getContainer()->query(ItemMapper::class);
$this->logger = \OC::$server->getLogger();
}
/**
@ -89,7 +59,7 @@ class Item implements IScannable{
* @return boolean
*/
public function isValid() {
return !$this->view->is_dir($this->path) && $this->isValidSize;
return $this->file->getSize() > 0;
}
/**
@ -105,7 +75,7 @@ class Item implements IScannable{
}
if (!is_null($this->fileHandle) && !$this->feof()) {
return fread($this->fileHandle, $this->chunkSize);
return fread($this->fileHandle, $this->config->getAvChunkSize());
}
return false;
}
@ -116,23 +86,20 @@ class Item implements IScannable{
* @param boolean $isBackground
*/
public function processInfected(Status $status, $isBackground) {
$application = new AppInfo\Application();
$appConfig = $application->getContainer()->query('AppConfig');
$infectedAction = $appConfig->getAvInfectedAction();
$infectedAction = $this->config->getAvInfectedAction();
$shouldDelete = !$isBackground || ($isBackground && $infectedAction === 'delete');
$message = $shouldDelete ? Provider::MESSAGE_FILE_DELETED : '';
$activityManager = \OC::$server->getActivityManager();
$activity = $activityManager->generateEvent();
$activity = $this->activityManager->generateEvent();
$activity->setApp(Application::APP_NAME)
->setSubject(Provider::SUBJECT_VIRUS_DETECTED, [$this->path, $status->getDetails()])
->setSubject(Provider::SUBJECT_VIRUS_DETECTED, [$this->file->getPath(), $status->getDetails()])
->setMessage($message)
->setObject('', 0, $this->path)
->setAffectedUser($this->view->getOwner($this->path))
->setObject('file', $this->file->getId(), $this->file->getPath())
->setAffectedUser($this->file->getOwner()->getUID())
->setType(Provider::TYPE_VIRUS_DETECTED);
$activityManager->publish($activity);
$this->activityManager->publish($activity);
if ($isBackground) {
if ($shouldDelete) {
@ -145,10 +112,10 @@ class Item implements IScannable{
$this->logError('Virus(es) found: ' . $status->getDetails());
//remove file
$this->deleteFile();
Notification::sendMail($this->path);
Notification::sendMail($this->file->getPath());
$message = $this->l10n->t(
"Virus detected! Can't upload the file %s",
[basename($this->path)]
[$this->file->getName()]
);
\OCP\JSON::error(['data' => ['message' => $message]]);
exit();
@ -175,19 +142,19 @@ class Item implements IScannable{
return;
}
try {
$stmt = \OCP\DB::prepare('DELETE FROM `*PREFIX*files_antivirus` WHERE `fileid` = ?');
$result = $stmt->execute([$this->id]);
if (\OCP\DB::isError($result)) {
//TODO: Use logger
$this->logError(__METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage());
}
$stmt = \OCP\DB::prepare('INSERT INTO `*PREFIX*files_antivirus` (`fileid`, `check_time`) VALUES (?, ?)');
$result = $stmt->execute([$this->id, time()]);
if (\OCP\DB::isError($result)) {
$this->logError(__METHOD__. ', DB error: ' . \OCP\DB::getErrorMessage());
try {
$item = $this->itemMapper->findByFileId($this->file->getId());
$this->itemMapper->delete($item);
} catch (DoesNotExistException $e) {
//Just ignore
}
$item = new \OCA\Files_Antivirus\Db\Item();
$item->setFileid($this->file->getId());
$item->setCheckTime(time());
$this->itemMapper->insert($item);
} catch(\Exception $e) {
\OCP\Util::writeLog('files_antivirus', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR);
$this->logger->error(__METHOD__.', exception: '.$e->getMessage(), ['app' => 'files_antivirus']);
}
}
@ -210,9 +177,9 @@ class Item implements IScannable{
* @throws \RuntimeException
*/
private function getFileHandle() {
$fileHandle = $this->view->fopen($this->path, 'r');
$fileHandle = $this->file->fopen('r');
if ($fileHandle === false) {
$this->logError('Can not open for reading.', $this->id, $this->path);
$this->logError('Can not open for reading.');
throw new \RuntimeException();
}
@ -228,7 +195,7 @@ class Item implements IScannable{
if (App::isEnabled('files_trashbin')) {
\OCA\Files_Trashbin\Storage::preRenameHook([]);
}
$this->view->unlink($this->path);
$this->file->delete();
if (App::isEnabled('files_trashbin')) {
\OCA\Files_Trashbin\Storage::postRenameHook([]);
}
@ -238,26 +205,20 @@ class Item implements IScannable{
* @param string $message
*/
public function logDebug($message) {
$extra = ' File: ' . $this->id
. 'Account: ' . $this->view->getOwner($this->path)
. ' Path: ' . $this->path;
\OCP\Util::writeLog('files_antivirus', $message . $extra, \OCP\Util::DEBUG);
$extra = ' File: ' . $this->file->getId()
. 'Account: ' . $this->file->getOwner()->getUID()
. ' Path: ' . $this->file->getPath();
$this->logger->debug($message . $extra, ['app' => 'files_antivirus']);
}
/**
* @param string $message
* @param int $id optional
* @param string $path optional
*/
public function logError($message, $id=null, $path=null) {
$ownerInfo = is_null($this->view) ? '' : 'Account: ' . $this->view->getOwner($path);
$extra = ' File: ' . (is_null($id) ? $this->id : $id)
public function logError($message) {
$ownerInfo = 'Account: ' . $this->file->getOwner()->getUID();
$extra = ' File: ' . $this->file->getId()
. $ownerInfo
. ' Path: ' . (is_null($path) ? $this->path : $path);
\OCP\Util::writeLog(
'files_antivirus',
$message . $extra,
\OCP\Util::ERROR
);
. ' Path: ' . $this->file->getPath();
$this->logger->error($message . $extra, ['app' => 'files_antivirus']);
}
}

Просмотреть файл

@ -138,7 +138,7 @@ class Status {
return array_merge($cleanRules, $infectedRules, $uncheckedRules);
}
public function dispatch($item, $isBackground = false){
public function dispatch(Item $item, $isBackground = false){
switch($this->getNumericStatus()) {
case self::SCANRESULT_UNCHECKED:
$item->processUnchecked($this, $isBackground);

Просмотреть файл

@ -1,57 +0,0 @@
<?php
/**
* Copyright (c) 2015 Victor Dubiniuk <victor.dubiniuk@gmail.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_Antivirus\Tests;
use OC\Files\Filesystem;
use OC\Files\Storage\Temporary;
use OC\Files\View;
use OCA\Files_Antivirus\Item;
use Test\Traits\MountProviderTrait;
use Test\Traits\UserTrait;
// mmm. IDK why autoloader fails on this class
//include_once dirname(dirname(dirname(__DIR__))) . '/tests/lib/Util/User/Dummy.php';
/**
* @group DB
*/
class ItemTest extends TestBase {
use UserTrait;
use MountProviderTrait;
/**
* @var Temporary
*/
private $storage;
/** @var View */
private $view;
const CONTENT = 'LoremIpsum';
public function setUp() {
parent::setUp();
$this->createUser('test', 'test');
$this->storage = new Temporary([]);
$this->registerMount('test', $this->storage, '/test/files');
\OC_Util::tearDownFS();
\OC_Util::setupFS('test');
$this->view = new View('/test/files');
$this->view->file_put_contents('file1', self::CONTENT);
}
public function testRead() {
$item = new Item($this->l10n, $this->view, '/file1');
$this->assertTrue($item->isValid());
$chunk = $item->fread();
$this->assertEquals(self::CONTENT, $chunk);
}
}

Просмотреть файл

@ -1,100 +0,0 @@
<?php
/**
* Copyright (c) 2014 Victor Dubiniuk <victor.dubiniuk@gmail.com>
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\Files_Antivirus\Tests;
use OC\Files\View;
use \OCA\Files_Antivirus\Db\RuleMapper;
use \OCA\Files_Antivirus\Item;
use \OCA\Files_Antivirus\Scanner\ScannerFactory;
use Test\Traits\UserTrait;
/**
* @group DB
*/
class ScannerTest extends TestBase {
use UserTrait;
const TEST_CLEAN_FILENAME = 'foo.txt';
const TEST_INFECTED_FILENAME = 'kitten.inf';
/** @var RuleMapper */
protected $ruleMapper;
/** @var View|\PHPUnit_Framework_MockObject_MockObject */
protected $view;
/** @var Item */
protected $cleanItem;
/** @var Item */
protected $infectedItem;
/** @var ScannerFactory */
protected $scannerFactory;
public function setUp() {
parent::setUp();
$this->view = $this->getMockBuilder('\OC\Files\View')
->disableOriginalConstructor()
->getMock()
;
$this->view->expects($this->any())->method('getOwner')->willReturn('Dummy');
$this->view->expects($this->any())->method('file_exists')->willReturn(true);
$this->view->expects($this->any())->method('filesize')->willReturn(42);
$this->cleanItem = new Item($this->l10n, $this->view, self::TEST_CLEAN_FILENAME, 42);
$this->infectedItem = new Item($this->l10n, $this->view, self::TEST_INFECTED_FILENAME, 42);
$this->ruleMapper = new RuleMapper($this->db);
$this->ruleMapper->deleteAll();
$this->ruleMapper->populate();
$this->createUser('test', 'test');
$this->scannerFactory = new ScannerFactory(
$this->config,
$this->container->query('Logger')
);
}
public function testCleanFile() {
$handle = fopen(__DIR__ . '/data/foo.txt', 'r');
$this->view->expects($this->any())->method('fopen')->willReturn($handle);
$this->assertTrue($this->cleanItem->isValid());
$scanner = $this->scannerFactory->getScanner();
$scanner->scan($this->cleanItem);
$cleanStatus = $scanner->getStatus();
$this->assertInstanceOf('\OCA\Files_Antivirus\Status', $cleanStatus);
$this->assertEquals(\OCA\Files_Antivirus\Status::SCANRESULT_CLEAN, $cleanStatus->getNumericStatus());
}
/**
* @expectedException \RuntimeException
*/
public function testNotExisting() {
$fileView = new \OC\Files\View('');
$nonExistingItem = new Item($this->l10n, $fileView, 'non-existing.file', 42);
$scanner = $this->scannerFactory->getScanner();
$scanner->scan($nonExistingItem);
$unknownStatus = $scanner->scan($nonExistingItem);
$this->assertInstanceOf('\OCA\Files_Antivirus\Status', $unknownStatus);
$this->assertEquals(\OCA\Files_Antivirus\Status::SCANRESULT_UNCHECKED, $unknownStatus->getNumericStatus());
}
public function testInfected() {
$handle = fopen(__DIR__ . '/data/kitten.inf', 'r');
$this->view->expects($this->any())->method('fopen')->willReturn($handle);
$this->assertTrue($this->infectedItem->isValid());
$scanner = $this->scannerFactory->getScanner();
$scanner->scan($this->infectedItem);
$infectedStatus = $scanner->getStatus();
$this->assertInstanceOf('\OCA\Files_Antivirus\Status', $infectedStatus);
$this->assertEquals(\OCA\Files_Antivirus\Status::SCANRESULT_INFECTED, $infectedStatus->getNumericStatus());
}
}