diff --git a/src/main/java/org/caosdb/server/database/BackendTransaction.java b/src/main/java/org/caosdb/server/database/BackendTransaction.java
index 4f41f7a3d4e5f4b077abbf7a2a6aa9e196112bc2..c4af8f462c3ece4b06e623bebc2d049433a36322 100644
--- a/src/main/java/org/caosdb/server/database/BackendTransaction.java
+++ b/src/main/java/org/caosdb/server/database/BackendTransaction.java
@@ -36,6 +36,7 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetFilesInDi
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetIDByName;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetInfo;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetUpdateableChecksums;
+import org.caosdb.server.database.backend.implementation.MySQL.MySQLGetVirtualFSO;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertEntityDatatype;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertEntityProperties;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLInsertFSODescriptor;
@@ -52,7 +53,6 @@ import org.caosdb.server.database.backend.implementation.MySQL.MySQLListUsers;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLLogUserVisit;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRegisterSubDomain;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveAll;
-import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveAllUncheckedFiles;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveDatatypes;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveEntityACL;
 import org.caosdb.server.database.backend.implementation.MySQL.MySQLRetrieveLogRecord;
@@ -88,6 +88,7 @@ import org.caosdb.server.database.backend.interfaces.GetFilesInDirectoryImpl;
 import org.caosdb.server.database.backend.interfaces.GetIDByNameImpl;
 import org.caosdb.server.database.backend.interfaces.GetInfoImpl;
 import org.caosdb.server.database.backend.interfaces.GetUpdateableChecksumsImpl;
+import org.caosdb.server.database.backend.interfaces.GetVirtualFSOImpl;
 import org.caosdb.server.database.backend.interfaces.InsertEntityDatatypeImpl;
 import org.caosdb.server.database.backend.interfaces.InsertEntityPropertiesImpl;
 import org.caosdb.server.database.backend.interfaces.InsertFSODescriptorImpl;
@@ -104,7 +105,6 @@ import org.caosdb.server.database.backend.interfaces.ListUsersImpl;
 import org.caosdb.server.database.backend.interfaces.LogUserVisitImpl;
 import org.caosdb.server.database.backend.interfaces.RegisterSubDomainImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveAllImpl;
-import org.caosdb.server.database.backend.interfaces.RetrieveAllUncheckedFilesImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveDatatypesImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveEntityACLImpl;
 import org.caosdb.server.database.backend.interfaces.RetrieveLogRecordImpl;
@@ -184,7 +184,6 @@ public abstract class BackendTransaction implements Undoable {
       setImpl(RetrieveSparseEntityImpl.class, MySQLRetrieveSparseEntity.class);
       setImpl(SyncStatsImpl.class, MySQLSyncStats.class);
       setImpl(SetFileCheckedTimestampImpl.class, MySQLSetFileCheckedTimestamp.class);
-      setImpl(RetrieveAllUncheckedFilesImpl.class, MySQLRetrieveAllUncheckedFiles.class);
       setImpl(UpdateUserImpl.class, MySQLUpdateUser.class);
       setImpl(DeleteUserImpl.class, MySQLDeleteUser.class);
       setImpl(SetPasswordImpl.class, MySQLSetPassword.class);
@@ -212,6 +211,7 @@ public abstract class BackendTransaction implements Undoable {
       setImpl(ListUsersImpl.class, MySQLListUsers.class);
       setImpl(LogUserVisitImpl.class, MySQLLogUserVisit.class);
       setImpl(RetrieveEntityACLImpl.class, MySQLRetrieveEntityACL.class);
+      setImpl(GetVirtualFSOImpl.class, MySQLGetVirtualFSO.class);
     }
   }
 
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java
new file mode 100644
index 0000000000000000000000000000000000000000..71b0ba1e99789e9fd08d7d1ca5abd5639baf2939
--- /dev/null
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLGetVirtualFSO.java
@@ -0,0 +1,42 @@
+package org.caosdb.server.database.backend.implementation.MySQL;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import org.caosdb.server.database.DatabaseUtils;
+import org.caosdb.server.database.access.Access;
+import org.caosdb.server.database.backend.interfaces.GetVirtualFSOImpl;
+import org.caosdb.server.database.exceptions.TransactionException;
+import org.caosdb.server.database.proto.SparseEntity;
+import org.caosdb.server.filesystem.FSODescriptor;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
+
+public class MySQLGetVirtualFSO extends MySQLTransaction implements GetVirtualFSOImpl {
+
+  public MySQLGetVirtualFSO(Access access) {
+    super(access);
+  }
+
+  public static final String STMT_GET_FSO =
+      "SELECT 'SHA-512' AS FileHashAlgo, file_id AS FileId, "
+          + /*parent_directory AS FileParentID,*/ " path AS FilePath, size AS FileSize, hex(hash) AS FileHash, checked_timestamp AS FileHashChecked, mimetype AS FileMimeType, file_storage_id AS FileStorageID, file_key AS FileKey FROM files WHERE file_storage_id = ? AND file_key = ?";
+
+  @Override
+  public VirtualFSODescriptorInterface execute(String fileStorageId, String key) {
+    try {
+      PreparedStatement stmt = prepareStatement(STMT_GET_FSO);
+      stmt.setString(1, fileStorageId);
+      stmt.setString(2, key);
+      ResultSet resultSet = stmt.executeQuery();
+      if (resultSet.next()) {
+        SparseEntity e = new SparseEntity();
+        e.id = resultSet.getInt("FileId");
+        DatabaseUtils.parseFSODescriptorFields(resultSet, e);
+        return new FSODescriptor(e);
+      }
+    } catch (SQLException | ConnectionException e) {
+      throw new TransactionException(e);
+    }
+    return null;
+  }
+}
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java
index dfeed05b36d4001ad46e24aa82dab2b968d14db0..504f992a8fc26cccea4c93eb6970c6e7655ab2d7 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLListFiles.java
@@ -10,8 +10,9 @@ import org.caosdb.server.database.access.Access;
 import org.caosdb.server.database.backend.interfaces.ListFilesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.database.proto.SparseEntity;
-import org.caosdb.server.filesystem.FSODescriptorInterface;
+import org.caosdb.server.filesystem.FSODescriptor;
 import org.caosdb.server.filesystem.Path;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
 
 public class MySQLListFiles extends MySQLTransaction implements ListFilesImpl {
 
@@ -20,7 +21,8 @@ public class MySQLListFiles extends MySQLTransaction implements ListFilesImpl {
   }
 
   public static final String LIST_FILES_STMT =
-      "SELECT 'SHA-512' AS FileHashAlgo, file_id AS FileId, parent_directory AS FileParentID, path AS FilePath, size AS FileSize, hex(hash) AS FileHash, checked_timestamp AS FileHashChecked, mimetype AS FileMimeType, file_storage_id AS FileStorageID, file_key AS FileKey FROM files";
+      "SELECT 'SHA-512' AS FileHashAlgo, file_id AS FileId, "
+          + /*parent_directory AS FileParentID,*/ " path AS FilePath, size AS FileSize, hex(hash) AS FileHash, checked_timestamp AS FileHashChecked, mimetype AS FileMimeType, file_storage_id AS FileStorageID, file_key AS FileKey FROM files";
   public static final String FILE_STORAGE_CLAUSE = " file_storage_id = ?";
   public static final String PATH_LIKE_CLAUSE = " path LIKE ?";
   public static final String LAST_CHECKED_BEFORE_CLAUSE = " checked_timestamp < ?";
@@ -29,18 +31,18 @@ public class MySQLListFiles extends MySQLTransaction implements ListFilesImpl {
   public static final String LIMIT = " LIMIT ";
 
   @Override
-  public Iterable<FSODescriptorInterface> execute(
+  public List<VirtualFSODescriptorInterface> execute(
       String fileStorageId, Path path, Long lastChecked, Integer limit) {
 
+    final List<VirtualFSODescriptorInterface> result = new LinkedList<>();
     try {
       PreparedStatement stmt = prepareStatement(fileStorageId, path, lastChecked, limit);
-      final List<SparseEntity> result = new LinkedList<>();
       try (ResultSet rs = stmt.executeQuery()) {
         while (rs.next()) {
           final SparseEntity entity = new SparseEntity();
           entity.id = rs.getInt("FileId");
           DatabaseUtils.parseFSODescriptorFields(rs, entity);
-          result.add(entity);
+          result.add(new FSODescriptor(entity));
         }
       }
     } catch (SQLException e) {
@@ -48,8 +50,9 @@ public class MySQLListFiles extends MySQLTransaction implements ListFilesImpl {
     } catch (ConnectionException e) {
       throw new TransactionException(e);
     }
+    System.err.println("MySQLListFiles: " + result.size());
 
-    return null;
+    return result;
   }
 
   private PreparedStatement prepareStatement(
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAllUncheckedFiles.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAllUncheckedFiles.java
deleted file mode 100644
index 9fc50c821f59325f3ac320d1d3693b6bad3510bc..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLRetrieveAllUncheckedFiles.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.database.backend.implementation.MySQL;
-
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Iterator;
-import org.caosdb.server.database.DatabaseUtils;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.interfaces.RetrieveAllUncheckedFilesImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.SparseEntity;
-
-/**
- * Implements {@link RetrieveAllUncheckedFilesImpl} for a MySQL/MariaDB back-end.
- *
- * @author Timm Fitschen <t.fitschen@indiscale.com>
- */
-public class MySQLRetrieveAllUncheckedFiles extends MySQLTransaction
-    implements RetrieveAllUncheckedFilesImpl {
-
-  private static final String STMT_RETRIEVE_ALL =
-      "SELECT hash_algorithm AS FileHashAlgo, file_id AS FileId, " /*parent_directory AS FileParentId,*/
-          + "path AS FilePath, size AS FileSize, hex(hash) AS FileHash, checked_timestamp AS FileHashChecked, mimetype AS FileMimeType, file_storage_id AS FileStorageId, file_key AS FileKey FROM files WHERE checked_timestamp<? AND path LIKE ? AND (mimetype IS NULL OR mimetype != 'inode/directory')";
-
-  public MySQLRetrieveAllUncheckedFiles(final Access access) {
-    super(access);
-  }
-
-  @Override
-  public Iterator<SparseEntity> execute(final long ts, final String directory)
-      throws TransactionException {
-    try {
-      final PreparedStatement stmt = getMySQLHelper().prepareStatement(STMT_RETRIEVE_ALL);
-      stmt.setLong(1, ts);
-      stmt.setString(2, directory + "%");
-      final ResultSet rs = stmt.executeQuery();
-      if (rs.next()) {
-        final SparseEntity first = parseResultSet(rs);
-        return new Iterator<SparseEntity>() {
-
-          SparseEntity next = first;
-
-          @Override
-          public boolean hasNext() {
-            return this.next != null;
-          }
-
-          @Override
-          public SparseEntity next() {
-            final SparseEntity ret = this.next;
-            try {
-              if (rs.next()) {
-                this.next = parseResultSet(rs);
-              } else {
-                this.next = null;
-                rs.close();
-              }
-            } catch (final SQLException e) {
-              e.printStackTrace();
-            }
-            return ret;
-          }
-
-          @Override
-          public void remove() {}
-
-          @Override
-          protected void finalize() throws Throwable {
-            super.finalize();
-            rs.close();
-          }
-        };
-      }
-
-    } catch (final SQLException e) {
-      throw new TransactionException(e);
-    } catch (final ConnectionException e) {
-      throw new TransactionException(e);
-    }
-    return null;
-  }
-
-  private SparseEntity parseResultSet(final ResultSet rs) throws SQLException {
-    final SparseEntity ret = new SparseEntity();
-    ret.id = rs.getInt("FileId");
-    DatabaseUtils.parseFSODescriptorFields(rs, ret);
-    return ret;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java
index 514ed5ea6548a43d00ae98ba6ebd183e65b85283..1e58fc9058bc2006ec8e782420add85b61aaffca 100644
--- a/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java
+++ b/src/main/java/org/caosdb/server/database/backend/implementation/MySQL/MySQLSetFileCheckedTimestamp.java
@@ -44,6 +44,7 @@ public class MySQLSetFileCheckedTimestamp extends MySQLTransaction
       final PreparedStatement stmt = getMySQLHelper().prepareStatement(STMT_SET_TS);
       stmt.setLong(1, ts);
       stmt.setInt(2, id.toInteger());
+      stmt.execute();
     } catch (final SQLException e) {
       throw new TransactionException(e);
     } catch (final ConnectionException e) {
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/GetVirtualFSOImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/GetVirtualFSOImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c86822d4f0c0e920ab68a01c236c628a3b678e5
--- /dev/null
+++ b/src/main/java/org/caosdb/server/database/backend/interfaces/GetVirtualFSOImpl.java
@@ -0,0 +1,8 @@
+package org.caosdb.server.database.backend.interfaces;
+
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
+
+public interface GetVirtualFSOImpl extends BackendTransactionImpl {
+
+  VirtualFSODescriptorInterface execute(String fileStorageId, String key);
+}
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/ListFilesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/ListFilesImpl.java
index d77a282e5e65dad69ebcb7687ab34e25599dd36d..8ca82f9581897f26b293928d1e00c19f26fdf2cb 100644
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/ListFilesImpl.java
+++ b/src/main/java/org/caosdb/server/database/backend/interfaces/ListFilesImpl.java
@@ -1,10 +1,11 @@
 package org.caosdb.server.database.backend.interfaces;
 
-import org.caosdb.server.filesystem.FSODescriptorInterface;
+import java.util.List;
 import org.caosdb.server.filesystem.Path;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
 
 public interface ListFilesImpl extends BackendTransactionImpl {
 
-  Iterable<FSODescriptorInterface> execute(
+  List<VirtualFSODescriptorInterface> execute(
       String fileStorageId, Path path, Long lastChecked, Integer limit);
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveAllUncheckedFilesImpl.java b/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveAllUncheckedFilesImpl.java
deleted file mode 100644
index dc247bf6b2975f96185245813647662e8e9217f3..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/interfaces/RetrieveAllUncheckedFilesImpl.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.database.backend.interfaces;
-
-import java.util.Iterator;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.SparseEntity;
-
-/**
- * Retrieve an iterator which iterates over all File entities which need to be consistency-checked.
- *
- * @author Timm Fitschen (t.fitschen@indiscale.com)
- */
-public interface RetrieveAllUncheckedFilesImpl extends BackendTransactionImpl {
-
-  /**
-   * Return an iterator over all (non-directory) file entities which have not been
-   * consistency-checked since the (unix) timestamp `ts` and which are located below the directory
-   * `directory`.
-   *
-   * @param ts
-   * @param directory
-   * @return
-   * @throws TransactionException
-   */
-  public Iterator<SparseEntity> execute(long ts, String directory) throws TransactionException;
-}
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/GetFileIterator.java b/src/main/java/org/caosdb/server/database/backend/transaction/GetFileIterator.java
index 4a150c428444b710aa25d80a18f8948d72abf509..47657eb79a621cddfc49b6927149c51aefdd227f 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/GetFileIterator.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/GetFileIterator.java
@@ -24,7 +24,10 @@ package org.caosdb.server.database.backend.transaction;
 
 import java.util.Iterator;
 import org.caosdb.server.database.BackendTransaction;
+import org.caosdb.server.database.backend.interfaces.ListFilesImpl;
 import org.caosdb.server.database.exceptions.TransactionException;
+import org.caosdb.server.filesystem.Path;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
 
 /**
  * Generate an Iterator over all file paths in a file storage (below a particular directory).
@@ -33,16 +36,47 @@ import org.caosdb.server.database.exceptions.TransactionException;
  */
 public class GetFileIterator extends BackendTransaction {
 
-  private Iterator<String> iterator;
+  public static class FSOIterator implements Iterator<VirtualFSODescriptorInterface> {
 
-  public GetFileIterator(final String fileStorageId, final String directory) {}
+    private GetFileIterator getFileIterator;
+
+    public FSOIterator(GetFileIterator getFileIterator) {
+      this.getFileIterator = getFileIterator;
+    }
+
+    @Override
+    public boolean hasNext() {
+      if (this.getFileIterator.chunk == null || !this.getFileIterator.chunk.hasNext()) {
+        this.getFileIterator.execute();
+      }
+      return this.getFileIterator.chunk.hasNext();
+    }
+
+    @Override
+    public VirtualFSODescriptorInterface next() {
+      return this.getFileIterator.chunk.next();
+    }
+  }
+
+  private Iterator<VirtualFSODescriptorInterface> chunk = null;
+  private Iterator<VirtualFSODescriptorInterface> iterator = new FSOIterator(this);
+  private String fileStorageId;
+  private Path belowPath;
+  private Long lastCheckedTimestamp;
+
+  public GetFileIterator(String fileStorageId, Path belowPath, Long lastCheckedTimestamp) {
+    this.fileStorageId = fileStorageId;
+    this.belowPath = belowPath;
+    this.lastCheckedTimestamp = lastCheckedTimestamp;
+  }
 
   @Override
   protected void execute() throws TransactionException {
-    // TODO
+    ListFilesImpl t = getImplementation(ListFilesImpl.class);
+    this.chunk = t.execute(fileStorageId, belowPath, lastCheckedTimestamp, 100).iterator();
   }
 
-  public Iterator<String> getIterator() {
+  public Iterator<VirtualFSODescriptorInterface> getIterator() {
     return this.iterator;
   }
 }
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/GetVirtualFSO.java b/src/main/java/org/caosdb/server/database/backend/transaction/GetVirtualFSO.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d3e082d58b2bc47296b531f7c91d65d421b0f42
--- /dev/null
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/GetVirtualFSO.java
@@ -0,0 +1,27 @@
+package org.caosdb.server.database.backend.transaction;
+
+import org.caosdb.server.database.BackendTransaction;
+import org.caosdb.server.database.backend.interfaces.GetVirtualFSOImpl;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
+
+public class GetVirtualFSO extends BackendTransaction {
+
+  private String fileStorageId;
+  private String key;
+  private VirtualFSODescriptorInterface fso;
+
+  public GetVirtualFSO(String fileStorageId, String key) {
+    this.fileStorageId = fileStorageId;
+    this.key = key;
+  }
+
+  @Override
+  protected void execute() {
+    GetVirtualFSOImpl t = getImplementation(GetVirtualFSOImpl.class);
+    fso = t.execute(fileStorageId, key);
+  }
+
+  public boolean isKnown() {
+    return fso != null;
+  }
+}
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/ListFilesTransaction.java b/src/main/java/org/caosdb/server/database/backend/transaction/ListFilesTransaction.java
index c26573d6c19bf8802b31cd6b0d64a51ceabf1472..59db67289b9fc45086315ef711689936f8350002 100644
--- a/src/main/java/org/caosdb/server/database/backend/transaction/ListFilesTransaction.java
+++ b/src/main/java/org/caosdb/server/database/backend/transaction/ListFilesTransaction.java
@@ -2,16 +2,17 @@ package org.caosdb.server.database.backend.transaction;
 
 import org.caosdb.server.database.BackendTransaction;
 import org.caosdb.server.database.backend.interfaces.ListFilesImpl;
-import org.caosdb.server.filesystem.FSODescriptorInterface;
 import org.caosdb.server.filesystem.FileStorageInterface;
 import org.caosdb.server.filesystem.Path;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
 
+@Deprecated
 public class ListFilesTransaction extends BackendTransaction {
 
   private Path path;
   private Long lastChecked;
   private String fileStorageId;
-  private Iterable<FSODescriptorInterface> files;
+  private Iterable<VirtualFSODescriptorInterface> files;
   private Integer limit;
 
   public ListFilesTransaction(
@@ -26,7 +27,7 @@ public class ListFilesTransaction extends BackendTransaction {
     this.limit = limit;
   }
 
-  public Iterable<FSODescriptorInterface> getFiles() {
+  public Iterable<VirtualFSODescriptorInterface> getFiles() {
     return files;
   }
 
diff --git a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveAllUncheckedFiles.java b/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveAllUncheckedFiles.java
deleted file mode 100644
index 655e47b8312c3a9772ae3e9055bc73494236a0ec..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/database/backend/transaction/RetrieveAllUncheckedFiles.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.database.backend.transaction;
-
-import java.util.Iterator;
-import org.caosdb.server.database.BackendTransaction;
-import org.caosdb.server.database.backend.interfaces.RetrieveAllUncheckedFilesImpl;
-import org.caosdb.server.database.exceptions.TransactionException;
-import org.caosdb.server.database.proto.SparseEntity;
-
-public class RetrieveAllUncheckedFiles extends BackendTransaction {
-
-  private Iterator<SparseEntity> iterator;
-  private final long ts;
-  private final String location;
-
-  public RetrieveAllUncheckedFiles(final long ts, final String location) {
-    this.ts = ts;
-    this.location = location;
-  }
-
-  @Override
-  protected void execute() throws TransactionException {
-    final RetrieveAllUncheckedFilesImpl t = getImplementation(RetrieveAllUncheckedFilesImpl.class);
-    this.iterator = t.execute(this.ts, this.location);
-  }
-
-  public Iterator<SparseEntity> getIterator() {
-    return this.iterator;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/filesystem/FSODescriptorInterface.java b/src/main/java/org/caosdb/server/filesystem/FSODescriptorInterface.java
index 5387dce0df7bbd1ad34de287027e52dadbbde6d5..34d0279fadc36d5ca5e330fa247a4edc5ff154c2 100644
--- a/src/main/java/org/caosdb/server/filesystem/FSODescriptorInterface.java
+++ b/src/main/java/org/caosdb/server/filesystem/FSODescriptorInterface.java
@@ -13,7 +13,7 @@ import org.caosdb.server.filesystem.FileSystem.ObjectType;
  *
  * @author Timm Fitschen (t.fitschen@indiscale.com)
  */
-public interface FSODescriptorInterface {
+public interface FSODescriptorInterface extends Comparable<FSODescriptorInterface> {
 
   /**
    * An identifier of the file. The key is guaranteed to be unique across the file storage where
@@ -55,4 +55,13 @@ public interface FSODescriptorInterface {
   public abstract String getMimeType();
 
   public abstract ObjectType getObjectType();
+
+  @Override
+  default int compareTo(FSODescriptorInterface other) {
+    int fileStorageCompared = other.getFileStorageId().compareTo(getFileStorageId());
+    if (fileStorageCompared == 0) {
+      return other.getKey().compareTo(getKey());
+    }
+    return fileStorageCompared;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/FileStorageInterface.java b/src/main/java/org/caosdb/server/filesystem/FileStorageInterface.java
index f0b8be54c72e20eaf3b75164a94ce5d3a85bfce1..19420a8527576c35ba29dc8eba39f1484840b9c9 100644
--- a/src/main/java/org/caosdb/server/filesystem/FileStorageInterface.java
+++ b/src/main/java/org/caosdb/server/filesystem/FileStorageInterface.java
@@ -50,7 +50,5 @@ public interface FileStorageInterface {
   /** Return true iff an object with this key exists. */
   public abstract boolean exists(String key);
 
-  /*
-   * fileIterator(String keyPrefix, Long lastModifiedAfter,
-   */
+  public abstract Iterable<? extends RealFSODescriptorInterface> list(String prefix);
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/FileSystem.java b/src/main/java/org/caosdb/server/filesystem/FileSystem.java
index 5212573e03f2a1eb74626b77bb08252a53dd7baf..e016d7952f96e0a43e5a8f878e41e5f1f44f9223 100644
--- a/src/main/java/org/caosdb/server/filesystem/FileSystem.java
+++ b/src/main/java/org/caosdb/server/filesystem/FileSystem.java
@@ -72,22 +72,6 @@ public class FileSystem implements FileSystemInterface {
     return result;
   }
 
-  public static Hash getHash(FSODescriptorInterface fso, String algorithm) {
-    FileStorageInterface fileStorage = getInstance().getFileStorage(fso);
-    if (fileStorage.getCapabilities().getHash(algorithm)) {
-      return fileStorage.getHash(fso, algorithm);
-    }
-    return null;
-  }
-
-  public static Long getSize(FSODescriptorInterface fso) {
-    FileStorageInterface fileStorage = getInstance().getFileStorage(fso);
-    if (fileStorage.getCapabilities().getSize) {
-      return fileStorage.getSize(fso);
-    }
-    return null;
-  }
-
   //  public static FSODescriptorInterface resolveLinkTarget(FSODescriptorInterface fso) {
   //    return resolveLinkTarget(fso.getFileStorageId(), fso.getKey());
   //  }
diff --git a/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java b/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java
index b2666d26e3b7f0fe0b29f45ecfbfa1fd9b0973c7..237b9428ce89d4c573793480853bcf41b2363b4c 100644
--- a/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java
+++ b/src/main/java/org/caosdb/server/filesystem/LocalFileStorage.java
@@ -8,6 +8,8 @@ import java.io.Reader;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import org.apache.commons.io.FileUtils;
@@ -58,6 +60,12 @@ public abstract class LocalFileStorage implements FileStorageInterface {
       this.key = key;
     }
 
+    private LocalFSODescriptor(File file) {
+      this.key = null;
+      this.fileStorage = null;
+      this.file = file;
+    }
+
     private ObjectType _getObjectType() {
       if (type == null) {
         type = fileStorage.getObjectType(key);
@@ -133,7 +141,35 @@ public abstract class LocalFileStorage implements FileStorageInterface {
 
     @Override
     public Boolean exists() {
-      return fileStorage.exists(key);
+      return getFile().exists();
+    }
+
+    @Override
+    public RealFSODescriptorInterface resolveLinkTarget() {
+      try {
+        Path resolvedPath = getFile().toPath().toRealPath();
+        if (fileStorage.belongsToUs(resolvedPath)) {
+          return new LocalFSODescriptor(fileStorage, fileStorage.getKey(resolvedPath));
+        }
+        for (String otherFileStorageId : FileSystem.getInstance().getFileStorages()) {
+          if (!otherFileStorageId.equals(fileStorage.getId())) {
+            FileStorageInterface otherFileStorage =
+                FileSystem.getInstance().getFileStorage(otherFileStorageId);
+            if (otherFileStorage instanceof LocalFileStorage) {
+              if (((LocalFileStorage) otherFileStorage).belongsToUs(resolvedPath)) {
+                // this file belongs to another file storage.
+                return new LocalFSODescriptor(
+                    (LocalFileStorage) otherFileStorage,
+                    ((LocalFileStorage) otherFileStorage).getKey(resolvedPath));
+              }
+            }
+          }
+        }
+        // We don't know this file.
+        return new LocalFSODescriptor(resolvedPath.toFile());
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
     }
   }
 
@@ -153,6 +189,14 @@ public abstract class LocalFileStorage implements FileStorageInterface {
     this.root = root.toAbsolutePath();
   }
 
+  private boolean belongsToUs(Path path) {
+    return path.startsWith(root);
+  }
+
+  private String getKey(Path path) {
+    return root.relativize(path).toString();
+  }
+
   public ObjectType getObjectType(String key) {
     File file = getFile(key);
     return getObjectType(file);
@@ -675,66 +719,67 @@ public abstract class LocalFileStorage implements FileStorageInterface {
     }
   }
 
-  //  @Override
-  //  public Iterator<String> execute(final String storage, final String location)
-  //      throws TransactionException {
-  //    File base;
-  //    try {
-  //      // TODO: move everything to GetFileIterator and refactor. Needs a listFiles
-  //      // Transaction
-  //      base = getFile(storage, location).getFile();
-  //    } catch (final Message e) {
-  //      throw new TransactionException(e);
-  //    }
-  //    return new FileNameIterator(base, location);
-  //  }
-  //
-  //  public static class FileNameIterator implements Iterator<String> {
-  //
-  //    private final File base;
-  //    private File[] files = null;
-  //    private FileNameIterator subfiles = null;
-  //    private int i = 0;
-  //    private final String rootPath;
-  //
-  //    public FileNameIterator(final File base, final String rootPath) {
-  //      this.rootPath = rootPath.length() == 0 || rootPath.endsWith("/") ? rootPath : rootPath +
-  // "/";
-  //      this.base = base;
-  //      this.files = this.base.listFiles();
-  //      Arrays.sort(this.files);
-  //    }
-  //
-  //    @Override
-  //    public boolean hasNext() {
-  //      return this.files.length > this.i;
-  //    }
-  //
-  //    @Override
-  //    public String next() {
-  //      if (this.subfiles != null) {
-  //        if (this.subfiles.hasNext()) {
-  //          return this.subfiles.next();
-  //        } else {
-  //          this.subfiles = null;
-  //        }
-  //      }
-  //      final File ret = this.files[this.i++];
-  //      if (ret.isDirectory()) {
-  //        this.subfiles = new FileNameIterator(ret, this.rootPath + ret.getName() + "/");
-  //        if (this.subfiles.hasNext()) {
-  //          return next();
-  //        } else {
-  //          return this.rootPath + ret.getName() + "/";
-  //        }
-  //      }
-  //      return this.rootPath + ret.getName();
-  //    }
-  //
-  //    @Override
-  //    public void remove() {
-  //      throw new UnsupportedOperationException();
-  //    }
-  //  }
-  // }
+  public static class LocalFSOIterator implements Iterator<LocalFSODescriptor> {
+
+    private final File base;
+    private File[] files = null;
+    private LocalFSOIterator subfiles = null;
+    private int i = 0;
+    private final org.caosdb.server.filesystem.Path rootPath;
+    private LocalFileStorage fileStorage;
+
+    public LocalFSOIterator(
+        LocalFileStorage fileStorage, File base, final org.caosdb.server.filesystem.Path rootPath) {
+      this.fileStorage = fileStorage;
+      this.rootPath = rootPath;
+      this.base = base;
+      this.files = this.base.listFiles();
+      Arrays.sort(this.files);
+    }
+
+    @Override
+    public boolean hasNext() {
+      return this.files.length > this.i;
+    }
+
+    @Override
+    public LocalFSODescriptor next() {
+      if (this.subfiles != null) {
+        if (this.subfiles.hasNext()) {
+          return this.subfiles.next();
+        } else {
+          this.subfiles = null;
+        }
+      }
+      final File ret = this.files[this.i++];
+      if (ret.isDirectory()) {
+        this.subfiles = new LocalFSOIterator(fileStorage, ret, this.rootPath.append(ret.getName()));
+        if (this.subfiles.hasNext()) {
+          return next();
+        } else {
+          return new LocalFSODescriptor(fileStorage, rootPath.append(ret.getName()).toString());
+        }
+      }
+      return new LocalFSODescriptor(fileStorage, rootPath.append(ret.getName()).toString());
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  @Override
+  public Iterable<LocalFSODescriptor> list(String prefix) {
+    final LocalFileStorage fileStorage = this;
+    final File base = prefix == null ? this.root.toFile() : this.root.resolve(prefix).toFile();
+    return new Iterable<LocalFSODescriptor>() {
+
+      @Override
+      public LocalFSOIterator iterator() {
+        return new LocalFSOIterator(
+            fileStorage, base, new org.caosdb.server.filesystem.Path(prefix == null ? "" : prefix));
+      }
+    };
+  }
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/RealFSODescriptorInterface.java b/src/main/java/org/caosdb/server/filesystem/RealFSODescriptorInterface.java
index 2d285365718634fe45764649a556b9d3b4f6ed07..aed0f89c052c0587c48826dadd1e5b9f6759844b 100644
--- a/src/main/java/org/caosdb/server/filesystem/RealFSODescriptorInterface.java
+++ b/src/main/java/org/caosdb/server/filesystem/RealFSODescriptorInterface.java
@@ -1,6 +1,7 @@
 package org.caosdb.server.filesystem;
 
 import java.io.File;
+import java.util.List;
 
 /**
  * Represents an object in a file storage (i.e. a POSIX file system, an S3 bucket server, or similar
@@ -40,4 +41,9 @@ public interface RealFSODescriptorInterface extends FSODescriptorInterface {
    * null if the existence cannot be determined.
    */
   public abstract Boolean exists();
+
+  public abstract RealFSODescriptorInterface resolveLinkTarget();
+
+  @Override
+  public abstract List<? extends RealFSODescriptorInterface> listChildren();
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/TemporaryFSODescriptor.java b/src/main/java/org/caosdb/server/filesystem/TemporaryFSODescriptor.java
index 7a7a398073c1762dc01f07ce6866a71f19237327..4c6d16963394beb97c3cba216229c4d544635ab8 100644
--- a/src/main/java/org/caosdb/server/filesystem/TemporaryFSODescriptor.java
+++ b/src/main/java/org/caosdb/server/filesystem/TemporaryFSODescriptor.java
@@ -64,7 +64,7 @@ public class TemporaryFSODescriptor implements RealFSODescriptorInterface, Undoa
   }
 
   @Override
-  public List<? extends FSODescriptorInterface> listChildren() {
+  public List<? extends RealFSODescriptorInterface> listChildren() {
     return null;
   }
 
@@ -91,4 +91,10 @@ public class TemporaryFSODescriptor implements RealFSODescriptorInterface, Undoa
   public String getTmpIdentifier() {
     return fileId;
   }
+
+  @Override
+  public RealFSODescriptorInterface resolveLinkTarget() {
+    // TODO Auto-generated method stub
+    return null;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/consistency/AbstractConsistencyEvent.java b/src/main/java/org/caosdb/server/filesystem/consistency/AbstractConsistencyEvent.java
deleted file mode 100644
index fabedaf7ffdf216a301e6e751a829ea620e2b687..0000000000000000000000000000000000000000
--- a/src/main/java/org/caosdb/server/filesystem/consistency/AbstractConsistencyEvent.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package org.caosdb.server.filesystem.consistency;
-
-import java.util.UUID;
-import org.caosdb.server.filesystem.FSODescriptorInterface;
-import org.caosdb.server.filesystem.FileStorageInterface;
-import org.caosdb.server.filesystem.FileSystem;
-
-public class AbstractConsistencyEvent implements ConsistencyEvent {
-
-  private String fileStorageId;
-  private String key;
-  private String type;
-  private String uuid = null;
-  private Long timestamp;
-
-  AbstractConsistencyEvent(String type, String fileStorageId, String key) {
-    this.timestamp = System.currentTimeMillis();
-    this.type = type;
-    //    this.parent = parent;
-    this.fileStorageId = fileStorageId;
-    this.key = key;
-  }
-
-  @Override
-  public String getFileStorageId() {
-    return fileStorageId;
-  }
-
-  @Override
-  public String getKey() {
-    return key;
-  }
-
-  @Override
-  public String getEventType() {
-    return type;
-  }
-  //
-  //  @Override
-  //  public ConsistencyEvent getParent() {
-  //    if (parent == null && type.contains(":")) {
-  //      String[] components = type.split(":");
-  //      components = Arrays.copyOfRange(components, 0, components.length - 2);
-  //      parent = new AbstractConsistencyEvent(String.join(":", components), fileStorageId, key);
-  //    }
-  //    return parent;
-  //  }
-
-  @Override
-  public boolean isSubtypeOf(String eventType) {
-    if (!eventType.endsWith(":")) {
-      return this.type.startsWith(eventType + ":");
-    }
-    return this.type.startsWith(eventType);
-  }
-
-  public static final ConsistencyEvent missingCapability(
-      String capability, FileStorageInterface fileStorage) {
-    return new AbstractConsistencyEvent("NEED_CAPABILITY:" + capability, fileStorage.getId(), null);
-  }
-
-  public static ConsistencyEvent unknownFile(FSODescriptorInterface next) {
-    return new AbstractConsistencyEvent("UNKNOWN", next.getFileStorageId(), next.getKey());
-  }
-
-  public static ConsistencyEvent missingFile(FSODescriptorInterface next) {
-    return new AbstractConsistencyEvent("MISSING", next.getFileStorageId(), next.getKey());
-  }
-
-  public static ConsistencyEvent wrongObjectType(
-      FSODescriptorInterface fso, FileSystem.ObjectType type) {
-    return new AbstractConsistencyEvent(
-        "CHANGED:TYPE:TO_" + type.toString(), fso.getFileStorageId(), fso.getKey());
-  }
-
-  public static ConsistencyEvent brokenLink(FSODescriptorInterface fso) {
-    return new AbstractConsistencyEvent("LINK:BROKEN", fso.getFileStorageId(), fso.getKey());
-  }
-
-  public static ConsistencyEvent linkToOtherFileStorage(FSODescriptorInterface fso) {
-    return new AbstractConsistencyEvent(
-        "LINK:TARGET:IN_OTHER_FILE_STORAGE", fso.getFileStorageId(), fso.getKey());
-  }
-
-  public static ConsistencyEvent linkToAlienTargetFromOuterSpace(FSODescriptorInterface fso) {
-    return new AbstractConsistencyEvent(
-        "LINK:TARGET:NOT_IN_FILE_STORAGE", fso.getFileStorageId(), fso.getKey());
-  }
-
-  public static ConsistencyEvent sizeMismatch(FSODescriptorInterface fso) {
-    return new AbstractConsistencyEvent("CHANGED:SIZE", fso.getFileStorageId(), fso.getKey());
-  }
-
-  public static ConsistencyEvent hashMismatch(FSODescriptorInterface fso) {
-    return new AbstractConsistencyEvent("CHANGED:HASH", fso.getFileStorageId(), fso.getKey());
-  }
-
-  public static ConsistencyEvent linkTargetChanged(FSODescriptorInterface fso) {
-    return new AbstractConsistencyEvent(
-        "CHANGED:LINK:TARGET", fso.getFileStorageId(), fso.getKey());
-  }
-
-  @Override
-  public String getUUID() {
-    if (uuid == null) {
-      uuid = UUID.randomUUID().toString();
-    }
-    return uuid;
-  }
-
-  @Override
-  public Long getTimestamp() {
-    return timestamp;
-  }
-}
diff --git a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyCheck.java b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyCheck.java
index 9eabd32b290afbf8ab17d33a5e2f51d8026c6379..8b52620551047a68d12b42763f189bd468a97112 100644
--- a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyCheck.java
+++ b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyCheck.java
@@ -1,30 +1,42 @@
 package org.caosdb.server.filesystem.consistency;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import org.caosdb.datetime.UTCDateTime;
+import org.caosdb.server.database.DatabaseAccessManager;
 import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.transaction.ListFilesTransaction;
+import org.caosdb.server.database.backend.transaction.GetFileIterator;
+import org.caosdb.server.database.backend.transaction.GetVirtualFSO;
+import org.caosdb.server.database.backend.transaction.SetFileCheckedTimestamp;
+import org.caosdb.server.entity.EntityID;
 import org.caosdb.server.filesystem.FSODescriptorInterface;
 import org.caosdb.server.filesystem.FileStorageCapabilities;
 import org.caosdb.server.filesystem.FileStorageInterface;
 import org.caosdb.server.filesystem.FileSystem;
 import org.caosdb.server.filesystem.FileSystem.ObjectType;
+import org.caosdb.server.filesystem.Hash;
 import org.caosdb.server.filesystem.Path;
+import org.caosdb.server.filesystem.RealFSODescriptorInterface;
+import org.caosdb.server.filesystem.VirtualFSODescriptorInterface;
 import org.caosdb.server.transaction.TransactionInterface;
 
-abstract class ConsistencyCheckStrategy implements TransactionInterface {
+abstract class ConsistencyCheckStrategy implements TransactionInterface, Runnable {
 
-  private List<ConsistencyEvent> events = new LinkedList<>();
+  private List<ConsistencyEventInterface> events = new LinkedList<>();
   private Access access;
+  protected Exception exception;
 
   public abstract void execute();
 
-  public void emit(ConsistencyEvent event) {
+  public void emit(ConsistencyEventInterface event) {
     events.add(event);
   }
 
-  public List<ConsistencyEvent> getEvents() {
+  public List<ConsistencyEventInterface> getEvents() {
     return events;
   }
 
@@ -35,134 +47,210 @@ abstract class ConsistencyCheckStrategy implements TransactionInterface {
   Access getAccess() {
     return access;
   }
+
+  @Override
+  public void run() {
+    execute();
+  }
+
+  public Exception getException() {
+    return exception;
+  }
 }
 
 class RunThroughInternalFileSystem extends ConsistencyCheckStrategy {
 
-  private ListFilesTransaction listFilesTransaction;
+  private Long lastChecked;
+  private Path path;
+  private String fileStorageId;
 
   public RunThroughInternalFileSystem(
-      Long lastChecked, Path path, FileStorageInterface fileStorage, Integer limit) {
-    this.listFilesTransaction = new ListFilesTransaction(fileStorage, path, lastChecked, limit);
+      Long lastChecked, Path path, FileStorageInterface fileStorage) {
+    this.lastChecked = lastChecked == null ? System.currentTimeMillis() : lastChecked;
+    this.path = path;
+    this.fileStorageId = fileStorage == null ? null : fileStorage.getId();
   }
 
-  public RunThroughInternalFileSystem(Long since, Integer limit) {
-    this(since, null, null, limit);
+  @Override
+  public void execute() {
+    try {
+      this.setAccess(DatabaseAccessManager.getInstance().acquireReadAccess(this));
+
+      // test all files in file system.
+      final GetFileIterator it = new GetFileIterator(fileStorageId, path, lastChecked);
+      Iterator<VirtualFSODescriptorInterface> iterator =
+          execute(it, this.getAccess()).getIterator();
+
+      while (iterator != null && iterator.hasNext()) {
+        long timestamp = System.currentTimeMillis();
+        System.err.println("here");
+
+        if (DatabaseAccessManager.whoHasReservedWriteAccess() != null) {
+          // there is a thread waiting to write. pause this one and
+          // try to acquire a new read access which will be granted when
+          // the write thread is done.
+          this.getAccess().release();
+          this.setAccess(DatabaseAccessManager.getInstance().acquireReadAccess(this));
+          iterator = execute(it, this.getAccess()).getIterator();
+          System.err.println("here2");
+        }
+
+        final VirtualFSODescriptorInterface howItShouldBe = iterator.next();
+        System.err.println("check: " + howItShouldBe.toString());
+
+        check(howItShouldBe);
+
+        execute(
+            new SetFileCheckedTimestamp(howItShouldBe.getEntityId(), timestamp), this.getAccess());
+        getAccess().commit();
+      }
+    } catch (final Exception e) {
+      this.exception = e;
+    } finally {
+      this.getAccess().release();
+    }
   }
 
-  public RunThroughInternalFileSystem(Path path, Integer limit) {
-    this(null, path, null, limit);
-  }
+  private void check(VirtualFSODescriptorInterface howItShouldBe) {
+    RealFSODescriptorInterface howItActuallyIs = resolve(howItShouldBe);
+    Boolean exists = howItActuallyIs.exists();
+    if (exists != null && !exists) {
+      emit(ConsistencyEvent.missingFile(howItActuallyIs));
+      return;
+    }
+
+    if (howItShouldBe.getObjectType() != howItActuallyIs.getObjectType()) {
+      emit(ConsistencyEvent.wrongObjectType(howItShouldBe, howItActuallyIs.getObjectType()));
+      return;
+    }
 
-  public RunThroughInternalFileSystem(FileStorageInterface fileStorage, Integer limit) {
-    this(null, null, fileStorage, limit);
+    if (howItActuallyIs.getObjectType() == ObjectType.LINK) {
+      checkLinkIntact(howItShouldBe, howItActuallyIs);
+    } else if (howItActuallyIs.getObjectType() == ObjectType.FILE) {
+      checkSize(howItShouldBe, howItActuallyIs);
+      checkHash(howItShouldBe, howItActuallyIs);
+    } else if (howItActuallyIs.getObjectType() == ObjectType.DIRECTORY) {
+      checkDirectoryContent(howItShouldBe, howItActuallyIs);
+    }
   }
 
-  @Override
-  public void execute() {
-    for (FSODescriptorInterface howItShouldBe : listFiles()) {
-      FSODescriptorInterface howItActuallyIs = resolve(howItShouldBe);
+  private void checkDirectoryContent(
+      VirtualFSODescriptorInterface howItShouldBe, RealFSODescriptorInterface howItActuallyIs) {
+    List<? extends VirtualFSODescriptorInterface> childrenHowItShouldBe =
+        howItShouldBe.listChildren();
+    Collections.sort(childrenHowItShouldBe);
+    List<? extends RealFSODescriptorInterface> childrenHowItActuallyIs =
+        howItActuallyIs.listChildren();
+    Collections.sort(childrenHowItActuallyIs);
+    boolean mismatch = false;
+    if (childrenHowItActuallyIs.size() == childrenHowItShouldBe.size()) {
+      Iterator<? extends RealFSODescriptorInterface> iterator1 = childrenHowItActuallyIs.iterator();
+      Iterator<? extends VirtualFSODescriptorInterface> iterator2 =
+          childrenHowItShouldBe.iterator();
+      while (iterator1.hasNext()) {
+        if (iterator1.next().compareTo(iterator2.next()) != 0) {
+          // damn, found a mismatching child
+          mismatch = true;
+          break;
+        }
+      }
+      if (!mismatch) {
+        return;
+      }
+    }
 
-      if (howItActuallyIs == null) {
-        continue;
+    // we already know, that there are problems. We just try and figure out the exact nature of the
+    // problems.
+    childrenHowItActuallyIs = new ArrayList<>(childrenHowItActuallyIs);
+    childrenHowItShouldBe = new ArrayList<>(childrenHowItShouldBe);
+    Iterator<? extends VirtualFSODescriptorInterface> iChildrenHowItShouldBe =
+        childrenHowItShouldBe.iterator();
+    while (iChildrenHowItShouldBe.hasNext()) {
+      Iterator<? extends RealFSODescriptorInterface> iChildrenHowItActuallyIs =
+          childrenHowItActuallyIs.iterator();
+      VirtualFSODescriptorInterface childHowItShouldBe = iChildrenHowItShouldBe.next();
+      while (iChildrenHowItActuallyIs.hasNext()) {
+        RealFSODescriptorInterface childHowItActuallyIs = iChildrenHowItActuallyIs.next();
+        if (childHowItActuallyIs.compareTo(childHowItShouldBe) == 0) {
+          // we have a match. remove from both lists
+          iChildrenHowItActuallyIs.remove();
+          iChildrenHowItShouldBe.remove();
+        }
       }
+    }
 
-      // TODO check permissions of the server:
-      // read permissions if read capability
-      // write permissions if any write capability
-      // execute permissions for traversal (directory capability)
+    // everything that is left in the lists is either...
+    for (RealFSODescriptorInterface unknownObject : childrenHowItActuallyIs) {
+      // ... an unknown object
+      emit(ConsistencyEvent.unknownChild(unknownObject));
+    }
+    for (VirtualFSODescriptorInterface missingObject : childrenHowItShouldBe) {
+      // ... or a missing object
+      emit(ConsistencyEvent.missingChild(missingObject));
+    }
+  }
 
-      if (howItShouldBe.getObjectType() != howItActuallyIs.getObjectType()) {
-        emit(
-            AbstractConsistencyEvent.wrongObjectType(
-                howItShouldBe, howItActuallyIs.getObjectType()));
-      }
+  private void checkHash(
+      FSODescriptorInterface howItShouldBe, FSODescriptorInterface howItActuallyIs) {
+    Hash hash1 = howItActuallyIs.getHash();
+    Hash hash2 = howItShouldBe.getHash();
+    if (hash1 != null && hash2 != null && !hash1.equals(hash2)) {
+      emit(ConsistencyEvent.hashMismatch(howItActuallyIs));
+    }
+  }
 
-      if (howItActuallyIs.getObjectType() == ObjectType.LINK) {
-        // checkLinkIntact(howItShouldBe, howItActuallyIs);
-        continue;
-      }
+  private void checkSize(
+      FSODescriptorInterface howItShouldBe, FSODescriptorInterface howItActuallyIs) {
+    Long size = howItActuallyIs.getSize();
+    if (size != null && !size.equals(howItShouldBe.getSize())) {
+      // size == null means we don't know.
+      emit(ConsistencyEvent.sizeMismatch(howItActuallyIs));
+    }
+  }
+
+  private void checkLinkIntact(
+      VirtualFSODescriptorInterface howItShouldBe, RealFSODescriptorInterface howItActuallyIs) {
+    EntityID linkTargetId = howItShouldBe.getLinkTarget();
 
-      // TODO check owner and group
+    RealFSODescriptorInterface linkTargetActuallyIs = howItActuallyIs.resolveLinkTarget();
+    Boolean exists = linkTargetActuallyIs.exists();
+    if (exists != null && !exists) {
+      // if link is broken (target is not existing/reachable from the host file system of the server
+      // - in case of network file servers, it might be intact from a different host with different
+      // mounts)
+      emit(ConsistencyEvent.brokenLink(howItActuallyIs));
+    }
+
+    FSODescriptorInterface linkTargetShouldBe = retrieveFSODescriptor(linkTargetId);
+    if (!(Objects.equals(
+            linkTargetShouldBe.getFileStorageId(), linkTargetActuallyIs.getFileStorageId())
+        && Objects.equals(linkTargetShouldBe.getKey(), linkTargetActuallyIs.getKey()))) {
+      emit(ConsistencyEvent.linkTargetChanged(howItShouldBe));
+    }
 
-      //      checkSize(howItShouldBe, howItActuallyIs);
-      //
-      //      checkHash(howItShouldBe, howItActuallyIs);
+    if (linkTargetActuallyIs.getFileStorageId() == null) {
+      // target does not belong to the CaosDB file system
+      emit(ConsistencyEvent.linkToAlienTargetFromOuterSpace(howItActuallyIs));
+    } else if (linkTargetActuallyIs
+        .getFileStorageId()
+        .equals(linkTargetShouldBe.getFileStorageId())) {
+      // link crosses file storage boundaries
+      emit(ConsistencyEvent.linkToOtherFileStorage(howItActuallyIs));
     }
   }
 
-  private Iterable<FSODescriptorInterface> listFiles() {
-    return execute(this.listFilesTransaction, getAccess()).getFiles();
+  private FSODescriptorInterface retrieveFSODescriptor(EntityID linkTargetId) {
+    // TODO Auto-generated method stub
+    return null;
   }
 
-  //  private void checkHash(
-  //      FSODescriptorInterface howItShouldBe, FSODescriptorInterface howItActuallyIs) {
-  //    if (!howItShouldBe.hasHash()) {
-  //      return;
-  //    }
-  //
-  //    String algorithm = howItShouldBe.getHash().getAlgorithm();
-  //    Hash hash = FileSystem.getHash(howItActuallyIs, algorithm);
-  //    if (hash != null && !hash.equals(howItShouldBe.getHash())) {
-  //      emit(AbstractConsistencyEvent.hashMismatch(howItActuallyIs));
-  //    }
-  //  }
-  //
-  //  private void checkSize(
-  //      FSODescriptorInterface howItShouldBe, FSODescriptorInterface howItActuallyIs) {
-  //    if (!howItShouldBe.hasSize()) {
-  //      return;
-  //    }
-  //
-  //    Long size = FileSystem.getSize(howItActuallyIs);
-  //    if (size != null && !size.equals(howItShouldBe.getSize())) {
-  //      emit(AbstractConsistencyEvent.sizeMismatch(howItActuallyIs));
-  //    }
-  //  }
-  //
-  //  private void checkLinkIntact(
-  //      FSODescriptorInterface howItShouldBe, FSODescriptorInterface howItActuallyIs) {
-  //    EntityID linkTargetId = howItShouldBe.getLinkTarget();
-  //
-  //    FSODescriptorInterface resolveLinkTarget = FileSystem.resolveLinkTarget(howItActuallyIs);
-  //    if (resolveLinkTarget.getFile() == null) {
-  //      // if link is broken (target is not existing/reachable from the host file system of the
-  // server
-  //      // - in case of network file servers, it might be intact from a different host with
-  // different
-  //      // mounts)
-  //      emit(AbstractConsistencyEvent.brokenLink(howItActuallyIs));
-  //    }
-  //
-  //    if (resolveLinkTarget.getFileStorageId() == null) {
-  //      if (FileSystem.resolveKey(resolveLinkTarget)) {
-  //        // link crosses file storage boundaries
-  //        emit(AbstractConsistencyEvent.linkToOtherFileStorage(howItActuallyIs));
-  //      } else {
-  //        // target does not belong to the CaosDB file system
-  //        emit(AbstractConsistencyEvent.linkToAlienTargetFromOuterSpace(howItActuallyIs));
-  //      }
-  //    }
-  //
-  //    FSODescriptorInterface linkTargetShouldBe = retrieveFSODescriptor(linkTargetId);
-  //    if (!(Objects.equal(linkTargetShouldBe.getFileStorageId(),
-  // resolveLinkTarget.getFileStorageId())
-  //        && Objects.equal(linkTargetShouldBe.getKey(), resolveLinkTarget.getKey()))) {
-  //      emit(AbstractConsistencyEvent.linkTargetChanged(howItShouldBe));
-  //    }
-  //  }
-  //
-  //  private FSODescriptorInterface retrieveFSODescriptor(EntityID linkTargetId) {
-  //    // TODO Auto-generated method stub
-  //    return null;
-  //  }
-
-  private FSODescriptorInterface resolve(FSODescriptorInterface fso) {
-    FSODescriptorInterface result = null;
+  private RealFSODescriptorInterface resolve(FSODescriptorInterface fso) {
+    RealFSODescriptorInterface result = null;
     result = FileSystem.getInstance().resolve(fso.getFileStorageId(), fso.getKey());
     Boolean exists = FileSystem.exists(fso);
     if (exists != null && !exists) {
-      emit(AbstractConsistencyEvent.missingFile(fso));
+      // exists == null means we cannot tell.
+      emit(ConsistencyEvent.missingFile(fso));
     }
     return result;
   }
@@ -204,47 +292,59 @@ class FindUnknownInFileStorage extends ConsistencyCheckStrategy {
 
   @Override
   public void execute() {
-    if (!hasNecessaryCapabilities()) {
+    try {
+      if (fileStorage != null) {
+        check(fileStorage);
+        return;
+      }
+
+      // else check all
+      for (String fileStorageId : FileSystem.getInstance().getFileStorages()) {
+        FileStorageInterface fileStorage = FileSystem.getInstance().getFileStorage(fileStorageId);
+        check(fileStorage);
+      }
+    } catch (Exception e) {
+      this.exception = e;
+    }
+  }
+
+  private void check(FileStorageInterface fileStorage) throws InterruptedException {
+    if (!hasNecessaryCapabilities(fileStorage)) {
       return;
     }
 
-    FSODescriptorInterface next = getNextFSOToBeChecked();
-    while (next != null) {
-      // TODO check if next is known in the internal file system
+    Iterator<? extends RealFSODescriptorInterface> iterator =
+        fileStorage.list(directoryKey).iterator();
+    while (iterator.hasNext()) {
+      RealFSODescriptorInterface next = iterator.next();
       if (!isKnown(next)) {
-        emit(AbstractConsistencyEvent.unknownFile(next));
+        emit(ConsistencyEvent.unknownFile(next));
       }
-
-      next = getNextFSOToBeChecked();
     }
   }
 
-  private boolean isKnown(FSODescriptorInterface next) {
-    // TODO Auto-generated method stub
-    return false;
+  private boolean isKnown(RealFSODescriptorInterface next) throws InterruptedException {
+    this.setAccess(DatabaseAccessManager.getInstance().acquireReadAccess(this));
+    try {
+      return execute(new GetVirtualFSO(next.getFileStorageId(), next.getKey()), getAccess())
+          .isKnown();
+    } finally {
+      this.getAccess().release();
+    }
   }
 
-  private boolean hasNecessaryCapabilities() {
+  private boolean hasNecessaryCapabilities(FileStorageInterface fileStorage) {
     if (!fileStorage.getCapabilities().list) {
-      emit(AbstractConsistencyEvent.missingCapability("LIST", fileStorage));
+      emit(ConsistencyEvent.missingCapability("LIST", fileStorage));
       return false;
     }
     if (directoryKey != null && !fileStorage.getCapabilities().directory) {
-      emit(AbstractConsistencyEvent.missingCapability("DIRECTORY", fileStorage));
+      emit(ConsistencyEvent.missingCapability("DIRECTORY", fileStorage));
       return false;
     }
     return true;
   }
 
-  /*
-   * Get next FSO which is to be checked (only files which haven't been checked since 'since' are being returned)
-   *
-   * Returns null if no next FSO is there which needs to be checked.
-   */
-  private FSODescriptorInterface getNextFSOToBeChecked() {
-    return null;
-  }
-
   @Override
   public UTCDateTime getTimestamp() {
     // TODO Auto-generated method stub
@@ -252,22 +352,37 @@ class FindUnknownInFileStorage extends ConsistencyCheckStrategy {
   }
 }
 
-public class ConsistencyCheck implements Runnable {
+public class ConsistencyCheck extends Thread {
 
-  private ConsistencyCheckStrategy strategy;
-  private Access access = null;
+  private Path belowPath;
+  private Exception exception;
+  private List<ConsistencyEventInterface> events;
 
-  public ConsistencyCheck(ConsistencyCheckStrategy strategy) {
-    this.strategy = strategy;
+  public ConsistencyCheck(Path belowPath) {
+    this.belowPath = belowPath;
   }
 
   @Override
   public void run() {
-    // TODO handle exceptions
+    RunThroughInternalFileSystem check1 = new RunThroughInternalFileSystem(null, belowPath, null);
+    check1.run();
+    exception = check1.getException();
+    events = check1.getEvents();
+    if (exception != null) {
+      return;
+    }
 
-    strategy.setAccess(access);
-    strategy.execute();
+    FindUnknownInFileStorage check2 = new FindUnknownInFileStorage(null, null);
+    check2.run();
+    exception = check2.getException();
+    events.addAll(check2.getEvents());
+  }
 
-    // TODO collect events
+  public Exception getException() {
+    return exception;
+  }
+
+  public List<ConsistencyEventInterface> getEvents() {
+    return events;
   }
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyConfig.java b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyConfig.java
index b176ddbbdf49a3162d744823107fb43c28d9203a..d4146fde4879edb111811410a4f2bc424b6affb9 100644
--- a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyConfig.java
+++ b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyConfig.java
@@ -15,6 +15,7 @@ public class ConsistencyConfig {
 
   public static class EventsMapping<T> {
     private Map<String, T> mapping = new HashMap<>();
+    protected T defaul;
 
     @JsonAnySetter
     public void set(String key, T val) {
@@ -22,22 +23,27 @@ public class ConsistencyConfig {
     }
 
     public T get(String event) {
-      return mapping.get(event);
+      return mapping.getOrDefault(event, defaul);
     }
   }
 
   /**
-   * The EventsLogConfig maps {@link ConsistencyEvent}s to {@link LogLevel}s.
+   * The EventsLogConfig maps {@link ConsistencyEventInterface}s to {@link LogLevel}s.
    *
    * <p>When the consistency check of a file storage fires an event this mapping determines the
    * severity of it.
    *
    * @author tf
    */
-  public static class EventsLogConfig extends EventsMapping<LogLevel> {}
+  public static class EventsLogConfig extends EventsMapping<LogLevel> {
+    public EventsLogConfig() {
+      this.defaul = LogLevel.ERROR;
+      this.set(ConsistencyEvent.UNKNOWN, LogLevel.WARN);
+    }
+  }
 
   /**
-   * The AutoUpdateConfig maps {@link ConsistencyEvent}s to {@link AutoUpdateAction}s.
+   * The AutoUpdateConfig maps {@link ConsistencyEventInterface}s to {@link AutoUpdateAction}s.
    *
    * <p>When the consistency check of a file storage fires an event this mapping determines whether
    * and how the server attempts to react to the event automatically. E.g. by inserting a newly
@@ -47,6 +53,11 @@ public class ConsistencyConfig {
    */
   public static class AutoUpdateConfig extends EventsMapping<AutoUpdateAction> {}
 
-  public EventsLogConfig eventsLogConfig = null;
-  public AutoUpdateConfig autoUpdateConfig = null;
+  public EventsLogConfig eventsLogConfig = new EventsLogConfig();
+  public AutoUpdateConfig autoUpdateConfig = new AutoUpdateConfig();
+  private static ConsistencyConfig instance = new ConsistencyConfig();
+
+  public static ConsistencyConfig getInstance() {
+    return instance;
+  }
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java
index f0c6b0b931285d3488a4c95a4ca93d97f8a5bbd4..373808396bcae936d4ea688f0b91c7dce974d7df 100644
--- a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java
+++ b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEvent.java
@@ -1,39 +1,139 @@
 package org.caosdb.server.filesystem.consistency;
 
-public interface ConsistencyEvent {
-
-  /**
-   * Return a universal unique identifier for this event. This can be used by other events or by
-   * triggered actions for referencing this event.
-   */
-  public String getUUID();
-
-  /**
-   * If applicable, return the id of the file storage which this event applies to.
-   *
-   * <p>Otherwise, return null.
-   */
-  public String getFileStorageId();
-
-  /**
-   * If applicable, return the key of the file which this event applies to.
-   *
-   * <p>Otherwise, return null.
-   *
-   * <p>If this is not null, {@link #getFileStorageId()} must not be null.
-   */
-  public String getKey();
-
-  /**
-   * Return the name of this event. The name of events of t
-   *
-   * @return
-   */
-  public String getEventType();
-
-  /** Return true iff this event is a subtype of the given other event type. */
-  public boolean isSubtypeOf(String eventType);
-
-  /** Return the unix time stamp when this event was initialized. */
-  Long getTimestamp();
+import java.util.UUID;
+import org.caosdb.server.filesystem.FSODescriptorInterface;
+import org.caosdb.server.filesystem.FileStorageInterface;
+import org.caosdb.server.filesystem.FileSystem;
+
+public class ConsistencyEvent implements ConsistencyEventInterface {
+
+  public static final String UNKNOWN = "UNKNOWN";
+  private String fileStorageId;
+  private String key;
+  private String type;
+  private String uuid = null;
+  private Long timestamp;
+
+  ConsistencyEvent(String type, String fileStorageId, String key) {
+    this.timestamp = System.currentTimeMillis();
+    this.type = type;
+    //    this.parent = parent;
+    this.fileStorageId = fileStorageId;
+    this.key = key;
+  }
+
+  @Override
+  public String getFileStorageId() {
+    return fileStorageId;
+  }
+
+  @Override
+  public String getKey() {
+    return key;
+  }
+
+  @Override
+  public String getEventType() {
+    return type;
+  }
+  //
+  //  @Override
+  //  public ConsistencyEvent getParent() {
+  //    if (parent == null && type.contains(":")) {
+  //      String[] components = type.split(":");
+  //      components = Arrays.copyOfRange(components, 0, components.length - 2);
+  //      parent = new AbstractConsistencyEvent(String.join(":", components), fileStorageId, key);
+  //    }
+  //    return parent;
+  //  }
+
+  @Override
+  public boolean isSubtypeOf(String eventType) {
+    if (!eventType.endsWith(":")) {
+      return this.type.startsWith(eventType + ":");
+    }
+    return this.type.startsWith(eventType);
+  }
+
+  public static final ConsistencyEventInterface missingCapability(
+      String capability, FileStorageInterface fileStorage) {
+    return new ConsistencyEvent("NEED_CAPABILITY:" + capability, fileStorage.getId(), null);
+  }
+
+  public static ConsistencyEventInterface unknownFile(FSODescriptorInterface next) {
+    return new ConsistencyEvent(UNKNOWN, next.getFileStorageId(), next.getKey());
+  }
+
+  public static ConsistencyEventInterface missingFile(FSODescriptorInterface next) {
+    return new ConsistencyEvent("MISSING", next.getFileStorageId(), next.getKey());
+  }
+
+  public static ConsistencyEventInterface wrongObjectType(
+      FSODescriptorInterface fso, FileSystem.ObjectType type) {
+    return new ConsistencyEvent(
+        "CHANGED:TYPE:TO_" + type.toString(), fso.getFileStorageId(), fso.getKey());
+  }
+
+  public static ConsistencyEventInterface brokenLink(FSODescriptorInterface fso) {
+    return new ConsistencyEvent("LINK:BROKEN", fso.getFileStorageId(), fso.getKey());
+  }
+
+  public static ConsistencyEventInterface linkToOtherFileStorage(FSODescriptorInterface fso) {
+    return new ConsistencyEvent(
+        "LINK:TARGET:IN_OTHER_FILE_STORAGE", fso.getFileStorageId(), fso.getKey());
+  }
+
+  public static ConsistencyEventInterface linkToAlienTargetFromOuterSpace(
+      FSODescriptorInterface fso) {
+    return new ConsistencyEvent(
+        "LINK:TARGET:NOT_IN_FILE_STORAGE", fso.getFileStorageId(), fso.getKey());
+  }
+
+  public static ConsistencyEventInterface sizeMismatch(FSODescriptorInterface fso) {
+    return new ConsistencyEvent("CHANGED:SIZE", fso.getFileStorageId(), fso.getKey());
+  }
+
+  public static ConsistencyEventInterface hashMismatch(FSODescriptorInterface fso) {
+    return new ConsistencyEvent("CHANGED:HASH", fso.getFileStorageId(), fso.getKey());
+  }
+
+  public static ConsistencyEventInterface linkTargetChanged(FSODescriptorInterface fso) {
+    return new ConsistencyEvent("CHANGED:LINK:TARGET", fso.getFileStorageId(), fso.getKey());
+  }
+
+  @Override
+  public String getUUID() {
+    if (uuid == null) {
+      uuid = UUID.randomUUID().toString();
+    }
+    return uuid;
+  }
+
+  @Override
+  public Long getTimestamp() {
+    return timestamp;
+  }
+
+  public static ConsistencyEventInterface missingChild(FSODescriptorInterface missingObject) {
+    return missingFile(missingObject);
+  }
+
+  public static ConsistencyEventInterface unknownChild(FSODescriptorInterface unknownObject) {
+    return unknownFile(unknownObject);
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder result = new StringBuilder();
+    result.append(type);
+    if (fileStorageId != null) {
+      result.append(" - ");
+      result.append(fileStorageId);
+      if (key != null) {
+        result.append(":");
+        result.append(key);
+      }
+    }
+    return result.toString();
+  }
 }
diff --git a/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEventInterface.java b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEventInterface.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c100cc7111bcf49401d9478b3bf91e77d1145ed
--- /dev/null
+++ b/src/main/java/org/caosdb/server/filesystem/consistency/ConsistencyEventInterface.java
@@ -0,0 +1,35 @@
+package org.caosdb.server.filesystem.consistency;
+
+public interface ConsistencyEventInterface {
+
+  /**
+   * Return a universal unique identifier for this event. This can be used by other events or by
+   * triggered actions for referencing this event.
+   */
+  public String getUUID();
+
+  /**
+   * If applicable, return the id of the file storage which this event applies to.
+   *
+   * <p>Otherwise, return null.
+   */
+  public String getFileStorageId();
+
+  /**
+   * If applicable, return the key of the file which this event applies to.
+   *
+   * <p>Otherwise, return null.
+   *
+   * <p>If this is not null, {@link #getFileStorageId()} must not be null.
+   */
+  public String getKey();
+
+  /** Return the type of this event. */
+  public String getEventType();
+
+  /** Return true iff this event is a subtype of the given other event type. */
+  public boolean isSubtypeOf(String eventType);
+
+  /** Return the unix time stamp when this event was initialized. */
+  Long getTimestamp();
+}
diff --git a/src/main/java/org/caosdb/server/jobs/core/CheckFileStorageConsistency.java b/src/main/java/org/caosdb/server/jobs/core/CheckFileStorageConsistency.java
index ff8cbc50578a79bec53869fe456e484b93b8a244..bd6b42d202f7b6acbc7bf16aba37d753fb219007 100644
--- a/src/main/java/org/caosdb/server/jobs/core/CheckFileStorageConsistency.java
+++ b/src/main/java/org/caosdb/server/jobs/core/CheckFileStorageConsistency.java
@@ -29,24 +29,31 @@ import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.Reader;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.caosdb.api.entity.v1.MessageCode;
 import org.caosdb.server.CaosDBServer;
 import org.caosdb.server.database.exceptions.TransactionException;
 import org.caosdb.server.entity.Message;
+import org.caosdb.server.entity.Message.MessageType;
+import org.caosdb.server.entity.xml.ToElementable;
 import org.caosdb.server.filesystem.FileSystem;
+import org.caosdb.server.filesystem.Path;
 import org.caosdb.server.filesystem.RealFSODescriptorInterface;
+import org.caosdb.server.filesystem.consistency.ConsistencyCheck;
+import org.caosdb.server.filesystem.consistency.ConsistencyConfig;
+import org.caosdb.server.filesystem.consistency.ConsistencyConfig.EventsLogConfig;
+import org.caosdb.server.filesystem.consistency.ConsistencyConfig.LogLevel;
+import org.caosdb.server.filesystem.consistency.ConsistencyEventInterface;
 import org.caosdb.server.jobs.FlagJob;
 import org.caosdb.server.jobs.JobAnnotation;
-import org.caosdb.server.transaction.FileStorageConsistencyCheck;
 import org.caosdb.server.transaction.Retrieve;
 import org.caosdb.server.transaction.Transaction;
 import org.caosdb.server.utils.Observable;
 import org.caosdb.server.utils.Observer;
 import org.caosdb.server.utils.Undoable;
-import org.jdom2.Document;
-import org.jdom2.output.Format;
-import org.jdom2.output.XMLOutputter;
+import org.jdom2.Element;
 
 @JobAnnotation(
     flag = "fileStorageConsistency",
@@ -63,7 +70,7 @@ public class CheckFileStorageConsistency extends FlagJob {
   public static final Pattern parseArgs =
       Pattern.compile("\\s*-t\\s*([0-9]+)\\s*|\\s*-c\\s*([^\\s]+)\\s*|\\s*([^\\s]+\\s*)");
   private int timeout = 1000 * 30; // milliseconds
-  private String location = "";
+  private Path location = null;
 
   public static enum TestCase {
     FILE_DOES_NOT_EXIST,
@@ -96,28 +103,12 @@ public class CheckFileStorageConsistency extends FlagJob {
         }
         if (matcher.group(3) != null) {
           // group three is file system location
-          this.location = matcher.group(3);
+          this.location = new Path(matcher.group(3));
         }
       }
     }
 
-    final FileStorageConsistencyCheck test = new FileStorageConsistencyCheck(this.location);
-    test.setOnFinish(
-        new Runnable() {
-          @Override
-          public void run() {
-
-            final Document doc = new Document(test.toElement());
-            final XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
-            try {
-              out.output(doc, new PrintStream(new File("./ConsistencyTest.xml")));
-            } catch (final FileNotFoundException e) {
-              throw new TransactionException(e);
-            } catch (final IOException e) {
-              throw new TransactionException(e);
-            }
-          }
-        });
+    ConsistencyCheck test = new ConsistencyCheck(this.location);
     test.start();
     try {
       test.join(this.timeout);
@@ -138,10 +129,56 @@ public class CheckFileStorageConsistency extends FlagJob {
         throw new TransactionException(e);
       }
 
-      getContainer().addMessage(test);
+      getContainer().addMessage(createMessages(test));
     }
   }
 
+  private ToElementable createMessages(ConsistencyCheck test) {
+    final List<ConsistencyEventInterface> events = test.getEvents();
+    return new ToElementable() {
+
+      @Override
+      public void addToElement(Element ret) {
+        EventsLogConfig c = ConsistencyConfig.getInstance().eventsLogConfig;
+        if (events.size() == 0) {
+          if (location != null) {
+            ret.addContent(
+                new Message("File system below " + location + "/ is consistent.").toElement());
+          } else {
+            ret.addContent(new Message("File system is consistent.").toElement());
+          }
+        } else {
+          for (ConsistencyEventInterface event : events) {
+            LogLevel level = c.get(event.getEventType());
+            switch (level) {
+              case ERROR:
+                ret.addContent(
+                    new Message(
+                            MessageType.Error, MessageCode.MESSAGE_CODE_UNKNOWN, event.toString())
+                        .toElement());
+                break;
+              case WARN:
+                ret.addContent(
+                    new Message(
+                            MessageType.Warning, MessageCode.MESSAGE_CODE_UNKNOWN, event.toString())
+                        .toElement());
+                break;
+              case INFO:
+                ret.addContent(
+                    new Message(
+                            MessageType.Info, MessageCode.MESSAGE_CODE_UNKNOWN, event.toString())
+                        .toElement());
+                break;
+              default:
+                // ignore
+                break;
+            }
+          }
+        }
+      }
+    };
+  }
+
   private void test(final TestCase testCase) throws IOException, Message {
     switch (testCase) {
       case FILE_DOES_NOT_EXIST:
@@ -168,7 +205,7 @@ public class CheckFileStorageConsistency extends FlagJob {
 
       case UNKNOWN_FILE:
         final File dir =
-            FileSystem.getInstance().getFileStorage("default").resolve("debug/").getFile();
+            FileSystem.getInstance().getFileStorage("default").resolve("test_unknown").getFile();
 
         dir.mkdirs();
 
@@ -179,7 +216,7 @@ public class CheckFileStorageConsistency extends FlagJob {
               public void undo() {
                 try {
                   Undoable delete =
-                      FileSystem.getInstance().getFileStorage("default").delete("debug/");
+                      FileSystem.getInstance().getFileStorage("default").delete("test_unknown");
                   delete.cleanUp();
                 } catch (Message e) {
                   e.printStackTrace();
diff --git a/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java b/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java
index 791ce3a6b30ae2e8f5e44e3842ba541de901ca2a..04d4bfc6f5302908e8bc4e18701f6877765bbbc8 100644
--- a/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java
+++ b/src/main/java/org/caosdb/server/transaction/ChecksumUpdater.java
@@ -72,7 +72,12 @@ public class ChecksumUpdater extends WriteTransaction
       }
 
       // we calculate the hash, even though we don't have reserved write access...
-      final Hash checksum = calcChecksum(fileEntity);
+      final Hash checksum =
+          FileSystem.getInstance()
+              .resolve(
+                  fileEntity.getFSODescriptor().getFileStorageId(),
+                  fileEntity.getFSODescriptor().getKey())
+              .getHash();
       if (checksum == null) {
         // TODO file system does not support hashing? Mark this file appropriately
         continue;
@@ -120,10 +125,6 @@ public class ChecksumUpdater extends WriteTransaction
     }
   }
 
-  private Hash calcChecksum(final EntityInterface fileEntity) {
-    return FileSystem.getHash(fileEntity.getFSODescriptor(), "SHA-512");
-  }
-
   private EntityInterface getNextUpdateableFileEntity() throws InterruptedException {
     final Access weakAccess = DatabaseAccessManager.getInstance().acquireReadAccess(this);
     try {
diff --git a/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java b/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java
index df876fdc418bbf1985a6e78b0de68b98888060ec..a9400eb937077aabd100a58982be82066a25d402 100644
--- a/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java
+++ b/src/main/java/org/caosdb/server/transaction/FileStorageConsistencyCheck.java
@@ -1,244 +1,245 @@
-/*
- * ** header v3.0
- * This file is a part of the CaosDB Project.
- *
- * Copyright (C) 2018 Research Group Biomedical Physics,
- * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- * ** end header
- */
-package org.caosdb.server.transaction;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.TimeZone;
-import org.caosdb.api.entity.v1.MessageCode;
-import org.caosdb.datetime.UTCDateTime;
-import org.caosdb.server.database.DatabaseAccessManager;
-import org.caosdb.server.database.access.Access;
-import org.caosdb.server.database.backend.transaction.FileConsistencyCheck;
-import org.caosdb.server.database.backend.transaction.GetFileEntityByPath;
-import org.caosdb.server.database.backend.transaction.GetFileIterator;
-import org.caosdb.server.database.backend.transaction.RetrieveAllUncheckedFiles;
-import org.caosdb.server.database.backend.transaction.SetFileCheckedTimestamp;
-import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
-import org.caosdb.server.database.proto.SparseEntity;
-import org.caosdb.server.entity.EntityID;
-import org.caosdb.server.entity.Message;
-import org.caosdb.server.entity.xml.ToElementable;
-import org.caosdb.server.filesystem.FileSystem;
-import org.caosdb.server.filesystem.Path;
-import org.jdom2.Element;
-
-public class FileStorageConsistencyCheck extends Thread
-    implements ToElementable, TransactionInterface {
-
-  private Access access = null;
-  private final HashMap<String, Integer> results = new HashMap<String, Integer>();
-  private Exception exception = null;
-  private Runnable finishRunnable = null;
-  private final String location;
-  private Long ts = null;
-  private UTCDateTime timestamp = null;
-
-  public Exception getException() {
-    return this.exception;
-  }
-
-  public FileStorageConsistencyCheck(final String location) {
-    setDaemon(true);
-    this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis());
-    this.location = location.startsWith("/") ? location.replaceFirst("^/", "") : location;
-  }
-
-  @Override
-  public void run() {
-    for (final String storage : FileSystem.getInstance().getFileStorages()) {
-      try {
-        this.access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
-
-        // test all files in file system.
-        final GetFileIterator it = new GetFileIterator(storage, this.location);
-        final Iterator<String> iterator = execute(it, this.access).getIterator();
-
-        this.ts = System.currentTimeMillis();
-        while (iterator != null && iterator.hasNext()) {
-          final String path = iterator.next();
-
-          if (DatabaseAccessManager.whoHasReservedWriteAccess() != null) {
-            // there is a thread waiting to write. pause this one and
-            // try to acquire a new read access which will be granted when
-            // the write thread is done.
-            this.access.release();
-            this.access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
-          }
-
-          try {
-            final GetFileEntityByPath t =
-                execute(new GetFileEntityByPath(new Path(path)), this.access);
-            final int result =
-                execute(new FileConsistencyCheck(path, t.getSize(), t.getHash()), this.access)
-                    .getResult();
-
-            if (result != FileConsistencyCheck.OK) {
-              this.results.put(path, result);
-            }
-
-            execute(new SetFileCheckedTimestamp(t.getId(), this.ts), this.access);
-          } catch (final EntityDoesNotExistException e) {
-            this.results.put(path, FileConsistencyCheck.UNKNOWN_FILE);
-            continue;
-          }
-        }
-
-        // test all remaining file records
-        final Iterator<SparseEntity> iterator2 =
-            execute(new RetrieveAllUncheckedFiles(this.ts, this.location), this.access)
-                .getIterator();
-        while (iterator2 != null && iterator2.hasNext()) {
-
-          final SparseEntity entity = iterator2.next();
-          final int result =
-              execute(
-                      new FileConsistencyCheck(entity.fileKey, entity.fileSize, entity.fileHash),
-                      this.access)
-                  .getResult();
-
-          if (result != FileConsistencyCheck.OK) {
-            this.results.put(entity.filePath.toString(), result);
-          }
-
-          execute(new SetFileCheckedTimestamp(new EntityID(entity.id), this.ts), this.access);
-        }
-
-      } catch (final Exception e) {
-        this.exception = e;
-      } finally {
-        this.access.release();
-      }
-    }
-    synchronized (this.results) {
-      if (this.finishRunnable != null) {
-        this.finishRunnable.run();
-      }
-    }
-  }
-
-  public HashMap<String, Integer> getResults() {
-    return this.results;
-  }
-
-  public void setOnFinish(final Runnable r) {
-    synchronized (this.results) {
-      this.finishRunnable = r;
-    }
-  }
-
-  @Override
-  public void addToElement(final Element e) {
-    if (this.ts != null) {
-      e.setAttribute(
-          "timestamp",
-          UTCDateTime.SystemMillisToUTCDateTime(this.ts).toDateTimeString(TimeZone.getDefault()));
-    }
-    if (this.location != null) {
-      e.setAttribute("location", this.location);
-    }
-
-    if (getException() != null) {
-      final StringBuilder sb = new StringBuilder();
-      sb.append(getException().toString());
-      for (final StackTraceElement t : getException().getStackTrace()) {
-        sb.append('\n').append(t.toString());
-      }
-
-      e.addContent(
-          new Message(
-                  "Error",
-                  MessageCode.MESSAGE_CODE_UNKNOWN,
-                  "An exception was thrown.",
-                  sb.toString())
-              .toElement());
-    }
-
-    final List<Message> results2Messages = results2Messages(getResults(), this.location);
-    for (final Message m : results2Messages) {
-      e.addContent(m.toElement());
-    }
-  }
-
-  public Element toElement() {
-    final Element results = new Element("Results");
-    addToElement(results);
-    return results;
-  }
-
-  private static List<Message> results2Messages(
-      final HashMap<String, Integer> results, final String location) {
-    final ArrayList<Message> ret = new ArrayList<Message>();
-    if (results.isEmpty()) {
-      if (location.length() > 0) {
-        ret.add(new Message("File system below " + location + " is consistent."));
-      } else {
-        ret.add(new Message("File system is consistent."));
-      }
-    }
-    for (final Entry<String, Integer> r : results.entrySet()) {
-      switch (r.getValue()) {
-        case FileConsistencyCheck.FILE_DOES_NOT_EXIST:
-          ret.add(
-              new Message(
-                  "Error",
-                  MessageCode.MESSAGE_CODE_UNKNOWN,
-                  r.getKey() + ": File does not exist."));
-          break;
-        case FileConsistencyCheck.FILE_MODIFIED:
-          ret.add(
-              new Message(
-                  "Error", MessageCode.MESSAGE_CODE_UNKNOWN, r.getKey() + ": File was modified."));
-          break;
-        case FileConsistencyCheck.UNKNOWN_FILE:
-          ret.add(
-              new Message(
-                  "Warning", MessageCode.MESSAGE_CODE_UNKNOWN, r.getKey() + ": Unknown file."));
-          break;
-        case FileConsistencyCheck.NONE:
-          ret.add(
-              new Message(
-                  "Warning",
-                  MessageCode.MESSAGE_CODE_UNKNOWN,
-                  r.getKey() + ": Test result not available."));
-          break;
-        default:
-          break;
-      }
-    }
-    return ret;
-  }
-
-  @Override
-  public void execute() throws Exception {
-    run();
-  }
-
-  @Override
-  public UTCDateTime getTimestamp() {
-    return timestamp;
-  }
-}
+/// *
+// * ** header v3.0
+// * This file is a part of the CaosDB Project.
+// *
+// * Copyright (C) 2018 Research Group Biomedical Physics,
+// * Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+// *
+// * This program is free software: you can redistribute it and/or modify
+// * it under the terms of the GNU Affero General Public License as
+// * published by the Free Software Foundation, either version 3 of the
+// * License, or (at your option) any later version.
+// *
+// * This program is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// * GNU Affero General Public License for more details.
+// *
+// * You should have received a copy of the GNU Affero General Public License
+// * along with this program. If not, see <https://www.gnu.org/licenses/>.
+// *
+// * ** end header
+// */
+// package org.caosdb.server.transaction;
+//
+// import java.util.ArrayList;
+// import java.util.HashMap;
+// import java.util.Iterator;
+// import java.util.List;
+// import java.util.Map.Entry;
+// import java.util.TimeZone;
+// import org.caosdb.api.entity.v1.MessageCode;
+// import org.caosdb.datetime.UTCDateTime;
+// import org.caosdb.server.database.DatabaseAccessManager;
+// import org.caosdb.server.database.access.Access;
+// import org.caosdb.server.database.backend.transaction.FileConsistencyCheck;
+// import org.caosdb.server.database.backend.transaction.GetFileEntityByPath;
+// import org.caosdb.server.database.backend.transaction.GetFileIterator;
+// import org.caosdb.server.database.backend.transaction.RetrieveAllUncheckedFiles;
+// import org.caosdb.server.database.backend.transaction.SetFileCheckedTimestamp;
+// import org.caosdb.server.database.exceptions.EntityDoesNotExistException;
+// import org.caosdb.server.database.proto.SparseEntity;
+// import org.caosdb.server.entity.EntityID;
+// import org.caosdb.server.entity.Message;
+// import org.caosdb.server.entity.xml.ToElementable;
+// import org.caosdb.server.filesystem.FileSystem;
+// import org.caosdb.server.filesystem.Path;
+// import org.jdom2.Element;
+//
+// public class FileStorageConsistencyCheck extends Thread
+//    implements ToElementable, TransactionInterface {
+//
+//  private Access access = null;
+//  private final HashMap<String, Integer> results = new HashMap<String, Integer>();
+//  private Exception exception = null;
+//  private Runnable finishRunnable = null;
+//  private final String location;
+//  private Long ts = null;
+//  private UTCDateTime timestamp = null;
+//
+//  public Exception getException() {
+//    return this.exception;
+//  }
+//
+//  public FileStorageConsistencyCheck(final String location) {
+//    setDaemon(true);
+//    this.timestamp = UTCDateTime.SystemMillisToUTCDateTime(System.currentTimeMillis());
+//    this.location = location.startsWith("/") ? location.replaceFirst("^/", "") : location;
+//  }
+//
+//  @Override
+//  public void run() {
+//    for (final String storage : FileSystem.getInstance().getFileStorages()) {
+//      try {
+//        this.access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
+//
+//        // test all files in file system.
+//        final GetFileIterator it = new GetFileIterator(storage, this.location);
+//        final Iterator<String> iterator = execute(it, this.access).getIterator();
+//
+//        this.ts = System.currentTimeMillis();
+//        while (iterator != null && iterator.hasNext()) {
+//          final String path = iterator.next();
+//
+//          if (DatabaseAccessManager.whoHasReservedWriteAccess() != null) {
+//            // there is a thread waiting to write. pause this one and
+//            // try to acquire a new read access which will be granted when
+//            // the write thread is done.
+//            this.access.release();
+//            this.access = DatabaseAccessManager.getInstance().acquireReadAccess(this);
+//          }
+//
+//          try {
+//            final GetFileEntityByPath t =
+//                execute(new GetFileEntityByPath(new Path(path)), this.access);
+//            final int result =
+//                execute(new FileConsistencyCheck(path, t.getSize(), t.getHash()), this.access)
+//                    .getResult();
+//
+//            if (result != FileConsistencyCheck.OK) {
+//              this.results.put(path, result);
+//            }
+//
+//            execute(new SetFileCheckedTimestamp(t.getId(), this.ts), this.access);
+//          } catch (final EntityDoesNotExistException e) {
+//            this.results.put(path, FileConsistencyCheck.UNKNOWN_FILE);
+//            continue;
+//          }
+//        }
+//
+//        // test all remaining file records
+//        final Iterator<SparseEntity> iterator2 =
+//            execute(new RetrieveAllUncheckedFiles(this.ts, this.location), this.access)
+//                .getIterator();
+//        while (iterator2 != null && iterator2.hasNext()) {
+//
+//          final SparseEntity entity = iterator2.next();
+//          final int result =
+//              execute(
+//                      new FileConsistencyCheck(entity.fileKey, entity.fileSize, entity.fileHash),
+//                      this.access)
+//                  .getResult();
+//
+//          if (result != FileConsistencyCheck.OK) {
+//            this.results.put(entity.filePath.toString(), result);
+//          }
+//
+//          execute(new SetFileCheckedTimestamp(new EntityID(entity.id), this.ts), this.access);
+//        }
+//
+//      } catch (final Exception e) {
+//        this.exception = e;
+//      } finally {
+//        this.access.release();
+//      }
+//    }
+//    synchronized (this.results) {
+//      if (this.finishRunnable != null) {
+//        this.finishRunnable.run();
+//      }
+//    }
+//  }
+//
+//  public HashMap<String, Integer> getResults() {
+//    return this.results;
+//  }
+//
+//  public void setOnFinish(final Runnable r) {
+//    synchronized (this.results) {
+//      this.finishRunnable = r;
+//    }
+//  }
+//
+//  @Override
+//  public void addToElement(final Element e) {
+//    if (this.ts != null) {
+//      e.setAttribute(
+//          "timestamp",
+//          UTCDateTime.SystemMillisToUTCDateTime(this.ts).toDateTimeString(TimeZone.getDefault()));
+//    }
+//    if (this.location != null) {
+//      e.setAttribute("location", this.location);
+//    }
+//
+//    if (getException() != null) {
+//      final StringBuilder sb = new StringBuilder();
+//      sb.append(getException().toString());
+//      for (final StackTraceElement t : getException().getStackTrace()) {
+//        sb.append('\n').append(t.toString());
+//      }
+//
+//      e.addContent(
+//          new Message(
+//                  "Error",
+//                  MessageCode.MESSAGE_CODE_UNKNOWN,
+//                  "An exception was thrown.",
+//                  sb.toString())
+//              .toElement());
+//    }
+//
+//    final List<Message> results2Messages = results2Messages(getResults(), this.location);
+//    for (final Message m : results2Messages) {
+//      e.addContent(m.toElement());
+//    }
+//  }
+//
+//  public Element toElement() {
+//    final Element results = new Element("Results");
+//    addToElement(results);
+//    return results;
+//  }
+//
+//  private static List<Message> results2Messages(
+//      final HashMap<String, Integer> results, final String location) {
+//    final ArrayList<Message> ret = new ArrayList<Message>();
+//    if (results.isEmpty()) {
+//      if (location.length() > 0) {
+//        ret.add(new Message("File system below " + location + " is consistent."));
+//      } else {
+//        ret.add(new Message("File system is consistent."));
+//      }
+//    }
+//    for (final Entry<String, Integer> r : results.entrySet()) {
+//      switch (r.getValue()) {
+//        case FileConsistencyCheck.FILE_DOES_NOT_EXIST:
+//          ret.add(
+//              new Message(
+//                  "Error",
+//                  MessageCode.MESSAGE_CODE_UNKNOWN,
+//                  r.getKey() + ": File does not exist."));
+//          break;
+//        case FileConsistencyCheck.FILE_MODIFIED:
+//          ret.add(
+//              new Message(
+//                  "Error", MessageCode.MESSAGE_CODE_UNKNOWN, r.getKey() + ": File was
+// modified."));
+//          break;
+//        case FileConsistencyCheck.UNKNOWN_FILE:
+//          ret.add(
+//              new Message(
+//                  "Warning", MessageCode.MESSAGE_CODE_UNKNOWN, r.getKey() + ": Unknown file."));
+//          break;
+//        case FileConsistencyCheck.NONE:
+//          ret.add(
+//              new Message(
+//                  "Warning",
+//                  MessageCode.MESSAGE_CODE_UNKNOWN,
+//                  r.getKey() + ": Test result not available."));
+//          break;
+//        default:
+//          break;
+//      }
+//    }
+//    return ret;
+//  }
+//
+//  @Override
+//  public void execute() throws Exception {
+//    run();
+//  }
+//
+//  @Override
+//  public UTCDateTime getTimestamp() {
+//    return timestamp;
+//  }
+// }