diff --git a/filecache.inc b/filecache.inc
index bfbc784..ce6ec6f 100644
--- a/filecache.inc
+++ b/filecache.inc
@@ -134,15 +134,17 @@ class DrupalFileCache implements DrupalCacheInterface {
     return $this->basedir()  . $this->encode_cid($cid);
   }
 
+  /**
+   * Implements DrupalCacheInterface::get().
+   */
   function get($cid) {
+    global $user;
+
     if (empty($cid)) {
       return FALSE;
     }
     $filename = $this->cid_file($cid);
 
-    // XXX should be in getMultiple() and get() to call getMultiple()
-    $this->delete_flushed();
-
     // Use @ because cache entry may not exist
     $content = @file_get_contents($filename);
     if ($content === FALSE) {
@@ -170,16 +172,53 @@ class DrupalFileCache implements DrupalCacheInterface {
       }
     }
 
-    // XXX Should reproduce the cache_lifetime / cache_flush_$bin logic
-    $cache_flush = variable_get('filecache_flush_'. $this->bin, 0);
-    if ($cache->expire != CACHE_TEMPORARY && // XXX how to handle this?
-        $cache->expire != CACHE_PERMANENT &&
-        ($cache->expire < REQUEST_TIME ||
-         ($cache_flush && $cache->created < $cache_flush))) {
+    // The unserialized data should always contain the cache ID.
+    if (empty($cache->cid) || $cache->cid != $cid) {
       unlink($filename);
       return FALSE;
     }
 
+    // Check that the data is currently valid by making sure the current bin's
+    // cache flush timer has not run out.
+    // Code note: this is equivalent to DrupalDatabaseCache::garbageCollection,
+    // except
+    // - DrupalDatabaseCache immediately deletes _all_ expired cache entries.
+    //   This is too expensive to do for us on every cache_get() because we have
+    //   no index on $cache->expire, so we'd need to read all files' content.
+    // - DrupalDatabaseCache ignores expiry (and therfore returns expired items)
+    //   if cache_flush_BIN is not set - which comes doen to: if the site has no
+    //   'minimum cache lifetime' set.
+    $cache_lifetime = variable_get('cache_lifetime', 0);
+    // Note: $cache_flush is in practice never set if $cache_lifetime == 0.
+    $cache_flush = variable_get('cache_flush_' . $this->bin, 0);
+    if ($cache_flush) {
+      if ($cache_flush + $cache_lifetime <= REQUEST_TIME
+          && $cache->expire != CACHE_PERMANENT && $cache->expire < $cache_flush) {
+        unlink($filename);
+        return FALSE;
+      }
+    }
+    else {
+      // This is the part not present in DrupalDatabaseCache
+      if ($cache->expire != CACHE_PERMANENT && $cache->expire != CACHE_TEMPORARY
+          && $cache->expire < REQUEST_TIME) {
+        unlink($filename);
+        return FALSE;
+      }
+    }
+
+    // Check if the data is currently valid for this user by making sure the
+    // cache entry was created after the timestamp in the current session's
+    // cache timer. If the data is permanent or we're not enforcing a minimum
+    //  cache lifetime, ignore this 'simulated cache flush'.
+    // The cache variable is loaded into the $user object by
+    // _drupal_session_read() in session.inc.
+    // (Code note: this is equivalent to DrupalDatabaseCache::prepareItem)
+    if ($cache->expire != CACHE_PERMANENT && $cache_lifetime && $user->cache > $cache->created) {
+      // This cache data is too old and thus not valid for us, ignore it.
+      return FALSE;
+    }
+
     // Some systems don't update access time so we do it this way
     // XXX There's a chance that file no longer exists at this point
     // XXX but it's ok because we deal fine with broken cache entries
@@ -188,11 +227,12 @@ class DrupalFileCache implements DrupalCacheInterface {
     // XXX should be configurable
     //touch($filename);
 
-    // XXX should assert($cache->cid == $cid)
-
     return $cache;
   }
 
+  /**
+   * Implements DrupalCacheInterface::getMultiple().
+   */
   function getMultiple(&$cids) {
     $results = array();
     foreach ($cids as $cid) {
@@ -205,6 +245,9 @@ class DrupalFileCache implements DrupalCacheInterface {
     return $results;
   }
 
+  /**
+   * Implements DrupalCacheInterface::set().
+   */
   function set($cid, $data, $expire = CACHE_PERMANENT) {
     if (empty($cid)) {
       return;
@@ -255,35 +298,26 @@ class DrupalFileCache implements DrupalCacheInterface {
     }
   }
 
+  /**
+   * Implements DrupalCacheInterface::clear().
+   */
   function clear($cid = NULL, $wildcard = FALSE) {
     global $user;
 
-    // parts are shamelessy copied from includes/cache.inc
-
     if (empty($cid)) {
       if (variable_get('cache_lifetime', 0)) {
         // We store the time in the current user's $user->cache variable which
-        // will be saved into the sessions bin by _drupal_session_write(). We then
-        // simulate that the cache was flushed for this user by not returning
-        // cached data that was cached before the timestamp.
+        // will be saved into the sessions bin by _drupal_session_write(). This
+        // can simulate a cache flush for this user, by not returning cached
+        // data that was cached before the timestamp by future get() calls.
+        // This affects ALL cache bins which implement this 'flush simulation'!
         $user->cache = REQUEST_TIME;
-
-        $cache_flush = variable_get('cache_flush_' . $this->bin, 0);
-        if ($cache_flush == 0) {
-          // This is the first request to clear the cache, start a timer.
-          variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
-        }
-        elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
-          // Clear the cache for everyone, cache_lifetime seconds have
-          // passed since the first request to clear the cache.
-          $this->delete_expired();
-          variable_set('cache_flush_' . $this->bin, 0);
-        }
-      }
-      else {
-        // No minimum cache lifetime, flush all temporary cache entries now.
-        $this->delete_expired();
       }
+      // We won't delete anything until <cache_lifetime> has passed since the
+      // last cache clear, but when that time comes only entries with an expiry
+      // time in the future are preserved. (i.e. the created time is not checked
+      // against <cache_lifetime> and all CACHE_TEMPORARY items are deleted.)
+      $this->garbageCollection('clear');
     }
     else {
       if ($wildcard) {
@@ -350,8 +384,25 @@ class DrupalFileCache implements DrupalCacheInterface {
     timer_stop('filecache_delete_wildcard');
   }
 
-  public function delete_expired($gather_stats = FALSE) {
-    $cache_size = 0;
+  /**
+   * Deletes expirable cache entries. (This reads the contents of all files in
+   * the cache bin, so could take a very long time.)
+   *
+   * @param $delete_temporary boolean
+   *   Whether to also delete entries with expire == CACHE_TEMPORARY
+   * @param $expiry integer
+   *   Timestamp to check against entries' expire time.
+   * @param $creation integer
+   *   Timestamp to check against entries' created time. Entries will only be
+   *   deleted if they match the 'expiry' criteria AND are older than this timestamp. (optional)
+   * @param $calc_size boolean
+   *   Calculate the size of the cache (only when actually deleting entries)
+   *
+   * @return
+   *   The cache size, or -1 if $calc_size = FALSE.
+   */
+   protected function delete_expired($delete_temporary, $expiry, $creation = 0, $calc_size = FALSE) {
+    $cache_size = $calc_size ? 0 : -1;
     $nr_deleted = 0;
     timer_start('filecache_delete_expired');
 
@@ -366,12 +417,14 @@ class DrupalFileCache implements DrupalCacheInterface {
       $cache = @unserialize($content);
       // Remove expired files and also broken ones; the 'basedir' for this bin
       // is supposed to be exclusively ours.
-      if (!is_object($cache) || $cache->expire == CACHE_TEMPORARY ||
-        ($cache->expire < REQUEST_TIME && $cache->expire != CACHE_PERMANENT)) {
+      if (!is_object($cache) ||
+          ($cache->expire != CACHE_PERMANENT && $cache->expire < $expiry
+           && ($delete_temporary || $cache->expire != CACHE_TEMPORARY)
+           && ($creation === 0 || $cache->created < $creation))) {
         @unlink($filename);
         ++$nr_deleted;
       }
-      elseif ($gather_stats) {
+      elseif ($calc_size) {
         // gather statistics
         $stat = @stat($filename);
         if ($stat === FALSE) {
@@ -387,39 +440,70 @@ class DrupalFileCache implements DrupalCacheInterface {
     return $cache_size;
   }
 
-  protected function delete_flushed() {
-    static $recursion = FALSE; // XXX how cache.inc survives this?
-    if ($recursion) {
-      return;
+  /**
+   * Garbage collection; 'expirable' cache entries.
+   *
+   * @param $type
+   *  The 'type' of expiry, which controls the various parameters. See the code.
+   * @param $calc_size
+   *   Calculate the size of the cache (only when actually deleting entries)
+   *
+   * @return
+   *   The cache size, or -1 if nothing was deleted (in which case the size is
+   *   not calculated; the caller has no control over this when not ignoring
+   *   cache lifetime.)
+   */
+  public function garbageCollection($type, $calc_size = FALSE) {
+    $cache_size = -1;
+
+    // All 'logic', except for the check on cache_lifetime,
+    // is contained in the variables defined by type.
+    switch ($type) {
+      case 'clear':
+        $check_cache_lifetime = TRUE;
+        $expiry = REQUEST_TIME;
+        $delete_temporary = TRUE;
+        $creation = 0; // no check
+        break;
+
+      default:
+        $check_cache_lifetime = TRUE;
+        $expiry = REQUEST_TIME;
+        $delete_temporary = FALSE;
+        $creation = -1; // last cache flush, or 'no check'
     }
-    $recursion = TRUE;
 
-    // Garbage collection necessary when enforcing a minimum cache lifetime.
-    $cache_flush = variable_get('cache_flush_' . $this->bin, 0);
-    if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) {
-      // Reset the variable immediately to prevent a meltdown in heavy load situations.
-      variable_set('cache_flush_' . $this->bin, 0);
-      // Time to flush old cache data
-      foreach ($this->all() as $filename) {
-        // XXX reads all entries XXX
-        $content = @file_get_contents($filename);
-        if ($content === FALSE) {
-          continue;
-        }
-        $cache = @unserialize($content);
-        if ($content === FALSE) {
-          continue;
-        }
-        if ($cache->expire != CACHE_PERMANENT &&
-            $cache->expire <= $cache_flush) {
-          @unlink($filename);
-        }
-      } // foreach $filename
-    } // if $cache_flush
+    if ($check_cache_lifetime && variable_get('cache_lifetime', 0)) {
+      $cache_flush = variable_get('cache_flush_' . $this->bin, 0);
+      if ($cache_flush == 0) {
+        // This is the first request to clear the cache, start a timer.
+        variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
+      }
+      elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) {
+        // cache_lifetime seconds have passed since the previous cache flush, so
+        // flush again. Reset the variable immediately to prevent concurrent
+        // deletion runs. (This is vulnerable to race conditions but that's ok;
+        // it still prevents meltdown in heavy load situations.)
+        variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
+        $cache_size = $this->delete_expired($delete_temporary,
+          $expiry == -1 ? $cache_flush : $expiry,
+          $creation == -1 ? $cache_flush : $creation, $calc_size);
+      }
+    }
+    else {
+      // No minimum cache lifetime, flush all temporary cache entries now.
+      // (We can assume 'cache_flush_BIN' is not set.)
+      $cache_size = $this->delete_expired($delete_temporary,
+        $expiry == -1 ? REQUEST_TIME : $expiry,
+        $creation == -1 ? 0 : $creation, $calc_size);
+    }
 
-    $recursion = FALSE;
+    return $cache_size;
   }
 
+  /**
+   * Implements DrupalCacheInterface::isEmpty().
+   */
   function isEmpty() {
     return count($this->all()) == 0;
   }
diff --git a/filecache.module b/filecache.module
index 2ad2b55..47d8ca5 100644
--- a/filecache.module
+++ b/filecache.module
@@ -16,7 +16,15 @@ function filecache_cron() {
   foreach (variable_get('filecache_bins', array()) as $bin) {
     $obj = _cache_get_object($bin);
     if ($obj instanceof DrupalFileCache) {
-      $cache_size += $obj->delete_expired(TRUE);
+      $bin_size = $obj->garbageCollection('cron', TRUE);
+      if ($bin_size == -1) {
+        // Nothing was deleted; get previous size.
+        $cache_size += variable_get('filecache_space_' . $bin, 0);
+      }
+      else {
+        $cache_size += $bin_size;
+        variable_set('filecache_space_' . $bin, $bin_size);
+      }
     }
   }
 
