filecache.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  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 associative 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=false){
  44. if(OC_FileCache_Update::hasUpdated($path,$root)){
  45. if($root===false){//filesystem hooks are only valid for the default root
  46. OC_Hook::emit('OC_Filesystem','post_write',array('path'=>$path));
  47. }else{
  48. OC_FileCache_Update::update($path,$root);
  49. }
  50. }
  51. return OC_FileCache_Cached::get($path,$root);
  52. }
  53. /**
  54. * put filesystem info in the cache
  55. * @param string $path
  56. * @param array data
  57. * @param string root (optional)
  58. *
  59. * $data is an assiciative array in the same format as returned by get
  60. */
  61. public static function put($path,$data,$root=false){
  62. if($root===false){
  63. $root=OC_Filesystem::getRoot();
  64. }
  65. $path=$root.$path;
  66. $parent=self::getParentId($path);
  67. $id=self::getId($path,'');
  68. if($id!=-1){
  69. self::update($id,$data);
  70. return;
  71. }
  72. if(isset(OC_FileCache_Cached::$savedData[$path])){
  73. $data=array_merge($data,OC_FileCache_Cached::$savedData[$path]);
  74. unset(OC_FileCache_Cached::$savedData[$path]);
  75. }
  76. if(!isset($data['size']) or !isset($data['mtime'])){//save incomplete data for the next time we write it
  77. OC_FileCache_Cached::$savedData[$path]=$data;
  78. return;
  79. }
  80. if(!isset($data['encrypted'])){
  81. $data['encrypted']=false;
  82. }
  83. if(!isset($data['versioned'])){
  84. $data['versioned']=false;
  85. }
  86. $mimePart=dirname($data['mimetype']);
  87. $data['size']=(int)$data['size'];
  88. $data['ctime']=(int)$data['mtime'];
  89. $data['writable']=(int)$data['writable'];
  90. $data['encrypted']=(int)$data['encrypted'];
  91. $data['versioned']=(int)$data['versioned'];
  92. $user=OC_User::getUser();
  93. $query=OC_DB::prepare('INSERT INTO *PREFIX*fscache(parent, name, path, path_hash, size, mtime, ctime, mimetype, mimepart,`user`,writable,encrypted,versioned) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)');
  94. $result=$query->execute(array($parent,basename($path),$path,md5($path),$data['size'],$data['mtime'],$data['ctime'],$data['mimetype'],$mimePart,$user,$data['writable'],$data['encrypted'],$data['versioned']));
  95. if(OC_DB::isError($result)){
  96. OC_Log::write('files','error while writing file('.$path.') to cache',OC_Log::ERROR);
  97. }
  98. }
  99. /**
  100. * update filesystem info of a file
  101. * @param int $id
  102. * @param array $data
  103. */
  104. private static function update($id,$data){
  105. $arguments=array();
  106. $queryParts=array();
  107. foreach(array('size','mtime','ctime','mimetype','encrypted','versioned','writable') as $attribute){
  108. if(isset($data[$attribute])){
  109. $arguments[]=$data[$attribute];
  110. $queryParts[]=$attribute.'=?';
  111. }
  112. }
  113. if(isset($data['mimetype'])){
  114. $arguments[]=dirname($data['mimetype']);
  115. $queryParts[]='mimepart=?';
  116. }
  117. $arguments[]=$id;
  118. $sql = 'UPDATE *PREFIX*fscache SET '.implode(' , ',$queryParts).' WHERE id=?';
  119. $query=OC_DB::prepare($sql);
  120. $result=$query->execute($arguments);
  121. if(OC_DB::isError($result)){
  122. OC_Log::write('files','error while updating file('.$path.') in cache',OC_Log::ERROR);
  123. }
  124. }
  125. /**
  126. * register a file move in the cache
  127. * @param string oldPath
  128. * @param string newPath
  129. * @param string root (optional)
  130. */
  131. public static function move($oldPath,$newPath,$root=false){
  132. if($root===false){
  133. $root=OC_Filesystem::getRoot();
  134. }
  135. $oldPath=$root.$oldPath;
  136. $newPath=$root.$newPath;
  137. $newParent=self::getParentId($newPath);
  138. $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET parent=? ,name=?, path=?, path_hash=? WHERE path_hash=?');
  139. $query->execute(array($newParent,basename($newPath),$newPath,md5($newPath),md5($oldPath)));
  140. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE path LIKE ?');
  141. $oldLength=strlen($oldPath);
  142. $updateQuery=OC_DB::prepare('UPDATE *PREFIX*fscache SET path=?, path_hash=? WHERE path_hash=?');
  143. while($row= $query->execute(array($oldPath.'/%'))->fetchRow()){
  144. $old=$row['path'];
  145. $new=$newPath.substr($old,$oldLength);
  146. $updateQuery->execute(array($new,md5($new),md5($old)));
  147. }
  148. }
  149. /**
  150. * delete info from the cache
  151. * @param string path
  152. * @param string root (optional)
  153. */
  154. public static function delete($path,$root=false){
  155. if($root===false){
  156. $root=OC_Filesystem::getRoot();
  157. }
  158. $query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE path_hash=?');
  159. $query->execute(array(md5($root.$path)));
  160. //delete everything inside the folder
  161. $query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE path LIKE ?');
  162. $query->execute(array($root.$path.'/%'));
  163. }
  164. /**
  165. * return array of filenames matching the querty
  166. * @param string $query
  167. * @param boolean $returnData
  168. * @param string root (optional)
  169. * @return array of filepaths
  170. */
  171. public static function search($search,$returnData=false,$root=false){
  172. if($root===false){
  173. $root=OC_Filesystem::getRoot();
  174. }
  175. $rootLen=strlen($root);
  176. if(!$returnData){
  177. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE name LIKE ? AND `user`=?');
  178. }else{
  179. $query=OC_DB::prepare('SELECT * FROM *PREFIX*fscache WHERE name LIKE ? AND `user`=?');
  180. }
  181. $result=$query->execute(array("%$search%",OC_User::getUser()));
  182. $names=array();
  183. while($row=$result->fetchRow()){
  184. if(!$returnData){
  185. $names[]=substr($row['path'],$rootLen);
  186. }else{
  187. $row['path']=substr($row['path'],$rootLen);
  188. $names[]=$row;
  189. }
  190. }
  191. return $names;
  192. }
  193. /**
  194. * get all files and folders in a folder
  195. * @param string path
  196. * @param string root (optional)
  197. * @return array
  198. *
  199. * returns an array of assiciative arrays with the following keys:
  200. * - name
  201. * - size
  202. * - mtime
  203. * - ctime
  204. * - mimetype
  205. * - encrypted
  206. * - versioned
  207. */
  208. public static function getFolderContent($path,$root=false,$mimetype_filter=''){
  209. if(OC_FileCache_Update::hasUpdated($path,$root,true)){
  210. OC_FileCache_Update::updateFolder($path,$root);
  211. }
  212. return OC_FileCache_Cached::getFolderContent($path,$root,$mimetype_filter);
  213. }
  214. /**
  215. * check if a file or folder is in the cache
  216. * @param string $path
  217. * @param string root (optional)
  218. * @return bool
  219. */
  220. public static function inCache($path,$root=false){
  221. return self::getId($path,$root)!=-1;
  222. }
  223. /**
  224. * get the file id as used in the cache
  225. * @param string path
  226. * @param string root (optional)
  227. * @return int
  228. */
  229. public static function getId($path,$root=false){
  230. if($root===false){
  231. $root=OC_Filesystem::getRoot();
  232. }
  233. $query=OC_DB::prepare('SELECT id FROM *PREFIX*fscache WHERE path_hash=?');
  234. $result=$query->execute(array(md5($root.$path)));
  235. if(OC_DB::isError($result)){
  236. OC_Log::write('files','error while getting file id of '.$path,OC_Log::ERROR);
  237. return -1;
  238. }
  239. $result=$result->fetchRow();
  240. if(is_array($result)){
  241. return $result['id'];
  242. }else{
  243. return -1;
  244. }
  245. }
  246. /**
  247. * get the file path from the id, relative to the home folder of the user
  248. * @param int id
  249. * @param string user (optional)
  250. * @return string
  251. */
  252. public static function getPath($id,$user=''){
  253. if(!$user){
  254. $user=OC_User::getUser();
  255. }
  256. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE id=? AND `user`=?');
  257. $result=$query->execute(array($id,$user));
  258. $row=$result->fetchRow();
  259. $path=$row['path'];
  260. $root='/'.$user.'/files';
  261. if(substr($path,0,strlen($root))!=$root){
  262. return false;
  263. }
  264. return substr($path,strlen($root));
  265. }
  266. /**
  267. * get the file id of the parent folder, taking into account '/' has no parent
  268. * @param string $path
  269. * @return int
  270. */
  271. private static function getParentId($path){
  272. if($path=='/'){
  273. return -1;
  274. }else{
  275. return self::getId(dirname($path),'');
  276. }
  277. }
  278. /**
  279. * adjust the size of the parent folders
  280. * @param string $path
  281. * @param int $sizeDiff
  282. * @param string root (optinal)
  283. */
  284. public static function increaseSize($path,$sizeDiff, $root=false){
  285. if($sizeDiff==0) return;
  286. $id=self::getId($path,'');
  287. while($id!=-1){//walk up the filetree increasing the size of all parent folders
  288. $query=OC_DB::prepare('UPDATE *PREFIX*fscache SET size=size+? WHERE id=?');
  289. $query->execute(array($sizeDiff,$id));
  290. $id=self::getParentId($path);
  291. $path=dirname($path);
  292. }
  293. }
  294. /**
  295. * recursively scan the filesystem and fill the cache
  296. * @param string $path
  297. * @param OC_EventSource $enventSource (optional)
  298. * @param int count (optional)
  299. * @param string root (optionak)
  300. */
  301. public static function scan($path,$eventSource=false,&$count=0,$root=false){
  302. if($eventSource){
  303. $eventSource->send('scanning',array('file'=>$path,'count'=>$count));
  304. }
  305. $lastSend=$count;
  306. if($root===false){
  307. $view=OC_Filesystem::getView();
  308. }else{
  309. $view=new OC_FilesystemView($root);
  310. }
  311. self::scanFile($path,$root);
  312. $dh=$view->opendir($path.'/');
  313. $totalSize=0;
  314. if($dh){
  315. while (($filename = readdir($dh)) !== false) {
  316. if($filename != '.' and $filename != '..'){
  317. $file=$path.'/'.$filename;
  318. if($view->is_dir($file.'/')){
  319. self::scan($file,$eventSource,$count,$root);
  320. }else{
  321. $totalSize+=self::scanFile($file,$root);
  322. $count++;
  323. if($count>$lastSend+25 and $eventSource){
  324. $lastSend=$count;
  325. $eventSource->send('scanning',array('file'=>$path,'count'=>$count));
  326. }
  327. }
  328. }
  329. }
  330. }
  331. OC_FileCache_Update::cleanFolder($path,$root);
  332. self::increaseSize($path,$totalSize,$root);
  333. }
  334. /**
  335. * scan a single file
  336. * @param string path
  337. * @param string root (optional)
  338. * @return int size of the scanned file
  339. */
  340. public static function scanFile($path,$root=false){
  341. if($root===false){
  342. $view=OC_Filesystem::getView();
  343. }else{
  344. $view=new OC_FilesystemView($root);
  345. }
  346. if(!$view->is_readable($path)) return; //cant read, nothing we can do
  347. clearstatcache();
  348. $mimetype=$view->getMimeType($path);
  349. $stat=$view->stat($path);
  350. if($mimetype=='httpd/unix-directory'){
  351. $writable=$view->is_writable($path.'/');
  352. }else{
  353. $writable=$view->is_writable($path);
  354. }
  355. $stat['mimetype']=$mimetype;
  356. $stat['writable']=$writable;
  357. if($path=='/'){
  358. $path='';
  359. }
  360. self::put($path,$stat,$root);
  361. return $stat['size'];
  362. }
  363. /**
  364. * find files by mimetype
  365. * @param string $part1
  366. * @param string $part2 (optional)
  367. * @param string root (optional)
  368. * @return array of file paths
  369. *
  370. * $part1 and $part2 together form the complete mimetype.
  371. * e.g. searchByMime('text','plain')
  372. *
  373. * seccond mimetype part can be ommited
  374. * e.g. searchByMime('audio')
  375. */
  376. public static function searchByMime($part1,$part2=null,$root=false){
  377. if($root===false){
  378. $root=OC_Filesystem::getRoot();
  379. }
  380. $rootLen=strlen($root);
  381. $root .= '%';
  382. $user=OC_User::getUser();
  383. if(!$part2){
  384. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE mimepart=? AND `user`=? AND path LIKE ?');
  385. $result=$query->execute(array($part1,$user, $root));
  386. }else{
  387. $query=OC_DB::prepare('SELECT path FROM *PREFIX*fscache WHERE mimetype=? AND `user`=? AND path LIKE ? ');
  388. $result=$query->execute(array($part1.'/'.$part2,$user, $root));
  389. }
  390. $names=array();
  391. while($row=$result->fetchRow()){
  392. $names[]=substr($row['path'],$rootLen);
  393. }
  394. return $names;
  395. }
  396. /**
  397. * clean old pre-path_hash entries
  398. */
  399. public static function clean(){
  400. $query=OC_DB::prepare('DELETE FROM *PREFIX*fscache WHERE LENGTH(path_hash)<30');
  401. $query->execute();
  402. }
  403. }
  404. //watch for changes and try to keep the cache up to date
  405. OC_Hook::connect('OC_Filesystem','post_write','OC_FileCache_Update','fileSystemWatcherWrite');
  406. OC_Hook::connect('OC_Filesystem','post_delete','OC_FileCache_Update','fileSystemWatcherDelete');
  407. OC_Hook::connect('OC_Filesystem','post_rename','OC_FileCache_Update','fileSystemWatcherRename');