filecache.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. <?php
  2. /**
  3. * @author Robin Appelman
  4. * @copyright 2011 Robin Appelman icewind1991@gmail.com
  5. *
  6. * This library is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or any later version.
  10. *
  11. * This library is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public
  17. * License along with this library. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. /**
  21. * provide caching for filesystem info in the database
  22. *
  23. * not used by OC_Filesystem for reading filesystem info,
  24. * instread apps should use OC_FileCache::get where possible
  25. *
  26. * It will try to keep the data up to date but changes from outside ownCloud can invalidate the cache
  27. */
  28. class OC_FileCache{
  29. /**
  30. * get the filesystem info from the cache
  31. * @param string path
  32. * @param string root (optional)
  33. * @return array
  34. *
  35. * returns an assiciative array with the following keys:
  36. * - size
  37. * - mtime
  38. * - ctime
  39. * - mimetype
  40. * - encrypted
  41. * - versioned
  42. */
  43. public static function get($path,$root=''){
  44. if(self::isUpdated($path,$root)){
  45. if(!$root){//filesystem hooks are only valid for the default root
  46. OC_Hook::emit('OC_Filesystem','post_write',array('path'=>$path));
  47. }else{
  48. self::fileSystemWatcherWrite(array('path'=>$path),$root);
  49. }
  50. }
  51. if(!$root){
  52. $root=OC_Filesystem::getRoot();
  53. }
  54. if($root=='/'){
  55. $root='';
  56. }
  57. $path=$root.$path;
  58. $query=OC_DB::prepare('SELECT ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE path=?');
  59. $result=$query->execute(array($path))->fetchRow();
  60. if(is_array($result)){
  61. return $result;
  62. }else{
  63. OC_Log::write('get(): file not found in cache ('.$path.')','core',OC_Log::DEBUG);
  64. return false;
  65. }
  66. }
  67. /**
  68. * put filesystem info in the cache
  69. * @param string $path
  70. * @param array data
  71. * @param string root (optional)
  72. *
  73. * $data is an assiciative array in the same format as returned by get
  74. */
  75. public static function put($path,$data,$root=''){
  76. if(!$root){
  77. $root=OC_Filesystem::getRoot();
  78. }
  79. if($root=='/'){
  80. $root='';
  81. }
  82. $path=$root.$path;
  83. if($path=='/'){
  84. $parent=-1;
  85. }else{
  86. $parent=self::getFileId(dirname($path));
  87. }
  88. $id=self::getFileId($path);
  89. if($id!=-1){
  90. self::update($id,$data);
  91. return;
  92. }
  93. if(!isset($data['encrypted'])){
  94. $data['encrypted']=false;
  95. }
  96. if(!isset($data['versioned'])){
  97. $data['versioned']=false;
  98. }
  99. $mimePart=dirname($data['mimetype']);
  100. $user=OC_User::getUser();
  101. $query=OC_DB::prepare('INSERT INTO *PREFIX*fscache(parent, name, path, size, mtime, ctime, mimetype, mimepart,user,writable) VALUES(?,?,?,?,?,?,?,?,?,?)');
  102. $query->execute(array($parent,basename($path),$path,$data['size'],$data['mtime'],$data['ctime'],$data['mimetype'],$mimePart,$user,$data['writable']));
  103. }
  104. /**
  105. * update filesystem info of a file
  106. * @param int $id
  107. * @param array $data
  108. */
  109. private static function update($id,$data){
  110. $arguments=array();
  111. $queryParts=array();
  112. foreach(array('size','mtime','ctime','mimetype','encrypted','versioned','writable') as $attribute){
  113. if(isset($data[$attribute])){
  114. $arguments[]=$data[$attribute];
  115. $queryParts[]=$attribute.'=?';
  116. }
  117. }
  118. if(isset($data['mimetype'])){
  119. $arguments[]=dirname($data['mimetype']);
  120. $queryParts[]='mimepart=?';
  121. }
  122. $arguments[]=$id;
  123. $sql = 'UPDATE *PREFIX*fscache SET '.implode(' , ',$queryParts).' WHERE id=?';
  124. $query=OC_DB::prepare($sql);
  125. $query->execute($arguments);
  126. }
  127. /**
  128. * register a file move in the cache
  129. * @param string oldPath
  130. * @param string newPath
  131. * @param string root (optional)
  132. */
  133. public static function move($oldPath,$newPath,$root=''){
  134. if(!$root){
  135. $root=OC_Filesystem::getRoot();
  136. }
  137. if($root=='/'){
  138. $root='';
  139. }
  140. $oldPath=$root.$oldPath;
  141. $newPath=$root.$newPath;
  142. $newParent=self::getParentId($newPath);
  143. $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET parent=? ,name=?, path=? WHERE path=?');
  144. $query->execute(array($newParent,basename($newPath),$newPath,$oldPath));
  145. }
  146. /**
  147. * delete info from the cache
  148. * @param string $path
  149. * @param string root (optional)
  150. */
  151. public static function delete($path,$root=''){
  152. if(!$root){
  153. $root=OC_Filesystem::getRoot();
  154. }
  155. if($root=='/'){
  156. $root='';
  157. }
  158. $path=$root.$path;
  159. $query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE path=?');
  160. $query->execute(array($path));
  161. }
  162. /**
  163. * return array of filenames matching the querty
  164. * @param string $query
  165. * @param boolean $returnData
  166. * @param string root (optional)
  167. * @return array of filepaths
  168. */
  169. public static function search($search,$returnData=false,$root=''){
  170. if(!$root){
  171. $root=OC_Filesystem::getRoot();
  172. }
  173. if($root=='/'){
  174. $root='';
  175. }
  176. $rootLen=strlen($root);
  177. if(!$returnData){
  178. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE name LIKE ? AND user=?');
  179. }else{
  180. $query=OC_DB::prepare('SELECT * FROM *PREFIX*fscache WHERE name LIKE ? AND user=?');
  181. }
  182. $result=$query->execute(array("%$search%",OC_User::getUser()));
  183. $names=array();
  184. while($row=$result->fetchRow()){
  185. if(!$returnData){
  186. $names[]=substr($row['path'],$rootLen);
  187. }else{
  188. $row['path']=substr($row['path'],$rootLen);
  189. $names[]=$row;
  190. }
  191. }
  192. return $names;
  193. }
  194. /**
  195. * get all files and folders in a folder
  196. * @param string path
  197. * @param string root (optional)
  198. * @return array
  199. *
  200. * returns an array of assiciative arrays with the following keys:
  201. * - name
  202. * - size
  203. * - mtime
  204. * - ctime
  205. * - mimetype
  206. * - encrypted
  207. * - versioned
  208. */
  209. public static function getFolderContent($path,$root=''){
  210. if(self::isUpdated($path,$root)){
  211. self::updateFolder($path,$root);
  212. }
  213. if(!$root){
  214. $root=OC_Filesystem::getRoot();
  215. }
  216. if($root=='/'){
  217. $root='';
  218. }
  219. $path=$root.$path;
  220. $parent=self::getFileId($path);
  221. $query=OC_DB::prepare('SELECT name,ctime,mtime,mimetype,size,encrypted,versioned,writable FROM *PREFIX*fscache WHERE parent=?');
  222. $result=$query->execute(array($parent))->fetchAll();
  223. if(is_array($result)){
  224. return $result;
  225. }else{
  226. OC_Log::write('getFolderContent(): file not found in cache ('.$path.')','core',OC_Log::DEBUG);
  227. return false;
  228. }
  229. }
  230. /**
  231. * check if a file or folder is in the cache
  232. * @param string $path
  233. * @param string root (optional)
  234. * @return bool
  235. */
  236. public static function inCache($path,$root=''){
  237. if(!$root){
  238. $root=OC_Filesystem::getRoot();
  239. }
  240. if($root=='/'){
  241. $root='';
  242. }
  243. $path=$root.$path;
  244. return self::getFileId($path)!=-1;
  245. }
  246. /**
  247. * get the file id as used in the cache
  248. * @param string $path
  249. * @return int
  250. */
  251. private static function getFileId($path){
  252. $query=OC_DB::prepare('SELECT id FROM *PREFIX*fscache WHERE path=?');
  253. $result=$query->execute(array($path))->fetchRow();
  254. if(is_array($result)){
  255. return $result['id'];
  256. }else{
  257. OC_Log::write('getFieldId(): file not found in cache ('.$path.')','core',OC_Log::DEBUG);
  258. return -1;
  259. }
  260. }
  261. /**
  262. * get the file id of the parent folder, taking into account '/' has no parent
  263. * @param string $path
  264. * @return int
  265. */
  266. private static function getParentId($path){
  267. if($path=='/'){
  268. return -1;
  269. }else{
  270. return self::getFileId(dirname($path));
  271. }
  272. }
  273. /**
  274. * called when changes are made to files
  275. * @param array $params
  276. * @param string root (optional)
  277. */
  278. public static function fileSystemWatcherWrite($params,$root=''){
  279. if(!$root){
  280. $view=OC_Filesystem::getView();
  281. }else{
  282. $view=new OC_FilesystemView(($root=='/')?'':$root);
  283. }
  284. $path=$params['path'];
  285. $fullPath=$view->getRoot().$path;
  286. $mimetype=$view->getMimeType($path);
  287. //dont use self::get here, we don't want inifinte loops when a file has changed
  288. $cachedSize=self::getCachedSize($path,$root);
  289. $size=0;
  290. if($mimetype=='httpd/unix-directory'){
  291. if(self::inCache($path,$root)){
  292. $parent=self::getFileId($fullPath);
  293. $query=OC_DB::prepare('SELECT size FROM *PREFIX*fscache WHERE parent=?');
  294. $result=$query->execute(array($parent));
  295. while($row=$result->fetchRow()){
  296. $size+=$row['size'];
  297. }
  298. $mtime=$view->filemtime($path);
  299. $ctime=$view->filectime($path);
  300. $writable=$view->is_writable($path);
  301. self::put($path,array('size'=>$size,'mtime'=>$mtime,'ctime'=>$ctime,'mimetype'=>$mimetype,'writable'=>$writable));
  302. }else{
  303. $count=0;
  304. self::scan($path,null,$count,$root);
  305. }
  306. }else{
  307. $size=self::scanFile($path,$root);
  308. }
  309. self::increaseSize(dirname($fullPath),$size-$cachedSize);
  310. }
  311. private static function getCachedSize($path,$root){
  312. if(!$root){
  313. $root=OC_Filesystem::getRoot();
  314. }else{
  315. if($root=='/'){
  316. $root='';
  317. }
  318. }
  319. $query=OC_DB::prepare('SELECT size FROM *PREFIX*fscache WHERE path=?');
  320. $result=$query->execute(array($path));
  321. if($row=$result->fetchRow()){
  322. return $row['size'];
  323. }else{//file not in cache
  324. return 0;
  325. }
  326. }
  327. /**
  328. * called when files are deleted
  329. * @param array $params
  330. * @param string root (optional)
  331. */
  332. public static function fileSystemWatcherDelete($params,$root=''){
  333. if(!$root){
  334. $root=OC_Filesystem::getRoot();
  335. }
  336. if($root=='/'){
  337. $root='';
  338. }
  339. $path=$params['path'];
  340. $fullPath=$root.$path;
  341. if(self::getFileId($fullPath)==-1){
  342. return;
  343. }
  344. $size=self::getCachedSize($path,$root);
  345. self::increaseSize(dirname($fullPath),-$size);
  346. self::delete($path);
  347. }
  348. /**
  349. * called when files are deleted
  350. * @param array $params
  351. * @param string root (optional)
  352. */
  353. public static function fileSystemWatcherRename($params,$root=''){
  354. if(!$root){
  355. $root=OC_Filesystem::getRoot();
  356. }
  357. if($root=='/'){
  358. $root='';
  359. }
  360. $oldPath=$params['oldpath'];
  361. $newPath=$params['newpath'];
  362. $fullOldPath=$root.$oldPath;
  363. $fullNewPath=$root.$newPath;
  364. if(($id=self::getFileId($fullOldPath))!=-1){
  365. $oldInfo=self::get($fullOldPath);
  366. $oldSize=$oldInfo['size'];
  367. }else{
  368. return;
  369. }
  370. $size=OC_Filesystem::filesize($oldPath);
  371. self::increaseSize(dirname($fullOldPath),-$oldSize);
  372. self::increaseSize(dirname($fullNewPath),$oldSize);
  373. self::move($oldPath,$newPath);
  374. }
  375. /**
  376. * adjust the size of the parent folders
  377. * @param string $path
  378. * @param int $sizeDiff
  379. */
  380. private static function increaseSize($path,$sizeDiff){
  381. if($sizeDiff==0) return;
  382. while(($id=self::getFileId($path))!=-1){//walk up the filetree increasing the size of all parent folders
  383. $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET size=size+? WHERE id=?');
  384. $query->execute(array($sizeDiff,$id));
  385. $path=dirname($path);
  386. }
  387. }
  388. /**
  389. * recursively scan the filesystem and fill the cache
  390. * @param string $path
  391. * @param OC_EventSource $enventSource (optional)
  392. * @param int count (optional)
  393. * @param string root (optionak)
  394. */
  395. public static function scan($path,$eventSource=false,&$count=0,$root=''){
  396. if(!$root){
  397. $view=OC_Filesystem::getView();
  398. }else{
  399. $view=new OC_FilesystemView(($root=='/')?'':$root);
  400. }
  401. self::scanFile($path,$root);
  402. $dh=$view->opendir($path);
  403. $totalSize=0;
  404. if($dh){
  405. while (($filename = readdir($dh)) !== false) {
  406. if($filename != '.' and $filename != '..'){
  407. $file=$path.'/'.$filename;
  408. if($view->is_dir($file)){
  409. if($eventSource){
  410. $eventSource->send('scanning',array('file'=>$file,'count'=>$count));
  411. }
  412. self::scan($file,$eventSource,$count,$root);
  413. }else{
  414. $totalSize+=self::scanFile($file,$root);
  415. $count++;
  416. }
  417. }
  418. }
  419. }
  420. self::increaseSize($view->getRoot().$path,$totalSize);
  421. }
  422. /**
  423. * scan a single file
  424. * @param string path
  425. * @param string root (optional)
  426. * @return int size of the scanned file
  427. */
  428. public static function scanFile($path,$root=''){
  429. if(!$root){
  430. $view=OC_Filesystem::getView();
  431. }else{
  432. $view=new OC_FilesystemView(($root=='/')?'':$root);
  433. }
  434. if(!$view->is_readable($path)) return; //cant read, nothing we can do
  435. $stat=$view->stat($path);
  436. $mimetype=$view->getMimeType($path);
  437. $writable=$view->is_writable($path);
  438. $stat['mimetype']=$mimetype;
  439. $stat['writable']=$writable;
  440. if($path=='/'){
  441. $path='';
  442. }
  443. self::put($path,$stat,$root);
  444. return $stat['size'];
  445. }
  446. /**
  447. * fine files by mimetype
  448. * @param string $part1
  449. * @param string $part2 (optional)
  450. * @param string root (optional)
  451. * @return array of file paths
  452. *
  453. * $part1 and $part2 together form the complete mimetype.
  454. * e.g. searchByMime('text','plain')
  455. *
  456. * seccond mimetype part can be ommited
  457. * e.g. searchByMime('audio')
  458. */
  459. public static function searchByMime($part1,$part2='',$root=''){
  460. if(!$root){
  461. $root=OC_Filesystem::getRoot();
  462. }elseif($root='/'){
  463. $root='';
  464. }
  465. $rootLen=strlen($root);
  466. $user=OC_User::getUser();
  467. if(!$part2){
  468. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE mimepart=? AND user=?');
  469. $result=$query->execute(array($part1,$user));
  470. }else{
  471. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE mimetype=? AND user=?');
  472. $result=$query->execute(array($part1.'/'.$part2,$user));
  473. }
  474. $names=array();
  475. while($row=$result->fetchRow()){
  476. $names[]=substr($row['path'],$rootLen);
  477. }
  478. return $names;
  479. }
  480. /**
  481. * check if a file or folder is updated outside owncloud
  482. * @param string path
  483. * @param string root (optional)
  484. * @return bool
  485. */
  486. public static function isUpdated($path,$root=''){
  487. if(!$root){
  488. $root=OC_Filesystem::getRoot();
  489. $view=OC_Filesystem::getView();
  490. }else{
  491. if($root=='/'){
  492. $root='';
  493. }
  494. $view=new OC_FilesystemView($root);
  495. }
  496. $mtime=$view->filemtime($path);
  497. $isDir=$view->is_dir($path);
  498. $path=$root.$path;
  499. $query=OC_DB::prepare('SELECT mtime FROM *PREFIX*fscache WHERE path=?');
  500. $result=$query->execute(array($path));
  501. if($row=$result->fetchRow()){
  502. $cachedMTime=$row['mtime'];
  503. return ($mtime>$cachedMTime);
  504. }else{//file not in cache, so it has to be updated
  505. return !($isDir);//new folders are handeled sperate
  506. }
  507. }
  508. /**
  509. * update the cache according to changes in the folder
  510. * @param string path
  511. * @param string root (optional)
  512. */
  513. private static function updateFolder($path,$root=''){
  514. if(!$root){
  515. $view=OC_Filesystem::getView();
  516. }else{
  517. $view=new OC_FilesystemView(($root=='/')?'':$root);
  518. }
  519. $dh=$view->opendir($path);
  520. if($dh){//check for changed/new files
  521. while (($filename = readdir($dh)) !== false) {
  522. if($filename != '.' and $filename != '..'){
  523. $file=$path.'/'.$filename;
  524. if(self::isUpdated($file,$root)){
  525. if(!$root){//filesystem hooks are only valid for the default root
  526. OC_Hook::emit('OC_Filesystem','post_write',array('path'=>$file));
  527. }else{
  528. self::fileSystemWatcherWrite(array('path'=>$file),$root);
  529. }
  530. }
  531. }
  532. }
  533. }
  534. //check for removed files, not using getFolderContent to prevent loops
  535. $parent=self::getFileId($view->getRoot().$path);
  536. $query=OC_DB::prepare('SELECT name FROM *PREFIX*fscache WHERE parent=?');
  537. $result=$query->execute(array($parent));
  538. while($row=$result->fetchRow()){
  539. $file=$path.'/'.$row['name'];
  540. if(!$view->file_exists($file)){
  541. if(!$root){//filesystem hooks are only valid for the default root
  542. OC_Hook::emit('OC_Filesystem','post_delete',array('path'=>$file));
  543. }else{
  544. self::fileSystemWatcherDelete(array('path'=>$file),$root);
  545. }
  546. }
  547. }
  548. //update the folder last, so we can calculate the size correctly
  549. if(!$root){//filesystem hooks are only valid for the default root
  550. OC_Hook::emit('OC_Filesystem','post_write',array('path'=>$path));
  551. }else{
  552. self::fileSystemWatcherWrite(array('path'=>$path),$root);
  553. }
  554. }
  555. }
  556. //watch for changes and try to keep the cache up to date
  557. OC_Hook::connect('OC_Filesystem','post_write','OC_FileCache','fileSystemWatcherWrite');
  558. OC_Hook::connect('OC_Filesystem','post_delete','OC_FileCache','fileSystemWatcherDelete');
  559. OC_Hook::connect('OC_Filesystem','post_rename','OC_FileCache','fileSystemWatcherRename');