1: <?php
2: /**
3: * Klutz Driver implementation for comics as files.
4: *
5: * Required parameters:<pre>
6: * 'directory' The main directory the comics are stored in
7: * 'sumsfile' The filename to hold md5sums for images</pre>
8: *
9: * @author Marcus I. Ryan <marcus@riboflavin.net>
10: * @since Klutz 0.1
11: * @package Klutz
12: */
13: class Klutz_Driver_File extends Klutz_Driver
14: {
15: /**
16: * The base directory we store comics in.
17: *
18: * @var string
19: */
20: var $basedir = null;
21:
22: /**
23: * The format for the various subdirectories.
24: * WARNING: DO NOT CHANGE THIS!
25: *
26: * @var string
27: */
28: var $subdir = 'Ymd';
29:
30: /**
31: * The file we store unique image identifiers in.
32: *
33: * @var string
34: */
35: var $sumsfile = 'sums';
36:
37: /**
38: * The actual array of unique image identifiers (md5 sums right now).
39: *
40: * Key is the full path of the comic, value is md5
41: *
42: * @var array
43: */
44: var $diffs = array();
45:
46: /**
47: * Constructs a new file storage object.
48: *
49: * @param array $params A hash containing connection parameters.
50: */
51: function Klutz_Driver_file($params = array())
52: {
53: if (empty($params['basedir'])) {
54: return null;
55: }
56:
57: $this->basedir = $params['basedir'];
58: if (substr($this->basedir,-1,1) != "/") {
59: $this->basedir .= "/";
60: }
61:
62: if (!empty($params['sumsfile'])) {
63: $this->sumsfile = $params['sumsfile'];
64: }
65: if (substr($this->sumsfile, 0, 1) != "/") {
66: $this->sumsfile = $this->basedir . $this->sumsfile;
67: }
68:
69: $this->loadSums();
70: }
71:
72: /**
73: * Load a list of unique identifiers for comics from the sumsfile.
74: *
75: * @return void
76: */
77: function loadSums()
78: {
79: if (file_exists($this->sumsfile)) {
80: ob_start();
81: readfile($this->sumsfile);
82: foreach (explode("\n", ob_get_contents()) as $entry) {
83: if (empty($entry)) {
84: continue;
85: }
86: list($file, $sum) = explode(') = ', $entry);
87: $this->diffs[substr($file, 5)] = $sum;
88: }
89: ob_end_clean();
90: }
91: }
92:
93: /**
94: * Save the list of unique identifiers for comics to the sumsfile.
95: *
96: * @return void
97: */
98: function saveSums()
99: {
100: $fp = fopen($this->sumsfile, "wb+");
101: if ($fp === false) {
102: Horde::logMessage('Unable to create/write to ' . $this->sumsfile,
103: __FILE__, __LINE__, PEAR_LOG_ERR);
104: return false;
105: }
106: foreach ($this->diffs as $file => $sum) {
107: if (empty($file)) { continue; }
108: fwrite($fp, "MD5 ($file) = $sum\n");
109: }
110: fclose($fp);
111: }
112:
113: /**
114: * Rebuild the table of unique identifiers.
115: *
116: * @return void
117: */
118: function rebuildSums()
119: {
120: $this->diffs = array();
121: $d = dir($this->basedir);
122: while (false !== ($entry = $d->read())) {
123: if (is_dir($d->path . $entry) && strlen($entry) == 8
124: && is_numeric($entry)) {
125:
126: // we're reasonably sure this is a valid dir
127: $sd = dir($this->basedir . $entry);
128: while (false !== ($file = $sd->read())) {
129: $file = $sd->path . '/' . $file;
130: if (is_file($file)) {
131: ob_start();
132: readfile($file);
133: $this->diffs[$file] = md5(ob_get_contents());
134: ob_end_clean();
135: }
136: }
137: }
138: }
139: $d->close();
140: $this->saveSums();
141: }
142:
143: /**
144: * Add a unique identifier for a given image.
145: *
146: * @param string $index The index for the comic
147: * @param timestamp $date The date of the comic
148: * @param string $data The md5 of the raw (binary) image data
149: *
150: * @return void
151: */
152: function addSum($index, $date, $data)
153: {
154: $key = $this->basedir . date($this->subdir, $date) . '/' . $index;
155: $this->diffs[$key] = $data;
156: }
157:
158: /**
159: * Remove the unique identifier for the given comic and/or
160: * date. If both are passed, removes the uid for that comic and
161: * date. If only a comic is passed, removes all uids for that
162: * comic. If only a date is passed removes uids for all comics on
163: * that date. If neither is passed, all uids are wiped out.
164: *
165: * @param string $index Index for the comic to delete. If left out all
166: * comics will be assumed.
167: * @param timestamp $date Date to remove. If left out, assumes all dates.
168: *
169: * @return void
170: */
171: function removeSum($index = null, $date = null)
172: {
173: if (is_null($date)) {
174: $date = mktime(0, 0, 0);
175: }
176:
177: if (is_null($index)) {
178: $cmp = $this->basedir . date($this->subdir, $date);
179: foreach (array_keys($this->diffs) as $key) {
180: if (strncmp($key, $cmp, strlen($cmp)) == 0) {
181: unset($this->diffs[$key]);
182: }
183: }
184: } else {
185: $key = $this->basedir . date($this->subdir, $date) . '/' . $index;
186: unset($this->diffs[$key]);
187: }
188: }
189:
190: /**
191: * Determine if the image passed is a unique image (one we don't already
192: * have).
193: *
194: * This allows for $days = random, etc., but keeps us from getting the same
195: * comic day after day.
196: *
197: * @param object Klutz_Image $image Raw (binary) image data
198: *
199: * @return boolean True if unique, false otherwise.
200: */
201: function isUnique($image)
202: {
203: if (!is_a($image, 'Klutz_Image')) {
204: return null;
205: }
206:
207: return !in_array(md5($image->data), $this->diffs);
208: }
209:
210: /**
211: * Get a list of the dates for which we have comics between
212: * $oldest and $newest. Only returns dates we have at least one
213: * comic for.
214: *
215: * @param timestamp $date The reference date (default today)
216: * @param timestamp $oldest The earliest possible date to return (default
217: * first of the month)
218: * @param timestamp $newest The latest possible date to return (default
219: * last date of the month)
220: *
221: * @return array timestamps Any dates between $oldest and $newest that we
222: * have comics for.
223: */
224: function listDates($date = null, $oldest = null, $newest = null)
225: {
226: if (is_null($date)) {
227: $date = mktime(0, 0, 0);
228: }
229:
230: // Using Date as a reference, return all dates for the same
231: // time of day.
232: $d = getdate($date);
233:
234: if (is_null($oldest)) {
235: $oldest = mktime(0, 0, 0, $d['mon'], 1, $d['year']);
236: }
237:
238: if (is_null($newest)) {
239: $newest = mktime(0, 0, 0, $d['mon'] + 1, 0, $d['year']);
240: }
241:
242: $d = @dir($this->basedir);
243: if (!$d) {
244: return array();
245: }
246:
247: $return = array();
248: while (false !== ($entry = $d->read())) {
249: if (is_dir($d->path . $entry) && strlen($entry) == 8
250: && is_numeric($entry)) {
251:
252: // we're reasonably sure this is a valid dir
253: $time = mktime(0, 0, 0, substr($entry, 4, 2),
254: substr($entry, -2), substr($entry, 0, 4));
255: if ($time >= $oldest && $time <= $newest) {
256: $return[] = $time;
257: }
258: }
259: }
260: $d->close();
261: sort($return, SORT_NUMERIC);
262: return $return;
263: }
264:
265: /**
266: * Get the image dimensions for the requested image.
267: *
268: * @param string $index The index of the comic to check
269: * @param timestamp $date The date of the comic to check (default today)
270: *
271: * @return string Attributes for an <img> tag giving height and width
272: */
273: function imageSize($index, $date = null)
274: {
275: $image = $this->retrieveImage($index, $date);
276: if (is_a($image, 'Klutz_Image') && !empty($image->size)) {
277: return $image->size;
278: } else {
279: return '';
280: }
281: }
282:
283: /**
284: * Find out if we already have a local copy of this image.
285: *
286: * @param string $index The index of the comic to check
287: * @param timestamp $date The date of the comic to check (default today)
288: *
289: * @return boolean False in this driver
290: */
291: function imageExists($index, $date = null)
292: {
293: if (is_null($date)) {
294: $date = mktime(0, 0, 0);
295: }
296:
297: $dir = $this->basedir . date($this->subdir, $date);
298: return (file_exists($dir . '/' . $index) && is_file($dir . '/' . $index));
299: }
300:
301: /**
302: * Retrieve an image from storage. Make sure the image exists
303: * first with imageExists().
304: *
305: * @param string $index The index of the comic to retrieve
306: * @param timestamp $date The date for which we want $comic
307: *
308: * @return mixed If the image exists locally, return a Klutz_Image object.
309: * If it doesn't, return a string with the URL pointing to
310: * the comic.
311: */
312: function retrieveImage($index, $date = null)
313: {
314: if (is_null($date)) {
315: $date = mktime(0, 0, 0);
316: }
317:
318: $dir = $this->basedir . date($this->subdir, $date);
319: return new Klutz_Image($dir . '/' . $index);
320: }
321:
322: /**
323: * Store an image for later retrieval.
324: *
325: * @param string $index The index of the comic to retrieve
326: * @param string $image Raw (binary) image data to store
327: * @param timestamp $data Date to store it under (default today)
328: *
329: * @return boolean True on success, false otherwise
330: */
331: function storeImage($index, $image, $date = null)
332: {
333: if (!is_a($image, 'Klutz_Image')) {
334: return false;
335: }
336:
337: if (is_null($date)) {
338: $date = mktime(0, 0, 0);
339: }
340:
341: // Make sure $this->basedir exists and is writeable.
342: if (!file_exists($this->basedir)) {
343: if (!file_exists(dirname($this->basedir))) {
344: return false;
345: }
346: if (!mkdir($this->basedir)) {
347: return false;
348: }
349: }
350: if (!is_writable($this->basedir)) {
351: return false;
352: }
353:
354: $dir = $this->basedir . date($this->subdir, $date);
355:
356: if (!file_exists($dir)) {
357: mkdir($dir);
358: } elseif (!is_writable($dir)) {
359: return false;
360: }
361:
362: $fp = fopen($dir . '/' . $index, 'w+');
363: fwrite($fp, $image->data);
364: fclose($fp);
365: $this->addSum($index, $date, md5($image->data));
366: return true;
367: }
368:
369: /**
370: * Remove an image from the storage system (including its unique
371: * ID).
372: *
373: * @param string $index The index of the comic to remove
374: * @param timestamp $date The date of the comic to remove (default today)
375: *
376: * @return boolean True on success, else false
377: */
378: function removeImage($index, $date = null)
379: {
380: if (is_null($date)) {
381: $date = mktime(0, 0, 0);
382: }
383:
384: if ($this->imageExists($index, $date)) {
385: $file = $this->basedir . date($this->subdir, $date) . '/' . $index;
386: if (unlink($file)) {
387: $this->removeSum($index, $date);
388: return true;
389: }
390: }
391: return false;
392: }
393:
394: /**
395: * Remove all images from the storage system (including unique
396: * IDs) for a given date.
397: *
398: * @param timestamp $date The date to remove comics for (default today)
399: *
400: * @return boolean True on success, else false
401: */
402: function removeDate($date = null)
403: {
404: if (is_null($date)) {
405: $date = mktime(0, 0, 0);
406: }
407:
408: $dir = $this->basedir . date($this->subdir, $date);
409: if (file_exists($dir) && is_dir($dir)) {
410: $d = dir($dir);
411: if (!$d) {
412: return false;
413: }
414:
415: while (false !== ($file = $d->read())) {
416: $file = $d->path . '/' . $file;
417: if (is_file($file)) {
418: unlink($file);
419: }
420: }
421: $d->close();
422: if (@rmdir($dir)) {
423: $this->removeSum(null, $date);
424: return true;
425: }
426: }
427:
428: return false;
429: }
430:
431: }
432: