aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/Cache.php46
-rw-r--r--application/CachedPage.php63
-rw-r--r--application/LinkDB.php7
-rwxr-xr-xindex.php113
-rw-r--r--tests/CacheTest.php63
-rw-r--r--tests/CachedPageTest.php121
-rw-r--r--tests/LinkDBTest.php8
-rw-r--r--tests/utils/ReferenceLinkDB.php1
8 files changed, 334 insertions, 88 deletions
diff --git a/application/Cache.php b/application/Cache.php
new file mode 100644
index 00000000..9c7e818f
--- /dev/null
+++ b/application/Cache.php
@@ -0,0 +1,46 @@
1<?php
2/**
3 * Cache utilities
4 */
5
6/**
7 * Purges all cached pages
8 *
9 * @param string $pageCacheDir page cache directory
10 */
11function purgeCachedPages($pageCacheDir)
12{
13 if (! is_dir($pageCacheDir)) {
14 return;
15 }
16
17 // TODO: check write access to the cache directory
18
19 $handler = opendir($pageCacheDir);
20 if ($handler == false) {
21 return;
22 }
23
24 while (($filename = readdir($handler)) !== false) {
25 if (endsWith($filename, '.cache')) {
26 unlink($pageCacheDir.'/'.$filename);
27 }
28 }
29 closedir($handler);
30}
31
32/**
33 * Invalidates caches when the database is changed or the user logs out.
34 *
35 * @param string $pageCacheDir page cache directory
36 */
37function invalidateCaches($pageCacheDir)
38{
39 // Purge cache attached to session.
40 if (isset($_SESSION['tags'])) {
41 unset($_SESSION['tags']);
42 }
43
44 // Purge page cache shared by sessions.
45 purgeCachedPages($pageCacheDir);
46}
diff --git a/application/CachedPage.php b/application/CachedPage.php
new file mode 100644
index 00000000..50cfa9ac
--- /dev/null
+++ b/application/CachedPage.php
@@ -0,0 +1,63 @@
1<?php
2/**
3 * Simple cache system, mainly for the RSS/ATOM feeds
4 */
5class CachedPage
6{
7 // Directory containing page caches
8 private $cacheDir;
9
10 // Full URL of the page to cache -typically the value returned by pageUrl()
11 private $url;
12
13 // Should this URL be cached (boolean)?
14 private $shouldBeCached;
15
16 // Name of the cache file for this URL
17 private $filename;
18
19 /**
20 * Creates a new CachedPage
21 *
22 * @param string $cacheDir page cache directory
23 * @param string $url page URL
24 * @param bool $shouldBeCached whether this page needs to be cached
25 */
26 public function __construct($cacheDir, $url, $shouldBeCached)
27 {
28 // TODO: check write access to the cache directory
29 $this->cacheDir = $cacheDir;
30 $this->url = $url;
31 $this->filename = $this->cacheDir.'/'.sha1($url).'.cache';
32 $this->shouldBeCached = $shouldBeCached;
33 }
34
35 /**
36 * Returns the cached version of a page, if it exists and should be cached
37 *
38 * @return a cached version of the page if it exists, null otherwise
39 */
40 public function cachedVersion()
41 {
42 if (!$this->shouldBeCached) {
43 return null;
44 }
45 if (is_file($this->filename)) {
46 return file_get_contents($this->filename);
47 }
48 return null;
49 }
50
51 /**
52 * Puts a page in the cache
53 *
54 * @param string $pageContent XML content to cache
55 */
56 public function cache($pageContent)
57 {
58 if (!$this->shouldBeCached) {
59 return;
60 }
61 file_put_contents($this->filename, $pageContent);
62 }
63}
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 1e16fef1..463aa47e 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -269,8 +269,10 @@ You use the community supported version of the original Shaarli project, by Seba
269 269
270 /** 270 /**
271 * Saves the database from memory to disk 271 * Saves the database from memory to disk
272 *
273 * @param string $pageCacheDir page cache directory
272 */ 274 */
273 public function savedb() 275 public function savedb($pageCacheDir)
274 { 276 {
275 if (!$this->_loggedIn) { 277 if (!$this->_loggedIn) {
276 // TODO: raise an Exception instead 278 // TODO: raise an Exception instead
@@ -280,7 +282,7 @@ You use the community supported version of the original Shaarli project, by Seba
280 $this->_datastore, 282 $this->_datastore,
281 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix 283 self::$phpPrefix.base64_encode(gzdeflate(serialize($this->_links))).self::$phpSuffix
282 ); 284 );
283 invalidateCaches(); 285 invalidateCaches($pageCacheDir);
284 } 286 }
285 287
286 /** 288 /**
@@ -439,4 +441,3 @@ You use the community supported version of the original Shaarli project, by Seba
439 return $linkDays; 441 return $linkDays;
440 } 442 }
441} 443}
442?>
diff --git a/index.php b/index.php
index 2c731e9f..84b8f015 100755
--- a/index.php
+++ b/index.php
@@ -70,6 +70,8 @@ if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
70} 70}
71 71
72// Shaarli library 72// Shaarli library
73require_once 'application/Cache.php';
74require_once 'application/CachedPage.php';
73require_once 'application/LinkDB.php'; 75require_once 'application/LinkDB.php';
74require_once 'application/TimeZone.php'; 76require_once 'application/TimeZone.php';
75require_once 'application/Utils.php'; 77require_once 'application/Utils.php';
@@ -203,63 +205,6 @@ function checkUpdate()
203 205
204 206
205// ----------------------------------------------------------------------------------------------- 207// -----------------------------------------------------------------------------------------------
206// Simple cache system (mainly for the RSS/ATOM feeds).
207
208class pageCache
209{
210 private $url; // Full URL of the page to cache (typically the value returned by pageUrl())
211 private $shouldBeCached; // boolean: Should this url be cached?
212 private $filename; // Name of the cache file for this url.
213
214 /*
215 $url = URL (typically the value returned by pageUrl())
216 $shouldBeCached = boolean. If false, the cache will be disabled.
217 */
218 public function __construct($url,$shouldBeCached)
219 {
220 $this->url = $url;
221 $this->filename = $GLOBALS['config']['PAGECACHE'].'/'.sha1($url).'.cache';
222 $this->shouldBeCached = $shouldBeCached;
223 }
224
225 // If the page should be cached and a cached version exists,
226 // returns the cached version (otherwise, return null).
227 public function cachedVersion()
228 {
229 if (!$this->shouldBeCached) return null;
230 if (is_file($this->filename)) { return file_get_contents($this->filename); exit; }
231 return null;
232 }
233
234 // Put a page in the cache.
235 public function cache($page)
236 {
237 if (!$this->shouldBeCached) return;
238 file_put_contents($this->filename,$page);
239 }
240
241 // Purge the whole cache.
242 // (call with pageCache::purgeCache())
243 public static function purgeCache()
244 {
245 if (is_dir($GLOBALS['config']['PAGECACHE']))
246 {
247 $handler = opendir($GLOBALS['config']['PAGECACHE']);
248 if ($handler!==false)
249 {
250 while (($filename = readdir($handler))!==false)
251 {
252 if (endsWith($filename,'.cache')) { unlink($GLOBALS['config']['PAGECACHE'].'/'.$filename); }
253 }
254 closedir($handler);
255 }
256 }
257 }
258
259}
260
261
262// -----------------------------------------------------------------------------------------------
263// Log to text file 208// Log to text file
264function logm($message) 209function logm($message)
265{ 210{
@@ -718,8 +663,16 @@ function showRSS()
718 663
719 // Cache system 664 // Cache system
720 $query = $_SERVER["QUERY_STRING"]; 665 $query = $_SERVER["QUERY_STRING"];
721 $cache = new pageCache(pageUrl(),startsWith($query,'do=rss') && !isLoggedIn()); 666 $cache = new CachedPage(
722 $cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; } 667 $GLOBALS['config']['PAGECACHE'],
668 pageUrl(),
669 startsWith($query,'do=rss') && !isLoggedIn()
670 );
671 $cached = $cache->cachedVersion();
672 if (! empty($cached)) {
673 echo $cached;
674 exit;
675 }
723 676
724 // If cached was not found (or not usable), then read the database and build the response: 677 // If cached was not found (or not usable), then read the database and build the response:
725 $LINKSDB = new LinkDB( 678 $LINKSDB = new LinkDB(
@@ -798,11 +751,19 @@ function showATOM()
798 751
799 // Cache system 752 // Cache system
800 $query = $_SERVER["QUERY_STRING"]; 753 $query = $_SERVER["QUERY_STRING"];
801 $cache = new pageCache(pageUrl(),startsWith($query,'do=atom') && !isLoggedIn()); 754 $cache = new CachedPage(
802 $cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; } 755 $GLOBALS['config']['PAGECACHE'],
803 // If cached was not found (or not usable), then read the database and build the response: 756 pageUrl(),
757 startsWith($query,'do=atom') && !isLoggedIn()
758 );
759 $cached = $cache->cachedVersion();
760 if (!empty($cached)) {
761 echo $cached;
762 exit;
763 }
804 764
805// Read links from database (and filter private links if used it not logged in). 765 // If cached was not found (or not usable), then read the database and build the response:
766 // Read links from database (and filter private links if used it not logged in).
806 $LINKSDB = new LinkDB( 767 $LINKSDB = new LinkDB(
807 $GLOBALS['config']['DATASTORE'], 768 $GLOBALS['config']['DATASTORE'],
808 isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'], 769 isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
@@ -884,7 +845,11 @@ function showATOM()
884function showDailyRSS() { 845function showDailyRSS() {
885 // Cache system 846 // Cache system
886 $query = $_SERVER["QUERY_STRING"]; 847 $query = $_SERVER["QUERY_STRING"];
887 $cache = new pageCache(pageUrl(), startsWith($query, 'do=dailyrss') && !isLoggedIn()); 848 $cache = new CachedPage(
849 $GLOBALS['config']['PAGECACHE'],
850 pageUrl(),
851 startsWith($query,'do=dailyrss') && !isLoggedIn()
852 );
888 $cached = $cache->cachedVersion(); 853 $cached = $cache->cachedVersion();
889 if (!empty($cached)) { 854 if (!empty($cached)) {
890 echo $cached; 855 echo $cached;
@@ -1076,7 +1041,7 @@ function renderPage()
1076 // -------- User wants to logout. 1041 // -------- User wants to logout.
1077 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=logout')) 1042 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=logout'))
1078 { 1043 {
1079 invalidateCaches(); 1044 invalidateCaches($GLOBALS['config']['PAGECACHE']);
1080 logout(); 1045 logout();
1081 header('Location: ?'); 1046 header('Location: ?');
1082 exit; 1047 exit;
@@ -1383,7 +1348,7 @@ function renderPage()
1383 $value['tags']=trim(implode(' ',$tags)); 1348 $value['tags']=trim(implode(' ',$tags));
1384 $LINKSDB[$key]=$value; 1349 $LINKSDB[$key]=$value;
1385 } 1350 }
1386 $LINKSDB->savedb(); // Save to disk. 1351 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk.
1387 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>'; 1352 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
1388 exit; 1353 exit;
1389 } 1354 }
@@ -1400,7 +1365,7 @@ function renderPage()
1400 $value['tags']=trim(implode(' ',$tags)); 1365 $value['tags']=trim(implode(' ',$tags));
1401 $LINKSDB[$key]=$value; 1366 $LINKSDB[$key]=$value;
1402 } 1367 }
1403 $LINKSDB->savedb(); // Save to disk. 1368 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk.
1404 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>'; 1369 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
1405 exit; 1370 exit;
1406 } 1371 }
@@ -1429,7 +1394,7 @@ function renderPage()
1429 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags)); 1394 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags));
1430 if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title. 1395 if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title.
1431 $LINKSDB[$linkdate] = $link; 1396 $LINKSDB[$linkdate] = $link;
1432 $LINKSDB->savedb(); // Save to disk. 1397 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk.
1433 pubsubhub(); 1398 pubsubhub();
1434 1399
1435 // If we are called from the bookmarklet, we must close the popup: 1400 // If we are called from the bookmarklet, we must close the popup:
@@ -1462,7 +1427,7 @@ function renderPage()
1462 // - we are protected from XSRF by the token. 1427 // - we are protected from XSRF by the token.
1463 $linkdate=$_POST['lf_linkdate']; 1428 $linkdate=$_POST['lf_linkdate'];
1464 unset($LINKSDB[$linkdate]); 1429 unset($LINKSDB[$linkdate]);
1465 $LINKSDB->savedb(); // save to disk 1430 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk
1466 1431
1467 // If we are called from the bookmarklet, we must close the popup: 1432 // If we are called from the bookmarklet, we must close the popup:
1468 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1433 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
@@ -1751,7 +1716,7 @@ function importFile()
1751 } 1716 }
1752 } 1717 }
1753 } 1718 }
1754 $LINKSDB->savedb(); 1719 $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']);
1755 1720
1756 echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>'; 1721 echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
1757 } 1722 }
@@ -2386,14 +2351,6 @@ function resizeImage($filepath)
2386 return true; 2351 return true;
2387} 2352}
2388 2353
2389// Invalidate caches when the database is changed or the user logs out.
2390// (e.g. tags cache).
2391function invalidateCaches()
2392{
2393 unset($_SESSION['tags']); // Purge cache attached to session.
2394 pageCache::purgeCache(); // Purge page cache shared by sessions.
2395}
2396
2397try { 2354try {
2398 mergeDeprecatedConfig($GLOBALS, isLoggedIn()); 2355 mergeDeprecatedConfig($GLOBALS, isLoggedIn());
2399} catch(Exception $e) { 2356} catch(Exception $e) {
diff --git a/tests/CacheTest.php b/tests/CacheTest.php
new file mode 100644
index 00000000..4caf6552
--- /dev/null
+++ b/tests/CacheTest.php
@@ -0,0 +1,63 @@
1<?php
2/**
3 * Cache tests
4 */
5
6// required to access $_SESSION array
7session_start();
8
9require_once 'application/Cache.php';
10
11/**
12 * Unitary tests for cached pages
13 */
14class CachedTest extends PHPUnit_Framework_TestCase
15{
16 // test cache directory
17 protected static $testCacheDir = 'tests/dummycache';
18
19 // dummy cached file names / content
20 protected static $pages = array('a', 'toto', 'd7b59c');
21
22
23 /**
24 * Populate the cache with dummy files
25 */
26 public function setUp()
27 {
28 if (! is_dir(self::$testCacheDir)) {
29 mkdir(self::$testCacheDir);
30 }
31
32 foreach (self::$pages as $page) {
33 file_put_contents(self::$testCacheDir.'/'.$page.'.cache', $page);
34 }
35 }
36
37 /**
38 * Purge cached pages
39 */
40 public function testPurgeCachedPages()
41 {
42 purgeCachedPages(self::$testCacheDir);
43 foreach (self::$pages as $page) {
44 $this->assertFileNotExists(self::$testCacheDir.'/'.$page.'.cache');
45 }
46 }
47
48 /**
49 * Purge cached pages and session cache
50 */
51 public function testInvalidateCaches()
52 {
53 $this->assertArrayNotHasKey('tags', $_SESSION);
54 $_SESSION['tags'] = array('goodbye', 'cruel', 'world');
55
56 invalidateCaches(self::$testCacheDir);
57 foreach (self::$pages as $page) {
58 $this->assertFileNotExists(self::$testCacheDir.'/'.$page.'.cache');
59 }
60
61 $this->assertArrayNotHasKey('tags', $_SESSION);
62 }
63}
diff --git a/tests/CachedPageTest.php b/tests/CachedPageTest.php
new file mode 100644
index 00000000..e97af030
--- /dev/null
+++ b/tests/CachedPageTest.php
@@ -0,0 +1,121 @@
1<?php
2/**
3 * PageCache tests
4 */
5
6require_once 'application/CachedPage.php';
7
8/**
9 * Unitary tests for cached pages
10 */
11class CachedPageTest extends PHPUnit_Framework_TestCase
12{
13 // test cache directory
14 protected static $testCacheDir = 'tests/pagecache';
15 protected static $url = 'http://shaar.li/?do=atom';
16 protected static $filename;
17
18 /**
19 * Create the cache directory if needed
20 */
21 public static function setUpBeforeClass()
22 {
23 if (! is_dir(self::$testCacheDir)) {
24 mkdir(self::$testCacheDir);
25 }
26 self::$filename = self::$testCacheDir.'/'.sha1(self::$url).'.cache';
27 }
28
29 /**
30 * Reset the page cache
31 */
32 public function setUp()
33 {
34 if (file_exists(self::$filename)) {
35 unlink(self::$filename);
36 }
37 }
38
39 /**
40 * Create a new cached page
41 */
42 public function testConstruct()
43 {
44 new CachedPage(self::$testCacheDir, '', true);
45 new CachedPage(self::$testCacheDir, '', false);
46 new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=rss', true);
47 new CachedPage(self::$testCacheDir, 'http://shaar.li/?do=atom', false);
48 }
49
50 /**
51 * Cache a page's content
52 */
53 public function testCache()
54 {
55 $page = new CachedPage(self::$testCacheDir, self::$url, true);
56
57 $this->assertFileNotExists(self::$filename);
58 $page->cache('<p>Some content</p>');
59 $this->assertFileExists(self::$filename);
60 $this->assertEquals(
61 '<p>Some content</p>',
62 file_get_contents(self::$filename)
63 );
64 }
65
66 /**
67 * "Cache" a page's content - the page is not to be cached
68 */
69 public function testShouldNotCache()
70 {
71 $page = new CachedPage(self::$testCacheDir, self::$url, false);
72
73 $this->assertFileNotExists(self::$filename);
74 $page->cache('<p>Some content</p>');
75 $this->assertFileNotExists(self::$filename);
76 }
77
78 /**
79 * Return a page's cached content
80 */
81 public function testCachedVersion()
82 {
83 $page = new CachedPage(self::$testCacheDir, self::$url, true);
84
85 $this->assertFileNotExists(self::$filename);
86 $page->cache('<p>Some content</p>');
87 $this->assertFileExists(self::$filename);
88 $this->assertEquals(
89 '<p>Some content</p>',
90 $page->cachedVersion()
91 );
92 }
93
94 /**
95 * Return a page's cached content - the file does not exist
96 */
97 public function testCachedVersionNoFile()
98 {
99 $page = new CachedPage(self::$testCacheDir, self::$url, true);
100
101 $this->assertFileNotExists(self::$filename);
102 $this->assertEquals(
103 null,
104 $page->cachedVersion()
105 );
106 }
107
108 /**
109 * Return a page's cached content - the page is not to be cached
110 */
111 public function testNoCachedVersion()
112 {
113 $page = new CachedPage(self::$testCacheDir, self::$url, false);
114
115 $this->assertFileNotExists(self::$filename);
116 $this->assertEquals(
117 null,
118 $page->cachedVersion()
119 );
120 }
121}
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 504c8190..451f1d6f 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -3,6 +3,7 @@
3 * Link datastore tests 3 * Link datastore tests
4 */ 4 */
5 5
6require_once 'application/Cache.php';
6require_once 'application/LinkDB.php'; 7require_once 'application/LinkDB.php';
7require_once 'application/Utils.php'; 8require_once 'application/Utils.php';
8require_once 'tests/utils/ReferenceLinkDB.php'; 9require_once 'tests/utils/ReferenceLinkDB.php';
@@ -180,11 +181,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
180 'tags'=>'unit test' 181 'tags'=>'unit test'
181 ); 182 );
182 $testDB[$link['linkdate']] = $link; 183 $testDB[$link['linkdate']] = $link;
183 184 $testDB->savedb('tests');
184 // TODO: move PageCache to a proper class/file
185 function invalidateCaches() {}
186
187 $testDB->savedb();
188 185
189 $testDB = new LinkDB(self::$testDatastore, true, false); 186 $testDB = new LinkDB(self::$testDatastore, true, false);
190 $this->assertEquals($dbSize + 1, sizeof($testDB)); 187 $this->assertEquals($dbSize + 1, sizeof($testDB));
@@ -514,4 +511,3 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
514 ); 511 );
515 } 512 }
516} 513}
517?>
diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php
index 0b225720..47b51829 100644
--- a/tests/utils/ReferenceLinkDB.php
+++ b/tests/utils/ReferenceLinkDB.php
@@ -125,4 +125,3 @@ class ReferenceLinkDB
125 return $this->_privateCount; 125 return $this->_privateCount;
126 } 126 }
127} 127}
128?>