Overview

Packages

  • Horde
    • Block
  • Klutz
  • None

Classes

  • Klutz
  • Klutz_Comic
  • Klutz_Comic_Bysize
  • Klutz_Comic_Direct
  • Klutz_Comic_Search
  • Klutz_Driver
  • Klutz_Driver_File
  • Klutz_Driver_Sql
  • Klutz_Image
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: require_once 'MDB2.php';
  3: 
  4: /**
  5:  * Klutz Driver implementation for comics as files with SUM info stored
  6:  * in SQL database.
  7:  *
  8:  * Required parameters:<pre>
  9:  *   'directory'  The main directory the comics are stored in</pre>
 10:  *
 11:  * @author  Marcus I. Ryan <marcus@riboflavin.net>
 12:  * @author  Florian Steinel <fsteinel@klutz.horde.flonet.net>
 13:  * @package Klutz
 14:  */
 15: class Klutz_Driver_Sql extends Klutz_Driver
 16: {
 17:     /**
 18:      * The base directory we store comics in.
 19:      *
 20:      * @var string
 21:      */
 22:     var $basedir = null;
 23: 
 24:     /**
 25:      * The format for the various subdirectories.
 26:      * WARNING: DO NOT CHANGE THIS!
 27:      *
 28:      * @var string
 29:      */
 30:     var $subdir = 'Ymd';
 31: 
 32:     /**
 33:      * The MDB2 database object
 34:      *
 35:      * @var
 36:      */
 37:      var $_db = null;
 38: 
 39:     /**
 40:      * Constructs a new SQL storage object.
 41:      *
 42:      * @param array $params  A hash containing connection parameters.
 43:      */
 44:     function Klutz_Driver_sql($params = array())
 45:     {
 46:         if (empty($params['basedir'])) {
 47:             return null;
 48:         }
 49: 
 50:         $this->basedir = $params['basedir'];
 51:         if (substr($this->basedir, -1, 1) != "/") {
 52:             $this->basedir .= "/";
 53:         }
 54: 
 55:         /* Setup the database */
 56:         $config = $GLOBALS['conf']['sql'];
 57:         unset($config['charset']);
 58:         $this->_db = MDB2::factory($config);
 59:         $this->_db->setOption('seqcol_name', 'id');
 60: 
 61:     }
 62: 
 63:     /**
 64:      * We do nothing in this function for the SQL driver since we grab
 65:      * the info on demand from the database.  We keep the function here,
 66:      * however to honor our 'interface' since we call this function from
 67:      * various places in the client code.
 68:      */
 69:     function loadSums()
 70:     {
 71:     }
 72: 
 73:     /**
 74:      * Rebuild the table of unique identifiers.
 75:      *
 76:      * @return void
 77:      */
 78:     function rebuildSums()
 79:     {
 80:         /* First, wipe out the existing SUMS */
 81:         $this->removeSum();
 82: 
 83:         $d = dir($this->basedir);
 84:         while (false !== ($entry = $d->read())) {
 85:             if (is_dir($d->path . $entry) && strlen($entry) == 8
 86:                 && is_numeric($entry)) {
 87:                 // we're reasonably sure this is a valid dir.
 88:                 $sd = dir($this->basedir . $entry);
 89:                 while (false !== ($file = $sd->read())) {
 90:                     $comicname = $file;
 91:                     $file = $sd->path . '/' . $file;
 92:                     if (is_file($file)) {
 93:                         ob_start();
 94:                         readfile($file);
 95: 
 96:                         // We need to strtotime() the date since we are grabing
 97:                         // it from the directory, and it's in the $subdir
 98:                         // format.
 99:                         $this->addSum($comicname, strtotime($entry), md5(ob_get_contents()));
100:                         ob_end_clean();
101:                     }
102:                 }
103:             }
104:         }
105:         $d->close();
106:     }
107: 
108:     /**
109:      * Add a unique identifier for a given image.
110:      *
111:      * @param string $index             The index for the comic
112:      * @param timestamp $date           The date of the comic
113:      * @param string $data              The md5 of the raw (binary) image data
114:      *
115:      * @return boolean|PEAR_Error       True on success, PEAR_Error on failure.
116:      */
117:     function addSum($index, $date, $data)
118:     {
119:         $id = $this->_db->nextId('klutz_comics');
120:         $key = $this->basedir . date($this->subdir, $date) . '/' . $index;
121: 
122:         /* Build the SQL query. */
123:         $query = $this->_db->prepare('INSERT INTO ' . 'klutz_comics (comicpic_id, comicpic_date, comicpic_key, comicpic_hash) VALUES (?, ?, ?, ?)');
124:         $values = array($id, $date, $key, $data);
125: 
126:         /* Log the query at a DEBUG log level. */
127:         Horde::logMessage(sprintf("Klutz_Driver_sql::addSum(): %s values: %s", $query->query, print_r($values, true)),
128:                           __FILE__, __LINE__, PEAR_LOG_DEBUG);
129: 
130:         /* Execute the query. */
131:         $result = $query->execute($values);
132:         if (is_a($result, 'PEAR_Error')) {
133:             return $result;
134:         }
135: 
136:         return true;
137:     }
138: 
139:     /**
140:      * Remove the unique identifier for the given comic and/or
141:      * date. If both are passed, removes the uid for that comic and
142:      * date. If only a comic is passed, removes all uids for that
143:      * comic. If only a date is passed removes uids for all comics on
144:      * that date. If neither is passed, all uids are wiped out.
145:      *
146:      * @param string $index    Index for the comic to delete.  If left out all
147:      *                         comics will be assumed.
148:      * @param timestamp $date  Date to remove. If left out, assumes all dates.
149:      *
150:      * @return int|PEAR_Error  number of affected Comics on success, PEAR_Error on failure.
151:      */
152:     function removeSum($index = null, $date = null)
153:     {
154:         $sql = 'DELETE FROM klutz_comics';
155: 
156:         if (is_null($index) && is_null($date)) {
157:             $values = array();
158:         } elseif (is_null($date) && !is_null($index)) {
159:             $sql .= ' WHERE comicpic_key = ?';
160:             $values = array($index);
161:         } elseif (is_null($index) && !is_null($date)) {
162:             $sql .= ' WHERE comicpic_date = ?';
163:             $values = array($date);
164:         } else {
165:             $sql .= ' WHERE comicpic_key = ? AND comicpic_date = ?';
166:             $values = array($index, $date);
167:         }
168:         $query = $this->_db->prepare($sql);
169: 
170:         /* Log the query at a DEBUG log level. */
171:         Horde::logMessage('Klutz_Driver_sql::removeSum(): ' . $sql,
172:                           __FILE__, __LINE__, PEAR_LOG_DEBUG);
173: 
174:         /* Execute the query. */
175:         $result = $query->execute($values);
176: 
177:         if (isset($result) && !is_a($result, 'PEAR_Error')) {
178:             return $result->numRows();
179:             $result->free();
180:         } else {
181:             return $result;
182:         }
183:     }
184: 
185:     /**
186:      * Determine if the image passed is a unique image (one we don't already
187:      * have).
188:      *
189:      * This allows for $days = random, etc., but keeps us from getting the same
190:      * comic day after day.
191:      *
192:      * @param Klutz_Image $image  Raw (binary) image data.
193:      *
194:      * @return boolean   True if unique, false otherwise.
195:      */
196:     function isUnique($image)
197:     {
198:         if (!is_a($image, 'Klutz_Image')) {
199:             return null;
200:         }
201: 
202:         /* Build the SQL query. */
203:         $query = $this->_db->prepare('SELECT COUNT(*) FROM klutz_comics WHERE comicpic_hash = ?');
204:         $params = array(md5($image->data));
205: 
206:         /* Log the query at a DEBUG log level. */
207:         Horde::logMessage('Klutz_Driver_sql::isUnique(): ' . $query->query,
208:                           __FILE__, __LINE__, PEAR_LOG_DEBUG);
209: 
210:         /* Execute the query. */
211:         $result = $query->execute($params);
212:         $result = $result->fetchOne();
213:         if (!is_a($result, 'PEAR_Error') && $result > 0) {
214:             return false;
215:         } else {
216:             return true;
217:         }
218:     }
219: 
220:     /**
221:      * Get a list of the dates for which we have comics between
222:      * $oldest and $newest. Only returns dates we have at least one
223:      * comic for.
224:      *
225:      * @param timestamp $date    The reference date (default today)
226:      * @param timestamp $oldest  The earliest possible date to return (default
227:      *                           first of the month)
228:      * @param timestamp $newest  The latest possible date to return (default
229:      *                           last date of the month)
230:      *
231:      * @return mixed An array of dates in $subdir format between $oldest and
232:      *               $newest that we have comics for | PEAR_Error
233:      */
234:     function listDates($date = null, $oldest = null, $newest = null)
235:     {
236: 
237:         if (is_null($date)) {
238:             $date = mktime(0, 0, 0);
239:         }
240: 
241:         $return = array();
242: 
243:         // Using Date as a reference, return all dates for the same
244:         // time of day.
245:         $d = getdate($date);
246: 
247:         if (is_null($oldest)) {
248:             $oldest = mktime(0, 0, 0, $d['mon'], 1, $d['year']);
249:         }
250: 
251:         if (is_null($newest)) {
252:             $newest = mktime(0, 0, 0, $d['mon'] + 1, 0, $d['year']);
253:         }
254: 
255:         /* Build the SQL query. */
256:         $query = $this->_db->prepare('SELECT DISTINCT comicpic_date FROM klutz_comics WHERE comicpic_date >= ? AND comicpic_date <= ?');
257:         $values = array($oldest, $newest);
258: 
259:         /* Log the query at a DEBUG log level. */
260:         Horde::logMessage(sprintf('Klutz_Driver_sql::listDates($date = %s, $oldest = %s, $newest = %s): %s',
261:                           $date, $oldest, $newest, $query->query),
262:                           __FILE__, __LINE__, PEAR_LOG_DEBUG);
263: 
264:         /* Execute the query. */
265:         $result = $query->execute($values);
266: 
267: 
268:         if (isset($result) && !is_a($result, 'PEAR_Error')) {
269:             $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
270:             if (is_a($row, 'PEAR_Error')) {
271:                 return $row;
272:             }
273: 
274:             /* Store the retrieved values in the $return variable. */
275:             while ($row && !is_a($row, 'PEAR_Error')) {
276:                 /* Add this new date to the $return list. */
277:                 $comicdate = date($this->subdir, $row['comicpic_date']);
278:                 $return[] = mktime(0, 0, 0, substr($comicdate, 4, 2),
279:                                    substr($comicdate, -2), substr($comicdate, 0, 4));
280: 
281:                 /* Advance to the new row in the result set. */
282:                 $row = $result->fetchRow(MDB2_FETCHMODE_ASSOC);
283:             }
284:             $result->free();
285:         } else {
286:             return $result;
287:         }
288: 
289:         /* Fallback solution, if the query db doesn't return a result */
290:         if (count($return) == 0) {
291:             $d = dir($this->basedir);
292:             while (false !== ($entry = $d->read())) {
293:                 if (is_dir($d->path . $entry) && strlen($entry) == 8
294:                     && is_numeric($entry)) {
295:                     // We're reasonably sure this is a valid dir.
296:                     $time = mktime(0, 0, 0, substr($entry, 4, 2),
297:                                    substr($entry, -2), substr($entry, 0, 4));
298:                     if ($time >= $oldest && $time <= $newest) {
299:                         $return[] = $time;
300:                     }
301:                 }
302:             }
303:             $d->close();
304:         }
305:         sort($return, SORT_NUMERIC);
306:         return $return;
307:     }
308: 
309:     /**
310:      * Get the image dimensions for the requested image.
311:      *
312:      * @param string $index    The index of the comic to check
313:      * @param timestamp $date  The date of the comic to check (default today)
314:      *
315:      * @return string  Attributes for an <img> tag giving height and width
316:      */
317:     function imageSize($index, $date = null)
318:     {
319:         $image = $this->retrieveImage($index, $date);
320:         if (get_class($image) == 'Klutz_Image' && !empty($image->size)) {
321:             return $image->size;
322:         } else {
323:             return '';
324:         }
325:     }
326: 
327:     /**
328:      * Find out if we already have a local copy of this image.
329:      *
330:      * @param string $index    The index of the comic to check
331:      * @param timestamp $date  The date of the comic to check (default today)
332:      *
333:      * @return boolean  False in this driver
334:      */
335:     function imageExists($index, $date = null)
336:     {
337:         if (is_null($date)) {
338:             $date = mktime(0, 0, 0);
339:         }
340: 
341:         $dir = $this->basedir . date($this->subdir, $date);
342:         return (file_exists($dir . '/' . $index) && is_file($dir . '/' . $index));
343:     }
344: 
345:     /**
346:      * Retrieve an image from storage. Make sure the image exists
347:      * first with imageExists().
348:      *
349:      * @param string $index    The index of the comic to retrieve
350:      * @param timestamp $date  The date for which we want $comic
351:      *
352:      * @return mixed  If the image exists locally, return a Klutz_Image object.
353:      *                If it doesn't, return a string with the URL pointing to
354:      *                the comic.
355:      */
356:     function retrieveImage($index, $date = null)
357:     {
358:         if (is_null($date)) {
359:             $date = mktime(0, 0, 0);
360:         }
361: 
362:         $dir = $this->basedir . date($this->subdir, $date);
363:         return new Klutz_Image($dir . '/' . $index);
364:     }
365: 
366:     /**
367:      * Store an image for later retrieval.
368:      *
369:      * @param string $index    The index of the comic to retrieve
370:      * @param string $image    Raw (binary) image data to store
371:      * @param timestamp $data  Date to store it under (default today)
372:      *
373:      * @return boolean  True on success, false otherwise
374:      */
375:     function storeImage($index, $image, $date = null)
376:     {
377:         if (!is_object($image) || get_class($image) != "Klutz_Image") {
378:             return false;
379:         }
380: 
381:         if (is_null($date)) {
382:             $date = mktime(0, 0, 0);
383:         }
384: 
385:         // Make sure $this->basedir exists and is writeable.
386:         if (!file_exists($this->basedir)) {
387:             if (!file_exists(dirname($this->basedir))) { return false; }
388:             if (!mkdir($this->basedir, 0700)) { return false; }
389:         }
390:         if (!is_writable($this->basedir)) {
391:             return false;
392:         }
393: 
394:         $dir = $this->basedir . date($this->subdir, $date);
395: 
396:         if (!file_exists($dir)) {
397:             mkdir($dir, 0700);
398:         } elseif (!is_writable($dir)) {
399:             return false;
400:         }
401: 
402:         $fp = fopen($dir . '/' . $index, 'w+');
403:         fwrite($fp, $image->data);
404:         fclose($fp);
405:         $this->addSum($index, $date, md5($image->data));
406:         return true;
407:     }
408: 
409:     /**
410:      * Remove an image from the storage system (including its unique
411:      * ID).
412:      *
413:      * @param string $index    The index of the comic to remove
414:      * @param timestamp $date  The date of the comic to remove (default today)
415:      *
416:      * @return boolean  True on success, else false
417:      */
418:     function removeImage($index, $date = null)
419:     {
420:         if (is_null($date)) {
421:             $date = mktime(0, 0, 0);
422:         }
423: 
424:         if ($this->imageExists($index, $date)) {
425:             $file = $this->basedir . date($this->subdir, $date) . '/' . $index;
426:             if (unlink($file)) {
427:                 $this->removeSum($index, $date);
428:                 return true;
429:             }
430:         }
431:         return false;
432:     }
433: 
434:     /**
435:      * Remove all images from the storage system (including unique
436:      * IDs) for a given date.
437:      *
438:      * @param timestamp $date  The date to remove comics for (default today)
439:      *
440:      * @return boolean  True on success, else false
441:      */
442:     function removeDate($date = null)
443:     {
444:         if (is_null($date)) {
445:             $date = mktime(0, 0, 0);
446:         }
447: 
448:         $dir = $this->basedir . date($this->subdir, $date);
449:         if (file_exists($dir) && is_dir($dir)) {
450:             $d = dir($dir);
451:             while (false !== ($file = $d->read())) {
452:                 $file = $d->path . '/' . $file;
453:                 if (is_file($file)) {
454:                     unlink($file);
455:                 }
456:             }
457:             $d->close();
458:             if (@rmdir($dir)) {
459:                 $this->removeSum(null, $date);
460:                 return true;
461:             }
462:         }
463:         return false;
464:     }
465: 
466: }
467: 
API documentation generated by ApiGen