OSDN Git Service

- removed misused PEAR::isError routine.
[ethna/ethna.git] / class / Plugin / Cachemanager / Ethna_Plugin_Cachemanager_Localfile.php
1 <?php
2 // vim: foldmethod=marker tabstop=4 shiftwidth=4 autoindent
3 /**
4  *  Ethna_Plugin_Cachemanager_Localfile.php
5  *
6  *  @author     Masaki Fujimoto <fujimoto@php.net>
7  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
8  *  @package    Ethna
9  *  @version    $Id$
10  */
11
12 /**
13  *  キャッシュマネージャクラス(ローカルファイルキャッシュ版)
14  *
15  *  @author     Masaki Fujimoto <fujimoto@php.net>
16  *  @access     public
17  *  @package    Ethna
18  */
19 class Ethna_Plugin_Cachemanager_Localfile extends Ethna_Plugin_Cachemanager
20 {
21     /**#@+  @access private */
22
23     /**#@-*/
24
25     /**
26      *  キャッシュに設定された値を取得する
27      *
28      *  キャッシュに値が設定されている場合はキャッシュ値
29      *  が戻り値となる。キャッシュに値が無い場合やlifetime
30      *  を過ぎている場合、エラーが発生した場合はEthna_Error
31      *  オブジェクトが戻り値となる。
32      *
33      *  @access public
34      *  @param  string  $key        キャッシュキー
35      *  @param  int     $lifetime   キャッシュ有効期間
36      *  @param  string  $namespace  キャッシュネームスペース
37      *  @return array   キャッシュ値
38      */
39     function get($key, $lifetime = null, $namespace = null)
40     {
41         $namespace = is_null($namespace) ? $this->namespace : $namespace;
42         $cache_file = $this->_getCacheFile($namespace, $key);
43
44         // ライフタイムチェック
45         clearstatcache();
46         if (is_readable($cache_file) === false
47             || ($st = stat($cache_file)) === false) {
48             return Ethna::raiseError('fopen failed', E_CACHE_NO_VALUE);
49         }
50         if (is_null($lifetime) == false) {
51             if (($st[9]+$lifetime) < time()) {
52                 return Ethna::raiseError('fopen failed', E_CACHE_EXPIRED);
53             }
54         }
55
56         $fp = fopen($cache_file, "r");
57         if ($fp == false) {
58             return Ethna::raiseError('fopen failed', E_CACHE_NO_VALUE);
59         }
60         // ロック
61         $timeout = 3;
62         while ($timeout > 0) {
63             $r = flock($fp, LOCK_EX|LOCK_NB);
64             if ($r) {
65                 break;
66             }
67             $timeout--;
68             sleep(1);
69         }
70         if ($timeout <= 0) {
71             fclose($fp);
72             return Ethna::raiseError('fopen failed', E_CACHE_GENERAL);
73         }
74
75         $n = 0;
76         while ($st[7] == 0) {
77             clearstatcache();
78             $st = stat($cache_file);
79             usleep(1000*1);
80             $n++;
81             if ($n > 5) {
82                 break;
83             }
84         }
85
86         if ($st == false || $n > 5) {
87             fclose($fp);
88             return Ethna::raiseError('stat failed', E_CACHE_NO_VALUE);
89         }
90         $value = fread($fp, $st[7]);
91         fclose($fp);
92
93         return unserialize($value);
94     }
95
96     /**
97      *  キャッシュの最終更新日時を取得する
98      *
99      *  @access public
100      *  @param  string  $key        キャッシュキー
101      *  @param  string  $namespace  キャッシュネームスペース
102      *  @return int     最終更新日時(unixtime)
103      */
104     function getLastModified($key, $namespace = null)
105     {
106         $namespace = is_null($namespace) ? $this->namespace : $namespace;
107         $cache_file = $this->_getCacheFile($namespace, $key);
108
109         clearstatcache();
110         if (is_readable($cache_file) === false
111             || ($st = stat($cache_file)) === false) {
112             return Ethna::raiseError('fopen failed', E_CACHE_NO_VALUE);
113         }
114         return $st[9];
115     }
116
117     /**
118      *  値がキャッシュされているかどうかを取得する
119      *
120      *  @access public
121      *  @param  string  $key        キャッシュキー
122      *  @param  int     $lifetime   キャッシュ有効期間
123      *  @param  string  $namespace  キャッシュネームスペース
124      */
125     function isCached($key, $lifetime = null, $namespace = null)
126     {
127         $namespace = is_null($namespace) ? $this->namespace : $namespace;
128         $cache_file = $this->_getCacheFile($namespace, $key);
129
130         // ライフタイムチェック
131         clearstatcache();
132         if (is_readable($cache_file) === false
133             || ($st = stat($cache_file)) === false) {
134             return false;
135         }
136         if (is_null($lifetime) == false) {
137             if (($st[9]+$lifetime) < time()) {
138                 return false;
139             }
140         }
141
142         return true;
143     }
144
145     /**
146      *  キャッシュに値を設定する
147      *
148      *  @access public
149      *  @param  string  $key        キャッシュキー
150      *  @param  mixed   $value      キャッシュ値
151      *  @param  int     $timestamp  キャッシュ最終更新時刻(unixtime)
152      *  @param  string  $namespace  キャッシュネームスペース
153      */
154     function set($key, $value, $timestamp = null, $namespace = null)
155     {
156         $namespace = is_null($namespace) ? $this->namespace : $namespace;
157         $dir = $this->_getCacheDir($namespace, $key);
158
159         // キャッシュディレクトリチェック
160         $r = Ethna_Util::mkdir($dir, 0777);
161         if ($r == false && is_dir($dir) == false) {
162             return Ethna::raiseError('mkdir(%s) failed', E_USER_WARNING, $dir);
163         }
164
165         $cache_file = $this->_getCacheFile($namespace, $key);
166         $fp = fopen($cache_file, "a+");
167         if ($fp == false) {
168             return Ethna::raiseError('fopen failed', E_CACHE_GENERAL);
169         }
170
171         // ロック
172         $timeout = 3;
173         while ($timeout > 0) {
174             $r = flock($fp, LOCK_EX|LOCK_NB);
175             if ($r) {
176                 break;
177             }
178             $timeout--;
179             sleep(1);
180         }
181         if ($timeout <= 0) {
182             fclose($fp);
183             return Ethna::raiseError('fopen failed', E_CACHE_GENERAL);
184         }
185         rewind($fp);
186         ftruncate($fp, 0);
187         fwrite($fp, serialize($value));
188         fclose($fp);
189         Ethna_Util::chmod($cache_file, 0666);
190
191         if (is_null($timestamp)) {
192             // this could suppress warning
193             touch($cache_file);
194         } else {
195             touch($cache_file, $timestamp);
196         }
197
198         return 0;
199     }
200
201     /**
202      *  キャッシュ値を削除する
203      *
204      *  @access public
205      *  @param  string  $key        キャッシュキー
206      *  @param  string  $namespace  キャッシュネームスペース
207      */
208     function clear($key, $namespace = null)
209     {
210         $namespace = is_null($namespace) ? $this->namespace : $namespace;
211         $cache_file = $this->_getCacheFile($namespace, $key);
212
213         if (file_exists($cache_file)) {
214             unlink($cache_file);
215         }
216     }
217
218     /**
219      *  キャッシュ対象ディレクトリを取得する
220      *
221      *  @access private
222      */
223     function _getCacheDir($namespace, $key)
224     {
225         $len = strlen($key);
226         // intentionally avoid using -2 or -4
227         $dir1 = substr($key, $len-4, 2);
228         if ($len-4 < 0 || strlen($dir1) < 2) {
229             $dir1 = "__dir1";
230         }
231         $dir2 = substr($key, $len-2, 2);
232         if ($len-2 < 0 || strlen($dir2) < 2) {
233             $dir2 = "__dir2";
234         }
235
236         $map = $this->config->get('cachemanager_localfile');
237         $tmp_key = $namespace . "::" . $key;
238         // PHP依存:)
239         $dir = "default";
240
241         if (is_array($map)) {
242             foreach ($map as $key => $value) {
243                 if (strncmp($key, $tmp_key, strlen($key)) == 0) {
244                     $dir = $value;
245                     break;
246                 }
247             }
248         }
249         
250         return sprintf("%s/cache/%s/cache_%s/%s/%s", $this->backend->getTmpdir(), $dir, $this->_escape($namespace), $this->_escape($dir1), $this->_escape($dir2));
251     }
252
253     /**
254      *  キャッシュファイルを取得する
255      *
256      *  @access private
257      */
258     function _getCacheFile($namespace, $key)
259     {
260         return sprintf("%s/%s", $this->_getCacheDir($namespace, $key), $this->_escape($key));
261     }
262
263     /**
264      *  キーをファイルシステム用にエスケープする
265      *
266      *  @access private
267      */
268     function _escape($string)
269     {
270         return preg_replace('/([^0-9A-Za-z_])/e', "sprintf('%%%02X', ord('\$1'))", $string);
271     }
272 }
273 ?>