2017-06-20 14:31:52 +00:00
< ? php
/**
* @ copyright Copyright ( c ) 2017 Robin Appelman < robin @ icewind . nl >
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http :// www . gnu . org / licenses />.
*
*/
2017-06-21 21:50:20 +00:00
namespace OC\Repair\NC13 ;
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
use OCP\DB\QueryBuilder\IQueryBuilder ;
2017-06-29 12:45:08 +00:00
use OCP\IConfig ;
2017-06-20 14:31:52 +00:00
use OCP\IDBConnection ;
use OCP\Migration\IOutput ;
use OCP\Migration\IRepairStep ;
class RepairInvalidPaths implements IRepairStep {
2017-07-12 13:49:36 +00:00
const MAX_ROWS = 1000 ;
2017-06-20 14:31:52 +00:00
/** @var IDBConnection */
private $connection ;
2017-06-29 12:45:08 +00:00
/** @var IConfig */
private $config ;
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
private $getIdQuery ;
private $updateQuery ;
private $reparentQuery ;
private $deleteQuery ;
2017-06-29 12:45:08 +00:00
public function __construct ( IDBConnection $connection , IConfig $config ) {
2017-06-20 14:31:52 +00:00
$this -> connection = $connection ;
2017-06-29 12:45:08 +00:00
$this -> config = $config ;
2017-06-20 14:31:52 +00:00
}
public function getName () {
return 'Repair invalid paths in file cache' ;
}
2017-07-20 20:48:13 +00:00
/**
* @ return \Generator
* @ suppress SqlInjectionChecker
*/
2017-06-20 14:31:52 +00:00
private function getInvalidEntries () {
$builder = $this -> connection -> getQueryBuilder ();
$computedPath = $builder -> func () -> concat (
'p.path' ,
$builder -> func () -> concat ( $builder -> createNamedParameter ( '/' ), 'f.name' )
);
//select f.path, f.parent,p.path from oc_filecache f inner join oc_filecache p on f.parent=p.fileid and p.path!='' where f.path != p.path || '/' || f.name;
2017-07-22 10:48:10 +00:00
$builder -> select ( 'f.fileid' , 'f.path' , 'f.name' , 'f.parent' , 'f.storage' )
-> selectAlias ( 'p.path' , 'parent_path' )
-> selectAlias ( 'p.storage' , 'parent_storage' )
2017-06-20 14:31:52 +00:00
-> from ( 'filecache' , 'f' )
-> innerJoin ( 'f' , 'filecache' , 'p' , $builder -> expr () -> andX (
$builder -> expr () -> eq ( 'f.parent' , 'p.fileid' ),
2017-07-25 13:34:59 +00:00
$builder -> expr () -> nonEmptyString ( 'p.name' )
2017-06-20 14:31:52 +00:00
))
2017-07-12 13:49:36 +00:00
-> where ( $builder -> expr () -> neq ( 'f.path' , $computedPath ))
-> setMaxResults ( self :: MAX_ROWS );
do {
2017-07-22 10:48:10 +00:00
$result = $builder -> execute ();
2017-07-12 13:49:36 +00:00
$rows = $result -> fetchAll ();
foreach ( $rows as $row ) {
yield $row ;
}
$result -> closeCursor ();
2017-07-18 12:01:49 +00:00
} while ( count ( $rows ) > 0 );
2017-06-20 14:31:52 +00:00
}
private function getId ( $storage , $path ) {
2017-07-12 13:49:36 +00:00
if ( ! $this -> getIdQuery ) {
$builder = $this -> connection -> getQueryBuilder ();
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
$this -> getIdQuery = $builder -> select ( 'fileid' )
-> from ( 'filecache' )
-> where ( $builder -> expr () -> eq ( 'storage' , $builder -> createParameter ( 'storage' )))
2017-08-09 08:56:16 +00:00
-> andWhere ( $builder -> expr () -> eq ( 'path_hash' , $builder -> createParameter ( 'path_hash' )));
2017-07-12 13:49:36 +00:00
}
$this -> getIdQuery -> setParameter ( 'storage' , $storage , IQueryBuilder :: PARAM_INT );
2017-08-09 08:56:16 +00:00
$this -> getIdQuery -> setParameter ( 'path_hash' , md5 ( $path ));
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
return $this -> getIdQuery -> execute () -> fetchColumn ();
2017-06-20 14:31:52 +00:00
}
2017-07-20 20:48:13 +00:00
/**
* @ param string $fileid
* @ param string $newPath
2017-07-19 10:30:59 +00:00
* @ param string $newStorage
2017-07-20 20:48:13 +00:00
* @ suppress SqlInjectionChecker
*/
2017-07-19 10:30:59 +00:00
private function update ( $fileid , $newPath , $newStorage ) {
2017-07-12 13:49:36 +00:00
if ( ! $this -> updateQuery ) {
$builder = $this -> connection -> getQueryBuilder ();
$this -> updateQuery = $builder -> update ( 'filecache' )
-> set ( 'path' , $builder -> createParameter ( 'newpath' ))
-> set ( 'path_hash' , $builder -> func () -> md5 ( $builder -> createParameter ( 'newpath' )))
2017-07-19 10:30:59 +00:00
-> set ( 'storage' , $builder -> createParameter ( 'newstorage' ))
2017-07-12 13:49:36 +00:00
-> where ( $builder -> expr () -> eq ( 'fileid' , $builder -> createParameter ( 'fileid' )));
}
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
$this -> updateQuery -> setParameter ( 'newpath' , $newPath );
2017-07-19 10:30:59 +00:00
$this -> updateQuery -> setParameter ( 'newstorage' , $newStorage );
2017-07-12 13:49:36 +00:00
$this -> updateQuery -> setParameter ( 'fileid' , $fileid , IQueryBuilder :: PARAM_INT );
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
$this -> updateQuery -> execute ();
2017-06-20 14:31:52 +00:00
}
private function reparent ( $from , $to ) {
2017-07-12 13:49:36 +00:00
if ( ! $this -> reparentQuery ) {
$builder = $this -> connection -> getQueryBuilder ();
$this -> reparentQuery = $builder -> update ( 'filecache' )
-> set ( 'parent' , $builder -> createParameter ( 'to' ))
-> where ( $builder -> expr () -> eq ( 'fileid' , $builder -> createParameter ( 'from' )));
}
$this -> reparentQuery -> setParameter ( 'from' , $from );
$this -> reparentQuery -> setParameter ( 'to' , $to );
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
$this -> reparentQuery -> execute ();
2017-06-20 14:31:52 +00:00
}
private function delete ( $fileid ) {
2017-07-12 13:49:36 +00:00
if ( ! $this -> deleteQuery ) {
$builder = $this -> connection -> getQueryBuilder ();
$this -> deleteQuery = $builder -> delete ( 'filecache' )
-> where ( $builder -> expr () -> eq ( 'fileid' , $builder -> createParameter ( 'fileid' )));
}
$this -> deleteQuery -> setParameter ( 'fileid' , $fileid , IQueryBuilder :: PARAM_INT );
2017-06-20 14:31:52 +00:00
2017-07-12 13:49:36 +00:00
$this -> deleteQuery -> execute ();
2017-06-20 14:31:52 +00:00
}
private function repair () {
2017-07-12 11:37:42 +00:00
$this -> connection -> beginTransaction ();
2017-06-20 14:31:52 +00:00
$entries = $this -> getInvalidEntries ();
2017-07-12 11:37:42 +00:00
$count = 0 ;
2017-06-20 14:31:52 +00:00
foreach ( $entries as $entry ) {
2017-07-12 11:37:42 +00:00
$count ++ ;
2017-06-20 14:31:52 +00:00
$calculatedPath = $entry [ 'parent_path' ] . '/' . $entry [ 'name' ];
2017-07-19 10:30:59 +00:00
if ( $newId = $this -> getId ( $entry [ 'parent_storage' ], $calculatedPath )) {
2017-06-20 14:31:52 +00:00
// a new entry with the correct path has already been created, reuse that one and delete the incorrect entry
$this -> reparent ( $entry [ 'fileid' ], $newId );
$this -> delete ( $entry [ 'fileid' ]);
} else {
2017-07-19 10:30:59 +00:00
$this -> update ( $entry [ 'fileid' ], $calculatedPath , $entry [ 'parent_storage' ]);
2017-06-20 14:31:52 +00:00
}
}
2017-07-12 11:37:42 +00:00
$this -> connection -> commit ();
return $count ;
2017-06-20 14:31:52 +00:00
}
public function run ( IOutput $output ) {
2017-06-21 21:50:20 +00:00
$versionFromBeforeUpdate = $this -> config -> getSystemValue ( 'version' , '0.0.0' );
// was added to 12.0.0.30 and 13.0.0.1
if ( version_compare ( $versionFromBeforeUpdate , '12.0.0.30' , '<' ) || version_compare ( $versionFromBeforeUpdate , '13.0.0.0' , '==' )) {
$count = $this -> repair ();
2017-06-20 14:31:52 +00:00
2017-06-21 21:50:20 +00:00
$output -> info ( 'Repaired ' . $count . ' paths' );
}
2017-06-20 14:31:52 +00:00
}
}