2017-06-20 17:31:52 +03: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-22 00:50:20 +03:00
namespace OC\Repair\NC13 ;
2017-06-20 17:31:52 +03:00
2017-06-29 15:45:08 +03:00
use OCP\IConfig ;
2017-06-20 17:31:52 +03:00
use OCP\IDBConnection ;
use OCP\Migration\IOutput ;
use OCP\Migration\IRepairStep ;
class RepairInvalidPaths implements IRepairStep {
/** @var IDBConnection */
private $connection ;
2017-06-29 15:45:08 +03:00
/** @var IConfig */
private $config ;
2017-06-20 17:31:52 +03:00
2017-06-29 15:45:08 +03:00
public function __construct ( IDBConnection $connection , IConfig $config ) {
2017-06-20 17:31:52 +03:00
$this -> connection = $connection ;
2017-06-29 15:45:08 +03:00
$this -> config = $config ;
2017-06-20 17:31:52 +03:00
}
public function getName () {
return 'Repair invalid paths in file cache' ;
}
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;
$query = $builder -> select ( 'f.fileid' , 'f.path' , 'p.path AS parent_path' , 'f.name' , 'f.parent' , 'f.storage' )
-> from ( 'filecache' , 'f' )
-> innerJoin ( 'f' , 'filecache' , 'p' , $builder -> expr () -> andX (
$builder -> expr () -> eq ( 'f.parent' , 'p.fileid' ),
$builder -> expr () -> neq ( 'p.name' , $builder -> createNamedParameter ( '' ))
))
-> where ( $builder -> expr () -> neq ( 'f.path' , $computedPath ));
return $query -> execute () -> fetchAll ();
}
private function getId ( $storage , $path ) {
$builder = $this -> connection -> getQueryBuilder ();
$query = $builder -> select ( 'fileid' )
-> from ( 'filecache' )
-> where ( $builder -> expr () -> eq ( 'storage' , $builder -> createNamedParameter ( $storage )))
-> andWhere ( $builder -> expr () -> eq ( 'path' , $builder -> createNamedParameter ( $path )));
return $query -> execute () -> fetchColumn ();
}
private function update ( $fileid , $newPath ) {
$builder = $this -> connection -> getQueryBuilder ();
$query = $builder -> update ( 'filecache' )
-> set ( 'path' , $builder -> createNamedParameter ( $newPath ))
-> set ( 'path_hash' , $builder -> createNamedParameter ( md5 ( $newPath )))
-> where ( $builder -> expr () -> eq ( 'fileid' , $builder -> createNamedParameter ( $fileid )));
$query -> execute ();
}
private function reparent ( $from , $to ) {
$builder = $this -> connection -> getQueryBuilder ();
$query = $builder -> update ( 'filecache' )
-> set ( 'parent' , $builder -> createNamedParameter ( $to ))
-> where ( $builder -> expr () -> eq ( 'fileid' , $builder -> createNamedParameter ( $from )));
$query -> execute ();
}
private function delete ( $fileid ) {
$builder = $this -> connection -> getQueryBuilder ();
$query = $builder -> delete ( 'filecache' )
-> where ( $builder -> expr () -> eq ( 'fileid' , $builder -> createNamedParameter ( $fileid )));
$query -> execute ();
}
private function repair () {
$entries = $this -> getInvalidEntries ();
foreach ( $entries as $entry ) {
$calculatedPath = $entry [ 'parent_path' ] . '/' . $entry [ 'name' ];
if ( $newId = $this -> getId ( $entry [ 'storage' ], $calculatedPath )) {
// 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 {
$this -> update ( $entry [ 'fileid' ], $calculatedPath );
}
}
return count ( $entries );
}
public function run ( IOutput $output ) {
2017-06-22 00:50:20 +03: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 17:31:52 +03:00
2017-06-22 00:50:20 +03:00
$output -> info ( 'Repaired ' . $count . ' paths' );
}
2017-06-20 17:31:52 +03:00
}
}