diff --git a/src/main/java/org/olat/admin/bc/BriefcaseAdminController.java b/src/main/java/org/olat/admin/bc/BriefcaseAdminController.java
index e6536024605eff11e7f8ccf346a7f320eadbf45b..83636a2fb20a4f000d455f1c3ddf1fc80e52de26 100644
--- a/src/main/java/org/olat/admin/bc/BriefcaseAdminController.java
+++ b/src/main/java/org/olat/admin/bc/BriefcaseAdminController.java
@@ -23,6 +23,7 @@ import java.io.File;
 
 import org.olat.core.commons.modules.bc.FolderConfig;
 import org.olat.core.commons.services.taskexecutor.TaskExecutorManager;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -32,7 +33,6 @@ import org.olat.core.gui.components.form.flexible.impl.FormEvent;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
-import org.olat.core.util.vfs.meta.MetaInfoFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
@@ -47,7 +47,7 @@ public class BriefcaseAdminController extends FormBasicController {
 	@Autowired
 	private TaskExecutorManager taskExecutor;
 	@Autowired
-	private MetaInfoFactory metaInfoFactory;
+	private VFSRepositoryService vfsRepositoryService;
 
 	public BriefcaseAdminController(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl, "bc_admin");
@@ -88,23 +88,10 @@ public class BriefcaseAdminController extends FormBasicController {
 			logInfo("Start reset of thumbnails", null);
 			
 			String metaRoot = FolderConfig.getCanonicalMetaRoot();
-			File metaRootFile = new File(metaRoot);
-			resetThumbnails(metaRootFile);
+			vfsRepositoryService.resetThumbnails(new File(metaRoot));
 			flc.contextPut("recalculating", Boolean.FALSE);
 			
 			logInfo("Finished reset of thumbnails in " + (System.currentTimeMillis() - start) + " (ms)", null);
 		}
-			
-		private void resetThumbnails(File directory) {
-			for(File file:directory.listFiles()) {
-				if(file.isHidden()) {
-					//do nothing
-				} else if(file.isDirectory()) {
-					resetThumbnails(file);
-				} else if(file.getName().endsWith(".xml")) {
-					metaInfoFactory.resetThumbnails(file);
-				}
-			}
-		}
 	}
 }
diff --git a/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java b/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java
index 5becf07bee417a0552aa200e18e63e67742c4e79..101f9c47e1c5ea6ec37859df925d03af95a89626 100644
--- a/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java
+++ b/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java
@@ -34,6 +34,8 @@ import javax.servlet.http.HttpServletRequest;
 import org.olat.commons.info.InfoMessage;
 import org.olat.commons.info.InfoMessageFrontendManager;
 import org.olat.commons.info.manager.MailFormatter;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.dispatcher.mapper.Mapper;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.date.DateComponentFactory;
@@ -73,7 +75,6 @@ import org.olat.core.util.resource.OresHelper;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.info.InfoCourseNodeConfiguration;
 import org.olat.group.BusinessGroup;
 import org.olat.modules.ModuleConfiguration;
@@ -122,6 +123,8 @@ public class InfoDisplayController extends FormBasicController {
 	private UserManager userManager;
 	@Autowired
 	private InfoMessageFrontendManager infoMessageManager;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	private LockResult lockEntry;
 	private MailFormatter sendMailFormatter;
@@ -538,14 +541,12 @@ public class InfoDisplayController extends FormBasicController {
 					Long infoKey = Long.valueOf(Long.parseLong(query[1]));
 					VFSLeaf attachment = infoKeyToAttachment.get(infoKey);
 					if(attachment != null && attachment.canMeta() == VFSConstants.YES) {
-						MetaInfo meta = attachment.getMetaInfo();
-						if (meta.getUUID().equals(query[2])) {
-							if (meta.isThumbnailAvailable()) {
-								VFSLeaf thumb = meta.getThumbnail(200, 200, false);
-								if(thumb != null) {
-									// Positive lookup, send as response
-									return new VFSMediaResource(thumb);
-								}
+						VFSMetadata meta = attachment.getMetaInfo();
+						if (meta.getUuid().equals(query[2])) {
+							VFSLeaf thumb = vfsRepositoryService.getThumbnail(attachment, meta, 200, 200, false);
+							if(thumb != null) {
+								// Positive lookup, send as response
+								return new VFSMediaResource(thumb);
 							}
 						}
 					}	
diff --git a/src/main/java/org/olat/commons/memberlist/ui/MembersPrintController.java b/src/main/java/org/olat/commons/memberlist/ui/MembersPrintController.java
index 32da7f36402094fa816ae79b77ee50641d16a39b..8878b5383fd9ea2d987c6b2d9cc0fb112ee2ce69 100644
--- a/src/main/java/org/olat/commons/memberlist/ui/MembersPrintController.java
+++ b/src/main/java/org/olat/commons/memberlist/ui/MembersPrintController.java
@@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletRequest;
 import org.olat.basesecurity.BaseSecurityModule;
 import org.olat.commons.memberlist.model.CurriculumElementInfos;
 import org.olat.commons.memberlist.model.CurriculumMemberInfos;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.dispatcher.mapper.Mapper;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
@@ -50,7 +51,6 @@ import org.olat.core.util.Util;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.members.Member;
 import org.olat.user.DisplayPortraitManager;
 import org.olat.user.UserManager;
@@ -73,9 +73,12 @@ public class MembersPrintController extends BasicController {
 	@Autowired
 	private UserManager userManager;
 	@Autowired
+	private BaseSecurityModule securityModule;
+	@Autowired
 	private DisplayPortraitManager portraitManager;
 	@Autowired
-	private BaseSecurityModule securityModule;
+	private VFSRepositoryService vfsRepositoryService;
+	
 	
 	public MembersPrintController(UserRequest ureq, WindowControl wControl, Translator translator, List<Identity> owners,
 			List<Identity> coaches, List<Identity> participants, List<Identity> waiting, Map<Long,CurriculumMemberInfos> curriculumInfos,
@@ -193,8 +196,7 @@ public class MembersPrintController extends BasicController {
 					String username = userManager.getUsername(key);
 					VFSLeaf portrait = portraitManager.getLargestVFSPortrait(username);
 					if(portrait.canMeta() == VFSConstants.YES) {
-						MetaInfo meta = portrait.getMetaInfo();
-						portrait = meta.getThumbnail(300, 300, false);
+						portrait = vfsRepositoryService.getThumbnail(portrait, 300, 300, false);
 					}
 					
 					if(portrait != null) {
diff --git a/src/main/java/org/olat/core/commons/modules/bc/FileInfo.java b/src/main/java/org/olat/core/commons/modules/bc/FileInfo.java
index 178c9c54e02d09bb12a18d45bd0edd915f1546d6..43cceb1e66fe1939dcccfb129508c48920f1b433 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/FileInfo.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/FileInfo.java
@@ -28,7 +28,7 @@ package org.olat.core.commons.modules.bc;
 
 import java.util.Date;
 
-import org.olat.core.util.vfs.meta.MetaInfo;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 
 /**
  * Initial Date:  11.02.2005 <br>
@@ -37,14 +37,14 @@ import org.olat.core.util.vfs.meta.MetaInfo;
  */
 public class FileInfo {
 	private String relPath;
-	private MetaInfo metaInfo;
+	private VFSMetadata metaInfo;
 	private Date lastModified;
 	/**
 	 * @param relPath e.g. chapter1/info.pdf -> will be used as title in notifications
 	 * @param metaInfo
 	 * @param lastModified
 	 */
-	public FileInfo(String relPath, MetaInfo metaInfo, Date lastModified) {
+	public FileInfo(String relPath, VFSMetadata metaInfo, Date lastModified) {
 		this.relPath = relPath;
 		this.metaInfo = metaInfo;
 		this.lastModified = lastModified;
@@ -54,7 +54,7 @@ public class FileInfo {
 	 * @return the author
 	 */
 	public Long getAuthorIdentityKey() {
-		return metaInfo == null ? null : metaInfo.getAuthorIdentityKey();
+		return metaInfo == null || metaInfo.getAuthor() == null ? null : metaInfo.getAuthor().getKey();
 	}
 
 	/**
@@ -75,7 +75,7 @@ public class FileInfo {
 	 * Get the file meta info or NULL if no meta info exists
 	 * @return
 	 */
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		return metaInfo;
 	}
 }
diff --git a/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java b/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java
index e7377580f23be2637d9494b9773f595354a97e9a..2690eb474d85f77b45d43412c7dc02169a5af721 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/FileUploadController.java
@@ -44,6 +44,8 @@ import org.olat.core.commons.modules.bc.meta.MetaInfoFormController;
 import org.olat.core.commons.modules.bc.version.RevisionListController;
 import org.olat.core.commons.modules.bc.version.VersionCommentController;
 import org.olat.core.commons.services.image.ImageService;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -79,7 +81,6 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSLockManager;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.core.util.vfs.version.Versionable;
 import org.olat.core.util.vfs.version.Versions;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -151,6 +152,8 @@ public class FileUploadController extends FormBasicController {
 	private ImageService imageHelper;
 	@Autowired
 	private VFSLockManager vfsLockManager;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	private String subfolderPath;
 	private TextElement targetSubPath ;
@@ -729,12 +732,14 @@ public class FileUploadController extends FormBasicController {
 	private void finishSuccessfullUpload(String filePath, VFSItem item, UserRequest ureq) {
 		if (item instanceof VFSLeaf && item.canMeta() == VFSConstants.YES) {
 			// create meta data
-			MetaInfo meta = item.getMetaInfo();
+			VFSMetadata meta = item.getMetaInfo();
 			if (metaDataCtr != null) {
 				meta = metaDataCtr.getMetaInfo(meta);
 			}
 			meta.setAuthor(getIdentity());
-			meta.clearThumbnails();//clear write the meta
+			//clear write the meta
+			vfsRepositoryService.updateMetadata(meta);
+			vfsRepositoryService.resetThumbnails((VFSLeaf)item);
 		}
 		
 		if(item == null) {
@@ -885,8 +890,7 @@ public class FileUploadController extends FormBasicController {
 			return validateFilename(metaDataCtr.getFilename(), metaDataCtr.getFilenameEl());
 		}
 
-		boolean allOk = validateFilename(fileEl);
-		return allOk;
+		return validateFilename(fileEl);
 	}
 	
 	private boolean validateFilename(FileElement itemEl) {
diff --git a/src/main/java/org/olat/core/commons/modules/bc/FolderManager.java b/src/main/java/org/olat/core/commons/modules/bc/FolderManager.java
index 7a508ba94cf4f70edc59742833a60ef6b3785caa..d5b3e0eb1122d8b8348035dfb4b7e672e114c222 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/FolderManager.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/FolderManager.java
@@ -31,6 +31,7 @@ import java.util.Date;
 import java.util.List;
 
 import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.helpers.Settings;
 import org.olat.core.util.WebappHelper;
 import org.olat.core.util.vfs.LocalFolderImpl;
@@ -40,7 +41,6 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.filters.SystemItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * Initial Date:  18.12.2002
@@ -90,7 +90,7 @@ public class FolderManager {
 			if(leaf.canMeta() == VFSConstants.YES) {
 				long lastModified = leaf.getLastModified();
 				if (lastModified > newerThan) {
-					MetaInfo meta = leaf.getMetaInfo();
+					VFSMetadata meta = leaf.getMetaInfo();
 					String bcrootPath = relPath.getRelPath();
 					String bcRelPath = bcrootPath.substring(basePathlen);
 					fileInfos.add(new FileInfo(bcRelPath, meta, new Date(lastModified)));
diff --git a/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java b/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java
index 158ecf45fae1c9dc145ae64fbeb458038b77e4af..5d69dbc670628f3f6400c85eeeb05c56c050b42d 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/FolderModule.java
@@ -27,6 +27,7 @@
 package org.olat.core.commons.modules.bc;
 
 import java.io.File;
+import java.nio.file.Path;
 
 import org.olat.core.configuration.AbstractSpringModule;
 import org.olat.core.helpers.Settings;
@@ -145,6 +146,10 @@ public class FolderModule extends AbstractSpringModule {
 		return FolderConfig.getCanonicalRoot();
 	}
 	
+	public Path getCanonicalRootPath() {
+		return FolderConfig.getCanonicalRootPath();
+	}
+	
 	public String getCanonicalTmpDir() {
 		return FolderConfig.getCanonicalTmpDir();
 	}
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java
index 9be9f7a1a9ef58deb22a5e4853529dd5a4dbbef0..2f95a91523e66b72ff8f1e5e08bd18600e880cd5 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFile.java
@@ -37,6 +37,8 @@ import org.olat.core.commons.services.license.LicenseService;
 import org.olat.core.commons.services.license.ui.LicenseUIFactory;
 import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
@@ -55,7 +57,6 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.core.util.vfs.util.ContainerAndFile;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -83,6 +84,8 @@ public class CmdCreateFile extends FormBasicController implements FolderCommand
 	private LicenseModule licenseModule;
 	@Autowired
 	private FolderLicenseHandler licenseHandler;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	protected CmdCreateFile(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
@@ -192,16 +195,16 @@ public class CmdCreateFile extends FormBasicController implements FolderCommand
 			notifyFinished(ureq);
 		} else {
 			if(item.canMeta() == VFSConstants.YES) {
-				MetaInfo meta = item.getMetaInfo();
+				VFSMetadata meta = item.getMetaInfo();
 				meta.setAuthor(ureq.getIdentity());
 				if (licenseModule.isEnabled(licenseHandler)) {
 					License license = licenseService.createDefaultLicense(licenseHandler, getIdentity());
-					meta.setLicenseTypeKey(String.valueOf(license.getLicenseType().getKey()));
+					meta.setLicenseType(license.getLicenseType());
 					meta.setLicenseTypeName(license.getLicenseType().getName());
 					meta.setLicensor(license.getLicensor());
 					meta.setLicenseText(LicenseUIFactory.getLicenseText(license));
 				}
-				meta.write();
+				vfsRepositoryService.updateMetadata(meta);
 			}
 
 			// start HTML editor with the folders root folder as base and the file
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFolder.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFolder.java
index fdc92feb2389061ff0d720975b5836543ca16bc2..25b08a0291be4adf3b573352e4aa1fb191490ced 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFolder.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdCreateFolder.java
@@ -29,6 +29,8 @@ package org.olat.core.commons.modules.bc.commands;
 
 import org.olat.core.commons.modules.bc.FolderEvent;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
@@ -42,7 +44,7 @@ import org.olat.core.util.FileUtils;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.meta.MetaInfo;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * A panel with a FolderComponent and a CreateFolderForm.
@@ -58,6 +60,9 @@ public class CmdCreateFolder extends FormBasicController implements FolderComman
 	private String folderName;
 	private String target;
 	private TextElement textElement;
+	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
   
 	public CmdCreateFolder(UserRequest ureq, WindowControl wControl) {			
 		super(ureq, wControl);
@@ -131,9 +136,9 @@ public class CmdCreateFolder extends FormBasicController implements FolderComman
 		VFSItem item = currentContainer.createChildContainer(name);
 		if (item instanceof VFSContainer && item.canMeta() == VFSConstants.YES) {
 			// update meta data
-			MetaInfo meta = item.getMetaInfo();
+			VFSMetadata meta = item.getMetaInfo();
 			meta.setAuthor(ureq.getIdentity());
-			meta.write();
+			vfsRepositoryService.updateMetadata(meta);
 			status = FolderCommandStatus.STATUS_FAILED;
 			
 			fireEvent(ureq, new FolderEvent(FolderEvent.NEW_FOLDER_EVENT, folderName));	
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java
index b3c522b6f09fb0211030013ad4da64b001935a8b..2e1720c9b0824da4c80c75f0a3fece7f53130604 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdDownloadZip.java
@@ -28,8 +28,10 @@ import java.util.zip.ZipOutputStream;
 
 import javax.servlet.http.HttpServletResponse;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FileSelection;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -45,7 +47,6 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * 
@@ -186,9 +187,7 @@ public class CmdDownloadZip implements FolderCommand {
 						vfsFiles.add(item);
 						// update download counter
 						if (item.canMeta() == VFSConstants.YES) {
-							MetaInfo meta = item.getMetaInfo();
-							meta.increaseDownloadCount();
-							meta.write();
+							CoreSpringFactory.getImpl(VFSRepositoryService.class).increaseDownloadCount(item);
 						}
 					}
 				}
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdEditMeta.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdEditMeta.java
index 0f3ffbba8c1f867df27d9abcfbca1c1b715b1d9d..13b135f9e6c13dbece94d4c6ad3bc1eb7b052685 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdEditMeta.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdEditMeta.java
@@ -26,7 +26,6 @@
 
 package org.olat.core.commons.modules.bc.commands;
 
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderEvent;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
 import org.olat.core.commons.modules.bc.components.ListRenderer;
@@ -34,6 +33,8 @@ import org.olat.core.commons.modules.bc.meta.MetaInfoController;
 import org.olat.core.commons.modules.bc.meta.MetaInfoFormController;
 import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.control.Controller;
@@ -52,7 +53,7 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLockManager;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
+import org.springframework.beans.factory.annotation.Autowired;
 
 public class CmdEditMeta extends BasicController implements FolderCommand {
 
@@ -64,11 +65,13 @@ public class CmdEditMeta extends BasicController implements FolderCommand {
 	private FolderComponent folderComponent;
 	private Translator translator;
 
-	private final VFSLockManager vfsLockManager;
+	@Autowired
+	private VFSLockManager vfsLockManager;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	protected CmdEditMeta(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl, Util.createPackageTranslator(MetaInfoController.class, ureq.getLocale()));
-		vfsLockManager = CoreSpringFactory.getImpl(VFSLockManager.class);
 	}
 
 	/**
@@ -147,10 +150,10 @@ public class CmdEditMeta extends BasicController implements FolderCommand {
 	@Override
 	public void event(UserRequest ureq, Controller source, Event event) {
 		if (source == metaInfoCtr && event == Event.DONE_EVENT) {
-			MetaInfo meta = metaInfoCtr.getMetaInfo();
+			VFSMetadata meta = metaInfoCtr.getMetaInfo();
 			String fileName = metaInfoCtr.getFilename();
 			if(meta != null) {
-				meta.write();
+				vfsRepositoryService.updateMetadata(meta);
 				if (metaInfoCtr.isFileRenamed()) {
 					// IMPORTANT: First rename the meta data because underlying file
 					// has to exist in order to work properly on it's meta data.
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java
index 18cb04a7f35364f9b5ef5b89d153446e4f45f1b2..9b2ede24f6a0bc5108be9d0caa2a30766381e667 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeResource.java
@@ -31,9 +31,11 @@ import java.io.InputStream;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderLoggingAction;
 import org.olat.core.commons.modules.bc.FolderManager;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -53,7 +55,6 @@ import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.VFSMediaResource;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 public class CmdServeResource implements FolderCommand {
 	
@@ -89,9 +90,7 @@ public class CmdServeResource implements FolderCommand {
 			if(vfsItem == null) {
 				folderComponent.updateChildren();
 			} else if (vfsItem.canMeta() == VFSConstants.YES) {
-				MetaInfo meta = vfsItem.getMetaInfo();
-				meta.increaseDownloadCount();
-				meta.write();
+				CoreSpringFactory.getImpl(VFSRepositoryService.class).increaseDownloadCount(vfsItem);
 			}
 		}
 
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeThumbnailResource.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeThumbnailResource.java
index d3a7f53685e4d586bd0b6c56f9d547efaedee7ac..918e00017e3fd49db94a658c4e07bd26e7fa4edc 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeThumbnailResource.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdServeThumbnailResource.java
@@ -26,7 +26,9 @@
 
 package org.olat.core.commons.modules.bc.commands;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -39,12 +41,17 @@ import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.VFSMediaResource;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 public class CmdServeThumbnailResource implements FolderCommand {
 	
 	private int status = FolderCommandStatus.STATUS_SUCCESS;
 	
+	private final VFSRepositoryService vfsRepositoryservice;
+	
+	public CmdServeThumbnailResource() {
+		vfsRepositoryservice = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+	}
+	
 	@Override
 	public Controller execute(FolderComponent folderComponent, UserRequest ureq, WindowControl wControl, Translator translator) {
 		VFSSecurityCallback inheritedSecCallback = VFSManager.findInheritedSecurityCallback(folderComponent.getCurrentContainer());
@@ -63,12 +70,9 @@ public class CmdServeThumbnailResource implements FolderCommand {
 			}
 			
 			if(vfsLeaf != null && vfsLeaf.canMeta() == VFSConstants.YES) {
-				MetaInfo info = vfsLeaf.getMetaInfo();
-				if(info != null && info.isThumbnailAvailable()) {
-					VFSLeaf thumbnail = info.getThumbnail(200, 200, false);
-					if(thumbnail != null) {
-						mr = new VFSMediaResource(thumbnail);
-					}
+				VFSLeaf thumbnail = vfsRepositoryservice.getThumbnail(vfsLeaf, 200, 200, false);
+				if(thumbnail != null) {
+					mr = new VFSMediaResource(thumbnail);
 				}
 			}
 			if(mr == null) {
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUnzip.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUnzip.java
index 282ca08944bab47e5c7173ea002c94582474b21e..a1334855f360f927938f0b287e596663fe4de91e 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUnzip.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdUnzip.java
@@ -35,6 +35,8 @@ import org.olat.core.commons.modules.bc.FolderConfig;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
 import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.control.Controller;
@@ -55,7 +57,7 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
+import org.springframework.beans.factory.annotation.Autowired;
 
 public class CmdUnzip extends BasicController implements FolderCommand {
 
@@ -64,6 +66,9 @@ public class CmdUnzip extends BasicController implements FolderCommand {
 	private Translator translator;
 	private DialogBoxController lockedFiledCtr;
 	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
+	
 	public CmdUnzip(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
 	}
@@ -201,10 +206,10 @@ public class CmdUnzip extends BasicController implements FolderCommand {
 				return false;
 			}
 		} else if (zipContainer.canMeta() == VFSConstants.YES) {
-			MetaInfo info = zipContainer.getMetaInfo();
+			VFSMetadata info = zipContainer.getMetaInfo();
 			if(info != null && ureq.getIdentity() != null) {
 				info.setAuthor(ureq.getIdentity());
-				info.write();
+				vfsRepositoryService.updateMetadata(info);
 			}
 		}
 		
diff --git a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdZip.java b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdZip.java
index 1c85da693dd15dd7c62f14573a34c191710000f6..87a48c3b13543e95a373138b3757729743bab030 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/commands/CmdZip.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/commands/CmdZip.java
@@ -32,6 +32,8 @@ import java.util.List;
 import org.olat.core.commons.modules.bc.FileSelection;
 import org.olat.core.commons.modules.bc.FolderEvent;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.TextElement;
@@ -48,7 +50,7 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.meta.MetaInfo;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
@@ -67,6 +69,9 @@ public class CmdZip extends FormBasicController implements FolderCommand {
 	private FileSelection selection;
 	private TextElement textElement;
 	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
+	
 	protected CmdZip(UserRequest ureq, WindowControl wControl) {
 		super(ureq, wControl);
 	}
@@ -172,9 +177,9 @@ public class CmdZip extends FormBasicController implements FolderCommand {
 			fireEvent(ureq, FOLDERCOMMAND_FINISHED);
 		} else {
 			if(zipFile.canMeta() == VFSConstants.YES) {
-				MetaInfo info = zipFile.getMetaInfo();
+				VFSMetadata info = zipFile.getMetaInfo();
 				info.setAuthor(ureq.getIdentity());
-				info.write();
+				vfsRepositoryService.updateMetadata(info);
 			}
 			
 			fireEvent(ureq, new FolderEvent(FolderEvent.ZIP_EVENT, selection.renderAsHtml()));				
diff --git a/src/main/java/org/olat/core/commons/modules/bc/comparators/MetaTitleComparator.java b/src/main/java/org/olat/core/commons/modules/bc/comparators/MetaTitleComparator.java
index 9d82e0569ba28a0ec7e462a726fd5bafef5ef0f3..f6021e4d38dedf2f634a676ec71628f198e4d5f2 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/comparators/MetaTitleComparator.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/comparators/MetaTitleComparator.java
@@ -23,7 +23,7 @@ import java.text.Collator;
 import java.util.Comparator;
 import java.util.Locale;
 
-import org.olat.core.util.vfs.meta.MetaInfo;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 
 /**
  * Compares the title of two MetaInfo objects based on a given collator
@@ -33,7 +33,7 @@ import org.olat.core.util.vfs.meta.MetaInfo;
  * 
  * @author gwassmann
  */
-public class MetaTitleComparator implements Comparator<MetaInfo> {
+public class MetaTitleComparator implements Comparator<VFSMetadata> {
 	private final Collator collator;
 
 	public MetaTitleComparator(Collator collator) {
@@ -45,7 +45,7 @@ public class MetaTitleComparator implements Comparator<MetaInfo> {
 	}
 
 	@Override
-	public int compare(MetaInfo m1, MetaInfo m2) {
+	public int compare(VFSMetadata m1, VFSMetadata m2) {
 		if(m1 == null) return -1;
 		if(m2 == null) return 1;
 		
diff --git a/src/main/java/org/olat/core/commons/modules/bc/comparators/TitleComparator.java b/src/main/java/org/olat/core/commons/modules/bc/comparators/TitleComparator.java
index aff2f27d4fd7b1e759e667d543522ff430b8e9a3..f4958ca23937404528827d498caef45f37cbc593 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/comparators/TitleComparator.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/comparators/TitleComparator.java
@@ -23,9 +23,9 @@ import java.text.Collator;
 import java.util.Comparator;
 import java.util.Locale;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * Compare the title, the filename and the last modification date
@@ -63,7 +63,7 @@ public class TitleComparator implements Comparator<VFSItem> {
 		if(item == null) return null;
 		
 		String name = null;
-		MetaInfo m = item.getMetaInfo();
+		VFSMetadata m = item.getMetaInfo();
 		if(m != null) {
 			name = m.getTitle();
 		}
diff --git a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java
index 96690738428976cc75fada9f6af26ff82f225b86..126ec4d53c1551a5b67d5b8bb818a7f40eaf554e 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/components/ListRenderer.java
@@ -27,8 +27,11 @@
 package org.olat.core.commons.modules.bc.components;
 
 import java.text.DateFormat;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FileSelection;
@@ -39,6 +42,8 @@ import org.olat.core.commons.services.license.License;
 import org.olat.core.commons.services.license.LicenseHandler;
 import org.olat.core.commons.services.license.LicenseModule;
 import org.olat.core.commons.services.license.ui.LicenseRenderer;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.components.form.flexible.impl.NameValuePair;
 import org.olat.core.gui.control.winmgr.AJAXFlags;
 import org.olat.core.gui.render.StringOutput;
@@ -59,8 +64,6 @@ import org.olat.core.util.vfs.VFSLockManager;
 import org.olat.core.util.vfs.VirtualContainer;
 import org.olat.core.util.vfs.filters.SystemItemFilter;
 import org.olat.core.util.vfs.lock.LockInfo;
-import org.olat.core.util.vfs.meta.MetaInfo;
-import org.olat.core.util.vfs.meta.MetaInfoFactory;
 import org.olat.core.util.vfs.version.Versionable;
 import org.olat.core.util.vfs.version.Versions;
 import org.olat.user.UserManager;
@@ -89,6 +92,7 @@ public class ListRenderer {
 	/** View thumbnail */
 	public static final String PARAM_SERV_THUMBNAIL = "servthumb";
 
+	private VFSRepositoryService vfsRepositoryService;
 	private VFSLockManager lockManager;
 	private UserManager userManager;
 	boolean licensesEnabled ;
@@ -117,6 +121,10 @@ public class ListRenderer {
 		if(userManager == null) {
 			userManager = CoreSpringFactory.getImpl(UserManager.class);
 		}
+		if(vfsRepositoryService == null) {
+			vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+		}
+		
 		LicenseModule licenseModule = CoreSpringFactory.getImpl(LicenseModule.class);
 		LicenseHandler licenseHandler = CoreSpringFactory.getImpl(FolderLicenseHandler.class);
 		licensesEnabled = licenseModule.isEnabled(licenseHandler);
@@ -130,7 +138,8 @@ public class ListRenderer {
 			return;
 		}
 
-		boolean canVersion = FolderConfig.versionsEnabled(fc.getCurrentContainer());
+		VFSContainer currentContainer = fc.getCurrentContainer();
+		boolean canVersion = FolderConfig.versionsEnabled(currentContainer);
 		String sortOrder = fc.getCurrentSortOrder();
 		boolean sortAsc = fc.isCurrentSortAsc();
 		String sortCss = (sortAsc ? "o_orderby_asc" : "o_orderby_desc");
@@ -171,9 +180,17 @@ public class ListRenderer {
 
 		sb.append("<tbody>");
 		
+		String relPath = currentContainer.getRelPath();
+		Map<String,VFSMetadata> metadatas = Collections.emptyMap();
+		if(relPath != null) {
+			List<VFSMetadata> m = vfsRepositoryService.getChildren(relPath);
+			metadatas = m.stream().collect(Collectors.toMap(VFSMetadata::getFilename, v -> v, (u, v) -> u));
+		}
+		
 		for (int i = 0; i < children.size(); i++) {
 			VFSItem child = children.get(i);
-			appendRenderedFile(fc, child, currentContainerPath, sb, ubu, translator, iframePostEnabled, canVersion, i);
+			VFSMetadata metadata = metadatas.get(child.getName());
+			appendRenderedFile(fc, child, metadata, currentContainerPath, sb, ubu, translator, iframePostEnabled, canVersion, i);
 		}		
 		sb.append("</tbody></table>");
 	} // getRenderedDirectoryContent
@@ -184,7 +201,7 @@ public class ListRenderer {
 	 * @param	f			The file or folder to render
 	 * @param	sb		StringOutput to append generated html code
 	 */
-	private void appendRenderedFile(FolderComponent fc, VFSItem child, String currentContainerPath, StringOutput sb, URLBuilder ubu, Translator translator,
+	private void appendRenderedFile(FolderComponent fc, VFSItem child, VFSMetadata metadata, String currentContainerPath, StringOutput sb, URLBuilder ubu, Translator translator,
 			boolean iframePostEnabled, boolean canContainerVersion, int pos) {
 	
 		// assume full access unless security callback tells us something different.
@@ -208,13 +225,7 @@ public class ListRenderer {
 			leaf = (VFSLeaf)child;
 		}
 		boolean isContainer = (leaf == null); // if not a leaf, it must be a container...
-		
-		MetaInfo metaInfo = null;
-		if(child.canMeta() == VFSConstants.YES) {
-			metaInfo = child.getMetaInfo();
-		}
-		
-		boolean lockedForUser = lockManager.isLockedForMe(child, metaInfo, fc.getIdentityEnvironnement().getIdentity(), fc.getIdentityEnvironnement().getRoles());
+		boolean lockedForUser = lockManager.isLockedForMe(child, metadata, fc.getIdentityEnvironnement().getIdentity(), fc.getIdentityEnvironnement().getRoles());
 		
 		String name = child.getName();
 		boolean xssErrors = StringHelper.xssScanForErrors(name);
@@ -289,23 +300,23 @@ public class ListRenderer {
 		}
 
 		//file metadata as tooltip
-		if (metaInfo != null) {
+		if (metadata != null) {
 			boolean hasMeta = false;
 			sb.append("<div id='o_sel_doc_tooltip_").append(pos).append("' class='o_bc_meta' style='display:none;'>");
-			if (StringHelper.containsNonWhitespace(metaInfo.getTitle())) {
-				String title = StringHelper.escapeHtml(metaInfo.getTitle());
+			if (StringHelper.containsNonWhitespace(metadata.getTitle())) {
+				String title = StringHelper.escapeHtml(metadata.getTitle());
 				sb.append("<h5>").append(Formatter.escapeDoubleQuotes(title)).append("</h5>");
 				hasMeta = true;
 			}
-			if (StringHelper.containsNonWhitespace(metaInfo.getComment())) {
+			if (StringHelper.containsNonWhitespace(metadata.getComment())) {
 				sb.append("<div class=\"o_comment\">");
-				String comment = StringHelper.escapeHtml(metaInfo.getComment());
+				String comment = StringHelper.escapeHtml(metadata.getComment());
 				sb.append(Formatter.escapeDoubleQuotes(comment));			
 				sb.append("</div>");
 				hasMeta = true;
 			}
 			
-			if(metaInfo.isThumbnailAvailable() && !xssErrors) {
+			if(!isContainer && !xssErrors && vfsRepositoryService.isThumbnailAvailable(leaf, metadata) ) {
 				sb.append("<div class='o_thumbnail' style='background-image:url("); 
 				ubu.buildURI(sb, new String[] { PARAM_SERV_THUMBNAIL}, new String[] { "x" }, pathAndName, AJAXFlags.MODE_NORMAL);
 				sb.append("); background-repeat:no-repeat; background-position:50% 50%;'></div>");
@@ -313,14 +324,13 @@ public class ListRenderer {
 			}
 
 			// first try author info from metadata (creator)
-			String author = metaInfo.getCreator();
+			String author = metadata.getCreator();
 			// fallback use file author (uploader)
 			if(StringHelper.containsNonWhitespace(author)) {
 				//
 			} else {
-				author = metaInfo.getAuthor();
-				if(!"-".equals(author)) {
-					author = UserManager.getInstance().getUserDisplayName(author);
+				if(metadata.getAuthor() != null) {
+					author = userManager.getUserDisplayName(metadata.getAuthor());
 				} else {
 					author = null;
 				}		
@@ -379,25 +389,23 @@ public class ListRenderer {
 		
 		// license
 		if (licensesEnabled) {
-			MetaInfoFactory metaInfoFactory = CoreSpringFactory.getImpl(MetaInfoFactory.class);
-			License license = metaInfoFactory.getLicense(metaInfo);
+			License license = vfsRepositoryService.getLicense(metadata);
 			LicenseRenderer licenseRenderer = new LicenseRenderer(translator.getLocale());
 			licenseRenderer.render(sb, license, true);
 			sb.append("</td><td>");
 		}
 
 		if(canContainerVersion) {
-			if (canVersion)
-				if (versions != null) {
-					sb.append("<span class='text-muted small'>");
-					sb.append(versions.getRevisionNr());
-					sb.append("</span>");					
-				}
+			if (canVersion && versions != null) {
+				sb.append("<span class='text-muted small'>")
+				  .append(versions.getRevisionNr())
+				  .append("</span>");					
+			}
 			sb.append("</td><td>");
 		}
 		
 		//locked
-		boolean locked = lockManager.isLocked(child, metaInfo);
+		boolean locked = lockManager.isLocked(child, metadata);
 		if(locked) {
 			LockInfo lock = lockManager.getLock(child);
 			sb.append("<i class=\"o_icon o_icon_locked\" title=\"");
diff --git a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoController.java b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoController.java
index 5ae4df91cef48fd90bc2db3dc17a250ea8828033..2d4ac8145e42d6c68b72a708f70c446cbe2b90fb 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoController.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoController.java
@@ -23,12 +23,13 @@ import java.text.DateFormat;
 import java.util.HashSet;
 import java.util.Set;
 
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderLicenseHandler;
 import org.olat.core.commons.services.license.License;
 import org.olat.core.commons.services.license.LicenseModule;
 import org.olat.core.commons.services.license.LicenseService;
 import org.olat.core.commons.services.license.ui.LicenseUIFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -45,12 +46,11 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.folder.FolderHelper;
 import org.olat.core.util.Formatter;
 import org.olat.core.util.StringHelper;
+import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSLockManager;
 import org.olat.core.util.vfs.lock.LockInfo;
-import org.olat.core.util.vfs.meta.MetaInfo;
-import org.olat.core.util.vfs.meta.MetaInfoFactory;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -81,6 +81,8 @@ public class MetaInfoController extends FormBasicController {
 	private LicenseService licenseService;
 	@Autowired
 	private FolderLicenseHandler licenseHandler;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	/**
 	 * Use this controller for editing meta data of an existing file.
@@ -129,7 +131,7 @@ public class MetaInfoController extends FormBasicController {
 		// filename
 		uifactory.addStaticTextElement("mf.filename", item.getName(), formLayout);
 
-		MetaInfo meta = item == null ? null : item.getMetaInfo();
+		VFSMetadata meta = item == null ? null : item.getMetaInfo();
 
 		// title
 		String titleVal = StringHelper.escapeHtml(meta != null ? meta.getTitle() : null);
@@ -141,8 +143,7 @@ public class MetaInfoController extends FormBasicController {
 		
 		// license
 		if (licenseModule.isEnabled(licenseHandler)) {
-			MetaInfoFactory metaInfoFactory = CoreSpringFactory.getImpl(MetaInfoFactory.class);
-			License license = metaInfoFactory.getOrCreateLicense(meta, getIdentity());
+			License license = vfsRepositoryService.getOrCreateLicense(meta, getIdentity());
 			boolean isNoLicense = !licenseService.isNoLicense(license.getLicenseType());
 			boolean isFreetext = licenseService.isFreetext(license.getLicenseType());
 
@@ -226,7 +227,7 @@ public class MetaInfoController extends FormBasicController {
 			setMetaFieldsVisible(false);
 		}
 
-		if(meta != null && !meta.isDirectory()) {
+		if(meta != null && !(item instanceof VFSContainer)) {
 			LockInfo lock = vfsLockManager.getLock(item);
 			//locked
 			String lockedTitle = getTranslator().translate("mf.locked");
@@ -258,14 +259,14 @@ public class MetaInfoController extends FormBasicController {
 			uifactory.addStaticTextElement("mf.lockedBy", lockedDetails, formLayout);
 			
 			// username
-			String author = StringHelper.escapeHtml(userManager.getUserDisplayName(meta.getAuthorIdentityKey()));
+			String author = StringHelper.escapeHtml(userManager.getUserDisplayName(meta.getAuthor()));
 			uifactory.addStaticTextElement("mf.author", author, formLayout);
 
 			// filesize
 			uifactory.addStaticTextElement("mf.size", StringHelper.escapeHtml(sizeText), formLayout);
 
 			// last modified date
-			String lastModified = StringHelper.formatLocaleDate(meta.getLastModified(), getLocale());
+			String lastModified = Formatter.getInstance(getLocale()).formatDate(meta.getFileLastModified());
 			uifactory.addStaticTextElement("mf.lastModified", lastModified, formLayout);
 
 			// file type
@@ -303,13 +304,13 @@ public class MetaInfoController extends FormBasicController {
 	/**
 	 * @return True if one or more metadata fields are non-emtpy.
 	 */
-	private boolean hasMetadata(MetaInfo meta) {
+	private boolean hasMetadata(VFSMetadata meta) {
 		if (meta != null) { return StringHelper.containsNonWhitespace(meta.getCreator())
 				|| StringHelper.containsNonWhitespace(meta.getPublisher()) || StringHelper.containsNonWhitespace(meta.getSource())
 				|| StringHelper.containsNonWhitespace(meta.getCity()) || StringHelper.containsNonWhitespace(meta.getPublicationDate()[0])
 				|| StringHelper.containsNonWhitespace(meta.getPublicationDate()[1]) || StringHelper.containsNonWhitespace(meta.getPages())
 				|| StringHelper.containsNonWhitespace(meta.getLanguage()) || StringHelper.containsNonWhitespace(meta.getUrl())
-				|| StringHelper.containsNonWhitespace(meta.getLicenseTypeKey()) || StringHelper.containsNonWhitespace(meta.getLicenseTypeName())
+				|| meta.getLicenseType() != null || StringHelper.containsNonWhitespace(meta.getLicenseTypeName())
 				|| StringHelper.containsNonWhitespace(meta.getLicenseText()) || StringHelper.containsNonWhitespace(meta.getLicensor());
 				}
 		return false;
diff --git a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java
index 55c374a537e9bfb2cb7ec92bb000979b5a8cbb72..ac12db606603bf3c1501cc738f9d86cdc1adc91a 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/meta/MetaInfoFormController.java
@@ -35,6 +35,8 @@ import org.olat.core.commons.services.license.LicenseService;
 import org.olat.core.commons.services.license.LicenseType;
 import org.olat.core.commons.services.license.ui.LicenseSelectionConfig;
 import org.olat.core.commons.services.license.ui.LicenseUIFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -61,8 +63,6 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSLockManager;
 import org.olat.core.util.vfs.lock.LockInfo;
-import org.olat.core.util.vfs.meta.MetaInfo;
-import org.olat.core.util.vfs.meta.MetaInfoFactory;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -76,7 +76,6 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class MetaInfoFormController extends FormBasicController {
 	private VFSItem item;
-	//private MetaInfo meta;
 	private FormLink moreMetaDataLink;
 	private String initialFilename;
 	private TextElement filename, title, publisher, creator, sourceEl, city, pages, language, url, comment, publicationMonth, publicationYear;
@@ -96,7 +95,7 @@ public class MetaInfoFormController extends FormBasicController {
 	@Autowired
 	private VFSLockManager vfsLockManager;
 	@Autowired
-	private MetaInfoFactory metaInfoFactory;
+	private VFSRepositoryService vfsRepositoryService;
 	@Autowired
 	private LicenseModule licenseModule;
 	@Autowired
@@ -187,7 +186,7 @@ public class MetaInfoFormController extends FormBasicController {
 		}
 		setFormContextHelp("Folders#_metadata");
 		
-		MetaInfo meta = item == null ? null : item.getMetaInfo();
+		VFSMetadata meta = item == null ? null : item.getMetaInfo();
 
 		// title
 		String titleVal = (meta != null ? meta.getTitle() : null);
@@ -206,7 +205,7 @@ public class MetaInfoFormController extends FormBasicController {
 		
 		// license
 		if (licenseModule.isEnabled(licenseHandler)) {
-			License license = metaInfoFactory.getOrCreateLicense(meta, getIdentity());
+			License license = vfsRepositoryService.getOrCreateLicense(meta, getIdentity());
 
 			LicenseSelectionConfig licenseSelectionConfig = LicenseUIFactory
 					.createLicenseSelectionConfig(licenseHandler, license.getLicenseType());
@@ -272,7 +271,8 @@ public class MetaInfoFormController extends FormBasicController {
 		url = uifactory.addTextElement("url", "mf.url", -1, urlVal, formLayout);	
 
 		/* static fields */
-		String sizeText, typeText;
+		String sizeText;
+		String typeText;
 		if (item instanceof VFSLeaf) {
 			sizeText = Formatter.formatBytes(((VFSLeaf) item).getSize());
 			typeText = FolderHelper.extractFileType(item.getName(), getLocale());
@@ -299,7 +299,7 @@ public class MetaInfoFormController extends FormBasicController {
 
 		if (!isSubform) {
 
-			if(meta != null && !meta.isDirectory()) {
+			if(meta != null && !(item instanceof VFSContainer)) {
 				LockInfo lock = vfsLockManager.getLock(item);
 				//locked
 				String lockedTitle = getTranslator().translate("mf.locked");
@@ -331,14 +331,14 @@ public class MetaInfoFormController extends FormBasicController {
 			}
 			
 			// username
-			String author = userManager.getUserDisplayName(meta == null ? null : meta.getAuthorIdentityKey());
+			String author = userManager.getUserDisplayName(meta == null ? null : meta.getAuthor());
 			uifactory.addStaticTextElement("mf.author", StringHelper.escapeHtml(author), formLayout);
 
 			// filesize
 			uifactory.addStaticTextElement("mf.size", StringHelper.escapeHtml(sizeText), formLayout);
 
 			// last modified date
-			String lastModified = meta == null ? "" : StringHelper.formatLocaleDate(meta.getLastModified(), getLocale());
+			String lastModified = meta == null ? "" : Formatter.getInstance(getLocale()).formatDate(meta.getFileLastModified());
 			uifactory.addStaticTextElement("mf.lastModified", lastModified, formLayout);
 
 			// file type
@@ -362,7 +362,7 @@ public class MetaInfoFormController extends FormBasicController {
 			formLayout.add(extUrlCont);
 		}
 
-		if (!isSubform && meta != null && meta.isDirectory()) {
+		if (!isSubform && meta != null && item instanceof VFSContainer) {
 			// Don't show any meta data except title and comment if the item is
 			// a directory.
 			// Hide the metadata.
@@ -384,13 +384,13 @@ public class MetaInfoFormController extends FormBasicController {
 	/**
 	 * @return True if one or more metadata fields are non-emtpy.
 	 */
-	private boolean hasMetadata(MetaInfo meta) {
+	private boolean hasMetadata(VFSMetadata meta) {
 		if (meta != null) { return StringHelper.containsNonWhitespace(meta.getCreator())
 				|| StringHelper.containsNonWhitespace(meta.getPublisher()) || StringHelper.containsNonWhitespace(meta.getSource())
 				|| StringHelper.containsNonWhitespace(meta.getCity()) || StringHelper.containsNonWhitespace(meta.getPublicationDate()[0])
 				|| StringHelper.containsNonWhitespace(meta.getPublicationDate()[1]) || StringHelper.containsNonWhitespace(meta.getPages())
 				|| StringHelper.containsNonWhitespace(meta.getLanguage()) || StringHelper.containsNonWhitespace(meta.getUrl())
-				|| StringHelper.containsNonWhitespace(meta.getLicenseTypeKey()) || StringHelper.containsNonWhitespace(meta.getLicenseTypeName())
+				|| meta.getLicenseType() != null || StringHelper.containsNonWhitespace(meta.getLicenseTypeName())
 				|| StringHelper.containsNonWhitespace(meta.getLicenseText()) || StringHelper.containsNonWhitespace(meta.getLicensor());
 				}
 		return false;
@@ -432,7 +432,7 @@ public class MetaInfoFormController extends FormBasicController {
 		filename.setValue(name);
 	}
 	
-	public MetaInfo getMetaInfo(MetaInfo meta) {
+	public VFSMetadata getMetaInfo(VFSMetadata meta) {
 		meta.setCreator(creator.getValue());
 		meta.setComment(comment.getValue());
 		meta.setTitle(title.getValue());
@@ -444,7 +444,7 @@ public class MetaInfoFormController extends FormBasicController {
 		meta.setUrl(url.getValue());
 		meta.setPages(pages.getValue());
 		License license = getLicenseFromFormItems();
-		meta.setLicenseTypeKey(license.getLicenseType() != null? String.valueOf(license.getLicenseType().getKey()): "");
+		meta.setLicenseType(license.getLicenseType() == null ? null : license.getLicenseType());
 		meta.setLicenseTypeName(license.getLicenseType() != null? license.getLicenseType().getName(): "");
 		meta.setLicensor(license.getLicensor() != null? license.getLicensor(): "");
 		meta.setLicenseText(LicenseUIFactory.getLicenseText(license));
@@ -478,7 +478,7 @@ public class MetaInfoFormController extends FormBasicController {
 	/**
 	 * @return The updated MeatInfo object
 	 */
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		if (!isSubform && (item instanceof VFSLeaf) && (locked != null && locked.isEnabled())) {
 			//isSubForm
 			boolean alreadyLocked = vfsLockManager.isLocked(item);
@@ -492,7 +492,7 @@ public class MetaInfoFormController extends FormBasicController {
 			}
 		}
 		
-		MetaInfo meta = item == null ? null : item.getMetaInfo();
+		VFSMetadata meta = item == null ? null : item.getMetaInfo();
 		if(meta == null) {
 			return null;
 		}
@@ -501,7 +501,7 @@ public class MetaInfoFormController extends FormBasicController {
 
 	@Override
 	protected boolean validateFormLogic(UserRequest ureq) {
-		boolean valid = true;
+		boolean valid = super.validateFormLogic(ureq);
 
 		// validate publication month
 		String monthStr = publicationMonth.getValue();
diff --git a/src/main/java/org/olat/core/commons/modules/bc/notifications/FolderNotificationsHandler.java b/src/main/java/org/olat/core/commons/modules/bc/notifications/FolderNotificationsHandler.java
index 7faf68314093ee8db142d6a6d3d41ad2d97a97f9..25d3ec27525bdbe73937c247f18df9ffbd3a5365 100644
--- a/src/main/java/org/olat/core/commons/modules/bc/notifications/FolderNotificationsHandler.java
+++ b/src/main/java/org/olat/core/commons/modules/bc/notifications/FolderNotificationsHandler.java
@@ -43,6 +43,7 @@ import org.olat.core.commons.services.notifications.SubscriptionInfo;
 import org.olat.core.commons.services.notifications.manager.NotificationsUpgradeHelper;
 import org.olat.core.commons.services.notifications.model.SubscriptionListItem;
 import org.olat.core.commons.services.notifications.model.TitleItem;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.gui.util.CSSHelper;
 import org.olat.core.id.context.BusinessControlFactory;
@@ -51,7 +52,6 @@ import org.olat.core.logging.Tracing;
 import org.olat.core.util.FileUtils;
 import org.olat.core.util.Util;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupService;
 import org.olat.repository.RepositoryEntry;
@@ -115,7 +115,7 @@ public class FolderNotificationsHandler implements NotificationsHandler {
 						// skip this file, continue with next item in folder
 						continue;
 					}						
-					MetaInfo metaInfo = fi.getMetaInfo();
+					VFSMetadata metaInfo = fi.getMetaInfo();
 					String iconCssClass =  null;
 					if (metaInfo != null) {
 						if (metaInfo.getTitle() != null) {
diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSMetadata.java b/src/main/java/org/olat/core/commons/services/vfs/VFSMetadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..d778f06ba3b71b003c0ec9fe01b999bfbf064263
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/VFSMetadata.java
@@ -0,0 +1,141 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs;
+
+import java.util.Date;
+
+import org.olat.core.commons.services.license.LicenseType;
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.Identity;
+import org.olat.core.id.ModifiedInfo;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface VFSMetadata extends VFSMetadataRef, ModifiedInfo, CreateInfo {
+	
+	public String getUuid();
+	
+	public void setUuid(String id);
+	
+	public String getRelativePath();
+	
+	public String getFilename();
+	
+	public Date getFileLastModified();
+	
+	public long getFileSize();
+	
+	public boolean isDirectory();
+	
+	public String getIconCssClass();
+	
+	public String getUri();
+	
+	public String getProtocol();
+	
+	public Identity getAuthor();
+	
+	public void setAuthor(Identity author);
+	
+	public String getTitle();
+	
+	public void setTitle(String title);
+	
+	public String getComment();
+	
+	public void setComment(String text);
+	
+	public int getDownloadCount();
+	
+	public String getCreator();
+	
+	public void setCreator(String creator);
+	
+	public String getPublisher();
+	
+	public void setPublisher(String publisher);
+	
+	public String getCity();
+	
+	public void setCity(String city);
+	
+	public String[] getPublicationDate();
+	
+	public void setPublicationDate(String month, String year);
+	
+	
+	public String getUrl();
+
+	public void setUrl(String url);
+
+	public String getSource();
+
+	public void setSource(String source);
+
+	public String getLanguage();
+
+	public void setLanguage(String language);
+	
+	public String getPages();
+	
+	public void setPages(String pages);
+	
+	
+	public Boolean getCannotGenerateThumbnails();
+	
+	public void setCannotGenerateThumbnails(Boolean val);
+	
+	
+	public LicenseType getLicenseType();
+	
+	public void setLicenseType(LicenseType type);
+	
+	public String getLicenseTypeName();
+	
+	public void setLicenseTypeName(String name);
+	
+	public String getLicenseText();
+	
+	public void setLicenseText(String text);
+	
+	public String getLicensor();
+	
+	public void setLicensor(String licensor);
+	
+	
+	public boolean isLocked();
+	
+	public void setLocked(boolean locked);
+	
+	public Identity getLockedBy();
+	
+	public void setLockedBy(Identity lockedBy);
+	
+	public Date getLockedDate();
+	
+	public void setLockedDate(Date date);
+	
+	public void copyValues(VFSMetadata metadata);
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSMetadataRef.java b/src/main/java/org/olat/core/commons/services/vfs/VFSMetadataRef.java
new file mode 100644
index 0000000000000000000000000000000000000000..d231dc6c14d95cc2f52e967654ec4651fa0b8a8d
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/VFSMetadataRef.java
@@ -0,0 +1,37 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs;
+
+import org.olat.core.commons.services.vfs.model.VFSMetadataRefImpl;
+
+/**
+ * 
+ * Initial date: 13 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface VFSMetadataRef {
+	
+	public Long getKey();
+	
+	public static VFSMetadataRef valueOf(final Long key) {
+		return new VFSMetadataRefImpl(key);
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryModule.java b/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..a36b0d7baebf7f753e123fdc5b9fd4c509549b22
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryModule.java
@@ -0,0 +1,30 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class VFSRepositoryModule {
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryService.java b/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryService.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a2200e0d7482a5214b0cb348b7e596f53b1f26b
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/VFSRepositoryService.java
@@ -0,0 +1,131 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs;
+
+import java.io.File;
+import java.util.List;
+
+import org.olat.core.commons.services.license.License;
+import org.olat.core.id.Identity;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.core.util.vfs.VFSItem;
+import org.olat.core.util.vfs.VFSLeaf;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface VFSRepositoryService {
+	
+	public VFSMetadata getMetadataFor(VFSItem path);
+	
+	public VFSMetadata getMetadataFor(File file);
+	
+	/**
+	 * The list of direct children in the specified
+	 * relative path.
+	 * 
+	 * @param relativePath The relative path
+	 * @return A list of metadata
+	 */
+	public List<VFSMetadata> getChildren(String relativePath);
+	
+	/**
+	 * The list of direct children in the directory
+	 * specified by the metadata.
+	 * 
+	 * @param metadata The metadata of the directory
+	 * @return A list of metadata
+	 */
+	public List<VFSMetadata> getChildren(VFSMetadataRef parentMetadata);
+	
+	public VFSMetadata updateMetadata(VFSMetadata data);
+	
+	public void deleteMetadata(VFSMetadata data);
+	
+	public void deleteMetadata(File file);
+
+	/**
+	 * Copy the metadata from one file to the other.
+	 * 
+	 * @param source The source file
+	 * @param target The target file
+	 * @param parentTarget The parent container of the target (useful if the target is not fully initialized)
+	 */
+	public void copyTo(VFSLeaf source, VFSLeaf target, VFSContainer parentTarget);
+	
+	public VFSMetadata rename(VFSMetadata data, String newName);
+	
+	public void increaseDownloadCount(VFSItem item);
+	
+	/**
+	 * Copy the binaries data saved in ZIP files.
+	 * 
+	 * @param metadata The metadata
+	 * @param binaries The binaries
+	 */
+	public void copyBinaries(VFSMetadata metadata, byte[] binaries);
+	
+	
+	public boolean isThumbnailAvailable(VFSItem item);
+	
+	/**
+	 * Use this if you have already collected the metadata has it is
+	 * database query free.
+	 * 
+	 * @param item The file (mandatory)
+	 * @param metadata The metadata object (mandatory)
+	 * @return true if the thumbnail can eventually be generated or are available
+	 */
+	public boolean isThumbnailAvailable(VFSItem item, VFSMetadata metadata);
+	
+	public VFSLeaf getThumbnail(VFSLeaf file, int maxWidth, int maxHeight, boolean fill);
+	
+	/**
+	 * This method prevent reloading the metadata
+	 * 
+	 * @param file The file
+	 * @param metadata The metadata of the file if you already have it
+	 * @param maxWidth
+	 * @param maxHeight
+	 * @param fill
+	 * @return
+	 */
+	public VFSLeaf getThumbnail(VFSLeaf file, VFSMetadata metadata, int maxWidth, int maxHeight, boolean fill);
+	
+	public void resetThumbnails(VFSLeaf file);
+	
+	/**
+	 * If the specified file is a directory, it will recursively
+	 * reset the thumbnails of all files.
+	 * 
+	 * @param file A file in /bcroot/
+	 */
+	public void resetThumbnails(File file);
+	
+	
+	
+	public License getLicense(VFSMetadata meta);
+	
+	public License getOrCreateLicense(VFSMetadata meta, Identity itentity);
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/VFSThumbnailMetadata.java b/src/main/java/org/olat/core/commons/services/vfs/VFSThumbnailMetadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..1db21aaaa9c7241005058cf91d73e2f60e769ad8
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/VFSThumbnailMetadata.java
@@ -0,0 +1,51 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs;
+
+import org.olat.core.id.CreateInfo;
+import org.olat.core.id.ModifiedInfo;
+
+/**
+ * 
+ * Initial date: 14 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public interface VFSThumbnailMetadata extends ModifiedInfo, CreateInfo {
+	
+	public Long getKey();
+	
+	public long getFileSize();
+	
+	public boolean isFill();
+	
+	public int getFinalHeight();
+	
+	public int getFinalWidth();
+	
+	public int getMaxHeight();
+	
+	public int getMaxWidth();
+	
+	public String getFilename();
+
+	public VFSMetadata getOwner();
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/MetaInfoReader.java b/src/main/java/org/olat/core/commons/services/vfs/manager/MetaInfoReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..eef6992e7271649c59fa290da73ed82d7dc76c0a
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/MetaInfoReader.java
@@ -0,0 +1,389 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.commons.services.license.LicenseService;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.model.VFSMetadataImpl;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.FileUtils;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.filter.FilterFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * 
+ * Initial date: 12 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class MetaInfoReader {
+
+	private static final OLog log = Tracing.createLoggerFor(MetaInfoReader.class);
+  
+	private static SAXParser saxParser;
+	static {
+		try {
+			saxParser = SAXParserFactory.newInstance().newSAXParser();
+		} catch(Exception ex) {
+			log.error("", ex);
+		}
+	}
+	
+	private final VFSMetadataImpl meta;
+	private final List<Thumbnail> thumbnails = new ArrayList<>();
+	
+	private LicenseService licenseService;
+	private final BaseSecurity securityManager;
+	
+	public MetaInfoReader(VFSMetadataImpl meta, LicenseService licenseService, BaseSecurity securityManager) {
+		this.meta = meta;
+		this.licenseService = licenseService;
+		this.securityManager = securityManager;
+	}
+	
+	public VFSMetadataImpl getMetadata() {
+		return meta;
+	}
+	
+	public List<Thumbnail> getThumbnails() {
+		return thumbnails;
+	}
+	
+	public void fromBinaries(byte[] binaries) {
+		try(InputStream in = new ByteArrayInputStream(binaries)) {
+			synchronized(saxParser) {
+				saxParser.parse(in, new MetaHandler(null));
+			}
+		} catch(Exception ex) {
+			log.error("Error while parsing binaries", ex);
+		}
+	}
+
+	public boolean parseSAX(File fMeta) {
+		if (fMeta == null || !fMeta.exists() || fMeta.isDirectory()) return false;
+
+		try(InputStream in = new FileInputStream(fMeta);
+				BufferedInputStream bis = new BufferedInputStream(in, FileUtils.BSIZE)) {
+			synchronized(saxParser) {
+				saxParser.parse(bis, new MetaHandler(fMeta));
+			}
+		} catch (SAXParseException ex) {
+			if(!parseSAXFiltered(fMeta)) {
+				log.warn("SAX Parser error while parsing " + fMeta, ex);
+			}
+		} catch(Exception ex) {
+			log.error("Error while parsing " + fMeta, ex);
+		}
+		return true;
+	}
+	
+	/**
+	 * Try to rescue xml files with invalid characters
+	 * @param fMeta
+	 * @return true if rescue is successful
+	 */
+	private boolean parseSAXFiltered(File fMeta) {
+		String original = FileUtils.load(fMeta, "UTF-8");
+		if(original == null) return false;
+		
+		String filtered = FilterFactory.getXMLValidCharacterFilter().filter(original);
+		if(!original.equals(filtered)) {
+			try {
+				synchronized(saxParser) {
+					InputSource in = new InputSource(new StringReader(filtered));
+					saxParser.parse(in, new MetaHandler(fMeta));
+				}
+				return true;
+			} catch (Exception e) {
+				//only a fallback, fail silently
+			}
+		}
+		return false;
+	}
+	
+	/**
+	 * Writes the meta data to file. If no changes have been made,
+	 * does not write anything.
+	 * @return True upon success.
+	 */
+	public static final byte[] toBinaries(VFSMetadata metadata) {
+		if (metadata == null) return new byte[0];
+		
+		try(ByteArrayOutputStream out = new ByteArrayOutputStream();
+				OutputStreamWriter sw = new OutputStreamWriter(out, Charset.forName("UTF-8"))) {
+			
+			sw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+			sw.write("<meta");
+			if(StringHelper.containsNonWhitespace(metadata.getUuid())) {
+				sw.write(" uuid=\"" + metadata.getUuid() + "\"");
+			}
+			sw.write(">");		
+			
+			Long authorIdentKey = metadata.getAuthor() == null ? null : metadata.getAuthor().getKey();
+			Long lockedByIdentKey = metadata.getLockedBy() == null ? null : metadata.getLockedBy().getKey();
+			Date lockedDate = metadata.getLockedDate();
+			Long licenseTypeKey = metadata.getLicenseType() == null ? null : metadata.getLicenseType().getKey();
+			String[] publicationDates = metadata.getPublicationDate();
+			String pubYear = publicationDates[0];
+			String pubMonth = publicationDates[1];
+			
+			sw.write("<author><![CDATA[" + (authorIdentKey == null ? "" : authorIdentKey.toString()) + "]]></author>");		
+			sw.write("<lock locked=\"" + metadata.isLocked() + "\"" + (lockedDate == null ? "" : " date=\"" + lockedDate.getTime() + "\"")	+ "><![CDATA[" + (lockedByIdentKey == null ? "" : lockedByIdentKey) + "]]></lock>");
+			sw.write("<comment><![CDATA[" + filterForCData(metadata.getComment()) + "]]></comment>");
+			sw.write("<title><![CDATA[" + filterForCData(metadata.getTitle()) + "]]></title>");
+			sw.write("<publisher><![CDATA[" + filterForCData(metadata.getPublisher()) + "]]></publisher>");
+			sw.write("<creator><![CDATA[" + filterForCData(metadata.getCreator()) + "]]></creator>");
+			sw.write("<source><![CDATA[" + filterForCData(metadata.getSource()) + "]]></source>");
+			sw.write("<city><![CDATA[" + filterForCData(metadata.getCity()) + "]]></city>");
+			sw.write("<pages><![CDATA[" + filterForCData(metadata.getPages()) + "]]></pages>");
+			sw.write("<language><![CDATA[" + filterForCData(metadata.getLanguage()) + "]]></language>");
+			sw.write("<url><![CDATA[" + filterForCData(metadata.getUrl()) + "]]></url>");
+			sw.write("<licenseTypeKey><![CDATA[" + licenseTypeKey + "]]></licenseTypeKey>");
+			sw.write("<licenseTypeName><![CDATA[" + filterForCData(metadata.getLicenseTypeName()) + "]]></licenseTypeName>");
+			sw.write("<licenseText><![CDATA[" + filterForCData(metadata.getLicenseText()) + "]]></licenseText>");
+			sw.write("<licensor><![CDATA[" + filterForCData(metadata.getLicensor()) + "]]></licensor>");
+			sw.write("<publicationDate><month><![CDATA[" + (pubMonth != null ? pubMonth.trim() : "") + "]]></month><year><![CDATA[" + (pubYear != null ? pubYear.trim() : "") + "]]></year></publicationDate>");
+			sw.write("<downloadCount><![CDATA[" + metadata.getDownloadCount() + "]]></downloadCount>");
+			if(metadata instanceof VFSMetadataImpl) {
+				Boolean cannotGenerateThumbnail = ((VFSMetadataImpl)metadata).getCannotGenerateThumbnails();
+				sw.write("<thumbnails cannotGenerateThumbnail=\"" + (cannotGenerateThumbnail == null ? "" : cannotGenerateThumbnail) + "\"></thumbnails>");
+			}
+			sw.write("</meta>");
+			sw.flush();
+			return out.toByteArray();
+		} catch (Exception e) { 
+			return new byte[0]; 
+		}
+	}
+	
+	private static final String filterForCData(String original) {
+		if(StringHelper.containsNonWhitespace(original)) {
+			return FilterFactory.getXMLValidCharacterFilter().filter(original);
+		}
+		return "";
+	}
+	
+	private class MetaHandler extends DefaultHandler {
+
+		private StringBuilder current;
+		private final File fMeta;
+		
+		public MetaHandler(File fMeta) {
+			this.fMeta = fMeta;
+		}
+		
+		@Override
+		public final void startElement(String uri, String localName, String qName, Attributes attributes) {
+			if("meta".equals(qName)) {
+				 meta.setUuid(attributes.getValue("uuid"));
+			} else if ("lock".equals(qName)) {
+				meta.setLocked("true".equals(attributes.getValue("locked")));
+				String date = attributes.getValue("date");
+				if (date != null && date.length() > 0) {
+					meta.setLockedDate(new Date(Long.parseLong(date)));
+				}
+			} else if ("thumbnails".equals(qName)) {
+				String valueStr = attributes.getValue("cannotGenerateThumbnail");
+				if(StringHelper.containsNonWhitespace(valueStr)) {
+					meta.setCannotGenerateThumbnails(Boolean.valueOf(valueStr).booleanValue());
+				}
+			}else if ("thumbnail".equals(qName)) {
+				Thumbnail thumbnail = new Thumbnail();
+				thumbnail.setMaxHeight(Integer.parseInt(attributes.getValue("maxHeight")));
+				thumbnail.setMaxWidth(Integer.parseInt(attributes.getValue("maxWidth")));
+				thumbnail.setFinalHeight(Integer.parseInt(attributes.getValue("finalHeight")));
+				thumbnail.setFinalWidth(Integer.parseInt(attributes.getValue("finalWidth")));
+				thumbnail.setFill("true".equals(attributes.getValue("fill")));
+				thumbnails.add(thumbnail);
+			}
+		}
+		
+		@Override
+		public final void characters(char[] ch, int start, int length) {
+			if(length == 0) return;
+			if(current == null) {
+				current = new StringBuilder();
+			}
+			current.append(ch, start, length);
+		}
+
+		@Override
+		public final void endElement(String uri, String localName, String qName) {
+			if(current == null) return;
+			
+			if("comment".equals(qName)) {
+				meta.setComment(current.toString());
+			} else if ("author".equals(qName)) {
+				Long authorKey = getLong();
+				if(authorKey != null) {
+					meta.setAuthor(securityManager.loadIdentityByKey(authorKey));
+				}
+			} else if ("lock".equals(qName)) {
+				Long lockedByKey = getLong();
+				if(lockedByKey != null) {
+					meta.setLockedBy(securityManager.loadIdentityByKey(lockedByKey));
+				}
+			} else if ("title".equals(qName)) {
+				meta.setTitle(current.toString());
+			} else if ("publisher".equals(qName)) {
+				meta.setPublisher(current.toString());
+			} else if ("source".equals(qName)) {
+				meta.setSource(current.toString());
+			} else if ("city".equals(qName)) {
+				meta.setCity(current.toString());
+			} else if ("pages".equals(qName)) {
+				meta.setPages(current.toString());
+			} else if ("language".equals(qName)) {
+				meta.setLanguage(current.toString());
+			} else if ("downloadCount".equals(qName)) {
+				Long key = getLong();
+				if(key != null) {
+					meta.setDownloadCount(key.intValue());
+				}
+			} else if ("month".equals(qName)) {
+				meta.setPubMonth(current.toString());
+			} else if ("year".equals(qName)) {
+				meta.setPubYear(current.toString());
+			} else if (qName.equals("creator")) {
+				meta.setCreator(current.toString());
+			} else if (qName.equals("url")) {
+				meta.setUrl(current.toString());
+			} else if (qName.equals("licenseTypeKey")) {
+				//
+			} else if (qName.equals("licenseTypeName")) {
+				String licenseTypeName = current.toString().trim();
+				if(StringHelper.containsNonWhitespace(licenseTypeName)) {
+					meta.setLicenseTypeName(current.toString());
+					meta.setLicenseType(licenseService.loadLicenseTypeByName(licenseTypeName));
+				}
+			} else if (qName.equals("licenseText")) {
+				meta.setLicenseText(current.toString());
+			} else if (qName.equals("licensor")) {
+				meta.setLicensor(current.toString());
+			} else if (qName.equals("thumbnail")) {
+				if(fMeta != null) {
+					String finalName = current.toString();
+					File thumbnailFile = new File(fMeta.getParentFile(), finalName);
+					thumbnails.get(thumbnails.size() - 1).setThumbnailFile(thumbnailFile);
+				}
+			}
+			current = null;
+		}
+		
+		private Long getLong() {
+			try {
+				String val = current.toString();
+				if(StringHelper.isLong(val)) {
+					return Long.valueOf(val);
+				}
+			} catch (NumberFormatException nEx) {
+				//nothing to say
+			}
+			return null;
+		}
+	}
+	
+	public static class Thumbnail implements Serializable {
+
+		private static final long serialVersionUID = 29491661959555446L;
+		
+		private int maxWidth;
+		private int maxHeight;
+		private int finalWidth;
+		private int finalHeight;
+		private boolean fill = false;
+		private File thumbnailFile;
+		
+		public int getMaxWidth() {
+			return maxWidth;
+		}
+		
+		public void setMaxWidth(int maxWidth) {
+			this.maxWidth = maxWidth;
+		}
+		
+		public int getMaxHeight() {
+			return maxHeight;
+		}
+		
+		public void setMaxHeight(int maxHeight) {
+			this.maxHeight = maxHeight;
+		}
+		
+		public int getFinalWidth() {
+			return finalWidth;
+		}
+		
+		public void setFinalWidth(int finalWidth) {
+			this.finalWidth = finalWidth;
+		}
+		
+		public int getFinalHeight() {
+			return finalHeight;
+		}
+		
+		public void setFinalHeight(int finalHeight) {
+			this.finalHeight = finalHeight;
+		}
+		
+		public boolean isFill() {
+			return fill;
+		}
+
+		public void setFill(boolean fill) {
+			this.fill = fill;
+		}
+
+		public File getThumbnailFile() {
+			return thumbnailFile;
+		}
+		
+		public void setThumbnailFile(File thumbnailFile) {
+			this.thumbnailFile = thumbnailFile;
+		}
+		
+		public boolean exists() {
+			return thumbnailFile != null && thumbnailFile.exists();
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAO.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..58b2a64c1fd47152a0c070abc74059b8f929a98e
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAO.java
@@ -0,0 +1,204 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSMetadataRef;
+import org.olat.core.commons.services.vfs.model.VFSMetadataImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class VFSMetadataDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public VFSMetadata createMetadata(String uuid, String relativePath, String filename,
+			Date fileLastModified, long size, boolean directory, String uri, String uriProtocol,
+			VFSMetadata parent) {
+		VFSMetadataImpl metadata = new VFSMetadataImpl();
+		metadata.setCreationDate(new Date());
+		metadata.setLastModified(metadata.getCreationDate());
+		metadata.setUuid(uuid);
+		metadata.setRelativePath(relativePath);
+		metadata.setFilename(filename);
+		metadata.setDirectory(directory);
+		metadata.setFileLastModified(fileLastModified);
+		metadata.setFileSize(size);
+		metadata.setUri(uri);
+		metadata.setProtocol(uriProtocol);
+		metadata.setParent(parent);
+		dbInstance.getCurrentEntityManager().persist(metadata);
+		metadata.setMaterializedPathKeys(getMaterializedPathKeys((VFSMetadataImpl)parent, metadata));
+		metadata = dbInstance.getCurrentEntityManager().merge(metadata);
+		return metadata;
+	}
+	
+	public VFSMetadata createMetadata(VFSMetadataImpl metadata, String relativePath, String filename,
+			Date fileLastModified, long size, boolean directory, String uri, String uriProtocol,
+			VFSMetadata parent) {
+
+		metadata.setCreationDate(new Date());
+		metadata.setLastModified(metadata.getCreationDate());
+		if(metadata.getUuid() == null) {
+			metadata.setUuid(UUID.randomUUID().toString());
+		}
+		metadata.setRelativePath(relativePath);
+		metadata.setFilename(filename);
+		metadata.setDirectory(directory);
+		metadata.setFileLastModified(fileLastModified);
+		metadata.setFileSize(size);
+		metadata.setUri(uri);
+		metadata.setProtocol(uriProtocol);
+		metadata.setParent(parent);
+		dbInstance.getCurrentEntityManager().persist(metadata);
+		metadata.setMaterializedPathKeys(getMaterializedPathKeys((VFSMetadataImpl)parent, metadata));
+		metadata = dbInstance.getCurrentEntityManager().merge(metadata);
+		return metadata;
+	}
+	
+	/**
+	 * Calculate the materialized path from the parent element.
+	 * 
+	 * @param parent The parent element (can be null if the element is a root one)
+	 * @param element The curriculum element
+	 * @return The materialized path of the specified element
+	 */
+	private String getMaterializedPathKeys(VFSMetadataImpl parent, VFSMetadataImpl element) {
+		if(parent != null) {
+			String parentPathOfKeys = parent.getMaterializedPathKeys();
+			if(parentPathOfKeys == null || "/".equals(parentPathOfKeys)) {
+				parentPathOfKeys = "";
+			}
+			return parentPathOfKeys + element.getKey() + "/";
+		}
+		return "/" + element.getKey() + "/";
+	}
+	
+	public VFSMetadata getMetadata(String uuid) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select metadata from filemetadata metadata")
+		  .append(" left join fetch metadata.author as author")
+		  .append(" left join fetch author.user as authorUser")
+		  .append(" left join fetch metadata.licenseType as licenseType")
+		  .append(" where metadata.uuid=:uuid");
+		  
+		  
+		List<VFSMetadata> metadata = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), VFSMetadata.class)
+			.setParameter("uuid", uuid)
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return metadata == null || metadata.isEmpty() ? null : metadata.get(0);
+	}
+	
+	public VFSMetadata getMetadata(String relativePath, String filename, boolean directory) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select metadata from filemetadata metadata")
+		  .append(" left join fetch metadata.author as author")
+		  .append(" left join fetch author.user as authorUser")
+		  .append(" left join fetch metadata.licenseType as licenseType")
+		  .append(" where metadata.filename=:filename and metadata.relativePath=:relativePath and metadata.directory=:directory");
+
+		List<VFSMetadata> metadata = dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), VFSMetadata.class)
+			.setParameter("filename", filename)
+			.setParameter("relativePath", relativePath)
+			.setParameter("directory", Boolean.valueOf(directory))
+			.setFirstResult(0)
+			.setMaxResults(1)
+			.getResultList();
+		return metadata == null || metadata.isEmpty() ? null : metadata.get(0);
+	}
+	
+	/**
+	 * This is an exact match to find the direct children of a specific
+	 * directory.
+	 * 
+	 * @param relativePath The relative path
+	 * @return A list of metadata
+	 */
+	public List<VFSMetadata> getMetadatas(String relativePath) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select metadata from filemetadata metadata")
+		  .append(" left join fetch metadata.author as author")
+		  .append(" left join fetch author.user as authorUser")
+		  .append(" left join fetch metadata.licenseType as licenseType")
+		  .append(" where metadata.relativePath=:relativePath");
+
+		return dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), VFSMetadata.class)
+			.setParameter("relativePath", relativePath)
+			.getResultList();
+	}
+	
+	/**
+	 * This is an exact match to find the direct children of the specific
+	 * directory described by its metadata.
+	 * 
+	 * @param relativePath The relative path
+	 * @return A list of metadata
+	 */
+	public List<VFSMetadata> getMetadatas(VFSMetadataRef parentMetadata) {
+		StringBuilder sb = new StringBuilder(256);
+		sb.append("select metadata from filemetadata metadata")
+		  .append(" left join fetch metadata.author as author")
+		  .append(" left join fetch author.user as authorUser")
+		  .append(" left join fetch metadata.licenseType as licenseType")
+		  .append(" where metadata.parent.key=:parentKey");
+
+		return dbInstance.getCurrentEntityManager()
+			.createQuery(sb.toString(), VFSMetadata.class)
+			.setParameter("parentKey", parentMetadata.getKey())
+			.getResultList();
+	}
+	
+	public VFSMetadata updateMetadata(VFSMetadata metadata) {
+		((VFSMetadataImpl)metadata).setLastModified(new Date());
+		return dbInstance.getCurrentEntityManager().merge(metadata);
+	}
+	
+	public void removeMetadata(VFSMetadata metadata) {
+		dbInstance.getCurrentEntityManager().remove(metadata);
+	}
+	
+	public void increaseDownloadCount(String relativePath, String filename) {
+		String updateQuery = "update vfsmetadatadownloadcount set downloadCount=downloadCount+1 where filename=:filename and relativePath=:relativePath";
+		dbInstance.getCurrentEntityManager()
+			.createQuery(updateQuery)
+			.setParameter("filename", filename)
+			.setParameter("relativePath", relativePath)
+			.executeUpdate();
+	}
+
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..0aeb6ebe2f1ebe7044775ffa56c5aef276082ea3
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceImpl.java
@@ -0,0 +1,609 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.modules.bc.FolderLicenseHandler;
+import org.olat.core.commons.modules.bc.FolderModule;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.license.License;
+import org.olat.core.commons.services.license.LicenseService;
+import org.olat.core.commons.services.license.LicenseType;
+import org.olat.core.commons.services.thumbnail.CannotGenerateThumbnailException;
+import org.olat.core.commons.services.thumbnail.FinalSize;
+import org.olat.core.commons.services.thumbnail.ThumbnailService;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSMetadataRef;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
+import org.olat.core.commons.services.vfs.VFSThumbnailMetadata;
+import org.olat.core.commons.services.vfs.manager.MetaInfoReader.Thumbnail;
+import org.olat.core.commons.services.vfs.model.VFSMetadataImpl;
+import org.olat.core.id.Identity;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.FileUtils;
+import org.olat.core.util.StringHelper;
+import org.olat.core.util.vfs.LocalFileImpl;
+import org.olat.core.util.vfs.VFSConstants;
+import org.olat.core.util.vfs.VFSContainer;
+import org.olat.core.util.vfs.VFSItem;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.core.util.vfs.VFSManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class VFSRepositoryServiceImpl implements VFSRepositoryService {
+	
+	private static final OLog log = Tracing.createLoggerFor(VFSRepositoryServiceImpl.class);
+	private static final String CANONICAL_ROOT_REL_PATH = "/";
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private FolderModule folderModule;
+	@Autowired
+	private VFSMetadataDAO metadataDao;
+	@Autowired
+	private VFSThumbnailDAO thumbnailDao;
+	@Autowired
+	private LicenseService licenseService;
+	@Autowired
+	private FolderLicenseHandler licenseHandler;
+	@Autowired
+	private ThumbnailService thumbnailService;
+	@Autowired
+	private BaseSecurity securityManager;
+
+	@Override
+	public VFSMetadata getMetadataFor(VFSItem path) {
+		File file = toFile(path);
+		return getMetadataFor(file);
+	}
+
+	@Override
+	public VFSMetadata getMetadataFor(File file) {
+		String relativePath = getRelativePath(file.getParentFile());
+		if(relativePath.equals("..")) {
+			return null;
+		}
+		if(relativePath.equals("")) {
+			relativePath = CANONICAL_ROOT_REL_PATH;
+		}
+		
+		String filename = file.getName();
+		VFSMetadata metadata = metadataDao.getMetadata(relativePath, filename, file.isDirectory());
+		if(metadata == null) {
+			String uuid = UUID.randomUUID().toString();
+			String uri = file.toURI().toString();
+			boolean directory = file.isDirectory();
+			long size = directory ? 0l : file.length();
+			
+			VFSMetadata parent = getMetadataFor(file.getParentFile());
+			metadata = metadataDao.createMetadata(uuid, relativePath, filename, new Date(), size, directory, uri, "file", parent);
+		}
+		return metadata;
+	}
+	
+	/**
+	 * This method doesn't create missing database entry.
+	 * 
+	 * @param file The file
+	 * @return A metadata object or null if not found
+	 */
+	private VFSMetadata loadMetadata(File file) {
+		if(file == null) return null;
+		
+		String relativePath = getRelativePath(file.getParentFile());
+		if(relativePath.equals("..")) {
+			return null;
+		}
+		if(relativePath.equals("")) {
+			relativePath = CANONICAL_ROOT_REL_PATH;
+		}
+		
+		String filename = file.getName();
+		return metadataDao.getMetadata(relativePath, filename, file.isDirectory());
+	}
+	
+	private File toFile(VFSItem item) {
+		String relPath = item.getRelPath();
+		return relPath == null ? null : VFSManager.olatRootFile(relPath);
+	}
+
+	@Override
+	public List<VFSMetadata> getChildren(String relativePath) {
+		if(relativePath == null) return new ArrayList<>();
+		if(!relativePath.equals(CANONICAL_ROOT_REL_PATH) && relativePath.startsWith("/")) {
+			relativePath = relativePath.substring(1, relativePath.length());
+		}
+		return metadataDao.getMetadatas(relativePath);
+	}
+	
+	@Override
+	public List<VFSMetadata> getChildren(VFSMetadataRef parentMetadata) {
+		return metadataDao.getMetadatas(parentMetadata);
+	}
+
+	/**
+	 * The relative path contains /bcroot/
+	 * 
+	 * @param file
+	 * @return
+	 */
+	private String getRelativePath(File file) {
+		return folderModule.getCanonicalRootPath().relativize(file.toPath()).toString();
+	}
+	
+	private String getRelativePath(VFSItem item) {
+		String relativePath = item.getRelPath();
+		if(!relativePath.equals(CANONICAL_ROOT_REL_PATH) && relativePath.startsWith("/")) {
+			relativePath = relativePath.substring(1, relativePath.length());
+		}
+		return relativePath;
+	}
+	
+	@Override
+	public VFSMetadata updateMetadata(VFSMetadata data) {
+		return metadataDao.updateMetadata(data);
+	}
+
+	@Override
+	public void deleteMetadata(VFSMetadata data) {
+		List<VFSThumbnailMetadata> thumbnails = thumbnailDao.loadByMetadata(data);
+		for(VFSThumbnailMetadata thumbnail:thumbnails) {
+			VFSItem item = VFSManager.olatRootLeaf("/" + data.getRelativePath(), thumbnail.getFilename());
+			if(item != null && item.exists()) {
+				item.deleteSilently();
+			}
+			thumbnailDao.removeThumbnail(thumbnail);
+		}
+		metadataDao.removeMetadata(data);
+	}
+
+	@Override
+	public void deleteMetadata(File file) {
+		VFSMetadata metadata = loadMetadata(file);
+		if(metadata != null) {
+			deleteMetadata(metadata);
+		}
+	}
+
+	@Override
+	public void copyBinaries(VFSMetadata metadata, byte[] binaries) {
+		if(binaries == null || binaries.length == 0) return;
+		
+		MetaInfoReader reader = new MetaInfoReader((VFSMetadataImpl)metadata, licenseService, securityManager);
+		reader.fromBinaries(binaries);
+	}
+
+	@Override
+	public void copyTo(VFSLeaf source, VFSLeaf target, VFSContainer parentTarget) {
+		if(source.canMeta() != VFSConstants.YES || target.canMeta() != VFSConstants.YES) return;
+		
+		VFSMetadataImpl sourceMetadata = (VFSMetadataImpl)loadMetadata(toFile(source));
+		if(sourceMetadata != null) {
+			File targetFile = toFile(target);
+			if(targetFile != null) {
+				VFSMetadata targetMetadata = loadMetadata(targetFile);
+				if(targetMetadata == null) {
+					VFSMetadata parentMetadata = getMetadataFor(parentTarget);
+					String relativePath = this.getRelativePath(targetFile.getParentFile());
+					targetMetadata = metadataDao.createMetadata(UUID.randomUUID().toString(), relativePath, targetFile.getName(),
+							new Date(), targetFile.length(), false, targetFile.toURI().toString(), "file", parentMetadata);
+				}
+				targetMetadata.copyValues(sourceMetadata);
+				metadataDao.updateMetadata(targetMetadata);
+			}
+		}
+	}
+
+	@Override
+	public VFSMetadata rename(VFSMetadata data, String newName) {
+		((VFSMetadataImpl)data).setFilename(newName);
+		Path newFile = Paths.get(folderModule.getCanonicalRoot(), data.getRelativePath(), newName);
+		String uri = newFile.toFile().toURI().toString();
+		((VFSMetadataImpl)data).setUri(uri);
+		return metadataDao.updateMetadata(data);
+	}
+
+	@Override
+	public void increaseDownloadCount(VFSItem item) {
+		String relPath = item.getRelPath();
+		if(StringHelper.containsNonWhitespace(relPath)) {
+			metadataDao.increaseDownloadCount(relPath, item.getName());
+		}
+	}
+	
+	@Override
+	public VFSLeaf getThumbnail(VFSLeaf file, VFSMetadata metadata, int maxWidth, int maxHeight, boolean fill) {
+		VFSLeaf thumbnailLeaf = null;
+		
+		VFSContainer parentContainer = file.getParentContainer();
+		String relativePath = getRelativePath(parentContainer);
+		if(relativePath != null) {
+			VFSThumbnailMetadata thumbnail = thumbnailDao.findThumbnail(relativePath, file.getName(), fill, maxWidth, maxHeight);
+			if(thumbnail == null) {
+				thumbnailLeaf = generateThumbnail(file, metadata, fill, maxWidth, maxHeight);
+			} else {
+				VFSItem item = parentContainer.resolve(thumbnail.getFilename());
+				if(item instanceof VFSLeaf) {
+					thumbnailLeaf = (VFSLeaf)item;
+				}
+			}
+		}
+		return thumbnailLeaf;
+	}
+
+	@Override
+	public VFSLeaf getThumbnail(VFSLeaf file, int maxWidth, int maxHeight, boolean fill) {
+		VFSLeaf thumbnailLeaf = null;
+		
+		VFSContainer parentContainer = file.getParentContainer();
+		String relativePath = getRelativePath(parentContainer);
+		if(relativePath != null) {
+			VFSThumbnailMetadata thumbnail = thumbnailDao.findThumbnail(relativePath, file.getName(), fill, maxWidth, maxHeight);
+			if(thumbnail == null) {
+				VFSMetadata metadata = metadataDao.getMetadata(relativePath, file.getName(), false);
+				thumbnailLeaf = generateThumbnail(file, metadata, fill, maxWidth, maxHeight);
+			} else {
+				VFSItem item = parentContainer.resolve(thumbnail.getFilename());
+				if(item instanceof VFSLeaf) {
+					thumbnailLeaf = (VFSLeaf)item;
+				}
+			}
+		}
+		return thumbnailLeaf;
+	}
+	
+	private VFSLeaf generateThumbnail(VFSLeaf file, VFSMetadata metadata, boolean fill, int maxWidth, int maxHeight) {
+		String name = file.getName();
+		String extension = FileUtils.getFileSuffix(name);
+		String nameOnly = name.substring(0, name.length() - extension.length() - 1);
+		String thumbnailExtension = preferedThumbnailType(extension);
+		String thumbnailName = generateFilenameForThumbnail(nameOnly, thumbnailExtension, fill, maxWidth, maxHeight);
+		
+		VFSContainer parentContainer = file.getParentContainer();
+		VFSLeaf thumbnailLeaf = parentContainer.createChildLeaf(thumbnailName);
+		if(thumbnailService.isThumbnailPossible(thumbnailLeaf)) {
+			try {
+				FinalSize finalSize = thumbnailService.generateThumbnail(file, thumbnailLeaf, maxWidth, maxHeight, fill);
+				if(finalSize == null) {
+					thumbnailLeaf = null;
+					metadata.setCannotGenerateThumbnails(Boolean.TRUE);
+					metadataDao.updateMetadata(metadata);
+				} else {
+					thumbnailDao.createThumbnailMetadata(metadata, thumbnailName, thumbnailLeaf.getSize(),
+							fill, maxWidth, maxHeight, finalSize.getWidth(), finalSize.getHeight());
+				}
+			} catch (CannotGenerateThumbnailException e) {
+				metadata.setCannotGenerateThumbnails(Boolean.TRUE);
+				metadataDao.updateMetadata(metadata);
+				thumbnailLeaf = null;
+			}
+		}
+		return thumbnailLeaf;
+	}
+	
+	private String preferedThumbnailType(String extension) {
+		if(extension.equalsIgnoreCase("png") || extension.equalsIgnoreCase("gif")) {
+			return extension;
+		}
+		if(extension.equalsIgnoreCase("pdf")) {
+			return "png";
+		}
+		return "jpg";
+	}
+
+	@Override
+	public boolean isThumbnailAvailable(VFSItem item, VFSMetadata metadata) {
+		if(metadata == null) return false;
+		
+		if(metadata.isDirectory() || (metadata.getCannotGenerateThumbnails() != null && metadata.getCannotGenerateThumbnails().booleanValue())) { 
+			return false;
+		}
+		return thumbnailService.isThumbnailPossible((VFSLeaf)item);
+	}
+
+	@Override
+	public boolean isThumbnailAvailable(VFSItem item) {
+		if(item instanceof VFSContainer) return false;
+		
+		File originFile = toFile(item);
+		if(originFile == null || originFile.isHidden()) {
+			return false;
+		}
+		
+		VFSMetadata metadata = loadMetadata(originFile);
+		if(metadata != null && metadata.getCannotGenerateThumbnails() != null && metadata.getCannotGenerateThumbnails().booleanValue()) {
+			return false;
+		}
+		return thumbnailService.isThumbnailPossible((VFSLeaf)item);
+	}
+
+	@Override
+	public void resetThumbnails(VFSLeaf file) {
+		VFSContainer parentContainer = file.getParentContainer();
+		String relativePath = getRelativePath(parentContainer);
+		if(relativePath == null) return;
+		
+		List<VFSThumbnailMetadata> thumbnails = thumbnailDao.findThumbnails(relativePath, file.getName());
+		for(VFSThumbnailMetadata thumbnail:thumbnails) {
+			VFSItem item = parentContainer.resolve(thumbnail.getFilename());
+			if(item != null) {
+				item.deleteSilently();
+			}
+			thumbnailDao.removeThumbnail(thumbnail);
+		}
+	}
+
+	@Override
+	public void resetThumbnails(File file) {
+		if(file.isFile()) {
+			VFSLeaf leaf = new LocalFileImpl(file);
+			if(leaf.getRelPath() != null) {
+				resetThumbnails(leaf);
+			}
+		} else if(file.isDirectory()) {
+			try {
+				Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
+					@Override
+					public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
+						VFSLeaf leaf = new LocalFileImpl(path.toFile());
+						if(leaf.getRelPath() != null) {
+							resetThumbnails(leaf);
+						}
+						return FileVisitResult.CONTINUE;
+					}
+				});
+			} catch (IOException e) {
+				log.error("", e);
+			}	
+		}
+	}
+	
+	/**
+	 * Get the license of the MetaInfo
+	 *
+	 * @param meta
+	 * @return the license or null if no license is stored in the MetaInfo
+	 */
+	@Override
+	public License getLicense(VFSMetadata meta) {
+		License license = null;
+		boolean hasLicense = meta != null && StringHelper.containsNonWhitespace(meta.getLicenseTypeName());
+		if (hasLicense) { 
+			String licenseTypeName = meta.getLicenseTypeName();
+			LicenseType licenseType = null;
+			if(meta.getLicenseType() != null) {
+				licenseType = meta.getLicenseType();
+			} else {
+				licenseType = licenseService.loadLicenseTypeByName(licenseTypeName);
+			}
+			if (licenseType == null) {
+				licenseType = licenseService.createLicenseType(licenseTypeName);
+				licenseType.setText(meta.getLicenseText());
+				licenseService.saveLicenseType(licenseType);
+			}
+			license = licenseService.createLicense(licenseType);
+			license.setLicensor(meta.getLicensor());
+			if (licenseService.isFreetext(licenseType)) {
+				license.setFreetext(meta.getLicenseText());
+			}
+		}
+		return license;
+	}
+	
+	/**
+	 * Get the license of the MetaInfo or create a new default license:
+	 *
+	 * @param meta
+	 * @param itentity the current user
+	 * @return
+	 */
+	@Override
+	public License getOrCreateLicense(VFSMetadata meta, Identity itentity) {
+		License license = getLicense(meta);
+		if (license == null) {
+			license = licenseService.createDefaultLicense(licenseHandler, itentity);
+		}
+		return license;
+	}
+	
+	public void migrateDirectories(File folder) throws IOException {
+		Deque<VFSMetadata> parentLine = new LinkedList<>();
+		AtomicInteger migrationCounter = new AtomicInteger(0);
+		
+		Files.walkFileTree(folder.toPath(), new SimpleFileVisitor<Path>() {
+			@Override
+			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+				File directory = dir.toFile();
+				if(directory.isHidden() || directory.getName().equals("__MACOSX")) {
+					return FileVisitResult.SKIP_SUBTREE;
+				}
+				VFSMetadata parent = parentLine.peekLast();
+				VFSMetadata metadata = migrateMetadata(dir.toFile(), parent);
+				parentLine.add(metadata);
+				return FileVisitResult.CONTINUE;
+			}
+
+			@Override
+			public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+				File f = file.toFile();
+				if(!f.isHidden() && !f.getName().equals("__MACOSX")) {
+					migrateMetadata(file.toFile(), parentLine.getLast());
+					
+					migrationCounter.incrementAndGet();
+					if(migrationCounter.get() % 25 == 0) {
+						dbInstance.commitAndCloseSession();
+					} else {
+						dbInstance.commit();
+					}
+					if(migrationCounter.get() % 100 == 0) {
+						log.info("Metadata: num. of files migrated: " + migrationCounter);
+					}
+				}
+				return FileVisitResult.CONTINUE;
+			}
+
+			@Override
+			public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+				parentLine.pollLast();
+				dbInstance.commitAndCloseSession();
+				return FileVisitResult.CONTINUE;
+			}
+		});
+	}
+	
+	public VFSMetadata migrate(File file, VFSMetadata parent) {
+		String relativePath = getRelativePath(file.getParentFile());
+		if(relativePath.equals("..")) {
+			return null;
+		}
+		if(relativePath.equals("")) {
+			relativePath = CANONICAL_ROOT_REL_PATH;
+		}
+		
+		String filename = file.getName();
+		VFSMetadata metadata = metadataDao.getMetadata(relativePath, filename, file.isDirectory());
+		if(metadata == null) {
+			if(parent == null) {
+				parent = migrate(file.getParentFile(), null);
+			}
+			metadata = migrateMetadata(file, parent);
+		}
+		return metadata;
+	}
+	
+	private VFSMetadata migrateMetadata(File file, VFSMetadata parent) {
+		VFSMetadata metadata = null;
+		String metaPath = getCanonicalMetaPath(file);
+		String relativePath = getRelativePath(file.getParentFile());
+		if(relativePath.equals("..")) {
+			return null;
+		}
+		if(relativePath.equals("")) {
+			relativePath = CANONICAL_ROOT_REL_PATH;
+		}
+		
+		boolean directory = file.isDirectory();
+		long size = directory ? 0l : file.length();
+
+		Date fileLastModified = new Date(file.lastModified());
+		if(metaPath != null) {
+			File metaFile = new File(metaPath);
+			if(metaFile.exists()) {
+				List<Thumbnail> thumbnails = null;
+				VFSMetadataImpl xmlMetadata = new VFSMetadataImpl(); 
+				MetaInfoReader metaReader = new MetaInfoReader(xmlMetadata, licenseService, securityManager);
+				if(metaReader.parseSAX(metaFile)) {
+					xmlMetadata = metaReader.getMetadata();
+					thumbnails = metaReader.getThumbnails();
+				}
+				metadata = metadataDao.createMetadata(xmlMetadata, relativePath, file.getName(), fileLastModified,
+						size, directory, file.toURI().toString(), "file", parent);
+				migrateThumbnails(metadata, file, thumbnails);
+			}
+		} 
+		
+		if(metadata == null) {
+			metadata = metadataDao.createMetadata(UUID.randomUUID().toString(), relativePath, file.getName(), fileLastModified,
+					size, directory, file.toURI().toString(), "file", parent);
+		}
+		return metadata;
+	}
+	
+	private String generateFilenameForThumbnail(File file, boolean fill, int maxWidth, int maxHeight) {
+		StringBuilder sb = new StringBuilder(128);
+		sb.append("._oo_th_").append(fill).append("_").append(maxWidth).append("_").append(maxHeight)
+		  .append("_").append(file.getName());
+		return sb.toString();
+	}
+	
+	private String generateFilenameForThumbnail(String name, String extension, boolean fill, int maxWidth, int maxHeight) {
+		StringBuilder sb = new StringBuilder(128);
+		sb.append("._oo_th_").append(fill).append("_").append(maxWidth).append("_").append(maxHeight)
+		  .append("_").append(name).append(".").append(extension);
+		return sb.toString();
+	}
+	
+	private void migrateThumbnails(VFSMetadata metadata, File file, List<Thumbnail> thumbnails) {
+		if(thumbnails == null || thumbnails.isEmpty()) return;
+		
+		// do copy them
+		for(Thumbnail thumbnail:thumbnails) {
+			try {
+				File thumbnailFile = thumbnail.getThumbnailFile();
+				if(thumbnailFile.exists()) {
+					String filename = generateFilenameForThumbnail(file, thumbnail.isFill(), thumbnail.getMaxWidth(), thumbnail.getMaxHeight());
+					thumbnailDao.createThumbnailMetadata(metadata, filename, thumbnailFile.length(),
+							thumbnail.isFill(), thumbnail.getMaxWidth(), thumbnail.getMaxHeight(),
+							thumbnail.getFinalWidth(), thumbnail.getFinalHeight());
+					File target = new File(file.getParentFile(), filename);
+					Files.move(thumbnailFile.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
+				}
+			} catch (IOException e) {
+				log.error("", e);
+			}
+		}
+	}
+	
+	private String getCanonicalMetaPath(File originFile) {
+		String canonicalMetaPath;
+		if (originFile == null || !originFile.exists()) {
+			canonicalMetaPath = null;
+		} else {
+			String relPath = FolderConfig.getCanonicalRootPath().relativize(originFile.toPath()).toString();
+			StringBuilder metaSb = new StringBuilder(128);
+			metaSb.append(FolderConfig.getCanonicalMetaRoot()).append("/").append(relPath);
+			if (originFile.isDirectory()) {
+				metaSb.append("/.xml");
+			} else {
+				metaSb.append(".xml");
+			}
+			canonicalMetaPath = metaSb.toString();
+		}
+		return canonicalMetaPath;
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSThumbnailDAO.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSThumbnailDAO.java
new file mode 100644
index 0000000000000000000000000000000000000000..074ee1d97e2b3e78d90656e577652bc8667e910b
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSThumbnailDAO.java
@@ -0,0 +1,134 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.util.Date;
+import java.util.List;
+
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSMetadataRef;
+import org.olat.core.commons.services.vfs.VFSThumbnailMetadata;
+import org.olat.core.commons.services.vfs.model.VFSThumbnailMetadataImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 
+ * Initial date: 14 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Service
+public class VFSThumbnailDAO {
+	
+	@Autowired
+	private DB dbInstance;
+	
+	public VFSThumbnailMetadata createThumbnailMetadata(VFSMetadata metadata, String filename,
+			long size, boolean fill, int maxWidth, int maxHeight, int finalWidth, int finalHeight) {
+		VFSThumbnailMetadataImpl thumbnail = new VFSThumbnailMetadataImpl();
+		thumbnail.setCreationDate(new Date());
+		thumbnail.setLastModified(thumbnail.getCreationDate());
+		thumbnail.setFilename(filename);
+		thumbnail.setFileSize(size);
+		thumbnail.setFill(fill);
+		thumbnail.setMaxWidth(maxWidth);
+		thumbnail.setMaxHeight(maxHeight);
+		thumbnail.setFinalWidth(finalWidth);
+		thumbnail.setFinalHeight(finalHeight);
+		thumbnail.setOwner(metadata);
+		dbInstance.getCurrentEntityManager().persist(thumbnail);
+		return thumbnail;
+	}
+	
+	public VFSThumbnailMetadata loadByKey(Long key) {
+		List<VFSThumbnailMetadata> metas = dbInstance.getCurrentEntityManager()
+				.createNamedQuery("loadThumbnailByKey", VFSThumbnailMetadata.class)
+				.setParameter("thumbnailKey", key)
+				.setFirstResult(0)
+				.setMaxResults(1)
+				.getResultList();
+		return metas == null || metas.isEmpty() ? null : metas.get(0);
+	}
+	
+	public List<VFSThumbnailMetadata> loadByMetadata(VFSMetadataRef metadata) {
+		String q = "select thumb from vfsthumbnail thumb where thumb.owner.key=:metadataKey";
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(q, VFSThumbnailMetadata.class)
+				.setParameter("metadataKey", metadata.getKey())
+				.getResultList();
+	}
+
+	public VFSThumbnailMetadata findThumbnail(String relativePath, String filename, boolean fill, int maxWidth, int maxHeight) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select thumb from vfsthumbnail thumb")
+		  .append(" inner join thumb.owner as meta")
+		  .append(" where meta.filename=:filename and meta.relativePath=:relativePath")
+		  .append(" and thumb.maxWidth=:maxWidth and thumb.maxHeight=:maxHeight and thumb.fill=:fill");
+
+		List<VFSThumbnailMetadata> metas = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), VFSThumbnailMetadata.class)
+				.setParameter("filename", filename)
+				.setParameter("relativePath", relativePath)
+				.setParameter("maxWidth", Integer.valueOf(maxWidth))
+				.setParameter("maxHeight", Integer.valueOf(maxHeight))
+				.setParameter("fill", Boolean.valueOf(fill))
+				.setFirstResult(0)
+				.setMaxResults(1)
+				.getResultList();
+		return metas == null || metas.isEmpty() ? null : metas.get(0);
+	}
+	
+	public List<VFSThumbnailMetadata> findThumbnails(String relativePath, String filename) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select thumb from vfsthumbnail thumb")
+		  .append(" inner join thumb.owner as meta")
+		  .append(" where meta.filename=:filename and meta.relativePath=:relativePath");
+
+		return dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), VFSThumbnailMetadata.class)
+				.setParameter("filename", filename)
+				.setParameter("relativePath", relativePath)
+				.getResultList();
+	}
+	
+	public VFSThumbnailMetadata findThumbnail(VFSMetadata owner, boolean fill, int maxWidth, int maxHeight) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("select thumb from vfsthumbnail thumb")
+		  .append(" inner join thumb.owner as meta")
+		  .append(" where meta.key=:ownerKey and thumb.maxWidth=:maxWidth and thumb.maxHeight=:maxHeight and thumb.fill=:fill");
+
+		List<VFSThumbnailMetadata> metas = dbInstance.getCurrentEntityManager()
+				.createQuery(sb.toString(), VFSThumbnailMetadata.class)
+				.setParameter("ownerKey", owner.getKey())
+				.setParameter("maxWidth", Integer.valueOf(maxWidth))
+				.setParameter("maxHeight", Integer.valueOf(maxHeight))
+				.setParameter("fill", Boolean.valueOf(fill))
+				.setFirstResult(0)
+				.setMaxResults(1)
+				.getResultList();
+		return metas == null || metas.isEmpty() ? null : metas.get(0);
+	}
+	
+	public void removeThumbnail(VFSThumbnailMetadata thumbnail) {
+		dbInstance.getCurrentEntityManager().remove(thumbnail);
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/manager/VFSXStream.java b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSXStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..870ab3505a25a881c74a17907c68fc14c82b9615
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/manager/VFSXStream.java
@@ -0,0 +1,139 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.io.InputStream;
+
+import org.olat.basesecurity.BaseSecurity;
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.license.LicenseService;
+import org.olat.core.commons.services.license.LicenseType;
+import org.olat.core.commons.services.license.model.LicenseTypeImpl;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.model.VFSMetadataImpl;
+import org.olat.core.id.Identity;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.core.util.vfs.version.RevisionFileImpl;
+import org.olat.core.util.vfs.version.VFSRevision;
+import org.olat.core.util.vfs.version.Versions;
+import org.olat.core.util.vfs.version.VersionsFileImpl;
+import org.olat.core.util.xml.XStreamHelper;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.SingleValueConverter;
+import com.thoughtworks.xstream.security.ExplicitTypePermission;
+
+/**
+ * 
+ * Initial date: 13 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class VFSXStream {
+	
+	private static XStream mystream;
+	static {
+		mystream = XStreamHelper.createXStreamInstance();
+		XStream.setupDefaultSecurity(mystream);
+		Class<?>[] types = new Class[] {
+				VersionsFileImpl.class, Versions.class, RevisionFileImpl.class, VFSRevision.class,
+				VFSMetadata.class, VFSMetadataImpl.class, Identity.class, IdentityImpl.class,
+				LicenseType.class, LicenseTypeImpl.class
+			};
+		mystream.addPermission(new ExplicitTypePermission(types));
+		
+		mystream.alias("versions", VersionsFileImpl.class);
+		mystream.alias("revision", RevisionFileImpl.class);
+		mystream.omitField(VersionsFileImpl.class, "currentVersion");
+		mystream.omitField(VersionsFileImpl.class, "versionFile");
+		mystream.omitField(RevisionFileImpl.class, "current");
+		mystream.omitField(RevisionFileImpl.class, "container");
+		mystream.omitField(RevisionFileImpl.class, "file");
+		mystream.omitField(VFSMetadataImpl.class, "originFile");
+		mystream.omitField(VFSMetadataImpl.class, "metaFile");
+		mystream.aliasAttribute(VFSMetadataImpl.class, "cannotGenerateThumbnails", "cannotGenerateThumbnail");
+		mystream.aliasAttribute(VFSMetadataImpl.class, "author", "authorIdentKey");
+		mystream.aliasAttribute(VFSMetadataImpl.class, "licenseType", "licenseTypeKey");
+		mystream.alias("metadata", VFSMetadataImpl.class);
+		mystream.omitField(VFSMetadataImpl.class, "thumbnail");
+		mystream.omitField(VFSMetadataImpl.class, "thumbnails");
+
+		mystream.registerLocalConverter(VFSMetadataImpl.class, "licenseType", new LicenseTypeConverter());
+		mystream.registerLocalConverter(VFSMetadataImpl.class, "author", new IdentityConverter());	
+	}
+	
+	public static final Object read(InputStream in) {
+		return XStreamHelper.readObject(mystream, in);
+	}
+	
+	public static final Object read(VFSLeaf leaf) {
+		return XStreamHelper.readObject(mystream, leaf);
+	}
+	
+	public static final void write(VFSLeaf leaf, Versions versions) {
+		XStreamHelper.writeObject(mystream, leaf, versions);
+	}
+	
+	private static class LicenseTypeConverter implements SingleValueConverter {
+
+		@Override
+		public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
+			return type.equals(LicenseType.class) || type.equals(LicenseTypeImpl.class);
+		}
+
+		@Override
+		public String toString(Object obj) {
+			if(obj instanceof LicenseType) {
+				Long key = ((LicenseType)obj).getKey();
+				return key == null ? null : key.toString();
+			}
+			return null;
+		}
+
+		@Override
+		public Object fromString(String str) {
+			return CoreSpringFactory.getImpl(LicenseService.class).loadLicenseTypeByKey(str);
+		}	
+	}
+	
+	private static class IdentityConverter implements SingleValueConverter {
+
+		@Override
+		public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
+			return type.equals(Identity.class) || type.equals(IdentityImpl.class);
+		}
+
+		@Override
+		public String toString(Object obj) {
+			if(obj instanceof Identity) {
+				Long key = ((Identity)obj).getKey();
+				return key == null ? null : key.toString();
+			}
+			return null;
+		}
+
+		@Override
+		public Object fromString(String str) {
+			Long identityKey = Long.valueOf(str);
+			return CoreSpringFactory.getImpl(BaseSecurity.class).loadIdentityByKey(identityKey);
+		}	
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataDownloadCount.java b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataDownloadCount.java
new file mode 100644
index 0000000000000000000000000000000000000000..e01db2e8d31cd984c6296ee6af8a7e27193ab8ff
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataDownloadCount.java
@@ -0,0 +1,109 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 13 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="vfsmetadatadownloadcount")
+@Table(name="o_vfs_metadata")
+public class VFSMetadataDownloadCount implements Persistable {
+
+	private static final long serialVersionUID = 8572616184723339987L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Column(name="f_download_count", nullable=false, insertable=false, updatable=true)
+	private int downloadCount;
+	@Column(name="f_filename", nullable=false, insertable=false, updatable=false)
+	private String filename;
+	@Column(name="f_relative_path", nullable=false, insertable=false, updatable=false)
+	private String relativePath;
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+	
+	public int getDownloadCount() {
+		return downloadCount;
+	}
+	
+	public void setDownloadCount(int downloadCount) {
+		this.downloadCount = downloadCount;
+	}
+	
+	public String getFilename() {
+		return filename;
+	}
+	
+	public void setFilename(String filename) {
+		this.filename = filename;
+	}
+	
+	public String getRelativePath() {
+		return relativePath;
+	}
+	
+	public void setRelativePath(String relativePath) {
+		this.relativePath = relativePath;
+	}
+	
+	@Override
+	public int hashCode() {
+		return getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(obj == this) {
+			return true;
+		}
+		if(obj instanceof VFSMetadataDownloadCount) {
+			VFSMetadataDownloadCount count = (VFSMetadataDownloadCount)obj;
+			return getKey().equals(count.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataImpl.java b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..bc0e8993f125225f3c43e88c29dc3d2d86786340
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataImpl.java
@@ -0,0 +1,524 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.basesecurity.IdentityImpl;
+import org.olat.core.commons.services.license.LicenseType;
+import org.olat.core.commons.services.license.model.LicenseTypeImpl;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.gui.util.CSSHelper;
+import org.olat.core.id.Identity;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="filemetadata")
+@Table(name="o_vfs_metadata")
+public class VFSMetadataImpl implements Persistable, VFSMetadata {
+
+	private static final long serialVersionUID = 1360000029480576628L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+	
+	@Column(name="f_uuid", nullable=false, insertable=true, updatable=true)
+	private String uuid;
+	@Column(name="f_filename", nullable=false, insertable=true, updatable=true)
+	private String filename;
+	@Column(name="f_relative_path", nullable=false, insertable=true, updatable=true)
+	private String relativePath;
+	@Column(name="f_directory", nullable=false, insertable=true, updatable=true)
+	private boolean directory;
+	@Column(name="f_lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date fileLastModified;
+	@Column(name="f_size", nullable=false, insertable=true, updatable=true)
+	private long fileSize;
+	@Column(name="f_uri", nullable=false, insertable=true, updatable=true)
+	private String uri;
+	@Column(name="f_uri_protocol", nullable=false, insertable=true, updatable=true)
+	private String protocol;
+
+	@Column(name="f_cannot_thumbnails", nullable=true, insertable=true, updatable=true)
+	private Boolean cannotGenerateThumbnails;
+	@Column(name="f_download_count", nullable=true, insertable=true, updatable=true)
+	private int downloadCount;
+	
+	@Column(name="f_comment", nullable=true, insertable=true, updatable=true)
+	private String comment;
+	@Column(name="f_title", nullable=true, insertable=true, updatable=true)
+	private String title;
+	@Column(name="f_publisher", nullable=true, insertable=true, updatable=true)
+	private String publisher;
+	@Column(name="f_creator", nullable=true, insertable=true, updatable=true)
+	private String creator;
+	@Column(name="f_source", nullable=true, insertable=true, updatable=true)
+	private String source;
+	@Column(name="f_city", nullable=true, insertable=true, updatable=true)
+	private String city;
+	@Column(name="f_pages", nullable=true, insertable=true, updatable=true)
+	private String pages;
+	@Column(name="f_language", nullable=true, insertable=true, updatable=true)
+	private String language;
+	@Column(name="f_url", nullable=true, insertable=true, updatable=true)
+	private String url;
+	@Column(name="f_pub_month", nullable=true, insertable=true, updatable=true)
+	private String pubMonth;
+	@Column(name="f_pub_year", nullable=true, insertable=true, updatable=true)
+	private String pubYear;
+
+	@ManyToOne(targetEntity=LicenseTypeImpl.class, optional=true)
+	@JoinColumn(name="fk_license_type", nullable=true, insertable=true, updatable=true)
+	private LicenseType licenseType;
+	@Column(name="f_license_type_name", nullable=true, insertable=true, updatable=true)
+	private String licenseTypeName;
+	@Column(name="f_license_text", nullable=true, insertable=true, updatable=true)
+	private String licenseText;
+	@Column(name="f_licensor", nullable=true, insertable=true, updatable=true)
+	private String licensor;
+
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="f_locked_date", nullable=true, insertable=true, updatable=true)
+	private Date lockedDate;
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=true)
+	@JoinColumn(name="fk_locked_identity", nullable=true, insertable=true, updatable=true)
+	private Identity lockedBy;
+	@Column(name="f_locked", nullable=true, insertable=true, updatable=true)
+	private boolean locked;
+	
+	@ManyToOne(targetEntity=IdentityImpl.class,fetch=FetchType.LAZY,optional=true)
+	@JoinColumn(name="fk_author", nullable=true, insertable=true, updatable=true)
+	private Identity author;
+	
+	@Column(name="f_m_path_keys", nullable=true, insertable=true, updatable=true)
+	private String materializedPathKeys;
+	
+	@ManyToOne(targetEntity=VFSMetadataImpl.class,fetch=FetchType.LAZY,optional=true)
+	@JoinColumn(name="fk_parent", nullable=true, insertable=true, updatable=true)
+	private VFSMetadata parent;
+
+	@Override
+	public Long getKey() {
+		return key;
+	}
+	
+	public void setKey(Long key) {
+		this.key = key;
+	}
+	
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+	
+	public void setCreationDate(Date date) {
+		creationDate = date;
+	}
+	
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+	
+	@Override
+	public void setLastModified(Date date) {
+		lastModified = date;
+	}
+
+	@Override
+	public String getFilename() {
+		return filename;
+	}
+
+	public void setFilename(String filename) {
+		this.filename = filename;
+	}
+
+	@Override
+	public Date getFileLastModified() {
+		return fileLastModified;
+	}
+	
+	public void setFileLastModified(Date date) {
+		fileLastModified = date;
+	}
+
+	@Override
+	public long getFileSize() {
+		return fileSize;
+	}
+
+	public void setFileSize(long fileSize) {
+		this.fileSize = fileSize;
+	}
+
+	@Override
+	public boolean isDirectory() {
+		return directory;
+	}
+
+	public void setDirectory(boolean directory) {
+		this.directory = directory;
+	}
+
+	@Override
+	public String getIconCssClass() {
+		String cssClass;
+		if (isDirectory()) {
+			cssClass =  CSSHelper.CSS_CLASS_FILETYPE_FOLDER;
+		} else {
+			cssClass = CSSHelper.createFiletypeIconCssClassFor(getFilename());
+		}
+		return cssClass;
+	}
+
+	@Override
+	public String getRelativePath() {
+		return relativePath;
+	}
+
+	public void setRelativePath(String relativePath) {
+		this.relativePath = relativePath;
+	}
+
+	@Override
+	public String getUri() {
+		return uri;
+	}
+
+	public void setUri(String uri) {
+		this.uri = uri;
+	}
+
+	@Override
+	public String getProtocol() {
+		return protocol;
+	}
+
+	public void setProtocol(String protocol) {
+		this.protocol = protocol;
+	}
+
+	@Override
+	public int getDownloadCount() {
+		return downloadCount;
+	}
+
+	public void setDownloadCount(int downloadCount) {
+		this.downloadCount = downloadCount;
+	}
+
+	@Override
+	public String getComment() {
+		return comment;
+	}
+
+	@Override
+	public void setComment(String comment) {
+		this.comment = comment;
+	}
+
+	@Override
+	public String getCreator() {
+		return creator;
+	}
+
+	@Override
+	public void setCreator(String creator) {
+		this.creator = creator;
+	}
+
+	@Override
+	public String getCity() {
+		return city;
+	}
+
+	@Override
+	public void setCity(String city) {
+		this.city = city;
+	}
+
+	@Override
+	public String getLanguage() {
+		return language;
+	}
+
+	@Override
+	public void setLanguage(String language) {
+		this.language = language;
+	}
+
+	@Override
+	public String getUuid() {
+		return uuid;
+	}
+
+	public void setUuid(String uuid) {
+		this.uuid = uuid;
+	}
+
+	@Override
+	public String getTitle() {
+		return title;
+	}
+
+	@Override
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	@Override
+	public String getPublisher() {
+		return publisher;
+	}
+
+	@Override
+	public void setPublisher(String publisher) {
+		this.publisher = publisher;
+	}
+
+	@Override
+	public String getSource() {
+		return source;
+	}
+
+	@Override
+	public void setSource(String source) {
+		this.source = source;
+	}
+
+	@Override
+	public String getPages() {
+		return pages;
+	}
+
+	@Override
+	public void setPages(String pages) {
+		this.pages = pages;
+	}
+
+	@Override
+	public String getUrl() {
+		return url;
+	}
+
+	@Override
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	public String getPubMonth() {
+		return pubMonth;
+	}
+
+	public void setPubMonth(String pubMonth) {
+		this.pubMonth = pubMonth;
+	}
+
+	public String getPubYear() {
+		return pubYear;
+	}
+
+	public void setPubYear(String pubYear) {
+		this.pubYear = pubYear;
+	}
+
+	@Override
+	public String[] getPublicationDate() {
+		return new String[] { pubYear, pubMonth };
+	}
+	
+	@Override
+	public void setPublicationDate(String month, String year) {
+		setPubMonth(month);
+		setPubYear(year);
+	}
+
+	@Override
+	public LicenseType getLicenseType() {
+		return licenseType;
+	}
+
+	@Override
+	public void setLicenseType(LicenseType licenseType) {
+		this.licenseType = licenseType;
+	}
+
+	@Override
+	public String getLicenseTypeName() {
+		return licenseTypeName;
+	}
+
+	@Override
+	public void setLicenseTypeName(String licenseTypeName) {
+		this.licenseTypeName = licenseTypeName;
+	}
+
+	@Override
+	public String getLicenseText() {
+		return licenseText;
+	}
+
+	@Override
+	public void setLicenseText(String licenseText) {
+		this.licenseText = licenseText;
+	}
+
+	@Override
+	public String getLicensor() {
+		return licensor;
+	}
+
+	@Override
+	public void setLicensor(String licensor) {
+		this.licensor = licensor;
+	}
+
+	@Override
+	public Identity getAuthor() {
+		return author;
+	}
+
+	@Override
+	public void setAuthor(Identity author) {
+		this.author = author;
+	}
+
+	@Override
+	public Date getLockedDate() {
+		return lockedDate;
+	}
+
+	@Override
+	public void setLockedDate(Date lockedDate) {
+		this.lockedDate = lockedDate;
+	}
+
+	@Override
+	public Identity getLockedBy() {
+		return lockedBy;
+	}
+
+	@Override
+	public void setLockedBy(Identity lockedBy) {
+		this.lockedBy = lockedBy;
+	}
+
+	@Override
+	public boolean isLocked() {
+		return locked;
+	}
+
+	@Override
+	public void setLocked(boolean locked) {
+		this.locked = locked;
+	}
+
+	public Boolean getCannotGenerateThumbnails() {
+		return cannotGenerateThumbnails;
+	}
+
+	public void setCannotGenerateThumbnails(Boolean cannotGenerateThumbnails) {
+		this.cannotGenerateThumbnails = cannotGenerateThumbnails;
+	}
+
+	public String getMaterializedPathKeys() {
+		return materializedPathKeys;
+	}
+
+	public void setMaterializedPathKeys(String materializedPathKeys) {
+		this.materializedPathKeys = materializedPathKeys;
+	}
+
+	public VFSMetadata getParent() {
+		return parent;
+	}
+
+	public void setParent(VFSMetadata parent) {
+		this.parent = parent;
+	}
+
+	@Override
+	public void copyValues(VFSMetadata fromMeta) {
+		setAuthor(fromMeta.getAuthor());
+		setComment(fromMeta.getComment());
+		setCity(fromMeta.getCity());
+		setCreator(fromMeta.getCreator());
+		setLanguage(fromMeta.getLanguage());
+		setPages(fromMeta.getPages());
+		setPublicationDate(fromMeta.getPublicationDate()[1], fromMeta.getPublicationDate()[0]);
+		setPublisher(fromMeta.getPublisher());
+		setSource(fromMeta.getSource());
+		setTitle(fromMeta.getTitle());
+		setUrl(fromMeta.getUrl());
+		setLicenseType(fromMeta.getLicenseType());
+		setLicenseTypeName(fromMeta.getLicenseTypeName());
+		setLicensor(fromMeta.getLicensor());
+		setLicenseText(fromMeta.getLicenseText());
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 7386459 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof VFSMetadataImpl) {
+			VFSMetadataImpl meta = (VFSMetadataImpl)obj;
+			return getKey().equals(meta.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataRefImpl.java b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataRefImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac9fedd3fdc9c2d460373802c507166bfa5f6aa0
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/model/VFSMetadataRefImpl.java
@@ -0,0 +1,59 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.model;
+
+import org.olat.core.commons.services.vfs.VFSMetadataRef;
+
+/**
+ * 
+ * Initial date: 13 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class VFSMetadataRefImpl implements VFSMetadataRef {
+	
+	private final Long key;
+	
+	public VFSMetadataRefImpl(Long key) {
+		this.key = key;
+	}
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	@Override
+	public int hashCode() {
+		return key == null ? 860573 : key.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(obj == this) {
+			return true;
+		}
+		if(obj instanceof VFSMetadataRefImpl) {
+			VFSMetadataRefImpl ref = (VFSMetadataRefImpl)obj;
+			return key != null && key.equals(ref.key);
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/vfs/model/VFSThumbnailMetadataImpl.java b/src/main/java/org/olat/core/commons/services/vfs/model/VFSThumbnailMetadataImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..e78a3821fff0d300011eac27b152ae65478f5a32
--- /dev/null
+++ b/src/main/java/org/olat/core/commons/services/vfs/model/VFSThumbnailMetadataImpl.java
@@ -0,0 +1,206 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.model;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSThumbnailMetadata;
+import org.olat.core.id.Persistable;
+
+/**
+ * 
+ * Initial date: 14 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+@Entity(name="vfsthumbnail")
+@Table(name="o_vfs_thumbnail")
+@NamedQuery(name="loadThumbnailByKey", query="select thumb from vfsthumbnail thumb where thumb.key=:thumbnailKey")
+public class VFSThumbnailMetadataImpl implements Persistable, VFSThumbnailMetadata {
+
+	private static final long serialVersionUID = -6149766723435220075L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	@Column(name="id", nullable=false, unique=true, insertable=true, updatable=false)
+	private Long key;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="creationdate", nullable=false, insertable=true, updatable=false)
+	private Date creationDate;
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="lastmodified", nullable=false, insertable=true, updatable=true)
+	private Date lastModified;
+
+	@Column(name="f_size", nullable=false, insertable=true, updatable=true)
+	private long fileSize;  
+	@Column(name="f_max_width", nullable=false, insertable=true, updatable=true)
+	private int maxWidth;
+	@Column(name="f_max_height", nullable=false, insertable=true, updatable=true)
+	private int maxHeight;
+	@Column(name="f_final_width", nullable=false, insertable=true, updatable=true)
+	private int finalWidth;
+	@Column(name="f_final_height", nullable=false, insertable=true, updatable=true)
+	private int finalHeight;
+	@Column(name="f_fill", nullable=false, insertable=true, updatable=true)
+	private boolean fill = false;
+	@Column(name="f_filename", nullable=false, insertable=true, updatable=true)
+	private String filename;
+	
+	@ManyToOne(targetEntity=VFSMetadataImpl.class,fetch=FetchType.LAZY,optional=false)
+	@JoinColumn(name="fk_metadata", nullable=false, insertable=true, updatable=false)
+	private VFSMetadata owner;
+	
+	@Override
+	public Long getKey() {
+		return key;
+	}
+
+	public void setKey(Long key) {
+		this.key = key;
+	}
+
+	@Override
+	public Date getCreationDate() {
+		return creationDate;
+	}
+
+	public void setCreationDate(Date creationDate) {
+		this.creationDate = creationDate;
+	}
+
+	@Override
+	public Date getLastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public void setLastModified(Date lastModified) {
+		this.lastModified = lastModified;
+	}
+
+	@Override
+	public long getFileSize() {
+		return fileSize;
+	}
+
+	public void setFileSize(long fileSize) {
+		this.fileSize = fileSize;
+	}
+
+	@Override
+	public int getMaxWidth() {
+		return maxWidth;
+	}
+	
+	public void setMaxWidth(int maxWidth) {
+		this.maxWidth = maxWidth;
+	}
+
+	@Override
+	public int getMaxHeight() {
+		return maxHeight;
+	}
+	
+	public void setMaxHeight(int maxHeight) {
+		this.maxHeight = maxHeight;
+	}
+
+	@Override
+	public int getFinalWidth() {
+		return finalWidth;
+	}
+	
+	public void setFinalWidth(int finalWidth) {
+		this.finalWidth = finalWidth;
+	}
+
+	@Override
+	public int getFinalHeight() {
+		return finalHeight;
+	}
+	
+	public void setFinalHeight(int finalHeight) {
+		this.finalHeight = finalHeight;
+	}
+
+	@Override
+	public boolean isFill() {
+		return fill;
+	}
+	
+	public void setFill(boolean fill) {
+		this.fill = fill;
+	}
+
+	@Override
+	public String getFilename() {
+		return filename;
+	}
+	
+	public void setFilename(String filename) {
+		this.filename = filename;
+	}
+
+	@Override
+	public VFSMetadata getOwner() {
+		return owner;
+	}
+
+	public void setOwner(VFSMetadata owner) {
+		this.owner = owner;
+	}
+
+	@Override
+	public int hashCode() {
+		return getKey() == null ? 7386459 : getKey().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if(this == obj) {
+			return true;
+		}
+		if(obj instanceof VFSThumbnailMetadataImpl) {
+			VFSThumbnailMetadataImpl meta = (VFSThumbnailMetadataImpl)obj;
+			return getKey().equals(meta.getKey());
+		}
+		return false;
+	}
+
+	@Override
+	public boolean equalsByPersistableKey(Persistable persistable) {
+		return equals(persistable);
+	}
+}
diff --git a/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java b/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java
index 638b6f9ad739d30758a29f37ad305005e4db12da..e3fbac08d6f15237f7a9bb9ba225a08bb934027d 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java
+++ b/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResource.java
@@ -25,6 +25,8 @@ import java.nio.file.Files;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Date;
 
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.commons.services.webdav.servlets.ConcurrentDateFormat;
 import org.olat.core.commons.services.webdav.servlets.WebResource;
 import org.olat.core.logging.OLog;
@@ -34,7 +36,6 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * 
@@ -151,9 +152,7 @@ public class VFSResource implements WebResource {
 	public void increaseDownloadCount() {
 		try {
 			if (item instanceof VFSLeaf && item.canMeta() == VFSConstants.YES) {
-				MetaInfo meta = item.getMetaInfo();
-				meta.increaseDownloadCount();
-				meta.write();
+				CoreSpringFactory.getImpl(VFSRepositoryService.class).increaseDownloadCount(item);
 			}
 		} catch (Exception e) {
 			log.error("Cannot increase download counter: " + item, e);
diff --git a/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResourceRoot.java b/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResourceRoot.java
index 2be220bae49a5cb0cc85842f99eca47bb1748d36..c63fbe62ded5c1c34bc5852a5cdde4934518720f 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResourceRoot.java
+++ b/src/main/java/org/olat/core/commons/services/webdav/manager/VFSResourceRoot.java
@@ -33,6 +33,8 @@ import org.olat.core.commons.services.license.LicenseService;
 import org.olat.core.commons.services.license.ui.LicenseUIFactory;
 import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.commons.services.webdav.servlets.WebResource;
 import org.olat.core.commons.services.webdav.servlets.WebResourceRoot;
 import org.olat.core.id.Identity;
@@ -47,7 +49,6 @@ import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.VFSStatus;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.core.util.vfs.version.Versionable;
 import org.olat.core.util.vfs.version.VersionsManager;
 
@@ -222,12 +223,14 @@ public class VFSResourceRoot implements WebResourceRoot  {
 		}
 		
 		if(identity != null && childLeaf.canMeta() == VFSConstants.YES) {
-			MetaInfo infos = childLeaf.getMetaInfo();
-			if(infos != null && !infos.hasAuthorIdentity()) {
+			VFSMetadata infos = childLeaf.getMetaInfo();
+			if(infos != null && infos.getAuthor() != null) {
 				infos.setAuthor(identity);
 				addLicense(infos, identity);
-				infos.clearThumbnails();
-				//infos.write(); the clearThumbnails call write()
+				
+				VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+				vfsRepositoryService.updateMetadata(infos);
+				vfsRepositoryService.resetThumbnails(childLeaf);
 			}
 		}
 		
@@ -243,13 +246,13 @@ public class VFSResourceRoot implements WebResourceRoot  {
 		return true;
 	}
 
-	private void addLicense(MetaInfo meta, Identity identity) {
+	private void addLicense(VFSMetadata meta, Identity identity) {
 		LicenseService licenseService = CoreSpringFactory.getImpl(LicenseService.class);
 		LicenseModule licenseModule = CoreSpringFactory.getImpl(LicenseModule.class);
 		FolderLicenseHandler licenseHandler = CoreSpringFactory.getImpl(FolderLicenseHandler.class);
 		if (licenseModule.isEnabled(licenseHandler)) {
 			License license = licenseService.createDefaultLicense(licenseHandler, identity);
-			meta.setLicenseTypeKey(String.valueOf(license.getLicenseType().getKey()));
+			meta.setLicenseType(license.getLicenseType());
 			meta.setLicenseTypeName(license.getLicenseType().getName());
 			meta.setLicensor(license.getLicensor());
 			meta.setLicenseText(LicenseUIFactory.getLicenseText(license));
diff --git a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java
index 8a5ea6acd92a3c1ca15328b170d8fedb0b3f9208..48d6fdb1e0d5649f200fa2b796ec0325d95f3ee1 100644
--- a/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java
+++ b/src/main/java/org/olat/core/commons/services/webdav/servlets/WebDAVDispatcherImpl.java
@@ -1212,7 +1212,7 @@ public class WebDAVDispatcherImpl
 
             // Generating lock id
             
-            String lockToken = lockManager.generateLockToken(lock, usess.getIdentity().getKey());
+            String lockToken = lockManager.generateLockToken(lock, usess.getIdentity());
             if (resource.isDirectory() && lock.getDepth() == maxDepth) {
 
                 // Locking a collection (and all its member resources)
diff --git a/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java b/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java
index f5f09ba03a276feae1fe849b36cf495e3cb91462..ac1f8e3404870d8d50912ceb2aa38b64822ba78d 100644
--- a/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java
+++ b/src/main/java/org/olat/core/gui/control/generic/folder/OlatRootFolderTreeModel.java
@@ -28,12 +28,12 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.components.tree.GenericTreeModel;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.vfs.LocalFolderImpl;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.filters.VFSItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * This TreeModel is intended for OlatRootFolderImpl and OlatRootFileImpl.
@@ -109,13 +109,13 @@ public class OlatRootFolderTreeModel extends GenericTreeModel {
 	 */
 	private OlatRootFolderTreeNode createNode(VFSItem item) {
 		OlatRootFolderTreeNode node = new OlatRootFolderTreeNode(item, this);
-		MetaInfo meta = item.getMetaInfo();
+		VFSMetadata meta = item.getMetaInfo();
 		if (meta != null) {
 			String title = meta.getTitle();
 			if (StringHelper.containsNonWhitespace(title)) {
 				node.setTitle(title);
 			} else {
-				node.setTitle(meta.getName());
+				node.setTitle(meta.getFilename());
 			}
 		}
 		node.setUserObject(item.getRelPath());
diff --git a/src/main/java/org/olat/core/util/ZipUtil.java b/src/main/java/org/olat/core/util/ZipUtil.java
index 3b2f960db3a2708649549ff2b17414e25c29be90..7c224da97841772b9b8d65857af9d486d992466b 100644
--- a/src/main/java/org/olat/core/util/ZipUtil.java
+++ b/src/main/java/org/olat/core/util/ZipUtil.java
@@ -49,6 +49,9 @@ import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
 
 import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
+import org.olat.core.commons.services.vfs.manager.MetaInfoReader;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Roles;
 import org.olat.core.logging.OLATRuntimeException;
@@ -66,7 +69,6 @@ import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSLockManager;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.filters.SystemItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.core.util.vfs.version.Versionable;
 
 /**
@@ -149,12 +151,15 @@ public class ZipUtil {
 	 * @param targetDir	The directory to unzip the file to
 	 * @param the identity of who unzip the file
 	 * @param versioning enabled or not
-	 * @return	True if successfull, false otherwise
+	 * @return	True if successful, false otherwise
 	 */
 	public static boolean unzip(VFSLeaf zipLeaf, VFSContainer targetDir, Identity identity, boolean versioning) {
-		InputStream in = zipLeaf.getInputStream();
-		boolean unzipped = unzip(in, targetDir, identity, versioning);
-		FileUtils.closeSafely(in);
+		boolean unzipped = false;
+		try(InputStream in = zipLeaf.getInputStream()) {
+			unzipped = unzip(in, targetDir, identity, versioning);
+		} catch(Exception e) {
+			log.error("", e);
+		}
 		return unzipped;
 	}	
 
@@ -164,9 +169,12 @@ public class ZipUtil {
 	 * @param targetDir	The directory to unzip the file to
 	 * @param the identity of who unzip the file
 	 * @param versioning enabled or not
-	 * @return	True if successfull, false otherwise
+	 * @return	True if successful, false otherwise
 	 */
 	private static boolean unzip(InputStream in, VFSContainer targetDir, Identity identity, boolean versioning) {
+		
+		VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+		
 		try(ZipInputStream oZip = new ZipInputStream(in)) {
 			// unzip files
 			ZipEntry oEntr = oZip.getNextEntry();
@@ -202,9 +210,9 @@ public class ZipUtil {
 							VFSLeaf newEntry = (VFSLeaf)createIn.resolve(name);
 							if(newEntry == null) {
 								newEntry = createIn.createChildLeaf(name);
-								OutputStream out = newEntry.getOutputStream(false);
-								if (!FileUtils.copy(oZip, out)) return false;
-								FileUtils.closeSafely(out);
+								if (!copy(oZip, newEntry)) {
+									return false;
+								}
 							} else if (newEntry instanceof Versionable) {
 								Versionable versionable = (Versionable)newEntry;
 								if(versionable.getVersions().isVersioned()) {
@@ -212,25 +220,25 @@ public class ZipUtil {
 								}
 							}
 							if(newEntry != null && identity != null && newEntry.canMeta() == VFSConstants.YES) {
-								MetaInfo info = newEntry.getMetaInfo();
+								VFSMetadata info = newEntry.getMetaInfo();
 								if(info != null) {
 									info.setAuthor(identity);
-									info.write();
+									vfsRepositoryService.updateMetadata(info);
 								}
 							}
 							
 						} else {
 							VFSLeaf newEntry = createIn.createChildLeaf(name);
 							if (newEntry != null) {
-								OutputStream out = newEntry.getOutputStream(false);
-								if (!FileUtils.copy(oZip, out)) return false;
-								FileUtils.closeSafely(out);
+								if (!copy(oZip, newEntry)) {
+									return false;
+								}
 					
 								if(identity != null && newEntry.canMeta() == VFSConstants.YES) {
-									MetaInfo info = newEntry.getMetaInfo();
+									VFSMetadata info = newEntry.getMetaInfo();
 									if(info != null) {
 										info.setAuthor(identity);
-										info.write();
+										vfsRepositoryService.updateMetadata(info);
 									}
 								}
 							}
@@ -246,6 +254,15 @@ public class ZipUtil {
 		return true;
 	} // unzip
 	
+	private static boolean copy(ZipInputStream oZip, VFSLeaf newEntry) {
+		try(OutputStream out = newEntry.getOutputStream(false)) {
+			return FileUtils.copy(oZip, out);
+		} catch(Exception e) {
+			log.error("", e);
+			return false;
+		}
+	}
+	
 	/**
 	 * Unzip a file to a directory using the versioning system of VFS and a ZIP
 	 * library which handle encoding errors. It may results in special characters
@@ -322,7 +339,7 @@ public class ZipUtil {
 							VFSLeaf newEntry = (VFSLeaf)createIn.resolve(name);
 							if(newEntry == null) {
 								newEntry = createIn.createChildLeaf(name);
-								if (!VFSManager.copyContent(new ShieldInputStream(oZip), newEntry)) {
+								if (!copyShielded(oZip, newEntry)) {
 									return false;
 								}
 							} else if (newEntry instanceof Versionable) {
@@ -335,7 +352,7 @@ public class ZipUtil {
 						} else {
 							VFSLeaf newEntry = createIn.createChildLeaf(name);
 							if (newEntry != null) {
-								if (!VFSManager.copyContent(new ShieldInputStream(oZip), newEntry)) {
+								if (!copyShielded(oZip, newEntry)) {
 									return false;
 								}
 								unzipMetadata(identity, extra, newEntry);
@@ -352,22 +369,41 @@ public class ZipUtil {
 		return true;
 	} // unzip
 	
+	private static boolean copyShielded(net.sf.jazzlib.ZipInputStream oZip, VFSLeaf newEntry) {
+		try(InputStream in = new ShieldInputStream(oZip)) {
+			return VFSManager.copyContent(in, newEntry);
+		} catch(Exception e) {
+			log.error("", e);
+			return false;
+		}
+	}
+	
+	private static boolean copyShielded(VFSLeaf leaf, ZipOutputStream out) {
+		try(OutputStream sout = new ShieldOutputStream(out)) {
+			return VFSManager.copyContent(leaf, sout);
+		} catch(Exception e) {
+			log.error("", e);
+			return false;
+		}
+	}
+	
 	private static void unzipMetadata(Identity identity, byte[] extra, VFSLeaf newEntry) {
 		if(newEntry.canMeta() != VFSConstants.YES
 				|| ((extra == null || extra.length == 0) && identity == null)) {
 			return;
 		}
 		
-		MetaInfo info = newEntry.getMetaInfo();
+		VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+		VFSMetadata info = vfsRepositoryService.getMetadataFor(newEntry);
 		if(info == null) {
 			return;
 		}
 		if(extra != null) {
-			info.writeBinary(extra);
+			vfsRepositoryService.copyBinaries(info, extra);
 		} else {
 			info.setAuthor(identity);
-			info.write();
 		}
+		vfsRepositoryService.updateMetadata(info);
 	}
 	
 	/**
@@ -511,6 +547,9 @@ public class ZipUtil {
 			// try it windows style, backslash is also valid format
 			st = new StringTokenizer(subDirPath, "\\", false);
 		}
+		
+		VFSRepositoryService vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+		
 		VFSContainer currentPath = base;
 		while (st.hasMoreTokens()) {
 			String nextSubpath = st.nextToken();
@@ -522,10 +561,10 @@ public class ZipUtil {
 				vfsSubpath = currentPath.createChildContainer(nextSubpath);
 				if (vfsSubpath == null) return null;
 				if (identity != null && vfsSubpath.canMeta() == VFSConstants.YES) {
-					MetaInfo info = vfsSubpath.getMetaInfo();
+					VFSMetadata info = vfsSubpath.getMetaInfo();
 					if(info != null) {
 						info.setAuthor(identity);
-						info.write();
+						vfsRepositoryService.updateMetadata(info);
 					}
 				}
 			}
@@ -658,11 +697,11 @@ public class ZipUtil {
 				VFSLeaf leaf = (VFSLeaf)vfsItem;
 				ZipEntry entry = new ZipEntry(itemName);
 				if(leaf.canMeta() == VFSConstants.YES) {
-					byte[] metadata = leaf.getMetaInfo().readBinary();
+					byte[] metadata = MetaInfoReader.toBinaries(leaf.getMetaInfo());
 					entry.setExtra(metadata);
 				}
 				out.putNextEntry(entry);
-				VFSManager.copyContent(leaf, new ShieldOutputStream(out));
+				copyShielded(leaf, out);
 				out.closeEntry();
 			}
 		} catch (IOException ioe) {
diff --git a/src/main/java/org/olat/core/util/mail/ui/SendDocumentsByEMailController.java b/src/main/java/org/olat/core/util/mail/ui/SendDocumentsByEMailController.java
index 958076bb1659c648746b6c419927ee2f54e334cb..2a96f228b0475d798cf9fb65ec8ed7f2fc851c0f 100644
--- a/src/main/java/org/olat/core/util/mail/ui/SendDocumentsByEMailController.java
+++ b/src/main/java/org/olat/core/util/mail/ui/SendDocumentsByEMailController.java
@@ -22,6 +22,7 @@ package org.olat.core.util.mail.ui;
 import java.io.File;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
@@ -36,6 +37,7 @@ import org.olat.core.commons.modules.bc.commands.FolderCommand;
 import org.olat.core.commons.modules.bc.commands.FolderCommandHelper;
 import org.olat.core.commons.modules.bc.commands.FolderCommandStatus;
 import org.olat.core.commons.modules.bc.components.FolderComponent;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -72,7 +74,6 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -110,8 +111,7 @@ public class SendDocumentsByEMailController extends FormBasicController implemen
 	private BaseSecurity securityManager;
 
 	public SendDocumentsByEMailController(UserRequest ureq, WindowControl wControl) {
-		super(ureq, wControl, null, Util.createPackageTranslator(MetaInfo.class, ureq.getLocale(),
-				Util.createPackageTranslator(MailModule.class, ureq.getLocale())));
+		super(ureq, wControl, null, Util.createPackageTranslator(MailModule.class, ureq.getLocale()));
 		setBasePackage(MailModule.class);
 
 		allowAttachments = !FolderConfig.getSendDocumentLinkOnly();
@@ -229,7 +229,7 @@ public class SendDocumentsByEMailController extends FormBasicController implemen
 		attachments = new ArrayList<>();
 		long fileSize = 0l;
 		for (VFSLeaf file : files) {
-			MetaInfo infos = null;
+			VFSMetadata infos = null;
 			if (file.canMeta() == VFSConstants.YES) {
 				infos = file.getMetaInfo();
 			}
@@ -270,7 +270,7 @@ public class SendDocumentsByEMailController extends FormBasicController implemen
 		bodyElement.setValue(bodySb.toString());
 	}
 
-	protected void appendToSubject(VFSLeaf file, MetaInfo infos, StringBuilder sb) {
+	protected void appendToSubject(VFSLeaf file, VFSMetadata infos, StringBuilder sb) {
 		if (sb.length() > 0)
 			sb.append(", ");
 		if (infos != null && StringHelper.containsNonWhitespace(infos.getTitle())) {
@@ -280,11 +280,11 @@ public class SendDocumentsByEMailController extends FormBasicController implemen
 		}
 	}
 
-	protected void appendMetadatas(VFSLeaf file, MetaInfo infos, StringBuilder sb) {
+	protected void appendMetadatas(VFSLeaf file, VFSMetadata infos, StringBuilder sb) {
 		if (infos == null) {
 			appendMetadata("mf.filename", file.getName(), sb);
 		} else {
-			appendMetadata("mf.filename", infos.getName(), sb);
+			appendMetadata("mf.filename", infos.getFilename(), sb);
 			String title = infos.getTitle();
 			if (StringHelper.containsNonWhitespace(title)) {
 				appendMetadata("mf.title", title, sb);
@@ -322,15 +322,15 @@ public class SendDocumentsByEMailController extends FormBasicController implemen
 			if (StringHelper.containsNonWhitespace(url)) {
 				appendMetadata("mf.url", url, sb);
 			}
-			String author = userManager.getUserDisplayName(infos.getAuthorIdentityKey());
+			String author = userManager.getUserDisplayName(infos.getAuthor());
 			if (StringHelper.containsNonWhitespace(author)) {
 				appendMetadata("mf.author", author, sb);
 			}
 			String size = Formatter.formatBytes(file.getSize());
 			appendMetadata("mf.size", size, sb);
-			long lastModifiedDate = infos.getLastModified();
-			if (lastModifiedDate > 0) {
-				appendMetadata("mf.lastModified", StringHelper.formatLocaleDate(lastModifiedDate, getLocale()), sb);
+			Date lastModifiedDate = infos.getFileLastModified();
+			if (lastModifiedDate != null) {
+				appendMetadata("mf.lastModified", Formatter.getInstance(getLocale()).formatDate(lastModifiedDate), sb);
 			}
 			String type = FolderHelper.extractFileType(file.getName(), getLocale());
 			if (StringHelper.containsNonWhitespace(type)) {
@@ -347,7 +347,7 @@ public class SendDocumentsByEMailController extends FormBasicController implemen
 		sb.append(translate(i18nKey)).append(": ").append(value).append('\n');
 	}
 
-	protected void appendPublicationDate(MetaInfo infos, StringBuilder sb) {
+	protected void appendPublicationDate(VFSMetadata infos, StringBuilder sb) {
 		String[] publicationDate = infos.getPublicationDate();
 		if (publicationDate == null || publicationDate.length != 2)
 			return;
@@ -411,7 +411,7 @@ public class SendDocumentsByEMailController extends FormBasicController implemen
 
 		List<Identity> invalidTos = getInvalidToAddressesFromTextBoxList();
 		userListBox.clearError();
-		if (invalidTos.size() > 0) {
+		if (!invalidTos.isEmpty()) {
 			String[] invalidTosArray = new String[invalidTos.size()];
 			userListBox.setErrorKey("mailhelper.error.addressinvalid", invalidTos.toArray(invalidTosArray));
 			allOk &= false;
diff --git a/src/main/java/org/olat/core/util/vfs/AbstractVirtualContainer.java b/src/main/java/org/olat/core/util/vfs/AbstractVirtualContainer.java
index fc0a5d37589791c964fd4de178a29683cbdc93b8..3fb6e3653944102612d23a1487bc23bf83d9d70d 100644
--- a/src/main/java/org/olat/core/util/vfs/AbstractVirtualContainer.java
+++ b/src/main/java/org/olat/core/util/vfs/AbstractVirtualContainer.java
@@ -26,8 +26,8 @@
 
 package org.olat.core.util.vfs;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.util.vfs.filters.VFSItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 
 /**
@@ -121,7 +121,7 @@ public abstract class AbstractVirtualContainer implements VFSContainer {
 	}
 
 	@Override
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		return null;
 	}
 
diff --git a/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java b/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
index ddf28ef0e0419e1d8cc7e74bedd83052fcf80135..c2f68241d8fea393ef305dab56ff3493c047b01f 100644
--- a/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/LocalFileImpl.java
@@ -39,6 +39,7 @@ import java.nio.file.Path;
 
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.logging.AssertException;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
@@ -140,7 +141,7 @@ public class LocalFileImpl extends LocalImpl implements VFSLeaf, Versionable {
 			CoreSpringFactory.getImpl(VersionsManager.class).rename(this, newname);
 		}
 		if(canMeta() == VFSConstants.YES) {
-			getMetaInfo().rename(newname);
+			CoreSpringFactory.getImpl(VFSRepositoryService.class).rename(getMetaInfo(), newname);
 		}
 		boolean ren = f.renameTo(nf);
 		if (ren) {
@@ -162,7 +163,7 @@ public class LocalFileImpl extends LocalImpl implements VFSLeaf, Versionable {
 		}
 		// Versioning makes a copy of the metadata, delete metadata after it
 		if(canMeta() == VFSConstants.YES) {
-			getMetaInfo().delete();
+			CoreSpringFactory.getImpl(VFSRepositoryService.class).deleteMetadata(getMetaInfo());
 		}
 		return deleteBasefile();
 	}
@@ -170,7 +171,7 @@ public class LocalFileImpl extends LocalImpl implements VFSLeaf, Versionable {
 	@Override
 	public VFSStatus deleteSilently() {
 		if(canMeta() == VFSConstants.YES) {
-			getMetaInfo().delete();
+			CoreSpringFactory.getImpl(VFSRepositoryService.class).deleteMetadata(getMetaInfo());
 		}
 		CoreSpringFactory.getImpl(VersionsManager.class).delete(this, true);
 		return deleteBasefile();
diff --git a/src/main/java/org/olat/core/util/vfs/LocalFolderImpl.java b/src/main/java/org/olat/core/util/vfs/LocalFolderImpl.java
index 0be27157ac49189a02f4641050f146c01ed2cba3..37b4e727fc0bf5126c21aa62e429c2c1b5e3c0be 100644
--- a/src/main/java/org/olat/core/util/vfs/LocalFolderImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/LocalFolderImpl.java
@@ -36,6 +36,7 @@ import java.util.List;
 
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.logging.AssertException;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
@@ -204,10 +205,12 @@ public class LocalFolderImpl extends LocalImpl implements VFSContainer {
 			if(s instanceof Versionable && ((Versionable)s).getVersions().isVersioned()) {
 				((Versionable)s).getVersions().copy(this);
 			}
-			
-			boolean copyMetaData = source.canMeta() == VFSConstants.YES && canMeta() == VFSConstants.YES;
-			if(copyMetaData) {
-				source.getMetaInfo().moveCopyToDir(this, false);
+
+			if(s.canMeta() == VFSConstants.YES) {
+				VFSItem target = resolve(sourcename);
+				if(target instanceof VFSLeaf && target.canMeta() == VFSConstants.YES) {
+					CoreSpringFactory.getImpl(VFSRepositoryService.class).copyTo(s, (VFSLeaf)target, this);
+				}
 			}
 		} else {
 			throw new RuntimeException("neither a leaf nor a container!");
@@ -259,7 +262,7 @@ public class LocalFolderImpl extends LocalImpl implements VFSContainer {
 		}
 		// Versioning makes a copy of the metadata, delete metadata after it
 		if(canMeta() == VFSConstants.YES) {
-			getMetaInfo().deleteAll();
+			CoreSpringFactory.getImpl(VFSRepositoryService.class).deleteMetadata(getMetaInfo());
 		}
 
 		// now delete the directory itself
@@ -278,7 +281,7 @@ public class LocalFolderImpl extends LocalImpl implements VFSContainer {
 		}
 		
 		if(canMeta() == VFSConstants.YES) {
-			getMetaInfo().deleteAll();
+			CoreSpringFactory.getImpl(VFSRepositoryService.class).deleteMetadata(getMetaInfo());
 		}
 		CoreSpringFactory.getImpl(VersionsManager.class).delete(this, true);
 		// now delete the directory itself
diff --git a/src/main/java/org/olat/core/util/vfs/LocalImpl.java b/src/main/java/org/olat/core/util/vfs/LocalImpl.java
index f0fba5b8d95d519210c5ff10c561108e4b25e91c..9951b01f5dc8b7db740815c749bd06f15dfc9e47 100644
--- a/src/main/java/org/olat/core/util/vfs/LocalImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/LocalImpl.java
@@ -31,9 +31,9 @@ import java.nio.file.Path;
 
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
-import org.olat.core.util.vfs.meta.MetaInfoFactory;
 
 /**
  * <P>
@@ -137,18 +137,20 @@ public abstract class LocalImpl implements VFSItem, JavaIOItem {
 	
 	@Override
 	public VFSStatus canMeta() {
-		Path bFile = getBasefile().toPath();
+		File f = getBasefile();
+		Path bFile = f.toPath();
 		Path bcRoot = FolderConfig.getCanonicalRootPath();
 		return bFile.startsWith(bcRoot)
 				&& !bFile.startsWith(FolderConfig.getCanonicalMetaRootPath())
 				&& !bFile.startsWith(FolderConfig.getCanonicalVersionRootPath())
+				&& !f.isHidden()
 				? VFSConstants.YES : VFSConstants.NO;
 	}
 
 	@Override
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		if(canMeta() == VFSConstants.YES) {
-			return CoreSpringFactory.getImpl(MetaInfoFactory.class).createMetaInfoFor(getBasefile());
+			return CoreSpringFactory.getImpl(VFSRepositoryService.class).getMetadataFor(getBasefile());
 		}
 		return null;
 	}
diff --git a/src/main/java/org/olat/core/util/vfs/MergeSource.java b/src/main/java/org/olat/core/util/vfs/MergeSource.java
index 2e448325ceb096af480ef018477476bb876e062a..bd2726d21e617aaf1df87e880186756a3ba91055 100644
--- a/src/main/java/org/olat/core/util/vfs/MergeSource.java
+++ b/src/main/java/org/olat/core/util/vfs/MergeSource.java
@@ -30,11 +30,11 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.logging.AssertException;
 import org.olat.core.util.CodeHelper;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
 import org.olat.core.util.vfs.filters.VFSItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * Initial Date: 23.06.2005 <br>
@@ -313,7 +313,7 @@ public class MergeSource extends AbstractVirtualContainer {
 	}
 
 	@Override
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		return null;
 	}
 
diff --git a/src/main/java/org/olat/core/util/vfs/NamedContainerImpl.java b/src/main/java/org/olat/core/util/vfs/NamedContainerImpl.java
index 37dc55d280d1686327b863ad1b14bdfbfcbc76b7..2d5b8232d4f45ba37d17c055b647ef328d9bf425 100644
--- a/src/main/java/org/olat/core/util/vfs/NamedContainerImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/NamedContainerImpl.java
@@ -28,9 +28,9 @@ package org.olat.core.util.vfs;
 
 import java.util.List;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
 import org.olat.core.util.vfs.filters.VFSItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * <P>
@@ -160,7 +160,7 @@ public class NamedContainerImpl extends AbstractVirtualContainer {
 	}
 
 	@Override
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		return getDelegate().getMetaInfo();
 	}
 
diff --git a/src/main/java/org/olat/core/util/vfs/NamedLeaf.java b/src/main/java/org/olat/core/util/vfs/NamedLeaf.java
index 1cf4fcd8d345448801f8df5dcc01189f31d9f67e..931f82fa27d32232d1d614c7f12fa3467bbcdfe3 100644
--- a/src/main/java/org/olat/core/util/vfs/NamedLeaf.java
+++ b/src/main/java/org/olat/core/util/vfs/NamedLeaf.java
@@ -22,8 +22,8 @@ package org.olat.core.util.vfs;
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * <h3>Description:</h3>
@@ -154,7 +154,7 @@ public class NamedLeaf implements VFSLeaf {
 	}
 
 	@Override
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		return delegate.getMetaInfo();
 	}
 
diff --git a/src/main/java/org/olat/core/util/vfs/VFSItem.java b/src/main/java/org/olat/core/util/vfs/VFSItem.java
index a44f0d0c35296bbd0f7188ff2d00b95aaed5dd09..b9cbee85dae572fb3d3e4fd4e5309e6fb42a44bc 100644
--- a/src/main/java/org/olat/core/util/vfs/VFSItem.java
+++ b/src/main/java/org/olat/core/util/vfs/VFSItem.java
@@ -26,8 +26,8 @@
 
 package org.olat.core.util.vfs;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 
 /**
@@ -138,7 +138,7 @@ public interface VFSItem {
 	/**
 	 * @return The metadata if the item can metadata or null if not
 	 */
-	public MetaInfo getMetaInfo();
+	public VFSMetadata getMetaInfo();
 	
 	/**
 	 * Get the local security callback for this item.
diff --git a/src/main/java/org/olat/core/util/vfs/VFSLockManager.java b/src/main/java/org/olat/core/util/vfs/VFSLockManager.java
index 9772db9c81406fbb809977ba6f62cd1063d32e45..e326475c15e9c127bc78afe653db542511b0f73a 100644
--- a/src/main/java/org/olat/core/util/vfs/VFSLockManager.java
+++ b/src/main/java/org/olat/core/util/vfs/VFSLockManager.java
@@ -19,10 +19,10 @@
  */
 package org.olat.core.util.vfs;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Roles;
 import org.olat.core.util.vfs.lock.LockInfo;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * The manager which locks / unlokcs the files
@@ -47,7 +47,7 @@ public interface VFSLockManager {
 	 * @param loadedInfo The up-to-date meta info
 	 * @return
 	 */
-	public boolean isLocked(VFSItem item, MetaInfo loadedInfo);
+	public boolean isLocked(VFSItem item, VFSMetadata loadedInfo);
 	
 	/**
 	 * 
@@ -66,7 +66,7 @@ public interface VFSLockManager {
 	 * @param roles
 	 * @return true if there is a lock owned by someone else, or there is a WebDAV lock on the item
 	 */
-	public boolean isLockedForMe(VFSItem item, MetaInfo loadedInfo, Identity me, Roles roles);
+	public boolean isLockedForMe(VFSItem item, VFSMetadata loadedInfo, Identity me, Roles roles);
 	
 	public LockInfo getLock(VFSItem item);
 	
@@ -86,5 +86,5 @@ public interface VFSLockManager {
 	/**
 	 * Method the generate the Lock-Token
 	 */
-	public String generateLockToken(LockInfo lock, Long identityKey);
+	public String generateLockToken(LockInfo lock, Identity identity);
 }
diff --git a/src/main/java/org/olat/core/util/vfs/VFSManager.java b/src/main/java/org/olat/core/util/vfs/VFSManager.java
index 1163bcf1ee0b6fdfe6a9b5cd0c9a832df56c22e9..69de8d3ea226e5caa5dd26ad76c4b9889db10526 100644
--- a/src/main/java/org/olat/core/util/vfs/VFSManager.java
+++ b/src/main/java/org/olat/core/util/vfs/VFSManager.java
@@ -36,7 +36,9 @@ import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.FileUtils;
@@ -60,6 +62,11 @@ public class VFSManager {
 		return new LocalFileImpl(file, null);
 	}
 	
+	public static LocalFileImpl olatRootLeaf(String relPath, String filename) {
+		File file = new File(FolderConfig.getCanonicalRoot() + relPath, filename);
+		return new LocalFileImpl(file, null);
+	}
+	
 	public static LocalFolderImpl olatRootContainer(String fileRelPath) {
 		File file = new File(FolderConfig.getCanonicalRoot() + fileRelPath);
 		return new LocalFolderImpl(file, null);
@@ -723,8 +730,7 @@ public class VFSManager {
 			}
 			
 			if(withMetadata && source.canMeta() == VFSConstants.YES && target.canMeta() == VFSConstants.YES) {
-				VFSContainer parentTargetContainer = target.getParentContainer();
-				source.getMetaInfo().moveCopyToDir(parentTargetContainer, false);
+				CoreSpringFactory.getImpl(VFSRepositoryService.class).copyTo(source, target, target.getParentContainer());
 			}
 		} else {
 			// source or target is null
diff --git a/src/main/java/org/olat/core/util/vfs/lock/LockInfo.java b/src/main/java/org/olat/core/util/vfs/lock/LockInfo.java
index 07bff06e5745bffbd42fdc72b923a274c5fb4559..037d085e1a12be52dc3c5cbaa1799ca43c3fb978 100644
--- a/src/main/java/org/olat/core/util/vfs/lock/LockInfo.java
+++ b/src/main/java/org/olat/core/util/vfs/lock/LockInfo.java
@@ -30,6 +30,7 @@ import org.olat.core.commons.services.webdav.servlets.FastHttpDateFormat;
 import org.olat.core.commons.services.webdav.servlets.WebDAVDispatcherImpl;
 import org.olat.core.commons.services.webdav.servlets.WebResource;
 import org.olat.core.commons.services.webdav.servlets.XMLWriter;
+import org.olat.core.id.Identity;
 
 /**
  * Holds a lock information.
@@ -44,7 +45,7 @@ public class LockInfo {
     private String scope = "exclusive";
     private int depth = 0;
     private String owner = "";
-    private Vector<String> tokens = new Vector<String>();
+    private Vector<String> tokens = new Vector<>();
     private long expiresAt = 0;
     private Date creationDate = new Date();
     
@@ -58,6 +59,12 @@ public class LockInfo {
     	this.vfsLock = vfsLock;
     	this.webdavLock = webdavLock;
     }
+    
+    public LockInfo(Identity lockedBy, boolean webdavLock, boolean vfsLock) {
+    	this.lockedBy = lockedBy == null ? null : lockedBy.getKey();
+    	this.vfsLock = vfsLock;
+    	this.webdavLock = webdavLock;
+    }
 
     public Long getLockedBy() {
 		return lockedBy;
diff --git a/src/main/java/org/olat/core/util/vfs/lock/VFSLockManagerImpl.java b/src/main/java/org/olat/core/util/vfs/lock/VFSLockManagerImpl.java
index 089be1ac8c94ec8eed7080e34b158fadbeee01d4..f4de045c6485beae41e17ef879ef7c2202067206 100644
--- a/src/main/java/org/olat/core/util/vfs/lock/VFSLockManagerImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/lock/VFSLockManagerImpl.java
@@ -28,6 +28,9 @@ import java.util.UUID;
 import java.util.Vector;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.commons.services.webdav.manager.VFSResource;
 import org.olat.core.commons.services.webdav.servlets.WebResource;
 import org.olat.core.helpers.Settings;
@@ -37,12 +40,16 @@ import org.olat.core.util.vfs.LocalImpl;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLockManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
-import org.olat.core.util.vfs.meta.MetaInfoFileImpl;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 @Service("vfsLockManager")
 public class VFSLockManagerImpl implements VFSLockManager {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private VFSRepositoryService fileService;
 
     /**
      * Repository of the locks put on single resources.
@@ -70,6 +77,9 @@ public class VFSLockManagerImpl implements VFSLockManager {
      * Value : LockInfo
      */
     private Vector<LockInfo> collectionLocks = new Vector<>();
+    
+    @Autowired
+    private VFSRepositoryService vfsRepositoryService;
 
 	@Override
 	public boolean isLocked(VFSItem item) {
@@ -77,7 +87,7 @@ public class VFSLockManagerImpl implements VFSLockManager {
 	}
 
 	@Override
-	public boolean isLocked(VFSItem item, MetaInfo loadedInfo) {
+	public boolean isLocked(VFSItem item, VFSMetadata loadedInfo) {
 		File file = extractFile(item);
     	if(file != null && fileLocks.containsKey(file)) {
     		LockInfo lock = fileLocks.get(file);
@@ -89,7 +99,7 @@ public class VFSLockManagerImpl implements VFSLockManager {
             }
     	}
 
-		Long lockedBy = getMetaLockedBy(item, loadedInfo);
+		Identity lockedBy = getMetaLockedBy(item, loadedInfo);
 		return (lockedBy != null);
 	}
 	
@@ -102,7 +112,7 @@ public class VFSLockManagerImpl implements VFSLockManager {
 	 * @return true If the lock owner is someone else or if it's a WebDAV lock
 	 */
 	@Override
-	public boolean isLockedForMe(VFSItem item, MetaInfo loadedInfo, Identity me, Roles roles) {
+	public boolean isLockedForMe(VFSItem item, VFSMetadata loadedInfo, Identity me, Roles roles) {
 		File file = extractFile(item);
     	if(file != null && fileLocks.containsKey(file)) {
     		LockInfo lock = fileLocks.get(file);
@@ -115,13 +125,13 @@ public class VFSLockManagerImpl implements VFSLockManager {
             }
     	}
 
-		Long lockedBy = getMetaLockedBy(item, loadedInfo);
-		return (lockedBy != null && !lockedBy.equals(me.getKey()));
+		Identity lockedBy = getMetaLockedBy(item, loadedInfo);
+		return (lockedBy != null && !lockedBy.getKey().equals(me.getKey()));
 	}
 
-	private Long getMetaLockedBy(VFSItem item, MetaInfo loadedInfo) {
-		MetaInfoFileImpl info = loadedInfo == null ? getMetaInfo(item) : (MetaInfoFileImpl)loadedInfo;
-		Long lockedBy = null;
+	private Identity getMetaLockedBy(VFSItem item, VFSMetadata loadedInfo) {
+		VFSMetadata info = loadedInfo == null ? getMetaInfo(item) : loadedInfo;
+		Identity lockedBy = null;
 		if(info != null) {
 			if(!info.isLocked()) {
 				return null;
@@ -131,22 +141,24 @@ public class VFSLockManagerImpl implements VFSLockManager {
 		return lockedBy;
 	}
 	
-	private MetaInfoFileImpl getMetaInfo(VFSItem item) {
-		MetaInfo info = null;
+	private VFSMetadata getMetaInfo(VFSItem item) {
+		VFSMetadata info = null;
 		if (item != null && item.canMeta() == VFSConstants.YES) {
-			info = item.getMetaInfo();
+			info = fileService.getMetadataFor(item);
 		}
-		return (MetaInfoFileImpl)info;
+		return info;
 	}
 	
     @Override
 	public boolean lock(VFSItem item, Identity identity, Roles roles) {
 		if (item != null && item.canMeta() == VFSConstants.YES) {
-			MetaInfoFileImpl info = (MetaInfoFileImpl)item.getMetaInfo();
-			info.setLockedBy(identity.getKey());
+			VFSMetadata info = item.getMetaInfo();
+			info.setLockedBy(identity);
 			info.setLockedDate(new Date());
 			info.setLocked(true);
-			info.write();
+			vfsRepositoryService.updateMetadata(info);
+			dbInstance.commit();
+			System.out.println("lock:" + info.getKey());
 			
 			File file = extractFile(item);
 			if(file != null && fileLocks.containsKey(file)) {
@@ -169,13 +181,14 @@ public class VFSLockManagerImpl implements VFSLockManager {
 	@Override
 	public boolean unlock(VFSItem item, Identity identity, Roles roles) {
 		if (item != null && item.canMeta() == VFSConstants.YES) {
-			MetaInfoFileImpl info = (MetaInfoFileImpl)item.getMetaInfo();
+			VFSMetadata info = item.getMetaInfo();
 			if(info == null) return false;
 			
 			info.setLockedBy(null);
 			info.setLockedDate(null);
 			info.setLocked(false);
-			info.write();
+			vfsRepositoryService.updateMetadata(info);
+			dbInstance.commit();
 			
 			boolean unlocked = false;
 			File file = extractFile(item);
@@ -223,7 +236,7 @@ public class VFSLockManagerImpl implements VFSLockManager {
      */
     public LockInfo getVFSLock(WebResource resource) {
     	VFSItem item = extractItem(resource);
-		MetaInfoFileImpl info = getMetaInfo(item);
+    	VFSMetadata info = getMetaInfo(item);
     	if(info != null && info.isLocked()) {
         	File file = extractFile(item);
         	LockInfo lock = null;
@@ -237,7 +250,7 @@ public class VFSLockManagerImpl implements VFSLockManager {
 	    		lock = new LockInfo(info.getLockedBy(), false, true);
 	    		lock.setWebResource(resource);
 	    		lock.setCreationDate(info.getLockedDate());
-	    		lock.setOwner(Settings.getServerContextPathURI() + "/Identity/" + info.getLockedBy());
+	    		lock.setOwner(Settings.getServerContextPathURI() + "/Identity/" + info.getLockedBy().getKey());
 	    		lock.setDepth(1);
 	    		lock.addToken(generateLockToken(lock, info.getLockedBy()));
 	    		fileLocks.put(file, lock);
@@ -257,11 +270,11 @@ public class VFSLockManagerImpl implements VFSLockManager {
     		}
     	}
     	
-    	MetaInfoFileImpl info = getMetaInfo(item);
+    	VFSMetadata info = getMetaInfo(item);
     	if(info != null && info.isLocked()) {
     		LockInfo lock = new LockInfo(info.getLockedBy(), false, true);
     		lock.setCreationDate(info.getLockedDate());
-    		lock.setOwner(Settings.getServerContextPathURI() + "/Identity/" + info.getLockedBy());
+    		lock.setOwner(Settings.getServerContextPathURI() + "/Identity/" + info.getLockedBy().getKey());
     		lock.setDepth(1);
     		lock.addToken(generateLockToken(lock, info.getLockedBy()));
     		fileLocks.put(file, lock);
@@ -276,7 +289,7 @@ public class VFSLockManagerImpl implements VFSLockManager {
 	 * + time in milliseconds seems to disturb the WebDAV client of Mac OS X 10.9
 	 */
 	@Override
-    public String generateLockToken(LockInfo lock, Long identityKey) {
+    public String generateLockToken(LockInfo lock, Identity identity) {
     	return UUID.randomUUID().toString();
     }
     
@@ -367,8 +380,8 @@ public class VFSLockManagerImpl implements VFSLockManager {
     	//check if someone else as not set a lock on the resource
     	if(resource instanceof VFSResource) {
     		VFSResource vfsResource = (VFSResource)resource;
-    		Long lockedBy = getMetaLockedBy(vfsResource.getItem(), null);
-    		if(lockedBy != null && !lockedBy.equals(identity.getKey())) {
+    		Identity lockedBy = getMetaLockedBy(vfsResource.getItem(), null);
+    		if(lockedBy != null && !lockedBy.getKey().equals(identity.getKey())) {
     			return true;
     		}
     	}
diff --git a/src/main/java/org/olat/core/util/vfs/meta/MetaInfo.java b/src/main/java/org/olat/core/util/vfs/meta/MetaInfo.java
deleted file mode 100644
index 64894dc857f954631b31367b8dfb837494d00939..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/core/util/vfs/meta/MetaInfo.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <p>
-* Licensed under the Apache License, Version 2.0 (the "License"); <br>
-* you may not use this file except in compliance with the License.<br>
-* You may obtain a copy of the License at
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <p>
-* Unless required by applicable law or agreed to in writing,<br>
-* software distributed under the License is distributed on an "AS IS" BASIS, <br>
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
-* See the License for the specific language governing permissions and <br>
-* limitations under the License.
-* <p>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-package org.olat.core.util.vfs.meta;
-
-import java.util.Date;
-
-import org.olat.core.id.Identity;
-import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.VFSLeaf;
-
-public interface MetaInfo {
-
-	/**
-	 * Rename the given meta info file
-	 * 
-	 * @param meta
-	 * @param newName
-	 */
-	public void rename(String newName);
-
-	/**
-	 * Move/Copy the given meta info to the target directory.
-	 * 
-	 * @param targetDir
-	 * @param move
-	 */
-	public void moveCopyToDir(VFSItem target, boolean move);
-
-	/**
-	 * Delete all associated meta info including sub files/directories
-	 */
-	public void deleteAll();
-
-	/**
-	 * Copy values from froMeta into this object except name, download count and UUID.
-	 * 
-	 * @param fromMeta the metadata to copy from
-	 */
-	public void copyValues(MetaInfo fromMeta);
-
-	/**
-	 * Delete this meta info
-	 * 
-	 * @return True upon success.
-	 */
-	public boolean delete();
-	
-	/**
-	 * @return the unique id of the file or create one if it previously not exists
-	 */
-	public String getUUID();
-	
-	/**
-	 * @return The last modification date of the metadata
-	 */
-	public Date getMetaLastModified();
-
-	/**
-	 * @return name of the initial author (OLAT user name)
-	 */
-	public String getAuthor();
-	
-	public Long getAuthorIdentityKey();
-	
-	public boolean hasAuthorIdentity();
-
-	/**
-	 * Corresponds to DublinCore:description
-	 * 
-	 * @return comment
-	 */
-	public String getComment();
-
-	public String getName();
-
-	/**
-	 * DublinCore compatible
-	 */
-	public String getTitle();
-
-	/**
-	 * DublinCore compatible
-	 */
-	public String getPublisher();
-
-	/**
-	 * In this context, the creator is the person or organization that is
-	 * primarily responsible for making the content. The author, by contrast, is
-	 * an OLAT user who uploaded the file.
-	 * 
-	 * @return The writer of the resource
-	 */
-	public String getCreator();
-
-	/**
-	 * DublinCore compatible
-	 */
-	public String getSource();
-
-	/**
-	 * @return The city or location of publication
-	 */
-	public String getCity();
-
-	public String getPages();
-
-	/**
-	 * DublinCore compatible
-	 */
-	public String getLanguage();
-
-	public String getUrl();
-
-	/**
-	 * Corresponds to DublinCore:date + refinement
-	 * 
-	 * @return The date in form of a {year, month} array.
-	 */
-	public String[] getPublicationDate();
-
-	/**
-	 * @return True if this is a directory
-	 */
-	public boolean isDirectory();
-
-	/**
-	 * @return Last modified timestamp
-	 */
-	public long getLastModified();
-
-	/**
-	 * @return size of file
-	 */
-	public long getSize();
-
-	/**
-	 * @return formatted representation of size of file
-	 */
-	public String getFormattedSize();
-	
-	public void setAuthor(Identity identy);
-
-	/**
-	 * @param string
-	 */
-	public void setComment(String string);
-
-	public void setTitle(String title);
-
-	public void setPublisher(String publisher);
-
-	public void setCreator(String creator);
-
-	public void setSource(String source);
-
-	public void setCity(String city);
-
-	public void setPages(String pages);
-
-	public void setLanguage(String language);
-
-	public void setUrl(String url);
-
-	public void setPublicationDate(String month, String year);
-	
-	public String getLicenseTypeKey();
-	
-	public void setLicenseTypeKey(String key);
-	
-	public String getLicenseTypeName();
-	
-	public void setLicenseTypeName(String name);
-	
-	public String getLicenseText();
-	
-	public void setLicenseText(String text);
-	
-	public String getLicensor();
-	
-	public void setLicensor(String licensor);
-	
-	
-	public boolean isThumbnailAvailable();
-	
-	/**
-	 * The 
-	 * 
-	 * 
-	 * @param maxWidth
-	 * @param maxHeight
-	 * @param fill True if you want to fill the surface defined above (overflow are cut)
-	 * @return
-	 */
-	public VFSLeaf getThumbnail(int maxWidth, int maxHeight, boolean fill);
-	
-	/**
-	 * Thumbnails are cleared and the metadata file is written on the disk
-	 */
-	public void clearThumbnails();
-
-	/**
-	 * Writes the meta data to file. If no changes have been made, does not
-	 * write anything.
-	 * 
-	 * @return True upon success.
-	 */
-	public boolean write();
-	
-	public byte[] readBinary();
-	
-	public void writeBinary(byte[] data);
-
-	/**
-	 * Increases the download count by one.
-	 */
-	public void increaseDownloadCount();
-
-	/**
-	 * @return The download count
-	 */
-	public int getDownloadCount();
-	
-	/**
-	 * @return An icon css class that represents this type of file
-	 */
-	public String getIconCssClass();
-}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/util/vfs/meta/MetaInfoFactory.java b/src/main/java/org/olat/core/util/vfs/meta/MetaInfoFactory.java
deleted file mode 100644
index 2113d653e3d267b3a65dc4695e5b929b97d9faf6..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/core/util/vfs/meta/MetaInfoFactory.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <p>
-* Licensed under the Apache License, Version 2.0 (the "License"); <br>
-* you may not use this file except in compliance with the License.<br>
-* You may obtain a copy of the License at
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <p>
-* Unless required by applicable law or agreed to in writing,<br>
-* software distributed under the License is distributed on an "AS IS" BASIS, <br>
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
-* See the License for the specific language governing permissions and <br>
-* limitations under the License.
-* <p>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.
-*/
-package org.olat.core.util.vfs.meta;
-
-import java.io.File;
-
-import org.olat.core.CoreSpringFactory;
-import org.olat.core.commons.modules.bc.FolderConfig;
-import org.olat.core.commons.modules.bc.FolderLicenseHandler;
-import org.olat.core.commons.services.license.License;
-import org.olat.core.commons.services.license.LicenseHandler;
-import org.olat.core.commons.services.license.LicenseService;
-import org.olat.core.commons.services.license.LicenseType;
-import org.olat.core.commons.services.thumbnail.ThumbnailService;
-import org.olat.core.id.Identity;
-import org.olat.core.logging.OLog;
-import org.olat.core.logging.Tracing;
-import org.olat.core.util.StringHelper;
-import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.VFSManager;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-@Service("metaInfoFactory")
-public class MetaInfoFactory {
-	
-	private static final OLog log = Tracing.createLoggerFor(MetaInfoFactory.class);
-	
-	@Autowired
-	private ThumbnailService thumbnailService;
-
-	public MetaInfo createMetaInfoFor(VFSItem path) {
-		File file = getOriginFile(path);
-		return createMetaInfoFor(file);
-	}
-	
-	public MetaInfo createMetaInfoFor(File file) {
-		if(file == null) {
-			return null;
-		}
-		String canonicalMetaPath = getCanonicalMetaPath(file, false);
-		if (canonicalMetaPath == null) {
-			return null;
-		}
-		
-		File metaFile = new File(canonicalMetaPath);
-		MetaInfoFileImpl meta = new MetaInfoFileImpl(metaFile, file);
-		meta.setThumbnailService(thumbnailService);
-		return meta;
-	}
-	
-	public MetaInfo createMetaShadowInfoFor(File metaFile) {
-		MetaInfoFileImpl meta = new MetaInfoFileImpl(metaFile);
-		meta.setThumbnailService(thumbnailService);
-		return meta;
-	}
-	
-	public void resetThumbnails(File metafile) {
-		try {
-			new MetaInfoFileImpl(metafile).clearThumbnails();
-		} catch (Exception e) {
-			log.error("", e);
-		}
-	}
-	
-	public void deleteMetaFile(File file) {
-		String metaPath = getCanonicalMetaPath(file, true);
-		File metaFile = new File(metaPath);
-		new MetaInfoFileImpl(metaFile).delete();
-	}
-	
-	protected static String getCanonicalMetaPath(VFSItem olatRelPathImpl) {
-		File f = getOriginFile(olatRelPathImpl);
-		return getCanonicalMetaPath(f, false);
-	}
-	
-	protected static String getCanonicalMetaPath(File originFile, boolean lenient) {
-		String canonicalMetaPath;
-		if (originFile == null || (!lenient && !originFile.exists())) {
-			canonicalMetaPath = null;
-		} else {
-			String relPath = FolderConfig.getCanonicalRootPath().relativize(originFile.toPath()).toString();
-			StringBuilder metaSb = new StringBuilder(128);
-			metaSb.append(FolderConfig.getCanonicalMetaRoot()).append("/").append(relPath);
-			if (originFile.isDirectory()) {
-				metaSb.append("/.xml");
-			} else {
-				metaSb.append(".xml");
-			}
-			canonicalMetaPath = metaSb.toString();
-		}
-		return canonicalMetaPath;
-	}
-	
-	private static File getOriginFile(VFSItem olatRelPathImpl) {
-		String relPath = olatRelPathImpl.getRelPath();
-		if(relPath == null) return null;
-		return VFSManager.olatRootFile(relPath);
-	}
-	
-	/**
-	 * Get the license of the MetaInfo
-	 *
-	 * @param meta
-	 * @return the license or null if no license is stored in the MetaInfo
-	 */
-	public License getLicense(MetaInfo meta) {
-		License license = null;
-		boolean hasLicense = meta != null && StringHelper.containsNonWhitespace(meta.getLicenseTypeName());
-		if (hasLicense) { 
-			String licenseTypeName = meta.getLicenseTypeName();
-			LicenseService licenseService = CoreSpringFactory.getImpl(LicenseService.class);
-			LicenseType licenseType = licenseService.loadLicenseTypeByName(licenseTypeName);
-			if (licenseType == null) {
-				licenseType = licenseService.createLicenseType(licenseTypeName);
-				licenseType.setText(meta.getLicenseText());
-				licenseService.saveLicenseType(licenseType);
-			}
-			license = licenseService.createLicense(licenseType);
-			license.setLicensor(meta.getLicensor());
-			if (licenseService.isFreetext(licenseType)) {
-				license.setFreetext(meta.getLicenseText());
-			}
-		}
-		return license;
-	}
-	
-	/**
-	 * Get the license of the MetaInfo or create a new default license:
-	 *
-	 * @param meta
-	 * @param itentity the current user
-	 * @return
-	 */
-	public License getOrCreateLicense(MetaInfo meta, Identity itentity) {
-		LicenseHandler licenseHandler = CoreSpringFactory.getImpl(FolderLicenseHandler.class);
-		License license = getLicense(meta);
-		if (license == null) {
-			LicenseService licenseService = CoreSpringFactory.getImpl(LicenseService.class);
-			license = licenseService.createDefaultLicense(licenseHandler, itentity);
-		}
-		return license;
-	}
-}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/util/vfs/meta/MetaInfoFileImpl.java b/src/main/java/org/olat/core/util/vfs/meta/MetaInfoFileImpl.java
deleted file mode 100644
index 91e6f91106e260fed15dc0cf5da0a07ae0da6b58..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/core/util/vfs/meta/MetaInfoFileImpl.java
+++ /dev/null
@@ -1,1070 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <p>
-* Licensed under the Apache License, Version 2.0 (the "License"); <br>
-* you may not use this file except in compliance with the License.<br>
-* You may obtain a copy of the License at
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <p>
-* Unless required by applicable law or agreed to in writing,<br>
-* software distributed under the License is distributed on an "AS IS" BASIS, <br>
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
-* See the License for the specific language governing permissions and <br>
-* limitations under the License.
-* <p>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.  
-* <p>
-*/ 
-
-package org.olat.core.util.vfs.meta;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStreamWriter;
-import java.io.Serializable;
-import java.io.StringReader;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-import org.olat.basesecurity.BaseSecurity;
-import org.olat.core.CoreSpringFactory;
-import org.olat.core.commons.services.thumbnail.CannotGenerateThumbnailException;
-import org.olat.core.commons.services.thumbnail.FinalSize;
-import org.olat.core.commons.services.thumbnail.ThumbnailService;
-import org.olat.core.gui.util.CSSHelper;
-import org.olat.core.id.Identity;
-import org.olat.core.logging.OLog;
-import org.olat.core.logging.Tracing;
-import org.olat.core.util.FileUtils;
-import org.olat.core.util.Formatter;
-import org.olat.core.util.StringHelper;
-import org.olat.core.util.filter.FilterFactory;
-import org.olat.core.util.vfs.LocalFileImpl;
-import org.olat.core.util.vfs.VFSContainer;
-import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.VFSLeaf;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXParseException;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * Initial Date:  08.07.2003
- *
- * @author Mike Stock<br>
- * 
- * Comment:
- * Meta files are in a shadow filesystem with the same directory structure as
- * their original files. Meta info for directories is stored in a file called
- * ".xml" residing in the respective directory. Meta info for files is stored
- * in a file with ".xml" appended to its filename.
- * 
- */
-public class MetaInfoFileImpl implements MetaInfo, Serializable {
-
-	private static final long serialVersionUID = -3521950840094823106L;
-	private static final OLog log = Tracing.createLoggerFor(MetaInfoFileImpl.class);
-  
-	private static transient SAXParser saxParser;
-	static {
-		try {
-			saxParser = SAXParserFactory.newInstance().newSAXParser();
-		} catch(Exception ex) {
-			log.error("", ex);
-		}
-	}
-	
-	// meta data
-	private String uuid;
-	private Long authorIdentKey;
-	private Long lockedByIdentKey;
-	private String comment = "";
-	private String title;
-	private String publisher;
-	private String creator;
-	private String source;
-	private String city;
-	private String pages;
-	private String language;
-	private String url;
-	private String pubMonth;
-	private String pubYear;
-	private String licenseTypeKey;
-	private String licenseTypeName;
-	private String licenseText;
-	private String licensor;
-	private Date lockedDate;
-	private int downloadCount;
-	private boolean locked;
-	
-	// internal
-	private File originFile = null;
-	private File metaFile = null;
-	
-	private boolean cannotGenerateThumbnail = false;
-	private final List<Thumbnail> thumbnails = new ArrayList<>();
-	private transient ThumbnailService thumbnailService;
-	
-
-	// make it a factory
-	private MetaInfoFileImpl() { 
-		//
-	}
-
-	public MetaInfoFileImpl(File metaFile) { 
-		this.metaFile = metaFile;
-		parseSAX(metaFile);
-	}
-	
-	protected MetaInfoFileImpl(File metaFile, File originFile) { 
-		this.metaFile = metaFile;
-		this.originFile = originFile;
-		// set
-		if (!parseSAX(metaFile)) {
-			metaFile.getParentFile().mkdirs();
-			if(uuid == null) {
-				generateUUID();
-			}
-			write();
-		}
-	}
-
-	public void setThumbnailService(ThumbnailService thumbnailService) {
-		this.thumbnailService = thumbnailService;
-	}
-	
-	/**
-	 * Rename the given meta info file
-	 * 
-	 * @param meta
-	 * @param newName
-	 */
-	@Override
-	public void rename(String newName) {
-		// rename meta info file name
-		if (isDirectory()) { // rename the directory, which is the parent of the actual ".xml" file
-			File metaFileDirectory = metaFile.getParentFile();
-			if(!metaFileDirectory.renameTo(new File(metaFileDirectory.getParentFile(), newName))) {
-				log.error("Cannot rename meta directory: " + this, null);
-			}
-		} else if(!metaFile.renameTo(new File(metaFile.getParentFile(), newName + ".xml"))) { // rename the file
-			log.error("Cannot rename meta file: " + this, null);
-		}
-	}
-	
-	/**
-	 * Move/Copy the given meta info to the target directory.
-	 * @param targetDir
-	 * @param move
-	 */
-	@Override
-	public void moveCopyToDir(VFSItem target, boolean move) {
-		File fSource = metaFile;
-		File fTarget = new File(MetaInfoFactory.getCanonicalMetaPath(target));
-		if (isDirectory()) { // move/copy whole meta directory
-			fSource = fSource.getParentFile();
-			fTarget = fTarget.getParentFile();
-		} else if (target instanceof VFSContainer) {
-			//getCanonicalMetaPath give the path to the xml file where the metadatas are saved
-			if(fTarget.getName().equals(".xml")) {
-				fTarget = fTarget.getParentFile();
-			}
-		}
-		
-		if (move) {
-			FileUtils.moveFileToDir(fSource, fTarget);
-		} else {
-			//copy
-			Map<String,String> pathToUuid = new HashMap<>();
-			File mTarget = new File(fTarget, fSource.getName());
-			collectUUIDRec(mTarget, pathToUuid);
-			
-			if(FileUtils.copyFileToDir(fSource, fTarget, "copy metadata")) {
-				File endTarget = new File(fTarget, fSource.getName());
-				postMetaCopy(endTarget, pathToUuid);
-			}
-		}
-	}
-	
-	private void collectUUIDRec(File mTarget, Map<String,String> pathToUuid) {
-		try {
-			if(mTarget.exists()) {
-				if(mTarget.isDirectory()) {
-					//TODO
-				} else {
-					MetaInfoFileImpl copyMeta = new MetaInfoFileImpl();
-					copyMeta.metaFile = mTarget;
-					if (copyMeta.parseSAX(mTarget)) {
-						pathToUuid.put(mTarget.getCanonicalPath(), copyMeta.getUUID());
-					}
-				}
-			}
-		} catch (IOException e) {
-			log.error("cannot collect current UUID before copy", e);
-		}
-	}
-
-	private void postMetaCopy(File endTarget, Map<String,String> pathToUuid) {
-		if(!endTarget.exists()) {
-			return;
-		}
-
-		try {
-			if(endTarget.isDirectory()) {
-				for(File subEndTarget:endTarget.listFiles(new XmlFilter())) {
-					postMetaCopy(subEndTarget, pathToUuid);
-				}
-			} else {
-				MetaInfoFileImpl copyMeta = new MetaInfoFileImpl();
-				copyMeta.metaFile = endTarget;
-				if (copyMeta.parseSAX(endTarget)) {
-					String tempUuid = pathToUuid.get(endTarget.getCanonicalPath());
-					if(StringHelper.containsNonWhitespace(tempUuid)) {
-						copyMeta.uuid = tempUuid;
-					} else {
-						copyMeta.generateUUID();
-					}
-					copyMeta.clearThumbnails();//clear write the meta
-				}
-			}
-		} catch (IOException e) {
-			log.error("Cannot generate a new uuid on copy", e);
-		}
-	}
-	
-	/**
-	 * Delete all associated meta info including sub files/directories
-	 * @param meta
-	 */
-	@Override
-	public void deleteAll() {
-		if (isDirectory()) { // delete whole meta directory (where the ".xml" resides within)
-			FileUtils.deleteDirsAndFiles(metaFile.getParentFile(), true, true);
-		} else { // delete this single meta file
-			delete();
-		}
-	}
-	
-	/**
-	 * Copy values from fromMeta into this object except name.
-	 * @param fromMeta
-	 */
-	@Override
-	public void copyValues(MetaInfo fromMeta) {
-		setAuthorIdentKey(fromMeta.getAuthorIdentityKey());
-		setComment(fromMeta.getComment());
-		setCity(fromMeta.getCity());
-		setCreator(fromMeta.getCreator());
-		setLanguage(fromMeta.getLanguage());
-		setPages(fromMeta.getPages());
-		setPublicationDate(fromMeta.getPublicationDate()[1], fromMeta.getPublicationDate()[0]);
-		setPublisher(fromMeta.getPublisher());
-		setSource(fromMeta.getSource());
-		setTitle(fromMeta.getTitle());
-		setUrl(fromMeta.getUrl());
-		setLicenseTypeKey(fromMeta.getLicenseTypeKey());
-		setLicenseTypeName(fromMeta.getLicenseTypeName());
-		setLicensor(fromMeta.getLicensor());
-		setLicenseText(fromMeta.getLicenseText());
-	}
-
-	public boolean isLocked() {
-		return locked;
-	}
-	
-	public void setLocked(boolean locked) {
-		this.locked = locked;
-		if(!locked) {
-			lockedByIdentKey = null;
-			lockedDate = null;
-		}
-	}
-
-	public Identity getLockedByIdentity() {
-		if(lockedByIdentKey != null) {
-			return CoreSpringFactory.getImpl(BaseSecurity.class).loadIdentityByKey(lockedByIdentKey);
-		}
-		return null;
-	}
-
-	public Long getLockedBy() {
-		return lockedByIdentKey;
-	}
-
-	public void setLockedBy(Long lockedBy) {
-		this.lockedByIdentKey = lockedBy;
-	}
-
-	public Date getLockedDate() {
-		return lockedDate;
-	}
-
-	public void setLockedDate(Date lockedDate) {
-		this.lockedDate = lockedDate;
-	}
-
-	/**
-	 * Writes the meta data to file. If no changes have been made,
-	 * does not write anything.
-	 * @return True upon success.
-	 */
-	@Override
-	public boolean write() {
-		if (metaFile == null) return false;
-		
-		try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(metaFile));
-				OutputStreamWriter sw = new OutputStreamWriter(bos, Charset.forName("UTF-8"))) {
-			
-			sw.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-			sw.write("<meta");
-			if(StringHelper.containsNonWhitespace(uuid)) {
-				sw.write(" uuid=\"" + uuid + "\"");
-			}
-			sw.write(">");		
-			sw.write("<author><![CDATA[" + (authorIdentKey == null ? "" : authorIdentKey.toString()) + "]]></author>");		
-			sw.write("<lock locked=\"" + locked + "\"" + (lockedDate == null ? "" : " date=\"" + lockedDate.getTime() + "\"")	+ "><![CDATA[" + (lockedByIdentKey == null ? "" : lockedByIdentKey) + "]]></lock>");
-			sw.write("<comment><![CDATA[" + filterForCData(comment) + "]]></comment>");
-			sw.write("<title><![CDATA[" + filterForCData(title) + "]]></title>");
-			sw.write("<publisher><![CDATA[" + filterForCData(publisher) + "]]></publisher>");
-			sw.write("<creator><![CDATA[" + filterForCData(creator) + "]]></creator>");
-			sw.write("<source><![CDATA[" + filterForCData(source) + "]]></source>");
-			sw.write("<city><![CDATA[" + filterForCData(city) + "]]></city>");
-			sw.write("<pages><![CDATA[" + filterForCData(pages) + "]]></pages>");
-			sw.write("<language><![CDATA[" + filterForCData(language) + "]]></language>");
-			sw.write("<url><![CDATA[" + filterForCData(url) + "]]></url>");
-			sw.write("<licenseTypeKey><![CDATA[" + filterForCData(licenseTypeKey) + "]]></licenseTypeKey>");
-			sw.write("<licenseTypeName><![CDATA[" + filterForCData(licenseTypeName) + "]]></licenseTypeName>");
-			sw.write("<licenseText><![CDATA[" + filterForCData(licenseText) + "]]></licenseText>");
-			sw.write("<licensor><![CDATA[" + filterForCData(licensor) + "]]></licensor>");
-			sw.write("<publicationDate><month><![CDATA[" + (pubMonth != null ? pubMonth.trim() : "") + "]]></month><year><![CDATA[" + (pubYear != null ? pubYear.trim() : "") + "]]></year></publicationDate>");
-			sw.write("<downloadCount><![CDATA[" + downloadCount + "]]></downloadCount>");
-			sw.write("<thumbnails cannotGenerateThumbnail=\"" + cannotGenerateThumbnail + "\">");
-			for(Thumbnail thumbnail:thumbnails) {
-				sw.write("<thumbnail maxHeight=\"");
-				sw.write(Integer.toString(thumbnail.getMaxHeight()));
-				sw.write("\" maxWidth=\"");
-				sw.write(Integer.toString(thumbnail.getMaxWidth()));
-				sw.write("\" finalHeight=\"");
-				sw.write(Integer.toString(thumbnail.getFinalHeight()));
-				sw.write("\" finalWidth=\"");
-				sw.write(Integer.toString(thumbnail.getFinalWidth()));
-				sw.write("\">");
-				sw.write("<![CDATA[" + thumbnail.getThumbnailFile().getName() + "]]>");
-				sw.write("</thumbnail>");
-			}
-			sw.write("</thumbnails>");
-			sw.write("</meta>");
-		} catch (Exception e) { 
-			return false; 
-		}
-		return true;
-	}
-	
-	private String filterForCData(String original) {
-		if(StringHelper.containsNonWhitespace(original)) {
-			return FilterFactory.getXMLValidCharacterFilter().filter(original);
-		}
-		return "";
-	}
-	
-	/**
-	 * Delete this meta info
-	 * 
-	 * @return True upon success.
-	 */
-	@Override
-	public boolean delete() {
-		if (metaFile == null) return false;
-		
-		for(Thumbnail thumbnail:thumbnails) {
-			File file = thumbnail.getThumbnailFile();
-			if(file != null && file.exists()) {
-				try {
-					Files.delete(file.toPath());
-				} catch (IOException e) {
-					log.error("Cannot delete thumbnail", e);
-				}
-			}
-		}
-		
-		boolean deleted = false;
-		try {
-			deleted = Files.deleteIfExists(metaFile.toPath());
-		} catch (IOException e) {
-			log.error("Cannot delete metadata file", e);
-		}
-		return deleted;
-	}
-
-	@Override
-	public byte[] readBinary() {
-		byte[] content = null;
-		try(InputStream in = new FileInputStream(metaFile);
-				BufferedInputStream bis = new BufferedInputStream(in, FileUtils.BSIZE);
-				ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-			FileUtils.cpio(bis, out, "");
-			content = out.toByteArray();
-		} catch(IOException e) {
-			log.error("", e);
-		}
-		return content;
-	}
-
-	@Override
-	public void writeBinary(byte[] data) {
-		if(data == null || data.length == 0) return;
-		
-		try(InputStream in = new ByteArrayInputStream(data)) {
-			synchronized(saxParser) {
-				MetaInfoFileImpl tmp = new MetaInfoFileImpl();
-				tmp.metaFile = metaFile;
-				saxParser.parse(in, new MetaHandler(tmp));
-				copyValues(tmp);
-				// restore last values
-				setDownloadCount(tmp.getDownloadCount());
-				write();
-			}
-		} catch(Exception e) {
-			log.error("", e);
-		}
-	}
-
-	/**
-	 * The parser is synchronized. Normally for such small files, this is
-	 * the quicker way. Creation of a SAXParser is really time consuming.
-	 * An other possibility would be to use a pool of parser.
-	 * @param fMeta
-	 * @return
-	 */
-	private boolean parseSAX(File fMeta) {
-		if (fMeta == null || !fMeta.exists() || fMeta.isDirectory()) return false;
-
-		try(InputStream in = new FileInputStream(fMeta);
-				BufferedInputStream bis = new BufferedInputStream(in, FileUtils.BSIZE)) {
-			//the performance gain of the SAX Parser over the DOM Parser allow
-			//this to be synchronized (factor 5 to 10 quicker)
-			synchronized(saxParser) {
-				saxParser.parse(bis, new MetaHandler(this));
-				if(uuid == null) {
-					generateUUID();
-					write();
-				}
-			}
-		} catch (SAXParseException ex) {
-			if(!parseSAXFiltered(fMeta)) {
-				//OLAT-5383,OLAT-5468: lowered error to warn to reduce error noise
-				log.warn("SAX Parser error while parsing " + fMeta, ex);
-			}
-		} catch(Exception ex) {
-			log.error("Error while parsing " + fMeta, ex);
-		}
-		return true;
-	}
-	
-	/**
-	 * Try to rescue xml files with invalid characters
-	 * @param fMeta
-	 * @return true if rescue is successful
-	 */
-	private boolean parseSAXFiltered(File fMeta) {
-		String original = FileUtils.load(fMeta, "UTF-8");
-		if(original == null) return false;
-		
-		String filtered = FilterFactory.getXMLValidCharacterFilter().filter(original);
-		if(!original.equals(filtered)) {
-			try {
-				synchronized(saxParser) {
-					InputSource in = new InputSource(new StringReader(filtered));
-					saxParser.parse(in, new MetaHandler(this));
-				}
-				write();//update with the new filtered write method
-				return true;
-			} catch (Exception e) {
-				//only a fallback, fail silently
-			}
-		}
-		return false;
-	}
-	
-	/* ------------------------- Getters ------------------------------ */
-	
-	/**
-	 * @return name of the initial author
-	 */
-	@Override
-	public String getAuthor() { 
-		if (authorIdentKey == null) {
-			return "-";
-		} else {
-			try {
-				Identity identity = CoreSpringFactory.getImpl(BaseSecurity.class).loadIdentityByKey(authorIdentKey);
-				if (identity == null) {
-					log.warn("Found no idenitiy with key='" + authorIdentKey + "'");
-					return "-";
-				}
-				return identity.getName();
-			} catch (Exception e) {
-				return "-";
-			}
-		}	
-	}
-	
-	@Override
-	public String getUUID() {
-		return uuid;
-	}
-	
-	public void setUUID(String uuid) {
-		this.uuid = uuid;
-	}
-	
-	public void generateUUID() {
-		uuid = UUID.randomUUID().toString().replace("-", "");
-	}
-
-	@Override
-	public Long getAuthorIdentityKey() {
-		return authorIdentKey;
-	}
-
-	@Override
-	public boolean hasAuthorIdentity() {
-		return (authorIdentKey != null);
-	}
-	
-	@Override
-	public String getComment() { return comment; }
-
-	@Override
-	public String getName() { return originFile.getName(); }
-
-	@Override
-	public boolean isDirectory() { return originFile.isDirectory(); }
-
-	@Override
-	public long getLastModified() {
-		return originFile.lastModified();
-	}
-	
-	@Override
-	public Date getMetaLastModified() {
-		if(metaFile == null) return null;
-		long lastModified = metaFile.lastModified();
-		return lastModified > 0 ? new Date(lastModified) : null;
-	}
-
-	@Override
-	public long getSize() {	return originFile.length(); }
-	
-	@Override
-	public String getFormattedSize() { return Formatter.formatBytes(getSize()); }
-
-	@Override
-	public void setAuthor(Identity identity) {
-		if (identity == null) {
-			log.warn("Found no idenity");
-			authorIdentKey = null;
-			return;
-		}
-		authorIdentKey = identity.getKey(); 
-	}
-
-	@Override
-	public void setComment(String string) { comment = string; }
-
-	@Override
-	public String toString() {
-		StringBuilder sb = new StringBuilder();
-		sb.append("Name [" + getName());
-		sb.append("] Author [" + getAuthor());
-		sb.append("] Comment [" + getComment());
-		sb.append("] IsDirectory [" + isDirectory());
-		sb.append("] Size [" + getFormattedSize());
-		sb.append("] LastModified [" + new Date(getLastModified()) + "]");
-		return sb.toString();
-	}
-
-	@Override
-	public String getCity() {
-		return city;
-	}
-
-	@Override
-	public String getLanguage() {
-		return language;
-	}
-
-	@Override
-	public String getPages() {
-		return pages;
-	}
-
-	@Override
-	public String[] getPublicationDate() {
-		return new String[] { pubYear, pubMonth };
-	}
-
-	@Override
-	public String getPublisher() {
-		return publisher;
-	}
-
-	@Override
-	public String getCreator() {
-		return creator;
-	}
-	
-	@Override
-	public String getSource() {
-		return source;
-	}
-
-	@Override
-	public String getTitle() {
-		return title;
-	}
-
-	@Override
-	public String getUrl() {
-		return url;
-	}
-
-	@Override
-	public void setCity(String city) {
-		this.city = city;
-	}
-
-	@Override
-	public void setLanguage(String language) {
-		this.language = language;
-	}
-
-	@Override
-	public void setPages(String pages) {
-		this.pages = pages;
-	}
-
-	@Override
-	public void setPublicationDate(String month, String year) {
-		this.pubMonth = month;
-		this.pubYear = year;
-	}
-
-	@Override
-	public void setPublisher(String publisher) {
-		this.publisher = publisher;
-	}
-
-	public void setWriter(String writer) {
-		this.creator = writer;
-	}
-
-	@Override
-	public void setCreator(String creator) {
-		this.creator = creator;
-	}
-
-	@Override
-	public void setSource(String source) {
-		this.source = source;
-	}
-
-	@Override
-	public void setTitle(String title) {
-		this.title = title;
-	}
-
-	@Override
-	public void setUrl(String url) {
-		this.url = url;
-	}
-
-	@Override
-	public String getLicenseTypeKey() {
-		return licenseTypeKey;
-	}
-
-	@Override
-	public void setLicenseTypeKey(String key) {
-		this.licenseTypeKey = key;
-	}
-
-	@Override
-	public String getLicenseTypeName() {
-		return licenseTypeName;
-	}
-
-	@Override
-	public void setLicenseTypeName(String name) {
-		this.licenseTypeName = name;
-	}
-
-	@Override
-	public String getLicenseText() {
-		return licenseText;
-	}
-
-	@Override
-	public void setLicenseText(String text) {
-		this.licenseText = text;
-	}
-
-	@Override
-	public String getLicensor() {
-		return licensor;
-	}
-
-	@Override
-	public void setLicensor(String licensor) {
-		this.licensor = licensor != null? licensor: "";
-	}
-	
-	@Override
-	public boolean isThumbnailAvailable() {
-		if(isDirectory()) return false;
-		if(originFile.isHidden()) return false;
-		if(cannotGenerateThumbnail) return false;
-		
-		VFSLeaf originLeaf = new LocalFileImpl(originFile);
-		if(thumbnailService == null) {
-			thumbnailService = CoreSpringFactory.getImpl(ThumbnailService.class);
-		}
-		return thumbnailService.isThumbnailPossible(originLeaf);
-	}
-	
-	@Override
-	public VFSLeaf getThumbnail(int maxWidth, int maxHeight, boolean fill) {
-		if(isDirectory()) return null;
-		Thumbnail thumbnailInfo =  getThumbnailInfo(maxWidth, maxHeight, fill);
-		if(thumbnailInfo == null) {
-			return null;
-		}
-		return new LocalFileImpl(thumbnailInfo.getThumbnailFile());
-	}
-	
-	/**
-	 * Thumbnails are cleared and the XML file is written on the disk
-	 */
-	@Override
-	public void clearThumbnails() {
-		cannotGenerateThumbnail = false;
-		for(Thumbnail thumbnail:thumbnails) {
-			File thumbnailFile = thumbnail.getThumbnailFile();
-			if(thumbnailFile != null && thumbnailFile.exists()) {
-				try {
-					Files.delete(thumbnailFile.toPath());
-				} catch (IOException e) {
-					log.error("Cannot delete thumbnail", e);
-				}
-			}
-		}
-		thumbnails.clear();
-		write();
-	}
-
-	private Thumbnail getThumbnailInfo(int maxWidth, int maxHeight, boolean fill) {
-		for(Thumbnail thumbnail:thumbnails) {
-			if(maxHeight == thumbnail.getMaxHeight() && maxWidth == thumbnail.getMaxWidth()
-					&& thumbnail.exists()) {
-				return thumbnail;
-			}
-		}
-
-		//generate a file name
-		File metaLoc = metaFile.getParentFile();
-		String name = originFile.getName();
-		String extension = FileUtils.getFileSuffix(name);
-		String nameOnly = name.substring(0, name.length() - extension.length() - 1);
-		String randuuid = UUID.randomUUID().toString();
-		String thumbnailExtension = preferedThumbnailType(extension);
-		File thumbnailFile = new File(metaLoc, nameOnly + "_" + randuuid + "_" + maxHeight + "x" + maxWidth + (fill ? "xfill" : "") + "." + thumbnailExtension);
-		
-		//generate thumbnail
-		long start = 0l;
-		if(log.isDebug()) start = System.currentTimeMillis();
-		
-		VFSLeaf thumbnailLeaf = new LocalFileImpl(thumbnailFile);
-		VFSLeaf originLeaf = new LocalFileImpl(originFile);
-		if(thumbnailService == null) {
-			thumbnailService = CoreSpringFactory.getImpl(ThumbnailService.class);
-		}
-		if(thumbnailService != null &&thumbnailService.isThumbnailPossible(thumbnailLeaf)) {
-			try {
-				if(thumbnails.isEmpty()) {
-					//be paranoid
-					cannotGenerateThumbnail = true;
-					write();
-				}
-				log.info("Start thumbnail: " + thumbnailLeaf);
-				
-				FinalSize finalSize = thumbnailService.generateThumbnail(originLeaf, thumbnailLeaf, maxWidth, maxHeight, fill);
-				if(finalSize == null) {
-					return null;
-				} else {
-					Thumbnail thumbnail = new Thumbnail();
-					thumbnail.setMaxHeight(maxHeight);
-					thumbnail.setMaxWidth(maxWidth);
-					thumbnail.setFinalHeight(finalSize.getHeight());
-					thumbnail.setFinalWidth(finalSize.getWidth());
-					thumbnail.setFill(true);
-					thumbnail.setThumbnailFile(thumbnailFile);
-					thumbnails.add(thumbnail);
-					cannotGenerateThumbnail = false;
-					write();
-					log.info("Create thumbnail: " + thumbnailLeaf);
-					if(log.isDebug()) { 
-						log.debug("Creation of thumbnail takes (ms): " + (System.currentTimeMillis() - start));
-					}
-					return thumbnail;
-				}
-			} catch (CannotGenerateThumbnailException e) {
-				//don't try every time to create the thumbnail.
-				cannotGenerateThumbnail = true;
-				write();
-				return null;
-			}
-		}
-		return null;
-	}
-	
-	private String preferedThumbnailType(String extension) {
-		if(extension.equalsIgnoreCase("png") || extension.equalsIgnoreCase("gif")) {
-			return extension;
-		}
-		if(extension.equalsIgnoreCase("pdf")) {
-			return "png";
-		}
-		return "jpg";
-	}
-
-	@Override
-	public void increaseDownloadCount() {
-		downloadCount++;
-	}
-
-	@Override
-	public int getDownloadCount() {
-		return downloadCount;
-	}
-	
-	public void setDownloadCount(int count) {
-		downloadCount = count;
-	}
-
-	public void setAuthorIdentKey(Long authorIdentKey) {
-		this.authorIdentKey = authorIdentKey;
-	}
-
-	@Override
-	public String getIconCssClass() {
-		String cssClass;
-		if (isDirectory()) {
-			cssClass =  CSSHelper.CSS_CLASS_FILETYPE_FOLDER;
-		} else {
-			cssClass = CSSHelper.createFiletypeIconCssClassFor(getName());
-		}
-		return cssClass;
-	}
-	
-	public static class XmlFilter implements FileFilter {
-		@Override
-		public boolean accept(File file) {
-			return file.getName().endsWith(".xml");
-		}
-	}
-	
-	private static class MetaHandler extends DefaultHandler {
-
-		private StringBuilder current;
-		private final MetaInfoFileImpl meta;
-		
-		public MetaHandler(MetaInfoFileImpl meta) {
-			this.meta = meta;
-		}
-		
-		@Override
-		public final void startElement(String uri, String localName, String qName, Attributes attributes) {
-			if("meta".equals(qName)) {
-				 meta.setUUID(attributes.getValue("uuid"));
-			} else if ("lock".equals(qName)) {
-				meta.setLocked("true".equals(attributes.getValue("locked")));
-				String date = attributes.getValue("date");
-				if (date != null && date.length() > 0) {
-					meta.setLockedDate(new Date(Long.parseLong(date)));
-				}
-			} else if ("thumbnails".equals(qName)) {
-				String valueStr = attributes.getValue("cannotGenerateThumbnail");
-				if(StringHelper.containsNonWhitespace(valueStr)) {
-					meta.cannotGenerateThumbnail = Boolean.valueOf(valueStr);
-				}
-			}	else if ("thumbnail".equals(qName)) {
-				Thumbnail thumbnail = new Thumbnail();
-				thumbnail.setMaxHeight(Integer.parseInt(attributes.getValue("maxHeight")));
-				thumbnail.setMaxWidth(Integer.parseInt(attributes.getValue("maxWidth")));
-				thumbnail.setFinalHeight(Integer.parseInt(attributes.getValue("finalHeight")));
-				thumbnail.setFinalWidth(Integer.parseInt(attributes.getValue("finalWidth")));
-				thumbnail.setFill("true".equals(attributes.getValue("fill")));
-				meta.thumbnails.add(thumbnail);
-			}
-		}
-		
-		@Override
-		public final void characters(char[] ch, int start, int length) {
-			if(length == 0) return;
-			if(current == null) {
-				current = new StringBuilder();
-			}
-			current.append(ch, start, length);
-		}
-
-		@Override
-		public final void endElement(String uri, String localName, String qName) {
-			if(current == null) return;
-			
-			if("comment".equals(qName)) {
-				meta.comment = current.toString();
-			} else if ("author".equals(qName)) {
-				try {
-					meta.authorIdentKey = Long.valueOf(current.toString());
-				} catch (NumberFormatException nEx) {
-					//nothing to say
-				}
-			} else if ("lock".equals(qName)) {
-				try {
-					meta.lockedByIdentKey = Long.valueOf(current.toString());
-				} catch (NumberFormatException nEx) {
-					//nothing to say
-				}
-			} else if ("title".equals(qName)) {
-				meta.title = current.toString();
-			} else if ("publisher".equals(qName)) {
-				meta.publisher = current.toString();
-			} else if ("source".equals(qName)) {
-				meta.source = current.toString();
-			} else if ("city".equals(qName)) {
-				meta.city = current.toString();
-			} else if ("pages".equals(qName)) {
-				meta.pages = current.toString();
-			} else if ("language".equals(qName)) {
-				meta.language = current.toString();
-			} else if ("downloadCount".equals(qName)) {
-				try {
-					meta.downloadCount = Integer.valueOf(current.toString());
-				} catch (NumberFormatException nEx) {
-					//nothing to say
-				}
-			} else if ("month".equals(qName)) {
-				meta.pubMonth = current.toString();
-			} else if ("year".equals(qName)) {
-				meta.pubYear = current.toString();
-			} else if (qName.equals("creator")) {
-				meta.creator = current.toString();
-			} else if (qName.equals("url")) {
-				meta.url = current.toString();
-			} else if (qName.equals("licenseTypeKey")) {
-				meta.licenseTypeKey = current.toString();
-			} else if (qName.equals("licenseTypeName")) {
-				meta.licenseTypeName = current.toString();
-			} else if (qName.equals("licenseText")) {
-				meta.licenseText = current.toString();
-			} else if (qName.equals("licensor")) {
-				meta.licensor = current.toString();
-			} else if (qName.equals("thumbnail")) {
-				String finalName = current.toString();
-				File thumbnailFile = new File(meta.metaFile.getParentFile(), finalName);
-				meta.thumbnails.get(meta.thumbnails.size() - 1).setThumbnailFile(thumbnailFile);
-			}
-			current = null;
-		}
-		
-	}
-	
-	public static class Thumbnail implements Serializable {
-
-		private static final long serialVersionUID = 29491661959555446L;
-		
-		private int maxWidth;
-		private int maxHeight;
-		private int finalWidth;
-		private int finalHeight;
-		private boolean fill = false;
-		private File thumbnailFile;
-		
-		public int getMaxWidth() {
-			return maxWidth;
-		}
-		
-		public void setMaxWidth(int maxWidth) {
-			this.maxWidth = maxWidth;
-		}
-		
-		public int getMaxHeight() {
-			return maxHeight;
-		}
-		
-		public void setMaxHeight(int maxHeight) {
-			this.maxHeight = maxHeight;
-		}
-		
-		public int getFinalWidth() {
-			return finalWidth;
-		}
-		
-		public void setFinalWidth(int finalWidth) {
-			this.finalWidth = finalWidth;
-		}
-		
-		public int getFinalHeight() {
-			return finalHeight;
-		}
-		
-		public void setFinalHeight(int finalHeight) {
-			this.finalHeight = finalHeight;
-		}
-		
-		public boolean isFill() {
-			return fill;
-		}
-
-		public void setFill(boolean fill) {
-			this.fill = fill;
-		}
-
-		public File getThumbnailFile() {
-			return thumbnailFile;
-		}
-		
-		public void setThumbnailFile(File thumbnailFile) {
-			this.thumbnailFile = thumbnailFile;
-		}
-		
-		public boolean exists() {
-			return thumbnailFile != null && thumbnailFile.exists();
-		}
-	}
-}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/util/vfs/restapi/VFSStreamingOutput.java b/src/main/java/org/olat/core/util/vfs/restapi/VFSStreamingOutput.java
index 49dabd53945f61774f09002d8d28695df7a88ad6..ba11edb7aa0c6d288e10cc80145749ccd5173bf0 100644
--- a/src/main/java/org/olat/core/util/vfs/restapi/VFSStreamingOutput.java
+++ b/src/main/java/org/olat/core/util/vfs/restapi/VFSStreamingOutput.java
@@ -22,9 +22,10 @@ package org.olat.core.util.vfs.restapi;
 import java.io.InputStream;
 import java.io.OutputStream;
 
-import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.StreamingOutput;
 
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
 import org.olat.core.util.FileUtils;
 import org.olat.core.util.vfs.VFSLeaf;
 
@@ -37,6 +38,8 @@ import org.olat.core.util.vfs.VFSLeaf;
  */
 public class VFSStreamingOutput implements StreamingOutput {
 	
+	private static final OLog log = Tracing.createLoggerFor(VFSStreamingOutput.class);
+	
 	private final VFSLeaf leaf;
 	
 	public VFSStreamingOutput(VFSLeaf leaf) {
@@ -44,9 +47,11 @@ public class VFSStreamingOutput implements StreamingOutput {
 	}
 
 	@Override
-	public void write(OutputStream output) throws WebApplicationException {
-		InputStream in = leaf.getInputStream();
-		FileUtils.copy(in, output);
-		FileUtils.closeSafely(in);
+	public void write(OutputStream output) {
+		try(InputStream in = leaf.getInputStream()) {
+			FileUtils.copy(in, output);
+		} catch(Exception e) {
+			log.error("", e);
+		}
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/util/vfs/util/ContainerAndFile.java b/src/main/java/org/olat/core/util/vfs/util/ContainerAndFile.java
index b4980b66292ee14a3bd58c25e73d42dcf8d9894d..4ddf5f0f0f64e0ca63677fbfb4bbb880df36e2aa 100644
--- a/src/main/java/org/olat/core/util/vfs/util/ContainerAndFile.java
+++ b/src/main/java/org/olat/core/util/vfs/util/ContainerAndFile.java
@@ -29,11 +29,7 @@ package org.olat.core.util.vfs.util;
 import org.olat.core.util.vfs.VFSContainer;
 
 /**
- * Description:<br>
- * TODO: HP_Besitzer Class Description for ContainerAndFile
- * 
- * <P>
- * Initial Date:  15.03.2006 <br>
+ * Initial Date:  15.03.2006
  *
  * @author HP_Besitzer
  */
diff --git a/src/main/java/org/olat/core/util/vfs/util/VFSUtil.java b/src/main/java/org/olat/core/util/vfs/util/VFSUtil.java
deleted file mode 100644
index 357b242a1f07c9c7811c0ececb346f2222979fde..0000000000000000000000000000000000000000
--- a/src/main/java/org/olat/core/util/vfs/util/VFSUtil.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
-* OLAT - Online Learning and Training<br>
-* http://www.olat.org
-* <p>
-* Licensed under the Apache License, Version 2.0 (the "License"); <br>
-* you may not use this file except in compliance with the License.<br>
-* You may obtain a copy of the License at
-* <p>
-* http://www.apache.org/licenses/LICENSE-2.0
-* <p>
-* Unless required by applicable law or agreed to in writing,<br>
-* software distributed under the License is distributed on an "AS IS" BASIS, <br>
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
-* See the License for the specific language governing permissions and <br>
-* limitations under the License.
-* <p>
-* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
-* University of Zurich, Switzerland.
-* <hr>
-* <a href="http://www.openolat.org">
-* OpenOLAT - Online Learning and Training</a><br>
-* This file has been modified by the OpenOLAT community. Changes are licensed
-* under the Apache 2.0 license as the original file.  
-* <p>
-*/ 
-
-package org.olat.core.util.vfs.util;
-
-import org.olat.core.util.vfs.VFSContainer;
-
-/**
- * Initial Date: 17.01.2006
- *
- * @author Felix Jost
- *
- * Description:
- * some utils for the VFS
- */
-public class VFSUtil {
-	
-	
-	/**
-	 * calculates a new vfscontainer and a filename where the container's base is where the file resides.<br>
-	 * e.g.<br>
-	 * container: /usr/local/olat/courses/123/coursefolder and filename: docs/bla/blu.doc<br>
-	 * -> container /usr/local/olat/courses/123/coursefolder/docs/bla and filename: blu.doc
-	 * 
-	 * @param oldRoot
-	 * @param fileName
-	 * @return
-	 */
-	public static ContainerAndFile calculateSubRoot(VFSContainer oldRoot, String fileUri) {
-		VFSContainer newC;
-		String newFile;
-		int sla = fileUri.lastIndexOf('/');
-		if (sla != -1) {
-			String root = fileUri.substring(0,sla);
-			newFile = fileUri.substring(sla+1);
-			newC = (VFSContainer)oldRoot.resolve(root);
-		} else {
-			newC = oldRoot;
-			newFile = fileUri;
-		}		
-		return new ContainerAndFile(newC, newFile);
-	}
-	
-
-}
\ No newline at end of file
diff --git a/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java b/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java
index 25b27b6a6230f38507ded5480cbaf22a295fc001..df713e6f7c1458c0ce2bbb43df5da6781a829d72 100644
--- a/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java
@@ -21,9 +21,9 @@ package org.olat.core.util.vfs.version;
 
 import java.io.InputStream;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * 
@@ -48,7 +48,7 @@ public class RevisionFileImpl implements VFSRevision {
 	private VFSContainer container;
 	private String revisionNr;
 	private String filename;
-	private MetaInfo metadata;
+	private VFSMetadata metadata;
 
 	/**
 	 * Only for the VersionsFileManager or XStream
@@ -140,11 +140,11 @@ public class RevisionFileImpl implements VFSRevision {
 		this.name = name;
 	}
 
-	public MetaInfo getMetadata() {
+	public VFSMetadata getMetadata() {
 		return metadata;
 	}
 
-	public void setMetadata(MetaInfo metadata) {
+	public void setMetadata(VFSMetadata metadata) {
 		this.metadata = metadata;
 	}
 
diff --git a/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java b/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java
index 869ebf3e413d4a0c485457085ec847e4242b3941..f29bb05f5fafcf390af482bcc46a1fbcc5daf4e4 100644
--- a/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java
+++ b/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java
@@ -38,8 +38,10 @@ import java.util.zip.Adler32;
 import java.util.zip.Checksum;
 
 import org.apache.commons.io.FileUtils;
-import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
+import org.olat.core.commons.services.vfs.manager.VFSXStream;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
@@ -61,16 +63,9 @@ import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.filters.SystemItemFilter;
 import org.olat.core.util.vfs.filters.VFSItemSuffixFilter;
 import org.olat.core.util.vfs.filters.VFSLeafFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
-import org.olat.core.util.vfs.meta.MetaInfoFactory;
-import org.olat.core.util.vfs.meta.MetaInfoFileImpl;
-import org.olat.core.util.xml.XStreamHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import com.thoughtworks.xstream.XStream;
-import com.thoughtworks.xstream.security.ExplicitTypePermission;
-
 /**
  * 
  * Description:<br>
@@ -90,32 +85,13 @@ public class VersionsFileManager implements VersionsManager {
 
 	private static final Versions NOT_VERSIONED = new NotVersioned();
 	private static final Pattern TAG_PATTERN = Pattern.compile("\\s*[<>]\\s*");
-	private static XStream mystream;
-	static {
-		mystream = XStreamHelper.createXStreamInstance();
-		XStream.setupDefaultSecurity(mystream);
-		Class<?>[] types = new Class[] {
-				VersionsFileImpl.class, Versions.class, RevisionFileImpl.class, VFSRevision.class,
-				MetaInfoFileImpl.class, MetaInfoFileImpl.Thumbnail.class
-			};
-		mystream.addPermission(new ExplicitTypePermission(types));
-		
-		mystream.alias("versions", VersionsFileImpl.class);
-		mystream.alias("revision", RevisionFileImpl.class);
-		mystream.omitField(VersionsFileImpl.class, "currentVersion");
-		mystream.omitField(VersionsFileImpl.class, "versionFile");
-		mystream.omitField(RevisionFileImpl.class, "current");
-		mystream.omitField(RevisionFileImpl.class, "container");
-		mystream.omitField(RevisionFileImpl.class, "file");
-		
-		mystream.alias("metadata", MetaInfoFileImpl.class);
-		mystream.alias("thumbnail", MetaInfoFileImpl.Thumbnail.class);
-	}
 
 	private File rootFolder;
 	private File rootVersionFolder;
 	private VFSContainer rootVersionsContainer;
 	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	@Autowired
 	private FolderVersioningConfigurator versioningConfigurator;
 
@@ -211,7 +187,7 @@ public class VersionsFileManager implements VersionsManager {
 
 		try {
 			VFSContainer fVersionContainer = fVersions.getParentContainer();
-			VersionsFileImpl versions = (VersionsFileImpl) XStreamHelper.readObject(mystream, fVersions);
+			VersionsFileImpl versions = (VersionsFileImpl) VFSXStream.read(fVersions);
 			versions.setVersionFile(fVersions);
 			versions.setCurrentVersion((Versionable) leaf);
 			if (versions.getRevisionNr() == null || versions.getRevisionNr().length() == 0) {
@@ -284,7 +260,7 @@ public class VersionsFileManager implements VersionsManager {
 		}
 
 		targetVersionsImpl.setRevisionNr(getNextRevisionNr(targetVersionsImpl));
-		XStreamHelper.writeObject(mystream, fTargetVersions, targetVersionsImpl);
+		VFSXStream.write(fTargetVersions, targetVersionsImpl);
 
 		return allOk;
 	}
@@ -317,15 +293,17 @@ public class VersionsFileManager implements VersionsManager {
 		newRevision.setUuid(revUuid);
 
 		//copy -> the files revision
-		InputStream revisionIn = revision.getInputStream();
-
-		VFSLeaf target = fNewVersions.getParentContainer().createChildLeaf(uuid);
-		if (VFSManager.copyContent(revisionIn, target)) {
-			targetVersions.setComment(revision.getComment());
-			targetVersions.getRevisions().add(newRevision);
-			targetVersions.setRevisionNr(getNextRevisionNr(targetVersions));
-			targetVersions.setAuthor(revision.getAuthor());
-			return true;
+		try(InputStream revisionIn = revision.getInputStream()) {
+			VFSLeaf target = fNewVersions.getParentContainer().createChildLeaf(uuid);
+			if (VFSManager.copyContent(revisionIn, target)) {
+				targetVersions.setComment(revision.getComment());
+				targetVersions.getRevisions().add(newRevision);
+				targetVersions.setRevisionNr(getNextRevisionNr(targetVersions));
+				targetVersions.setAuthor(revision.getAuthor());
+				return true;
+			}
+		} catch(Exception e) {
+			log.error("", e);
 		}
 		return false;
 	}
@@ -400,37 +378,39 @@ public class VersionsFileManager implements VersionsManager {
 		
 		if (restoredItem instanceof VFSLeaf) {
 			VFSLeaf restoredLeaf = (VFSLeaf) restoredItem;
-			InputStream inStream = revision.getInputStream();
-			if (VFSManager.copyContent(inStream, restoredLeaf)) {
-				VFSLeaf versionFile = getCanonicalVersionXmlFile(restoredLeaf, true);
-				Versions versions = readVersions(restoredLeaf, versionFile);
-				if (versions instanceof VersionsFileImpl) {
-					versions.getRevisions().remove(revision);
-					((VersionsFileImpl) versions).setRevisionNr(getNextRevisionNr(versions));
-					if (revision instanceof RevisionFileImpl) {
-						VFSLeaf fileToDelete = ((RevisionFileImpl)revision).getFile();
-						if(fileToDelete != null) {
-							fileToDelete.deleteSilently();
+			try(InputStream inStream = revision.getInputStream()) {
+				if (VFSManager.copyContent(inStream, restoredLeaf)) {
+					VFSLeaf versionFile = getCanonicalVersionXmlFile(restoredLeaf, true);
+					Versions versions = readVersions(restoredLeaf, versionFile);
+					if (versions instanceof VersionsFileImpl) {
+						versions.getRevisions().remove(revision);
+						((VersionsFileImpl) versions).setRevisionNr(getNextRevisionNr(versions));
+						if (revision instanceof RevisionFileImpl) {
+							VFSLeaf fileToDelete = ((RevisionFileImpl)revision).getFile();
+							if(fileToDelete != null) {
+								fileToDelete.deleteSilently();
+							}
 						}
 					}
-				}
-				if (restoreDeletedFile && revision instanceof RevisionFileImpl) {
-					MetaInfo versionedMetadata = ((RevisionFileImpl)revision).getMetadata();
-					MetaInfoFileImpl metadata = (MetaInfoFileImpl)restoredItem.getMetaInfo();
-					if(versionedMetadata != null && metadata != null) {
-						metadata.copyValues(versionedMetadata);
-						// make sure the restored file is not locked
-						metadata.setLocked(false);
-						metadata.setLockedBy(null);
-						metadata.setLockedDate(null);
-						// restore last values
-						metadata.setUUID(versionedMetadata.getUUID());
-						metadata.setDownloadCount(versionedMetadata.getDownloadCount());
-						metadata.write();
+					if (restoreDeletedFile && revision instanceof RevisionFileImpl) {
+						VFSMetadata versionedMetadata = ((RevisionFileImpl)revision).getMetadata();
+						VFSMetadata metadata = restoredItem.getMetaInfo();
+						if(versionedMetadata != null && metadata != null) {
+							metadata.copyValues(versionedMetadata);
+							// make sure the restored file is not locked
+							metadata.setLocked(false);
+							metadata.setLockedBy(null);
+							metadata.setLockedDate(null);
+							// restore last values
+							metadata.setUuid(versionedMetadata.getUuid());
+							vfsRepositoryService.updateMetadata(metadata);
+						}
 					}
+					VFSXStream.write(versionFile, versions);
+					return true;
 				}
-				XStreamHelper.writeObject(mystream, versionFile, versions);
-				return true;
+			} catch(Exception e) {
+				log.error("", e);
 			}
 		}
 		return false;
@@ -478,7 +458,7 @@ public class VersionsFileManager implements VersionsManager {
 		}
 
 		VFSLeaf versionFile = getCanonicalVersionXmlFile(currentFile, true);
-		XStreamHelper.writeObject(mystream, versionFile, versions);
+		VFSXStream.write(versionFile, versions);
 		if (currentVersion.getVersions() instanceof VersionsFileImpl) {
 			((VersionsFileImpl) currentVersion.getVersions()).update(versions);
 		}
@@ -509,7 +489,7 @@ public class VersionsFileManager implements VersionsManager {
 						File dir = ((JavaIOItem)container).getBasefile();
 						File file = new File(dir, originFilename);
 						if(!file.exists()) {
-							CoreSpringFactory.getImpl(MetaInfoFactory.class).deleteMetaFile(file);
+							vfsRepositoryService.deleteMetadata(file);
 						}
 					}
 				}
@@ -651,24 +631,24 @@ public class VersionsFileManager implements VersionsManager {
 		
 		String versionNr = getNextRevisionNr(versions);
 		String currentAuthor = versions.getAuthor();
-		long lastModifiedDate = 0;
+		Date lastModifiedDate = null;
 		
-		MetaInfo metaInfo = null;
+		VFSMetadata metaInfo = null;
 		if (currentFile.canMeta() == VFSConstants.YES) {
 			metaInfo = currentFile.getMetaInfo();
 			if(metaInfo != null) {
-				metaInfo.clearThumbnails();
-				if(currentAuthor == null) { 
-					currentAuthor = metaInfo.getAuthor();
+				vfsRepositoryService.resetThumbnails(currentFile);
+				if(currentAuthor == null && metaInfo.getAuthor() != null) { 
+					currentAuthor = metaInfo.getAuthor().getName();
 				}
 				lastModifiedDate = metaInfo.getLastModified();
 			}
 		}
 		
-		if(lastModifiedDate <= 0) {
+		if(lastModifiedDate == null) {
 			Calendar cal = Calendar.getInstance();
 			cal.setTime(new Date());
-			lastModifiedDate = cal.getTimeInMillis();
+			lastModifiedDate = cal.getTime();
 		}
 
 		RevisionFileImpl newRevision = new RevisionFileImpl();
@@ -678,13 +658,13 @@ public class VersionsFileManager implements VersionsManager {
 		newRevision.setRevisionNr(versionNr);
 		newRevision.setComment(versions.getComment());
 		newRevision.setAuthor(currentAuthor);
-		newRevision.setLastModified(lastModifiedDate);
-		newRevision.setMetadata(metaInfo);
+		newRevision.setLastModified(lastModifiedDate.getTime());
+		//TODO metadata newRevision.setMetadata(metaInfo);
 
 		if (versions.getRevisions().isEmpty() && currentVersion instanceof VFSItem) {
-			MetaInfo currentMeta = ((VFSItem)currentVersion).getMetaInfo();
-			if(currentMeta != null) {
-				versions.setCreator(currentMeta.getAuthor());
+			VFSMetadata currentMeta = ((VFSItem)currentVersion).getMetaInfo();
+			if(currentMeta != null && currentMeta.getAuthor() != null) {
+				versions.setCreator(currentMeta.getAuthor().getName());
 			}
 		}
 
@@ -705,7 +685,7 @@ public class VersionsFileManager implements VersionsManager {
 			versions.setComment(comment);
 			versions.getRevisions().add(newRevision);
 			versions.setRevisionNr(getNextRevisionNr(versions));
-			XStreamHelper.writeObject(mystream, versionFile, versions);
+			VFSXStream.write(versionFile, versions);
 			if (currentVersion.getVersions() instanceof VersionsFileImpl) {
 				((VersionsFileImpl) currentVersion.getVersions()).update(versions);
 			}
@@ -802,7 +782,7 @@ public class VersionsFileManager implements VersionsManager {
 			versions.setRevisionNr(getNextRevisionNr(versions));
 			VFSLeaf fVersions = localVersionContainer.createChildLeaf(fVersion.getName());
 			if(fVersions != null) {
-				XStreamHelper.writeObject(mystream, fVersions, versions);
+				VFSXStream.write(fVersions, versions);
 			}
 			return fVersions;
 		}
@@ -1063,7 +1043,7 @@ public class VersionsFileManager implements VersionsManager {
 	private Versions isOrphan(VFSLeaf potentialOrphan) {
 		try {
 			if(potentialOrphan.exists()) {
-				return (VersionsFileImpl) XStreamHelper.readObject(mystream, potentialOrphan);
+				return (VersionsFileImpl) VFSXStream.read(potentialOrphan);
 			}
 			return null;
 		} catch (Exception e) {
diff --git a/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java b/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java
index 7d28a5598501658167d4b218e2e2939ac9bb1228..242c3b901e0bc221f6a01c39fc2284a7f1ce659e 100644
--- a/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java
+++ b/src/main/java/org/olat/course/nodes/gta/manager/GTANotifications.java
@@ -33,6 +33,7 @@ import org.olat.basesecurity.GroupRoles;
 import org.olat.core.commons.services.notifications.Publisher;
 import org.olat.core.commons.services.notifications.Subscriber;
 import org.olat.core.commons.services.notifications.model.SubscriptionListItem;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
 import org.olat.core.id.context.BusinessControlFactory;
@@ -44,7 +45,6 @@ import org.olat.core.util.io.SystemFilenameFilter;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentHelper;
@@ -824,8 +824,8 @@ class GTANotifications {
 		String author = null;
 		VFSItem item = container.resolve(file.getName());
 		if(item.canMeta() == VFSConstants.YES) {
-			MetaInfo info = item.getMetaInfo();
-			Long identityKey = info.getAuthorIdentityKey();
+			VFSMetadata info = item.getMetaInfo();
+			Identity identityKey = info.getAuthor();
 			if(identityKey != null) {
 				author = userManager.getUserDisplayName(identityKey);
 			}		
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java b/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java
index 32e9521c0c69f944fea7fccad7893f88615060ab..8eb55a18b0a6e34c23397d0145e679aa3a97540f 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/DirectoryController.java
@@ -25,6 +25,7 @@ import java.util.Date;
 import java.util.List;
 
 import org.olat.core.commons.modules.singlepage.SinglePageController;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.download.DisplayOrDownloadComponent;
@@ -51,7 +52,6 @@ import org.olat.core.util.io.SystemFileFilter;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.gta.ui.component.DownloadDocumentMapper;
 import org.olat.user.UserManager;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -130,9 +130,9 @@ public class DirectoryController extends BasicController implements Activateable
 				VFSItem item = documentsContainer.resolve(document.getName());
 				lastModified = format.formatDateAndTime(new Date(item.getLastModified()));
 				if(item.canMeta() == VFSConstants.YES) {
-					MetaInfo metaInfo = item.getMetaInfo();
-					if(metaInfo != null && metaInfo.getAuthorIdentityKey() != null) {
-						uploadedBy = userManager.getUserDisplayName(metaInfo.getAuthorIdentityKey());
+					VFSMetadata metaInfo = item.getMetaInfo();
+					if(metaInfo != null && metaInfo.getAuthor() != null) {
+						uploadedBy = userManager.getUserDisplayName(metaInfo.getAuthor());
 					}
 				}
 			}
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/EditSolutionController.java b/src/main/java/org/olat/course/nodes/gta/ui/EditSolutionController.java
index 923e0f51a29633a67e0618708a9be0b1c27a0796..eae532a4927d778aa6973f2677ff2678945dcde2 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/EditSolutionController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/EditSolutionController.java
@@ -24,6 +24,8 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
 import org.olat.core.gui.components.form.flexible.elements.FileElement;
@@ -40,8 +42,8 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.gta.model.Solution;
+import org.springframework.beans.factory.annotation.Autowired;
 
 
 /**
@@ -60,6 +62,9 @@ public class EditSolutionController extends FormBasicController {
 	private final File solutionDir;
 	private final VFSContainer solutionContainer;
 	private final String filenameToReplace;
+	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	public EditSolutionController(UserRequest ureq, WindowControl wControl,
 			File solutionDir, VFSContainer solutionContainer) {
@@ -172,9 +177,9 @@ public class EditSolutionController extends FormBasicController {
 
 				VFSItem uploadedItem = solutionContainer.resolve(filename);
 				if(uploadedItem.canMeta() == VFSConstants.YES) {
-					MetaInfo metaInfo = uploadedItem.getMetaInfo();
+					VFSMetadata metaInfo = uploadedItem.getMetaInfo();
 					metaInfo.setAuthor(ureq.getIdentity());
-					metaInfo.write();
+					vfsRepositoryService.updateMetadata(metaInfo);
 				}
 			} catch(Exception ex) {
 				logError("", ex);
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java b/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java
index 20136ed7bc155101918bd8eb2d9183f7325e3a73..5bbf2e75aadd30bb52a61aea4e4c290cc54719dc 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/GTASampleSolutionsEditController.java
@@ -25,6 +25,8 @@ import java.util.List;
 
 import org.olat.core.commons.editor.htmleditor.HTMLEditorController;
 import org.olat.core.commons.editor.htmleditor.WysiwygFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -49,7 +51,6 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.model.Solution;
@@ -66,7 +67,8 @@ import org.springframework.beans.factory.annotation.Autowired;
  */
 public class GTASampleSolutionsEditController extends FormBasicController {
 	
-	private FormLink addSolutionLink, createSolutionLink;
+	private FormLink addSolutionLink;
+	private FormLink createSolutionLink;
 	private SolutionTableModel solutionModel;
 	private FlexiTableElement solutionTable;
 	
@@ -74,7 +76,8 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 	private EditSolutionController addSolutionCtrl;
 	private EditSolutionController editSolutionCtrl;
 	private NewSolutionController newSolutionCtrl;
-	private HTMLEditorController newSolutionEditorCtrl, editSolutionEditorCtrl;
+	private HTMLEditorController newSolutionEditorCtrl;
+	private HTMLEditorController editSolutionEditorCtrl;
 	
 	private final File solutionDir;
 	private final boolean readOnly;
@@ -88,6 +91,8 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 	private UserManager userManager;
 	@Autowired
 	private GTAManager gtaManager;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	public GTASampleSolutionsEditController(UserRequest ureq, WindowControl wControl, GTACourseNode gtaNode,
 			CourseEnvironment courseEnv, boolean readOnly) {
@@ -139,9 +144,9 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 			
 			VFSItem item = solutionContainer.resolve(filename);
 			if(item.canMeta() == VFSConstants.YES) {
-				MetaInfo metaInfo = item.getMetaInfo();
-				if(metaInfo.getAuthorIdentityKey() != null) {
-					author = userManager.getUserDisplayName(metaInfo.getAuthorIdentityKey());
+				VFSMetadata metaInfo = item.getMetaInfo();
+				if(metaInfo.getAuthor() != null) {
+					author = userManager.getUserDisplayName(metaInfo.getAuthor());
 				}
 			}
 			
@@ -295,9 +300,9 @@ public class GTASampleSolutionsEditController extends FormBasicController {
 			item = solutionContainer.createChildLeaf(documentName);
 		}
 		if(item.canMeta() == VFSConstants.YES) {
-			MetaInfo metaInfo = item.getMetaInfo();
+			VFSMetadata metaInfo = item.getMetaInfo();
 			metaInfo.setAuthor(getIdentity());
-			metaInfo.write();
+			vfsRepositoryService.updateMetadata(metaInfo);
 		}
 
 		newSolutionEditorCtrl = WysiwygFactory.createWysiwygController(ureq, getWindowControl(),
diff --git a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java
index 45bd958a8e62df377aadbeeeb55334f398d24279..f416c816541adc6361134b51aeba75594e1ee1a9 100644
--- a/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java
+++ b/src/main/java/org/olat/course/nodes/gta/ui/SubmitDocumentsController.java
@@ -32,6 +32,8 @@ import java.util.List;
 import org.olat.core.commons.editor.htmleditor.HTMLEditorController;
 import org.olat.core.commons.editor.htmleditor.WysiwygFactory;
 import org.olat.core.commons.modules.singlepage.SinglePageController;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.form.flexible.FormItem;
 import org.olat.core.gui.components.form.flexible.FormItemContainer;
@@ -59,7 +61,6 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.GTACourseNode;
 import org.olat.course.nodes.gta.GTAManager;
 import org.olat.course.nodes.gta.Task;
@@ -101,10 +102,12 @@ class SubmitDocumentsController extends FormBasicController {
 	private final boolean readOnly;
 	private final Date deadline;
 	
+	@Autowired
+	private GTAManager gtaManager;
 	@Autowired
 	private UserManager userManager;
 	@Autowired
-	private GTAManager gtaManager;
+	private VFSRepositoryService vfsRepositoryService;
 	
 	public SubmitDocumentsController(UserRequest ureq, WindowControl wControl, Task assignedTask,
 			File documentsDir, VFSContainer documentsContainer, int maxDocs, GTACourseNode cNode,
@@ -184,9 +187,9 @@ class SubmitDocumentsController extends FormBasicController {
 			String uploadedBy = null;
 			VFSItem item = documentsContainer.resolve(filename);
 			if(item.canMeta() == VFSConstants.YES) {
-				MetaInfo metaInfo = item.getMetaInfo();
-				if(metaInfo != null && metaInfo.getAuthorIdentityKey() != null) {
-					uploadedBy = userManager.getUserDisplayName(metaInfo.getAuthorIdentityKey());
+				VFSMetadata metaInfo = item.getMetaInfo();
+				if(metaInfo != null) {
+					uploadedBy = userManager.getUserDisplayName(metaInfo.getAuthor());
 				}
 			}
 			
@@ -417,9 +420,9 @@ class SubmitDocumentsController extends FormBasicController {
 			
 			VFSItem downloadedFile = documentsContainer.resolve(filename);
 			if(downloadedFile != null && downloadedFile.canMeta() == VFSConstants.YES) {
-				MetaInfo  metadata = downloadedFile.getMetaInfo();
+				VFSMetadata  metadata = downloadedFile.getMetaInfo();
 				metadata.setAuthor(ureq.getIdentity());
-				metadata.write();
+				vfsRepositoryService.updateMetadata(metadata);
 			}
 		} catch (IOException e) {
 			logError("", e);
@@ -475,9 +478,9 @@ class SubmitDocumentsController extends FormBasicController {
 			// add missing identity in meta info
 			item = documentsContainer.resolve(documentName);
 			if(item != null && item.canMeta() == VFSConstants.YES) {
-				MetaInfo  metadata = item.getMetaInfo();
+				VFSMetadata  metadata = item.getMetaInfo();
 				metadata.setAuthor(ureq.getIdentity());
-				metadata.write();
+				vfsRepositoryService.updateMetadata(metadata);
 			}				
 	
 			newDocumentEditorCtrl = WysiwygFactory.createWysiwygController(ureq, getWindowControl(),
diff --git a/src/main/java/org/olat/course/nodes/pf/manager/PFNotifications.java b/src/main/java/org/olat/course/nodes/pf/manager/PFNotifications.java
index 52e8b1a6e1be4b1f50030c09ccbc1e9be2025884..52c61efd327012a4563272d9aff8a5ba51106da1 100644
--- a/src/main/java/org/olat/course/nodes/pf/manager/PFNotifications.java
+++ b/src/main/java/org/olat/course/nodes/pf/manager/PFNotifications.java
@@ -37,13 +37,13 @@ import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.Publisher;
 import org.olat.core.commons.services.notifications.Subscriber;
 import org.olat.core.commons.services.notifications.model.SubscriptionListItem;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
 import org.olat.core.id.context.BusinessControlFactory;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.Util;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.CourseFactory;
 import org.olat.course.ICourse;
 import org.olat.course.groupsandrights.CourseGroupManager;
@@ -125,7 +125,7 @@ public class PFNotifications {
 		SubscriptionListItem subListItem;
 		for (Iterator<FileInfo> it_infos = fInfos.iterator(); it_infos.hasNext();) {
 			FileInfo fi = it_infos.next();
-			MetaInfo metaInfo = fi.getMetaInfo();
+			VFSMetadata metaInfo = fi.getMetaInfo();
 			String filePath = fi.getRelPath();
 			Date modDate = fi.getLastModified();
 			String action = "upload";
@@ -154,7 +154,7 @@ public class PFNotifications {
 			if (metaInfo != null) {
 				iconCssClass = metaInfo.getIconCssClass();
 			}
-			if (metaInfo != null && !metaInfo.getName().startsWith(".")) {
+			if (metaInfo != null && !metaInfo.getFilename().startsWith(".")) {
 				subListItem = new SubscriptionListItem(desc, urlToSend, businessPath, modDate, iconCssClass);
 				items.add(subListItem);
 			}
diff --git a/src/main/java/org/olat/course/nodes/ta/AbstractTaskNotificationHandler.java b/src/main/java/org/olat/course/nodes/ta/AbstractTaskNotificationHandler.java
index fbca21b5c7c31d4738e32f065f0c9de8494d36e4..ef9fdcd86ca409033d09e051bf4fe0e6b2382a32 100644
--- a/src/main/java/org/olat/course/nodes/ta/AbstractTaskNotificationHandler.java
+++ b/src/main/java/org/olat/course/nodes/ta/AbstractTaskNotificationHandler.java
@@ -44,6 +44,7 @@ import org.olat.core.commons.services.notifications.manager.NotificationsUpgrade
 import org.olat.core.commons.services.notifications.model.SubscriptionListItem;
 import org.olat.core.commons.services.notifications.model.TitleItem;
 import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.translator.Translator;
@@ -53,7 +54,6 @@ import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.Util;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.repository.RepositoryEntry;
 import org.olat.repository.RepositoryManager;
 
@@ -101,7 +101,7 @@ public abstract class AbstractTaskNotificationHandler {
 				SubscriptionListItem subListItem;
 				for (Iterator<FileInfo> it_infos = fInfos.iterator(); it_infos.hasNext();) {
 					FileInfo fi = it_infos.next();
-					MetaInfo metaInfo = fi.getMetaInfo();
+					VFSMetadata metaInfo = fi.getMetaInfo();
 					String filePath = fi.getRelPath();
 					if(logDebug) log.debug("filePath=", filePath);
 					String fullUserName = getUserNameFromFilePath(metaInfo, filePath);
@@ -134,12 +134,11 @@ public abstract class AbstractTaskNotificationHandler {
 	 * @param filePath E.g. '/username/abgabe.txt'
 	 * @return 'firstname lastname'
 	 */
-	protected String getUserNameFromFilePath(MetaInfo info, String filePath) {
+	protected String getUserNameFromFilePath(VFSMetadata info, String filePath) {
 		// remove first '/'
 		try {
 			if(info != null) {
-				Long identityKey = info.getAuthorIdentityKey();
-				return NotificationHelper.getFormatedName(identityKey);
+				return NotificationHelper.getFormatedName(info.getAuthor());
 			}
 
 			String path = filePath.substring(1);
diff --git a/src/main/java/org/olat/course/nodes/ta/ConvertToGTACourseNode.java b/src/main/java/org/olat/course/nodes/ta/ConvertToGTACourseNode.java
index 6b7dd1c4a113002fddf9ddb54411952470274572..2c4bb30b8bac1676da43f3908660c69f274b7c8d 100644
--- a/src/main/java/org/olat/course/nodes/ta/ConvertToGTACourseNode.java
+++ b/src/main/java/org/olat/course/nodes/ta/ConvertToGTACourseNode.java
@@ -30,6 +30,8 @@ import org.olat.basesecurity.BaseSecurity;
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
 import org.olat.core.commons.persistence.DBFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
@@ -40,7 +42,6 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.ICourse;
 import org.olat.course.assessment.AssessmentHelper;
 import org.olat.course.assessment.AssessmentManager;
@@ -374,8 +375,8 @@ public class ConvertToGTACourseNode {
 		VFSItem sourceItem = source.resolve(name);
 		VFSItem targetItem = target.resolve(name);
 		if(sourceItem.canMeta() == VFSConstants.YES && targetItem.canMeta() == VFSConstants.YES) {
-			MetaInfo metaSource = sourceItem.getMetaInfo();
-			MetaInfo metaTarget = targetItem.getMetaInfo();
+			VFSMetadata metaSource = sourceItem.getMetaInfo();
+			VFSMetadata metaTarget = targetItem.getMetaInfo();
 			
 			if(metaSource != null) {
 				if(taskDef != null) {
@@ -391,7 +392,7 @@ public class ConvertToGTACourseNode {
 				
 				if(metaTarget != null) {
 					metaTarget.copyValues(metaSource);
-					metaTarget.write();
+					CoreSpringFactory.getImpl(VFSRepositoryService.class).updateMetadata(metaTarget);
 				}	
 			}	
 		}
diff --git a/src/main/java/org/olat/course/nodes/ta/DropboxController.java b/src/main/java/org/olat/course/nodes/ta/DropboxController.java
index fc0d26660039715b4148564a58501dc3478103e7..e838077b2d15bce45cd25757d991f319790e8a97 100644
--- a/src/main/java/org/olat/course/nodes/ta/DropboxController.java
+++ b/src/main/java/org/olat/course/nodes/ta/DropboxController.java
@@ -43,6 +43,8 @@ import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
 import org.olat.core.commons.services.notifications.ui.ContextualSubscriptionController;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.link.Link;
@@ -74,7 +76,6 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.callbacks.FullAccessWithQuotaCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.auditing.UserNodeAuditManager;
 import org.olat.course.nodes.AssessableCourseNode;
 import org.olat.course.nodes.CourseNode;
@@ -108,6 +109,8 @@ public class DropboxController extends BasicController {
 
 	@Autowired
 	private QuotaManager quotaManager;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	// Constructor for ProjectBrokerDropboxController
 	protected DropboxController(UserRequest ureq, WindowControl wControl) {
@@ -258,9 +261,9 @@ public class DropboxController extends BasicController {
 				}
 				
 				if(fOut.canMeta() == VFSConstants.YES) {
-					MetaInfo info = fOut.getMetaInfo();
+					VFSMetadata info = fOut.getMetaInfo();
 					info.setAuthor(ureq.getIdentity());
-					info.write();
+					vfsRepositoryService.updateMetadata(info);
 				}
 					
 				if (success) {
diff --git a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementListController.java b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementListController.java
index 93cac76e0fc1cb602131db6d59bf016d25976e52..ea530c0e69bd27d657035b26c90b98b45f89b9a0 100644
--- a/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementListController.java
+++ b/src/main/java/org/olat/modules/curriculum/ui/CurriculumElementListController.java
@@ -289,7 +289,7 @@ public class CurriculumElementListController extends FormBasicController impleme
 		rows.forEach(row -> {
 			row.setParent(keyToRow.get(row.getParentKey()));
 			if(row.getOlatResource() != null) {
-				VFSLeaf image = repositoryManager.getImage(row.getRepositoryEntryResourceable());
+				VFSLeaf image = repositoryManager.getImage(row.getRepositoryEntryResourceable().getResourceableId(), row.getOlatResource());
 				if(image != null) {
 					row.setThumbnailRelPath(mapperThumbnailKey.getUrl() + "/" + image.getName());
 				}
diff --git a/src/main/java/org/olat/modules/docpool/manager/DocumentPoolNotificationsHandler.java b/src/main/java/org/olat/modules/docpool/manager/DocumentPoolNotificationsHandler.java
index 47dbb987e959172af7383d576469f7a3fcf8cb94..6a05dbab37a665a0f35283ceed818cba732a6f46 100644
--- a/src/main/java/org/olat/modules/docpool/manager/DocumentPoolNotificationsHandler.java
+++ b/src/main/java/org/olat/modules/docpool/manager/DocumentPoolNotificationsHandler.java
@@ -36,6 +36,7 @@ import org.olat.core.commons.services.notifications.SubscriptionContext;
 import org.olat.core.commons.services.notifications.SubscriptionInfo;
 import org.olat.core.commons.services.notifications.model.SubscriptionListItem;
 import org.olat.core.commons.services.notifications.model.TitleItem;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.components.tree.TreeModel;
 import org.olat.core.gui.translator.Translator;
 import org.olat.core.id.Identity;
@@ -48,7 +49,6 @@ import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
 import org.olat.core.util.tree.TreeVisitor;
 import org.olat.core.util.vfs.VFSContainer;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.modules.docpool.DocumentPoolModule;
 import org.olat.modules.docpool.ui.DocumentPoolMainController;
 import org.olat.modules.taxonomy.Taxonomy;
@@ -154,7 +154,7 @@ public class DocumentPoolNotificationsHandler implements NotificationsHandler {
 				// skip this file, continue with next item in folder
 				continue;
 			}						
-			MetaInfo metaInfo = infos.getMetaInfo();
+			VFSMetadata metaInfo = infos.getMetaInfo();
 			String iconCssClass =  null;
 			if (metaInfo != null) {
 				if (metaInfo.getTitle() != null) {
diff --git a/src/main/java/org/olat/modules/fo/ui/MessageListController.java b/src/main/java/org/olat/modules/fo/ui/MessageListController.java
index 17de0884c23ebbd53700bf118784ee383466f25f..96510f26fed76f24b6ec38e9cd36eec9cbbfae05 100644
--- a/src/main/java/org/olat/modules/fo/ui/MessageListController.java
+++ b/src/main/java/org/olat/modules/fo/ui/MessageListController.java
@@ -35,6 +35,8 @@ import org.olat.core.commons.persistence.DBFactory;
 import org.olat.core.commons.services.mark.Mark;
 import org.olat.core.commons.services.mark.MarkResourceStat;
 import org.olat.core.commons.services.mark.MarkingService;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.dispatcher.mapper.Mapper;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
@@ -80,7 +82,6 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
 import org.olat.core.util.vfs.filters.VFSItemMetaFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.FOCourseNode;
 import org.olat.modules.fo.Forum;
 import org.olat.modules.fo.ForumCallback;
@@ -183,6 +184,8 @@ public class MessageListController extends BasicController implements GenericEve
 	private PortfolioV2Module portfolioModule;
 	@Autowired
 	private ForumMediaHandler forumMediaHandler;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	public MessageListController(UserRequest ureq, WindowControl wControl,
 			Forum forum, ForumCallback foCallback) {
@@ -438,7 +441,7 @@ public class MessageListController extends BasicController implements GenericEve
 		}
 		
 		List<MarkResourceStat> statList = markingService.getMarkManager().getStats(forumOres, subPaths, getIdentity());
-		Map<String,MarkResourceStat> stats = new HashMap<String,MarkResourceStat>(statList.size() * 2 + 1);
+		Map<String,MarkResourceStat> stats = new HashMap<>(statList.size() * 2 + 1);
 		for(MarkResourceStat stat:statList) {
 			stats.put(stat.getSubPath(), stat);
 		}
@@ -1491,14 +1494,12 @@ public class MessageListController extends BasicController implements GenericEve
 					if (view != null) {
 						List<VFSItem> attachments = view.getAttachments();
 						for (VFSItem vfsItem : attachments) {
-							MetaInfo meta = vfsItem.getMetaInfo();
-							if (meta != null && meta.getUUID().equals(query[2])) {
-								if (meta.isThumbnailAvailable()) {
-									VFSLeaf thumb = meta.getThumbnail(200, 200, false);
-									if(thumb != null) {
-										// Positive lookup, send as response
-										return new VFSMediaResource(thumb);
-									}
+							VFSMetadata meta = vfsItem.getMetaInfo();
+							if (meta instanceof VFSLeaf && meta.getUuid().equals(query[2])) {
+								VFSLeaf thumb = vfsRepositoryService.getThumbnail((VFSLeaf)vfsItem, meta, 200, 200, false);
+								if(thumb != null) {
+									// Positive lookup, send as response
+									return new VFSMediaResource(thumb);
 								}
 								break;
 							}
diff --git a/src/main/java/org/olat/modules/forms/ui/FileUploadListingController.java b/src/main/java/org/olat/modules/forms/ui/FileUploadListingController.java
index a20f2fdd637291b52fd3b3fb625d6fd065be7248..ad206d8f1fbf7f4597a63f881d73f743e247d60f 100644
--- a/src/main/java/org/olat/modules/forms/ui/FileUploadListingController.java
+++ b/src/main/java/org/olat/modules/forms/ui/FileUploadListingController.java
@@ -23,6 +23,7 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.link.Link;
@@ -35,10 +36,8 @@ import org.olat.core.gui.media.ZippedDirectoryMediaResource;
 import org.olat.core.gui.util.CSSHelper;
 import org.olat.core.util.CodeHelper;
 import org.olat.core.util.Formatter;
-import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaMapper;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.modules.forms.EvaluationFormManager;
 import org.olat.modules.forms.EvaluationFormResponse;
 import org.olat.modules.forms.EvaluationFormSession;
@@ -61,6 +60,8 @@ public class FileUploadListingController extends BasicController {
 	private final ResponseDataSource dataSource;
 	private final ReportHelper reportHelper;
 	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	@Autowired
 	private EvaluationFormManager evaluationFormManager;
 
@@ -112,14 +113,10 @@ public class FileUploadListingController extends BasicController {
 			filesize = Formatter.formatBytes((leaf).getSize());
 			mapperUri = registerCacheableMapper(ureq, "file-upload-" + CodeHelper.getRAMUniqueID() + "-" + leaf.getLastModified(), new VFSMediaMapper(leaf));
 			iconCss = CSSHelper.createFiletypeIconCssClassFor(leaf.getName());
-			if (leaf.canMeta() == VFSConstants.YES) {
-				MetaInfo meta = leaf.getMetaInfo();
-				if (meta != null && meta.isThumbnailAvailable()) {
-					VFSLeaf thumb = meta.getThumbnail(200, 200, false);
-					if (thumb != null) {
-						thumbUri = registerCacheableMapper(ureq, "file-upload-thumb" + CodeHelper.getRAMUniqueID() + "-" + leaf.getLastModified(), new VFSMediaMapper(thumb));
-					}
-				}
+
+			VFSLeaf thumb = vfsRepositoryService.getThumbnail(leaf, 200, 200, false);
+			if (thumb != null) {
+				thumbUri = registerCacheableMapper(ureq, "file-upload-thumb" + CodeHelper.getRAMUniqueID() + "-" + leaf.getLastModified(), new VFSMediaMapper(thumb));
 			}
 		}
 		return new FileUploadListingWrapper(legend.getColor(), legend.getName(), filename, filesize, mapperUri, iconCss, thumbUri);
diff --git a/src/main/java/org/olat/modules/portfolio/handler/FileHandler.java b/src/main/java/org/olat/modules/portfolio/handler/FileHandler.java
index e422abc5adb9dbb5780a353ffc4fef6f80a7ca71..a7f9302c341d919640596c7f7d372118b9ff428a 100644
--- a/src/main/java/org/olat/modules/portfolio/handler/FileHandler.java
+++ b/src/main/java/org/olat/modules/portfolio/handler/FileHandler.java
@@ -25,6 +25,8 @@ import java.util.List;
 import java.util.Locale;
 
 import org.olat.core.commons.services.image.Size;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -38,7 +40,6 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.modules.ceditor.InteractiveAddPageElementHandler;
 import org.olat.modules.ceditor.PageElementAddController;
 import org.olat.modules.ceditor.PageElementCategory;
@@ -77,6 +78,8 @@ public class FileHandler extends AbstractMediaHandler implements InteractiveAddP
 	private PortfolioFileStorage fileStorage;
 	@Autowired
 	private EPFrontendManager oldPortfolioManager;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	public FileHandler() {
 		super(FILE_TYPE);
@@ -117,8 +120,7 @@ public class FileHandler extends AbstractMediaHandler implements InteractiveAddP
 			VFSContainer storageContainer = fileStorage.getMediaContainer(media);
 			VFSItem item = storageContainer.resolve(media.getRootFilename());
 			if(item instanceof VFSLeaf && item.canMeta() == VFSConstants.YES) {
-				MetaInfo metaInfo = item.getMetaInfo();
-				thumbnail = metaInfo.getThumbnail(size.getHeight(), size.getWidth(), true);
+				thumbnail = vfsRepositoryService.getThumbnail((VFSLeaf)item, size.getHeight(), size.getWidth(), true);
 			}
 		}
 		
@@ -135,7 +137,7 @@ public class FileHandler extends AbstractMediaHandler implements InteractiveAddP
 		String title = null;
 		String description = null;
 		if (mediaObject instanceof VFSItem && ((VFSItem)mediaObject).canMeta() == VFSConstants.YES) {
-			MetaInfo meta = ((VFSItem)mediaObject).getMetaInfo();
+			VFSMetadata meta = ((VFSItem)mediaObject).getMetaInfo();
 			title = meta.getTitle();
 			description = meta.getComment();
 		}
diff --git a/src/main/java/org/olat/modules/portfolio/handler/ImageHandler.java b/src/main/java/org/olat/modules/portfolio/handler/ImageHandler.java
index a1dab3788b6f1fdca67af1035000a8ba59da0c1e..41343ff5d83266e876fb89381bb2e6caf18c0818 100644
--- a/src/main/java/org/olat/modules/portfolio/handler/ImageHandler.java
+++ b/src/main/java/org/olat/modules/portfolio/handler/ImageHandler.java
@@ -28,6 +28,8 @@ import java.util.Set;
 
 import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.services.image.Size;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -40,7 +42,6 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.modules.ceditor.InteractiveAddPageElementHandler;
 import org.olat.modules.ceditor.PageElement;
 import org.olat.modules.ceditor.PageElementAddController;
@@ -91,6 +92,8 @@ public class ImageHandler extends AbstractMediaHandler implements PageElementSto
 	private MediaDAO mediaDao;
 	@Autowired
 	private PortfolioFileStorage fileStorage;
+	@Autowired
+	private VFSRepositoryService VFSRepositoryService;
 	
 	public ImageHandler() {
 		super(IMAGE_TYPE);
@@ -129,8 +132,7 @@ public class ImageHandler extends AbstractMediaHandler implements PageElementSto
 			VFSContainer storageContainer = fileStorage.getMediaContainer(media);
 			VFSItem item = storageContainer.resolve(media.getRootFilename());
 			if(item instanceof VFSLeaf && item.canMeta() == VFSConstants.YES) {
-				MetaInfo metaInfo = item.getMetaInfo();
-				thumbnail = metaInfo.getThumbnail(size.getHeight(), size.getWidth(), true);
+				thumbnail = VFSRepositoryService.getThumbnail((VFSLeaf)item, size.getHeight(), size.getWidth(), true);
 			}
 		}
 		
@@ -147,7 +149,7 @@ public class ImageHandler extends AbstractMediaHandler implements PageElementSto
 		String title = null;
 		String description = null;
 		if (mediaObject instanceof VFSLeaf && ((VFSLeaf)mediaObject).canMeta() == VFSConstants.YES) {
-			MetaInfo meta = ((VFSLeaf)mediaObject).getMetaInfo();
+			VFSMetadata meta = ((VFSLeaf)mediaObject).getMetaInfo();
 			title = meta.getTitle();
 			description = meta.getComment();
 		}
diff --git a/src/main/java/org/olat/modules/portfolio/handler/VideoHandler.java b/src/main/java/org/olat/modules/portfolio/handler/VideoHandler.java
index 4406a5ff87608fc407bd75ca639b43b78ed8cc61..ad794b85e74ef70dfb4b8d400a234ac55c3bdec2 100644
--- a/src/main/java/org/olat/modules/portfolio/handler/VideoHandler.java
+++ b/src/main/java/org/olat/modules/portfolio/handler/VideoHandler.java
@@ -27,6 +27,8 @@ import java.util.Locale;
 import java.util.Set;
 
 import org.olat.core.commons.services.image.Size;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -39,7 +41,6 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.modules.ceditor.InteractiveAddPageElementHandler;
 import org.olat.modules.ceditor.PageElementAddController;
 import org.olat.modules.ceditor.PageElementCategory;
@@ -78,6 +79,8 @@ public class VideoHandler extends AbstractMediaHandler implements InteractiveAdd
 	private MediaDAO mediaDao;
 	@Autowired
 	private PortfolioFileStorage fileStorage;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	public VideoHandler() {
 		super(VIDEO_TYPE);
@@ -116,8 +119,7 @@ public class VideoHandler extends AbstractMediaHandler implements InteractiveAdd
 			VFSContainer storageContainer = fileStorage.getMediaContainer(media);
 			VFSItem item = storageContainer.resolve(media.getRootFilename());
 			if(item instanceof VFSLeaf && item.canMeta() == VFSConstants.YES) {
-				MetaInfo metaInfo = item.getMetaInfo();
-				thumbnail = metaInfo.getThumbnail(size.getHeight(), size.getWidth(), true);
+				thumbnail = vfsRepositoryService.getThumbnail((VFSLeaf)item, size.getHeight(), size.getWidth(), true);
 			}
 		}
 		
@@ -134,7 +136,7 @@ public class VideoHandler extends AbstractMediaHandler implements InteractiveAdd
 		String title = null;
 		String description = null;
 		if (mediaObject instanceof VFSLeaf && ((VFSLeaf)mediaObject).canMeta() == VFSConstants.YES) {
-			MetaInfo meta = ((VFSLeaf)mediaObject).getMetaInfo();
+			VFSMetadata meta = ((VFSLeaf)mediaObject).getMetaInfo();
 			title = meta.getTitle();
 			description = meta.getComment();
 		}
diff --git a/src/main/java/org/olat/modules/portfolio/ui/BinderListController.java b/src/main/java/org/olat/modules/portfolio/ui/BinderListController.java
index 7ef5d63cd84344900c62e53327ed7335af0317bc..804b33d04511d0fec0e65759c0bf9fc36a92da4b 100644
--- a/src/main/java/org/olat/modules/portfolio/ui/BinderListController.java
+++ b/src/main/java/org/olat/modules/portfolio/ui/BinderListController.java
@@ -26,11 +26,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import javax.servlet.http.HttpServletRequest;
-
 import org.olat.core.commons.persistence.DBFactory;
-import org.olat.core.commons.services.image.Size;
-import org.olat.core.dispatcher.mapper.Mapper;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.form.flexible.FormItem;
@@ -72,10 +68,7 @@ import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.prefs.Preferences;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.VFSMediaResource;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.course.nodes.PortfolioCourseNode;
 import org.olat.modules.portfolio.Binder;
 import org.olat.modules.portfolio.BinderConfiguration;
@@ -116,7 +109,6 @@ import org.springframework.beans.factory.annotation.Autowired;
 public class BinderListController extends FormBasicController
 	implements Activateable2, TooledController, FlexiTableComponentDelegate {
 	
-	private static final Size BACKGROUND_SIZE = new Size(400, 230, false);
 	
 	private int counter = 1;
 	private Link newBinderLink;
@@ -781,44 +773,6 @@ public class BinderListController extends FormBasicController
 		MediaResource resource = new ExportBinderAsCPResource(row, ureq, getLocale());
 		ureq.getDispatchResult().setResultingMediaResource(resource);
 	}
-
-	public static class ImageMapper implements Mapper {
-		
-		private final BindersDataModel binderModel;
-		
-		public ImageMapper(BindersDataModel model) {
-			this.binderModel = model;
-		}
-
-		@Override
-		public MediaResource handle(String relPath, HttpServletRequest request) {
-			String row = relPath;
-			if(row.startsWith("/")) {
-				row = row.substring(1, row.length());
-			}
-			int index = row.indexOf('/');
-			if(index > 0) {
-				row = row.substring(0, index);
-				Long key = Long.valueOf(row); 
-				List<BinderRow> rows = binderModel.getObjects();
-				for(BinderRow prow:rows) {
-					if(key.equals(prow.getKey())) {
-						VFSLeaf image = prow.getBackgroundImage();
-						if(image.canMeta() == VFSConstants.YES) {
-							MetaInfo info = image.getMetaInfo();
-							VFSLeaf thumbnail = info.getThumbnail(BACKGROUND_SIZE.getWidth(), BACKGROUND_SIZE.getHeight(), true);
-							if(thumbnail != null) {
-								image = thumbnail;
-							}
-						}
-						return new VFSMediaResource(image);
-					}
-				}
-			}
-			
-			return null;
-		}
-	}
 	
 	private static class BinderCssDelegate extends DefaultFlexiTableCssDelegate {
 		@Override
diff --git a/src/main/java/org/olat/modules/portfolio/ui/ImageMapper.java b/src/main/java/org/olat/modules/portfolio/ui/ImageMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..28b0f02918a52fe2186bd4316f1b3c6e003e78dd
--- /dev/null
+++ b/src/main/java/org/olat/modules/portfolio/ui/ImageMapper.java
@@ -0,0 +1,78 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules.portfolio.ui;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.image.Size;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
+import org.olat.core.dispatcher.mapper.Mapper;
+import org.olat.core.gui.media.MediaResource;
+import org.olat.core.util.vfs.VFSLeaf;
+import org.olat.core.util.vfs.VFSMediaResource;
+import org.olat.modules.portfolio.ui.model.BinderRow;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class ImageMapper implements Mapper {
+
+	private static final Size BACKGROUND_SIZE = new Size(400, 230, false);
+	
+	private final BindersDataModel binderModel;
+	private final VFSRepositoryService vfsRepositoryService;
+	
+	public ImageMapper(BindersDataModel model) {
+		this.binderModel = model;
+		vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+	}
+
+	@Override
+	public MediaResource handle(String relPath, HttpServletRequest request) {
+		String row = relPath;
+		if(row.startsWith("/")) {
+			row = row.substring(1, row.length());
+		}
+		int index = row.indexOf('/');
+		if(index > 0) {
+			row = row.substring(0, index);
+			Long key = Long.valueOf(row); 
+			List<BinderRow> rows = binderModel.getObjects();
+			for(BinderRow prow:rows) {
+				if(key.equals(prow.getKey())) {
+					VFSLeaf image = prow.getBackgroundImage();
+					VFSLeaf thumbnail = vfsRepositoryService.getThumbnail(image, BACKGROUND_SIZE.getWidth(), BACKGROUND_SIZE.getHeight(), true);
+					if(thumbnail != null) {
+						image = thumbnail;
+					}
+					return new VFSMediaResource(image);
+				}
+			}
+		}
+		
+		return null;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/portfolio/ui/LastBinderListController.java b/src/main/java/org/olat/modules/portfolio/ui/LastBinderListController.java
index 7e14c694fb23d15f548843e32f34b73bbf3716d6..fe154519d5cd3443d9861f1819945e3aed84c2ea 100644
--- a/src/main/java/org/olat/modules/portfolio/ui/LastBinderListController.java
+++ b/src/main/java/org/olat/modules/portfolio/ui/LastBinderListController.java
@@ -43,7 +43,6 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.modules.portfolio.PortfolioService;
 import org.olat.modules.portfolio.model.BinderStatistics;
-import org.olat.modules.portfolio.ui.BinderListController.ImageMapper;
 import org.olat.modules.portfolio.ui.BindersDataModel.PortfolioCols;
 import org.olat.modules.portfolio.ui.event.OpenBinderEvent;
 import org.olat.modules.portfolio.ui.event.OpenMyBindersEvent;
diff --git a/src/main/java/org/olat/modules/video/ui/VideoListingController.java b/src/main/java/org/olat/modules/video/ui/VideoListingController.java
index 595cd3ac9a48630f77e00bf8b7e9bc179f915bea..ffc266d06782deca710ac63f703e53ccc7f5be46 100644
--- a/src/main/java/org/olat/modules/video/ui/VideoListingController.java
+++ b/src/main/java/org/olat/modules/video/ui/VideoListingController.java
@@ -47,14 +47,12 @@ import org.olat.core.gui.control.WindowControl;
 import org.olat.core.gui.control.generic.dtabs.Activateable2;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.gui.media.NotFoundMediaResource;
-import org.olat.core.id.OLATResourceable;
 import org.olat.core.id.context.BusinessControlFactory;
 import org.olat.core.id.context.ContextEntry;
 import org.olat.core.id.context.StateEntry;
 import org.olat.core.logging.activity.ThreadLocalUserActivityLogger;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.Util;
-import org.olat.core.util.resource.OresHelper;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
 import org.olat.fileresource.types.VideoFileResource;
@@ -224,9 +222,9 @@ public class VideoListingController extends FormBasicController implements Activ
 				int start = relPath.lastIndexOf("/");
 				if (start != -1) {
 					relPath = relPath.substring(start+1);
-					long id = Long.parseLong(relPath);
-					OLATResourceable videoResource = OresHelper.createOLATResourceableInstance("RepositoryEntry", id);
-					VFSLeaf imageFile = repositoryManager.getImage(videoResource);
+					Long id = Long.valueOf(relPath);
+					RepositoryEntry entry = repositoryService.loadByKey(id);
+					VFSLeaf imageFile = repositoryManager.getImage(entry);
 					return new VFSMediaResource(imageFile);
 				}
 			}
diff --git a/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java b/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java
index 73d6e9093f645950562cd4dc0534319449b466e4..ceebf3300c66d3a577f314a5acd0759c097307d2 100644
--- a/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java
+++ b/src/main/java/org/olat/modules/webFeed/manager/FeedManagerImpl.java
@@ -37,6 +37,7 @@ import org.olat.core.commons.persistence.PersistenceHelper;
 import org.olat.core.commons.services.commentAndRating.CommentAndRatingService;
 import org.olat.core.commons.services.image.Size;
 import org.olat.core.commons.services.notifications.NotificationsManager;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.components.form.flexible.elements.FileElement;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.helpers.Settings;
@@ -115,13 +116,15 @@ public class FeedManagerImpl extends FeedManager {
 	@Autowired
 	private QuotaManager quotaManager;
 	@Autowired
+	private BaseSecurity securityManager;
+	@Autowired
 	private FeedFileStorge feedFileStorage;
 	@Autowired
 	private ExternalFeedFetcher externalFeedFetcher;
 	@Autowired
 	private NotificationsManager notificationsManager;
 	@Autowired
-	private BaseSecurity securityManager;
+	private VFSRepositoryService vfsRepositoryService;
 
 	/**
 	 * spring only
@@ -654,9 +657,8 @@ public class FeedManagerImpl extends FeedManager {
 			VFSItem item =feedFileStorage.getOrCreateFeedMediaContainer(feed);
 			item = item.resolve(fileName);
 			if (thumbnailSize != null && thumbnailSize.getHeight() > 0 && thumbnailSize.getWidth() > 0
-					&& item.canMeta() == VFSConstants.YES) {
-				item = item.getMetaInfo().getThumbnail(thumbnailSize.getWidth(),
-						thumbnailSize.getHeight(), false);
+					&& item instanceof VFSLeaf && item.canMeta() == VFSConstants.YES) {
+				item = vfsRepositoryService.getThumbnail((VFSLeaf)item, thumbnailSize.getWidth(), thumbnailSize.getHeight(), false);
 			}
 			if (item instanceof VFSLeaf) {
 				mediaResource = (VFSLeaf) item;
diff --git a/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java b/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java
index b95f438a70f30edd5cadd1548d8b0c2a3feb8584..abc61b39a51ff2a03e9eabfa9bee544373c535db 100644
--- a/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java
+++ b/src/main/java/org/olat/portfolio/model/artefacts/FileArtefactHandler.java
@@ -22,6 +22,7 @@ package org.olat.portfolio.model.artefacts;
 
 import org.apache.lucene.document.Document;
 import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -32,7 +33,6 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.group.BusinessGroup;
 import org.olat.group.BusinessGroupService;
 import org.olat.portfolio.EPAbstractHandler;
@@ -69,7 +69,7 @@ public class FileArtefactHandler extends EPAbstractHandler<FileArtefact> {
 			VFSItem fileSource = (VFSItem) source;
 			((FileArtefact) artefact).setFilename(fileSource.getName());
 			
-			MetaInfo meta = null;
+			VFSMetadata meta = null;
 			if(fileSource.canMeta() == VFSConstants.YES) {
 				meta = fileSource.getMetaInfo();
 			}
@@ -90,7 +90,8 @@ public class FileArtefactHandler extends EPAbstractHandler<FileArtefact> {
 			String finalBusinessPath = null;
 			String sourceInfo = null;
 			// used to rebuild businessPath and source for a file:
-			if (pathElements[1].equals("homes") && meta != null && pathElements[2].equals(meta.getAuthor())) {
+			String author = meta != null && meta.getAuthor() != null ? meta.getAuthor().getKey().toString() : null;
+			if (pathElements[1].equals("homes") && meta != null && pathElements[2].equals(author)) {
 				// from users briefcase
 				String lastParts = "/";
 				for (int i = 4; i < (pathElements.length - 1); i++) {
@@ -103,19 +104,19 @@ public class FileArtefactHandler extends EPAbstractHandler<FileArtefact> {
 				for (int i = 5; i < (pathElements.length - 1); i++) {
 					lastParts = lastParts + pathElements[i] + "/";
 				}
-				BusinessGroup bGroup = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(new Long(pathElements[4]));
+				BusinessGroup bGroup = CoreSpringFactory.getImpl(BusinessGroupService.class).loadBusinessGroup(Long.valueOf(pathElements[4]));
 				if (bGroup != null) {
 					sourceInfo = bGroup.getName() + " -> " + lastParts + " -> " + fileSource.getName();
 				}
 				finalBusinessPath = "[BusinessGroup:" + pathElements[4] + "][toolfolder:0][path=" + lastParts + fileSource.getName() + ":0]";
 			} else if (pathElements[4].equals("coursefolder")) {
 				// the course folder
-				sourceInfo = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(new Long(pathElements[2]))
+				sourceInfo = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(Long.valueOf(pathElements[2]))
 						+ " -> " + fileSource.getName();
 
 			} else if (pathElements[1].equals("course") && pathElements[3].equals("foldernodes")) {
 				// folders inside a course
-				sourceInfo = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(new Long(pathElements[2]))
+				sourceInfo = RepositoryManager.getInstance().lookupDisplayNameByOLATResourceableId(Long.valueOf(pathElements[2]))
 						+ " -> " + pathElements[4] + " -> " + fileSource.getName();
 				finalBusinessPath = "[RepositoryEntry:" + pathElements[2] + "][CourseNode:" + pathElements[4] + "]";
 			}
diff --git a/src/main/java/org/olat/portfolio/ui/artefacts/view/details/FileArtefactDetailsController.java b/src/main/java/org/olat/portfolio/ui/artefacts/view/details/FileArtefactDetailsController.java
index 94777c997cd9781a15776e4577de683656d72cc3..d0b672b3935be522ece0d09798b934fb04ca1501 100644
--- a/src/main/java/org/olat/portfolio/ui/artefacts/view/details/FileArtefactDetailsController.java
+++ b/src/main/java/org/olat/portfolio/ui/artefacts/view/details/FileArtefactDetailsController.java
@@ -19,7 +19,8 @@
  */
 package org.olat.portfolio.ui.artefacts.view.details;
 
-import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.gui.components.Component;
 import org.olat.core.gui.components.download.DownloadComponent;
@@ -39,11 +40,11 @@ import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.portfolio.manager.EPFrontendManager;
 import org.olat.portfolio.model.artefacts.AbstractArtefact;
 import org.olat.portfolio.model.artefacts.FileArtefact;
 import org.olat.portfolio.ui.artefacts.collect.EPCreateFileArtefactStepForm00;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * Description:<br>
@@ -63,16 +64,19 @@ public class FileArtefactDetailsController extends BasicController {
 	private DialogBoxController delDialog;
 	private FileArtefact fArtefact;
 	private Controller fileUploadCtrl;
-	private EPFrontendManager ePFMgr;
 	private Link uploadLink;
 	private CloseableCalloutWindowController calloutCtrl;
 	private Panel viewPanel;
+	
+	@Autowired
+	private EPFrontendManager ePFMgr;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	public FileArtefactDetailsController(UserRequest ureq, WindowControl wControl, AbstractArtefact artefact, boolean readOnlyMode) {
 		super(ureq, wControl);
 		this.readOnlyMode = readOnlyMode;
 		fArtefact = (FileArtefact)artefact;
-		ePFMgr = (EPFrontendManager) CoreSpringFactory.getBean("epFrontendManager");
 		
 		viewPanel = new Panel("empty");
 		initViewDependingOnFileExistance(ureq);
@@ -96,18 +100,16 @@ public class FileArtefactDetailsController extends BasicController {
 		vC.contextPut("filename", fArtefact.getFilename());
 		
 		if(file.canMeta() == VFSConstants.YES) {
-			MetaInfo meta = file.getMetaInfo();
+			VFSMetadata meta = file.getMetaInfo();
 			vC.contextPut("meta", meta);
 			// show a preview thumbnail if possible
-			if (meta.isThumbnailAvailable()) {
-				VFSLeaf thumb = meta.getThumbnail(200, 200, false);
+			if(file instanceof VFSLeaf) {
+				VFSLeaf thumb = vfsRepositoryService.getThumbnail((VFSLeaf)file, meta, 200, 200, false);
 				if(thumb != null) {
 					mr = new VFSMediaResource(thumb);
 				}
 				if(mr != null) {
-					String thumbMapper = registerMapper(ureq, (relPath, request) -> {
-						return mr;
-					});					
+					String thumbMapper = registerMapper(ureq, (relPath, request) ->  mr);					
 					vC.contextPut("thumbMapper", thumbMapper);
 				}
 			}
diff --git a/src/main/java/org/olat/repository/RepositoryManager.java b/src/main/java/org/olat/repository/RepositoryManager.java
index d29991f5099c968370318f821b0dcd10914ad79c..ac4e1a1dc544ea702e0209c49bd5a8cd27f2f7e4 100644
--- a/src/main/java/org/olat/repository/RepositoryManager.java
+++ b/src/main/java/org/olat/repository/RepositoryManager.java
@@ -74,6 +74,8 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
+import org.olat.course.PersistingCourseImpl;
+import org.olat.fileresource.FileResourceManager;
 import org.olat.group.GroupLoggingAction;
 import org.olat.modules.taxonomy.TaxonomyLevel;
 import org.olat.repository.manager.RepositoryEntryDAO;
@@ -166,8 +168,8 @@ public class RepositoryManager {
 		}
 
 		String sourceImageSuffix = FileUtils.getFileSuffix(srcFile.getName());
-		VFSContainer repositoryHome = new LocalFolderImpl(new File(FolderConfig.getCanonicalRepositoryHome()));
-		VFSLeaf newImage = repositoryHome.createChildLeaf(target.getResourceableId() + "." + sourceImageSuffix);
+		VFSContainer targetMediaDir = this.getMediaDirectory(target.getOlatResource());
+		VFSLeaf newImage = targetMediaDir.createChildLeaf(target.getResourceableId() + "." + sourceImageSuffix);
 		if (newImage != null) {
 			return VFSManager.copyContent(srcFile, newImage, false);
 		}
@@ -181,20 +183,37 @@ public class RepositoryManager {
 		}
 	}
 
-	public VFSLeaf getImage(OLATResourceable re) {
-		VFSContainer repositoryHome = new LocalFolderImpl(new File(FolderConfig.getCanonicalRepositoryHome()));
-		String imageName = re.getResourceableId() + ".jpg";
+	public VFSLeaf getImage(RepositoryEntry re) {
+		return getImage(re.getKey(), re.getOlatResource());
+	}
+	
+	public VFSLeaf getImage(Long repoEntryKey, OLATResource re) {
+		VFSContainer repositoryHome = getMediaDirectory(re);
+		
+		String imageName = repoEntryKey + ".jpg";
 		VFSItem image = repositoryHome.resolve(imageName);
 		if(image instanceof VFSLeaf) {
 			return (VFSLeaf)image;
 		}
-		imageName = re.getResourceableId() + ".png";
+		imageName = repoEntryKey + ".png";
 		image = repositoryHome.resolve(imageName);
 		if(image instanceof VFSLeaf) {
 			return (VFSLeaf)image;
 		}
 		return null;
 	}
+	
+	private VFSContainer getMediaDirectory(OLATResource re) {
+		File fResourceFileroot;
+		if("CourseModule".equals(re.getResourceableTypeName())) {
+			fResourceFileroot = new File(FolderConfig.getCanonicalRoot(), PersistingCourseImpl.COURSE_ROOT_DIR_NAME);
+			fResourceFileroot = new File(fResourceFileroot, re.getResourceableId().toString());
+		} else {
+			fResourceFileroot = FileResourceManager.getInstance().getFileResourceRoot(re);
+		}
+		File mediaHome = new File(fResourceFileroot, "media");
+		return new LocalFolderImpl(mediaHome);
+	}
 
 	public boolean setImage(VFSLeaf newImageFile, RepositoryEntry re) {
 		VFSLeaf currentImage = getImage(re);
@@ -211,9 +230,15 @@ public class RepositoryManager {
 		if("jpg".equalsIgnoreCase(extension) || "jpeg".equalsIgnoreCase(extension)) {
 			targetExtension = ".jpg";
 		}
-
-		VFSContainer repositoryHome = new LocalFolderImpl(new File(FolderConfig.getCanonicalRepositoryHome()));
+		
+		VFSContainer repositoryHome = getMediaDirectory(re.getOlatResource());
 		VFSLeaf repoImage = repositoryHome.createChildLeaf(re.getResourceableId() + targetExtension);
+		if(repoImage == null) {
+			VFSItem item = repositoryHome.resolve(re.getResourceableId() + targetExtension);
+			if(item instanceof VFSLeaf) {
+				repoImage = (VFSLeaf)item;
+			}
+		}
 
 		if(targetExtension.equals(".png") || targetExtension.equals(".jpg")) {
 			Size newImageSize = imageHelper.getSize(newImageFile, extension);
@@ -226,7 +251,6 @@ public class RepositoryManager {
 		return size != null;
 	}
 
-
 	/**
 	 * Lookup repo entry by key.
 	 * @param the repository entry key (not the olatresourceable key)
diff --git a/src/main/java/org/olat/repository/manager/CatalogManager.java b/src/main/java/org/olat/repository/manager/CatalogManager.java
index 36bb6828e07157ec12ad0b28958c15d332689701..294146e9ee1a87c050b3d5a6e5fe5caffc7f5588 100644
--- a/src/main/java/org/olat/repository/manager/CatalogManager.java
+++ b/src/main/java/org/olat/repository/manager/CatalogManager.java
@@ -44,6 +44,7 @@ import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.persistence.PersistenceHelper;
 import org.olat.core.commons.services.image.ImageService;
 import org.olat.core.commons.services.image.Size;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.logging.OLog;
@@ -115,6 +116,8 @@ public class CatalogManager implements UserDataDeletable, InitializingBean {
 	private RepositoryService repositoryService;
 	@Autowired
 	private OrganisationService organisationService;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 
 	/**
@@ -748,7 +751,7 @@ public class CatalogManager implements UserDataDeletable, InitializingBean {
 		VFSLeaf imgFile =  getImage(entry);
 		if (imgFile != null) {
 			if(imgFile.canMeta() == VFSConstants.YES) {
-				imgFile.getMetaInfo().clearThumbnails();
+				vfsRepositoryService.resetThumbnails(imgFile);
 			}
 			imgFile.delete();
 		}
@@ -758,7 +761,7 @@ public class CatalogManager implements UserDataDeletable, InitializingBean {
 		VFSLeaf currentImage = getImage(re);
 		if(currentImage != null) {
 			if(currentImage.canMeta() == VFSConstants.YES) {
-				currentImage.getMetaInfo().clearThumbnails();
+				vfsRepositoryService.resetThumbnails(currentImage);
 			}
 			currentImage.delete();
 		}
diff --git a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
index ecff11dbf904c29a2c84a111fe2b73dd6d38452c..1c34939a7023946aba37c98a5b61ee15b4f7d2e9 100644
--- a/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
+++ b/src/main/java/org/olat/repository/manager/RepositoryServiceImpl.java
@@ -19,7 +19,6 @@
  */
 package org.olat.repository.manager;
 
-import java.io.File;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
@@ -37,7 +36,6 @@ import org.olat.basesecurity.OrganisationDataDeletable;
 import org.olat.basesecurity.OrganisationRoles;
 import org.olat.basesecurity.manager.GroupDAO;
 import org.olat.core.CoreSpringFactory;
-import org.olat.core.commons.modules.bc.FolderConfig;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.services.license.LicenseService;
 import org.olat.core.commons.services.mark.MarkManager;
@@ -59,7 +57,6 @@ import org.olat.core.util.mail.MailPackage;
 import org.olat.core.util.mail.MailTemplate;
 import org.olat.core.util.mail.MailerResult;
 import org.olat.core.util.resource.OresHelper;
-import org.olat.core.util.vfs.LocalFolderImpl;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
@@ -386,14 +383,15 @@ public class RepositoryServiceImpl implements RepositoryService, OrganisationDat
 
 	@Override
 	public VFSLeaf getIntroductionImage(RepositoryEntry re) {
-		VFSContainer repositoryHome = new LocalFolderImpl(new File(FolderConfig.getCanonicalRepositoryHome()));
+		RepositoryHandler handler = repositoryHandlerFactory.getRepositoryHandler(re);
+		VFSContainer mediaContainer = handler.getMediaContainer(re);
 		String imageName = re.getResourceableId() + ".jpg";
-		VFSItem image = repositoryHome.resolve(imageName);
+		VFSItem image = mediaContainer.resolve(imageName);
 		if(image instanceof VFSLeaf) {
 			return (VFSLeaf)image;
 		}
 		imageName = re.getResourceableId() + ".png";
-		image = repositoryHome.resolve(imageName);
+		image = mediaContainer.resolve(imageName);
 		if(image instanceof VFSLeaf) {
 			return (VFSLeaf)image;
 		}
diff --git a/src/main/java/org/olat/repository/ui/CatalogEntryImageMapper.java b/src/main/java/org/olat/repository/ui/CatalogEntryImageMapper.java
index 5f5f32dbeca00f9ec92068e6493c548516fc64e9..94655efbe41e8181bc04368dd2c6f0d9835fa16f 100644
--- a/src/main/java/org/olat/repository/ui/CatalogEntryImageMapper.java
+++ b/src/main/java/org/olat/repository/ui/CatalogEntryImageMapper.java
@@ -22,6 +22,7 @@ package org.olat.repository.ui;
 import javax.servlet.http.HttpServletRequest;
 
 import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.dispatcher.mapper.Mapper;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.gui.media.NotFoundMediaResource;
@@ -30,7 +31,6 @@ import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.repository.manager.CatalogManager;
 
 
@@ -43,6 +43,7 @@ import org.olat.repository.manager.CatalogManager;
 public class CatalogEntryImageMapper implements Mapper {
 	
 	private CatalogManager catalogManager;
+	private VFSRepositoryService vfsRepositoryService;
 	
 	public CatalogEntryImageMapper() {
 		catalogManager = CoreSpringFactory.getImpl(CatalogManager.class);
@@ -60,8 +61,7 @@ public class CatalogEntryImageMapper implements Mapper {
 		MediaResource resource = null;
 		if(image instanceof  VFSLeaf) {
 			if(image.canMeta() == VFSConstants.YES) {
-				MetaInfo info = image.getMetaInfo();
-				VFSLeaf thumbnail = info.getThumbnail(180, 180, true);
+				VFSLeaf thumbnail = vfsRepositoryService.getThumbnail((VFSLeaf)image, 180, 180, true);
 				if(thumbnail != null) {
 					resource = new VFSMediaResource(thumbnail);
 				}
diff --git a/src/main/java/org/olat/repository/ui/RepositoryEntryImageMapper.java b/src/main/java/org/olat/repository/ui/RepositoryEntryImageMapper.java
index 64a1a4a0f795296e2983399d221c61243c0c4cec..f4042e6bfff51588765024fa98fe4d18f2a7c660 100644
--- a/src/main/java/org/olat/repository/ui/RepositoryEntryImageMapper.java
+++ b/src/main/java/org/olat/repository/ui/RepositoryEntryImageMapper.java
@@ -23,16 +23,16 @@ import java.io.File;
 
 import javax.servlet.http.HttpServletRequest;
 
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.dispatcher.mapper.Mapper;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.util.vfs.LocalFolderImpl;
-import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSMediaResource;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * 
@@ -43,6 +43,12 @@ import org.olat.core.util.vfs.meta.MetaInfo;
 public class RepositoryEntryImageMapper implements Mapper {
 	
 	private VFSContainer rootContainer;
+	
+	private VFSRepositoryService vfsRepositoryService;
+	
+	public RepositoryEntryImageMapper() {
+		vfsRepositoryService = CoreSpringFactory.getImpl(VFSRepositoryService.class);
+	}
 
 	@Override
 	public MediaResource handle(String relPath, HttpServletRequest request) {
@@ -56,13 +62,10 @@ public class RepositoryEntryImageMapper implements Mapper {
 		MediaResource resource = null;
 		VFSItem image = rootContainer.resolve(relPath);
 		if(image instanceof VFSLeaf) {
-			if(image.canMeta() == VFSConstants.YES) {
-				MetaInfo info = image.getMetaInfo();
-				//121 is needed to fill the div
-				VFSLeaf thumbnail = info.getThumbnail(180, 120, true);
-				if(thumbnail != null) {
-					resource = new VFSMediaResource(thumbnail);
-				}	
+			//121 is needed to fill the div
+			VFSLeaf thumbnail = vfsRepositoryService.getThumbnail((VFSLeaf)image, 180, 120, true);
+			if(thumbnail != null) {
+				resource = new VFSMediaResource(thumbnail);
 			}
 			
 			if(resource == null) {
diff --git a/src/main/java/org/olat/repository/ui/list/DefaultRepositoryEntryDataSource.java b/src/main/java/org/olat/repository/ui/list/DefaultRepositoryEntryDataSource.java
index 24168b01b34371229690381730b2b4150a54abe6..50ecf8e618185385417c13cd56cc04cedd0bcca7 100644
--- a/src/main/java/org/olat/repository/ui/list/DefaultRepositoryEntryDataSource.java
+++ b/src/main/java/org/olat/repository/ui/list/DefaultRepositoryEntryDataSource.java
@@ -154,7 +154,7 @@ public class DefaultRepositoryEntryDataSource implements FlexiTableDataSourceDel
 		for(RepositoryEntryMyView entry:repoEntries) {
 			RepositoryEntryRow row = new RepositoryEntryRow(entry);
 
-			VFSLeaf image = repositoryManager.getImage(entry);
+			VFSLeaf image = repositoryManager.getImage(entry.getKey(), entry.getOlatResource());
 			if(image != null) {
 				row.setThumbnailRelPath(uifactory.getMapperThumbnailUrl() + "/" + image.getName());
 			}
diff --git a/src/main/java/org/olat/repository/ui/settings/LazyRepositoryEdusharingProvider.java b/src/main/java/org/olat/repository/ui/settings/LazyRepositoryEdusharingProvider.java
index 98c162d2bbe0037eefa398b49df6cef92ec27012..3657d7af8207ccc0b0bd0a1467c1409d0f88bf1c 100644
--- a/src/main/java/org/olat/repository/ui/settings/LazyRepositoryEdusharingProvider.java
+++ b/src/main/java/org/olat/repository/ui/settings/LazyRepositoryEdusharingProvider.java
@@ -60,7 +60,7 @@ public class LazyRepositoryEdusharingProvider implements VFSEdusharingProvider {
 
 	@Override
 	public void setSubPath(VFSItem item) {
-		this.subPath = "file-meta-uuid-" + item.getMetaInfo().getUUID();
+		this.subPath = "file-meta-uuid-" + item.getMetaInfo().getUuid();
 	}
 
 	@Override
diff --git a/src/main/java/org/olat/resource/OLATResourceManager.java b/src/main/java/org/olat/resource/OLATResourceManager.java
index d8e8ee5e3c6df433908c6c11d32c858c5530ef9b..60d1dcaff687b8284a7c9753b2ca4c93b732310d 100644
--- a/src/main/java/org/olat/resource/OLATResourceManager.java
+++ b/src/main/java/org/olat/resource/OLATResourceManager.java
@@ -31,10 +31,10 @@ import java.util.List;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.id.OLATResourceable;
 import org.olat.core.logging.AssertException;
-import org.olat.core.manager.BasicManager;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
 import org.olat.core.util.CodeHelper;
 import org.olat.core.util.coordinate.CoordinatorManager;
-import org.olat.core.util.coordinate.SyncerCallback;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.course.CourseModule;
 
@@ -45,7 +45,9 @@ import org.olat.course.CourseModule;
  * @author Andreas Ch. Kapp
  *
  */
-public class OLATResourceManager extends BasicManager {
+public class OLATResourceManager {
+	
+	private static final OLog log = Tracing.createLoggerFor(OLATResourceManager.class);
 	
 	private static OLATResourceManager INSTANCE;
 	private DB dbInstance;
@@ -154,20 +156,18 @@ public class OLATResourceManager extends BasicManager {
 		}
 		// Second there exists no resourcable => try to find and create(if no exists) in a synchronized block
 		//o_clusterOK by:cg
-		ores = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(resourceable, new SyncerCallback<OLATResource>(){
-			public OLATResource execute() {
-				logDebug("start synchronized-block in findOrPersistResourceable");
-				OLATResource oresSync  = findResourceable(resourceable);
-				// if not found, persist it.
-				if (oresSync == null ) {
-					if(CourseModule.ORES_TYPE_COURSE.equals(resourceable.getResourceableTypeName())) {
-					  logInfo("OLATResourceManager - createOLATResourceInstance if not found: " + resourceable.getResourceableTypeName() + " " + resourceable.getResourceableId());
-					}
-					oresSync = createOLATResourceInstance(resourceable);
-					saveOLATResource(oresSync);
+		ores = CoordinatorManager.getInstance().getCoordinator().getSyncer().doInSync(resourceable, () -> {
+			log.debug("start synchronized-block in findOrPersistResourceable");
+			OLATResource oresSync  = findResourceable(resourceable);
+			// if not found, persist it.
+			if (oresSync == null ) {
+				if(CourseModule.ORES_TYPE_COURSE.equals(resourceable.getResourceableTypeName())) {
+				  log.info("OLATResourceManager - createOLATResourceInstance if not found: " + resourceable.getResourceableTypeName() + " " + resourceable.getResourceableId());
 				}
-				return oresSync;
+				oresSync = createOLATResourceInstance(resourceable);
+				saveOLATResource(oresSync);
 			}
+			return oresSync;
 		});
 		return ores;
 	}
@@ -207,7 +207,7 @@ public class OLATResourceManager extends BasicManager {
 				.getResultList();
 
 		// if not found, it is an empty list
-		if (resources.size() == 0) {
+		if (resources.isEmpty()) {
 			return null;
 		}
 		return resources.get(0);
@@ -228,12 +228,9 @@ public class OLATResourceManager extends BasicManager {
 		if(types == null || types.isEmpty()) return Collections.<OLATResource>emptyList();
 		
 		String s = "select ori from org.olat.resource.OLATResourceImpl ori where ori.resName in (:restrictedType)";
-		List<OLATResource> resources = dbInstance.getCurrentEntityManager()
+		return dbInstance.getCurrentEntityManager()
 				.createQuery(s, OLATResource.class)
 				.setParameter("restrictedType", types)
 				.getResultList();
-		return resources;
-		
-		
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/restapi/repository/course/CourseResourceFolderWebService.java b/src/main/java/org/olat/restapi/repository/course/CourseResourceFolderWebService.java
index 941c2876fb70a3c27e183e7df70c2f44d1bb71a9..23b5976e8421ea1e9a35af867452a8917f5f69ae 100644
--- a/src/main/java/org/olat/restapi/repository/course/CourseResourceFolderWebService.java
+++ b/src/main/java/org/olat/restapi/repository/course/CourseResourceFolderWebService.java
@@ -47,6 +47,8 @@ import javax.ws.rs.core.Response.Status;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.UserRequest;
 import org.olat.core.id.Identity;
 import org.olat.core.id.Roles;
@@ -61,7 +63,6 @@ import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSLockManager;
 import org.olat.core.util.vfs.callbacks.ReadOnlyCallback;
 import org.olat.core.util.vfs.filters.SystemItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.core.util.vfs.version.Versionable;
 import org.olat.course.ICourse;
 import org.olat.course.config.CourseConfig;
@@ -108,6 +109,8 @@ public class CourseResourceFolderWebService {
 	private RepositoryManager repositoryManager;
 	@Autowired
 	private RepositoryService repositoryService;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	/**
 	 * The version of the resources folders Web Service
@@ -371,9 +374,9 @@ public class CourseResourceFolderWebService {
 		}
 
 		if(newFile.canMeta() == VFSConstants.YES ) {
-			MetaInfo infos = newFile.getMetaInfo();
+			VFSMetadata infos = newFile.getMetaInfo();
 			infos.setAuthor(ureq.getIdentity());
-			infos.write();
+			vfsRepositoryService.updateMetadata(infos);
 		}
 
 		return Response.ok().build();
diff --git a/src/main/java/org/olat/search/service/document/file/FileDocument.java b/src/main/java/org/olat/search/service/document/file/FileDocument.java
index 2b15cc9d34df78808349fab0217df1bf316a7049..6b81571bb43571c46b1f46c5408a5e2cdc8f2f97 100644
--- a/src/main/java/org/olat/search/service/document/file/FileDocument.java
+++ b/src/main/java/org/olat/search/service/document/file/FileDocument.java
@@ -29,11 +29,12 @@ import java.io.IOException;
 import java.util.Calendar;
 import java.util.Date;
 
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.id.User;
 import org.olat.core.util.StringHelper;
 import org.olat.core.util.WebappHelper;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.search.model.OlatDocument;
 import org.olat.search.service.SearchResourceContext;
 import org.olat.search.service.SimpleDublinCoreMetadataFieldsProvider;
@@ -47,11 +48,11 @@ public abstract class FileDocument extends OlatDocument {
 	private static final long serialVersionUID = -8977326187286155071L;
 	// Must correspond with LocalString_xx.properties
 	// Do not use '_' because we want to seach for certain documenttype and lucene haev problems with '_' 
-	public final static String TYPE = "type.file";
+	public static final String TYPE = "type.file";
 	
 	protected void init(SearchResourceContext leafResourceContext, VFSLeaf leaf) throws IOException,DocumentException,DocumentAccessException {
 		// Load metadata for this file
-		MetaInfo meta = null;
+		VFSMetadata meta = null;
 		if (leaf.canMeta() == VFSConstants.YES) {
 			meta = leaf.getMetaInfo();
 		}
@@ -131,7 +132,23 @@ public abstract class FileDocument extends OlatDocument {
 			addMetadata(SimpleDublinCoreMetadataFieldsProvider.DC_SOURCE, meta.getSource());
 			addMetadata(SimpleDublinCoreMetadataFieldsProvider.DC_SOURCE, meta.getUrl());
 			// use creator and author as olat author 
-			setAuthor((meta.getCreator() == null ? meta.getAuthor() : meta.getAuthor() + " " + meta.getCreator()));
+			StringBuilder authors = new StringBuilder(64);
+			if(meta.getAuthor() != null) {
+				User user = meta.getAuthor().getUser();
+				if(StringHelper.containsNonWhitespace(user.getFirstName())) {
+					authors.append(user.getFirstName());
+				}
+				if(StringHelper.containsNonWhitespace(user.getLastName())) {
+					if(authors.length() > 0) authors.append(" ");
+					authors.append(user.getLastName());
+				}
+			}
+			
+			if(meta.getCreator() != null) {
+				if(authors.length() > 0) authors.append(" ");
+				authors.append(authors);
+			}
+			setAuthor(authors.toString());
 			addMetadata(SimpleDublinCoreMetadataFieldsProvider.DC_CREATOR, meta.getCreator());
 		}
 		// Add file type
@@ -140,8 +157,8 @@ public abstract class FileDocument extends OlatDocument {
 		
 		// License
 		String licenseTypeKey = "";
-		if (meta != null && StringHelper.containsNonWhitespace(meta.getLicenseTypeKey())) {
-			licenseTypeKey = meta.getLicenseTypeKey();
+		if (meta != null && meta.getLicenseType() != null) {
+			licenseTypeKey = meta.getLicenseType().getKey().toString();
 		}
 		setLicenseTypeKey(licenseTypeKey);
 	}
diff --git a/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java b/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java
index aaa7bd1fa168b479cda269041506fb5d3b70b7ed..27ad1394caf3980a5d183093347de35490b55895 100644
--- a/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java
+++ b/src/main/java/org/olat/search/service/document/file/FileDocumentFactory.java
@@ -32,12 +32,12 @@ import org.apache.lucene.document.DateTools;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.queryparser.classic.ParseException;
 import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.vfs.LocalImpl;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSLeaf;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.search.QueryException;
 import org.olat.search.SearchModule;
 import org.olat.search.SearchService;
@@ -113,8 +113,8 @@ public class FileDocumentFactory {
 					Date indexLastModification = DateTools.stringToDate(timestamp);
 					Date docLastModificationDate = new Date(leaf.getLastModified());
 					if(leaf.canMeta() == VFSConstants.YES) {
-						MetaInfo metaInfo = leaf.getMetaInfo();
-						Date metaDate = metaInfo.getMetaLastModified();
+						VFSMetadata metaInfo = leaf.getMetaInfo();
+						Date metaDate = metaInfo.getLastModified();
 						if(metaDate != null && metaDate.after(docLastModificationDate)) {
 							docLastModificationDate = metaDate;
 						}
diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_13_3_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_13_3_0.java
new file mode 100644
index 0000000000000000000000000000000000000000..faaf358461df16480712ae227e30ce9200ef707d
--- /dev/null
+++ b/src/main/java/org/olat/upgrade/OLATUpgrade_13_3_0.java
@@ -0,0 +1,208 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.upgrade;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+
+import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.vfs.manager.VFSRepositoryServiceImpl;
+import org.olat.core.util.StringHelper;
+import org.olat.repository.RepositoryEntry;
+import org.olat.repository.RepositoryService;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 4 janv. 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class OLATUpgrade_13_3_0 extends OLATUpgrade {
+	
+	private static final String VERSION = "OLAT_13.3.0";
+	private static final String MOVE_REPO_IMAGES = "MOVE REPO IMAGES";
+	private static final String MIGRATE_FILE_METADATA = "MIGRATE FILE METADATA";
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private RepositoryService repositoryService;
+	@Autowired
+	private VFSRepositoryServiceImpl vfsRepositoryService;
+	
+	public OLATUpgrade_13_3_0() {
+		super();
+	}
+	
+	@Override
+	public String getVersion() {
+		return VERSION;
+	}
+
+	@Override
+	public boolean doPreSystemInitUpgrade(UpgradeManager upgradeManager) {
+		return false;
+	}
+
+	@Override
+	public boolean doPostSystemInitUpgrade(UpgradeManager upgradeManager) {
+		UpgradeHistoryData uhd = upgradeManager.getUpgradesHistory(VERSION);
+		if (uhd == null) {
+			// has never been called, initialize
+			uhd = new UpgradeHistoryData();
+		} else if (uhd.isInstallationComplete()) {
+			return false;
+		}
+		
+		boolean allOk = true;
+		allOk &= migrateRepositoryImages(upgradeManager, uhd);
+		allOk &= migrateMetadata(upgradeManager, uhd);
+
+		uhd.setInstallationComplete(allOk);
+		upgradeManager.setUpgradesHistory(uhd, VERSION);
+		if(allOk) {
+			log.audit("Finished OLATUpgrade_13_3_0 successfully!");
+		} else {
+			log.audit("OLATUpgrade_13_3_0 not finished, try to restart OpenOLAT!");
+		}
+		return allOk;
+	}
+	
+	/**
+	 * Migrate the images of learn resources and courses.
+	 * 
+	 * @param upgradeManager The upgrade manage
+	 * @param uhd The history
+	 * @return true if successful
+	 */
+	private boolean migrateRepositoryImages(UpgradeManager upgradeManager, UpgradeHistoryData uhd) {
+		boolean allOk = true;
+		if (!uhd.getBooleanDataValue(MOVE_REPO_IMAGES)) {
+			
+			try {
+				int counter = 0;
+				
+				File repositoryHome = new File(FolderConfig.getCanonicalRepositoryHome());
+				File[] images = repositoryHome.listFiles(new ImageFilter());
+				for(File image:images) {
+					String name = image.getName();
+					int index = name.lastIndexOf('.');
+					String resourceId = name.substring(0, index);
+					if(StringHelper.isLong(resourceId)) {
+						migrateRepositoryImage(image, Long.valueOf(resourceId));
+					}
+					if(counter++ % 50 == 0) {
+						log.info("Images of lear resources moved: " + counter);
+						dbInstance.commitAndCloseSession();
+					}
+				}
+			} catch (Exception e) {
+				log.error("", e);
+				allOk = false;
+			}
+			
+			uhd.setBooleanDataValue(MOVE_REPO_IMAGES, allOk);
+			upgradeManager.setUpgradesHistory(uhd, VERSION);
+		}
+		return allOk;
+	}
+	
+	private void migrateRepositoryImage(File image, Long resourceId) {
+		RepositoryEntry resource = repositoryService.loadByKey(resourceId);// yes, resource id is repository entry primary key
+		if(resource == null) {
+			deleteImage(image);
+		} else {
+			File home;
+			if("CourseModule".equals(resource.getOlatResource().getResourceableTypeName())) {
+				home = new File(FolderConfig.getCanonicalRoot(), "course");
+			} else {
+				home = new File(FolderConfig.getCanonicalRepositoryHome());
+			}
+			
+			try {
+				File resourceDir = new File(home, resource.getOlatResource().getResourceableId().toString());
+				if(resourceDir.exists()) {
+					File mediaDir = new File(resourceDir, "media");
+					if(!mediaDir.exists()) {
+						mediaDir.mkdir();
+					}
+					
+					File movedImage = new File(mediaDir, image.getName());
+					if(movedImage.exists()) {
+						deleteImage(image);
+					} else {
+						Files.move(image.toPath(), movedImage.toPath(), StandardCopyOption.REPLACE_EXISTING);
+					}
+				} else {
+					deleteImage(image);
+				}
+			} catch (IOException e) {
+				log.error("", e);
+			}	
+		}
+	}
+	
+	private void deleteImage(File image) {
+		try {
+			Files.delete(image.toPath());
+		} catch (IOException e) {
+			log.error("", e);
+		}
+	}
+	
+	/**
+	 * Migrate the metadata
+	 * 
+	 * @param upgradeManager The upgrade manager
+	 * @param uhd The upgrade history
+	 * @return true if successful
+	 */
+	private boolean migrateMetadata(UpgradeManager upgradeManager, UpgradeHistoryData uhd) {
+		boolean allOk = true;
+		if (!uhd.getBooleanDataValue(MIGRATE_FILE_METADATA)) {
+			
+			try {
+				//go through /bcroot/
+				File canonicalRoot = new File(FolderConfig.getCanonicalRoot());
+				vfsRepositoryService.migrateDirectories(canonicalRoot);
+			} catch (IOException e) {
+				log.error("", e);
+				allOk = false;
+			}
+			
+			uhd.setBooleanDataValue(MIGRATE_FILE_METADATA, allOk);
+			upgradeManager.setUpgradesHistory(uhd, VERSION);
+		}
+		return allOk;
+	}
+	
+	private static class ImageFilter implements FilenameFilter {
+		@Override
+		public boolean accept(File dir, String name) {
+			File file = new File(dir, name);
+			return file.isFile() && !file.isHidden();
+		}
+	}
+}
diff --git a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
index 49c142d1e132e89fc6e93d67adc8f3545ab01bcf..46b174368dbacc87159b708ecf5b1d8167438776 100644
--- a/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
+++ b/src/main/java/org/olat/upgrade/_spring/databaseUpgradeContext.xml
@@ -176,6 +176,10 @@
 					<constructor-arg index="0" value="OLAT_13.2.0" />
 					<property name="alterDbStatements" value="alter_13_1_x_to_13_2_0.sql" />
 				</bean>
+				<bean id="database_upgrade_13_3_0" class="org.olat.upgrade.DatabaseUpgrade">
+					<constructor-arg index="0" value="OLAT_13.3.0" />
+					<property name="alterDbStatements" value="alter_13_2_x_to_13_3_0.sql" />
+				</bean>
 			</list>
 		</property>
 	</bean>
diff --git a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml
index 397770715be3cb0a030c63ac79f27abe9401b92e..9a0983fcf024d896526e4e2e443d96d7fafa733e 100644
--- a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml
+++ b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml
@@ -48,6 +48,7 @@
 				<bean id="upgrade_13_0_0_beta9" class="org.olat.upgrade.OLATUpgrade_13_0_0_beta9"/>
 				<bean id="upgrade_13_1_0" class="org.olat.upgrade.OLATUpgrade_13_1_0"/>
 				<bean id="upgrade_13_2_0" class="org.olat.upgrade.OLATUpgrade_13_2_0"/>
+				<bean id="upgrade_13_3_0" class="org.olat.upgrade.OLATUpgrade_13_3_0"/>
 			</list>
 		</property>
 	</bean>
diff --git a/src/main/java/org/olat/user/DisplayPortraitManager.java b/src/main/java/org/olat/user/DisplayPortraitManager.java
index cc4194d1815cda9e408b4e6e25713492158d6184..8cfe896e4de3a39ff35672f23ff98b7c93122b55 100644
--- a/src/main/java/org/olat/user/DisplayPortraitManager.java
+++ b/src/main/java/org/olat/user/DisplayPortraitManager.java
@@ -37,6 +37,7 @@ import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.modules.bc.FolderConfig;
 import org.olat.core.commons.services.image.ImageService;
 import org.olat.core.commons.services.image.Size;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.gui.media.FileMediaResource;
 import org.olat.core.gui.media.MediaResource;
 import org.olat.core.gui.media.ServletUtil;
@@ -53,6 +54,7 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.user.manager.ManifestBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
@@ -94,6 +96,10 @@ public class DisplayPortraitManager implements UserDataDeletable, UserDataExport
 	
 	public static final int WIDTH_LOGO_BIG = HEIGHT_BIG * 4;  // 4-8 kbytes (jpeg)
 	public static final int WIDTH_LOGO_SMALL = HEIGHT_SMALL * 4; // 2-4
+	
+	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 
 	public MediaResource getSmallPortraitResource(String username) {
 		return getPortraitResource(username, PORTRAIT_SMALL_FILENAME);
@@ -307,7 +313,7 @@ public class DisplayPortraitManager implements UserDataDeletable, UserDataExport
 		
 		VFSLeaf vfsPortrait = getLargestVFSPortrait(username);
 		if(vfsPortrait.canMeta() == VFSConstants.YES) {
-			vfsPortrait.getMetaInfo().clearThumbnails();
+			vfsRepositoryService.resetThumbnails(vfsPortrait);
 		}
 	}
 
diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml
index 3546993e17b37d88b2e72e3c26a999bc16f0c78c..c301f71b6d01ab9cd442af28439ce56d6bead39a 100644
--- a/src/main/resources/META-INF/persistence.xml
+++ b/src/main/resources/META-INF/persistence.xml
@@ -65,7 +65,7 @@
 		<class>org.olat.basesecurity.model.GrantImpl</class>
 		<class>org.olat.basesecurity.model.GroupMembershipImpl</class>
 		<class>org.olat.basesecurity.model.UserProperty</class>
-		<class>org.olat.basesecurity.model.IdentityLastLoginImpl</class><class>org.olat.basesecurity.model.AuthenticationHistoryImpl</class>
+		<class>org.olat.basesecurity.model.IdentityLastLoginImpl</class>
 		<class>org.olat.basesecurity.model.AuthenticationHistoryImpl</class>
 		<class>org.olat.basesecurity.model.OrganisationTypeImpl</class>
 		<class>org.olat.basesecurity.model.OrganisationTypeToTypeImpl</class>
@@ -92,6 +92,9 @@
 		<class>org.olat.core.commons.services.commentAndRating.model.UserRatingImpl</class>
 		<class>org.olat.core.commons.services.commentAndRating.model.UserCommentImpl</class>
 		<class>org.olat.core.commons.services.sms.model.MessageLogImpl</class>
+		<class>org.olat.core.commons.services.vfs.model.VFSMetadataImpl</class>
+		<class>org.olat.core.commons.services.vfs.model.VFSMetadataDownloadCount</class>
+		<class>org.olat.core.commons.services.vfs.model.VFSThumbnailMetadataImpl</class>
 		<class>org.olat.course.assessment.model.AssessmentModeImpl</class>
 		<class>org.olat.course.assessment.model.AssessmentModeToAreaImpl</class>
 		<class>org.olat.course.assessment.model.AssessmentModeToGroupImpl</class>
diff --git a/src/main/resources/database/mysql/alter_13_2_x_to_13_3_0.sql b/src/main/resources/database/mysql/alter_13_2_x_to_13_3_0.sql
new file mode 100644
index 0000000000000000000000000000000000000000..9e53a48280516c2c00ccf473bef12f57e94a6a13
--- /dev/null
+++ b/src/main/resources/database/mysql/alter_13_2_x_to_13_3_0.sql
@@ -0,0 +1,72 @@
+-- vfs metadata
+create table o_vfs_metadata (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   f_uuid varchar(64) not null,
+   f_filename varchar(256) not null,
+   f_relative_path varchar(2048) not null,
+   f_directory bool default false,
+   f_lastmodified datetime not null,
+   f_size bigint default 0,
+   f_uri varchar(2000) not null,
+   f_uri_protocol varchar(16) not null,
+   f_cannot_thumbnails bool default false,
+   f_download_count bigint default 0,
+   f_comment text(32000),
+   f_title varchar(2000),
+   f_publisher varchar(2000),
+   f_creator varchar(2000),
+   f_source varchar(2000),
+   f_city varchar(256),
+   f_pages varchar(16),
+   f_language varchar(16),
+   f_url text(1024),
+   f_pub_month varchar(16),
+   f_pub_year varchar(16),
+   f_license_type_name varchar(256),
+   f_license_text mediumtext,
+   f_licensor text(4000),
+   f_locked_date timestamp,
+   f_locked bool default false,
+   f_m_path_keys varchar(1024),
+   fk_locked_identity bigint,
+   fk_license_type bigint,
+   fk_author bigint,
+   fk_parent bigint,
+   primary key (id)
+);
+
+alter table o_vfs_metadata ENGINE = InnoDB;
+
+alter table o_vfs_metadata add constraint fmeta_to_author_idx foreign key (fk_locked_identity) references o_bs_identity (id);
+alter table o_vfs_metadata add constraint fmeta_to_lockid_idx foreign key (fk_author) references o_bs_identity (id);
+alter table o_vfs_metadata add constraint fmeta_to_lic_type_idx foreign key (fk_license_type) references o_lic_license_type (id);
+alter table o_vfs_metadata add constraint fmeta_to_parent_idx foreign key (fk_parent) references o_vfs_metadata (id);
+create index f_m_path_keys_idx on o_vfs_metadata (f_m_path_keys(100));
+create index f_m_rel_path_idx on o_vfs_metadata (f_relative_path(255));
+create index f_m_filename_idx on o_vfs_metadata (f_filename(255));
+
+
+create table o_vfs_thumbnail (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   f_size bigint default 0 not null,
+   f_max_width bigint default 0 not null,
+   f_max_height bigint default 0 not null,
+   f_final_width bigint default 0 not null,
+   f_final_height bigint default 0 not null,
+   f_fill bool default false not null,
+   f_filename varchar(256) not null,
+   fk_metadata bigint not null,
+   primary key (id)
+);
+
+alter table o_vfs_thumbnail ENGINE = InnoDB;
+
+alter table o_vfs_thumbnail add constraint fthumb_to_meta_idx foreign key (fk_metadata) references o_vfs_metadata (id);
+
+
+
+
diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql
index f59d0bd5196189f23c45cc38b1c05b6960c5a35d..b70d5d51b38df7b38e20b28ff7fa9490a4253a2c 100644
--- a/src/main/resources/database/mysql/setupDatabase.sql
+++ b/src/main/resources/database/mysql/setupDatabase.sql
@@ -1680,6 +1680,59 @@ create table if not exists o_qp_license (
    primary key (id)
 );
 
+-- vfs metadata
+create table o_vfs_metadata (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   f_uuid varchar(64) not null,
+   f_filename varchar(256) not null,
+   f_relative_path varchar(2048) not null,
+   f_directory bool default false,
+   f_lastmodified datetime not null,
+   f_size bigint default 0,
+   f_uri varchar(2000) not null,
+   f_uri_protocol varchar(16) not null,
+   f_cannot_thumbnails bool default false,
+   f_download_count bigint default 0,
+   f_comment text(32000),
+   f_title varchar(2000),
+   f_publisher varchar(2000),
+   f_creator varchar(2000),
+   f_source varchar(2000),
+   f_city varchar(256),
+   f_pages varchar(16),
+   f_language varchar(16),
+   f_url text(1024),
+   f_pub_month varchar(16),
+   f_pub_year varchar(16),
+   f_license_type_name varchar(256),
+   f_license_text mediumtext,
+   f_licensor text(4000),
+   f_locked_date timestamp,
+   f_locked bool default false,
+   f_m_path_keys varchar(1024),
+   fk_locked_identity bigint,
+   fk_license_type bigint,
+   fk_author bigint,
+   fk_parent bigint,
+   primary key (id)
+);
+
+create table o_vfs_thumbnail (
+   id bigint not null auto_increment,
+   creationdate datetime not null,
+   lastmodified datetime not null,
+   f_size bigint default 0 not null,
+   f_max_width bigint default 0 not null,
+   f_max_height bigint default 0 not null,
+   f_final_width bigint default 0 not null,
+   f_final_height bigint default 0 not null,
+   f_fill bool default false not null,
+   f_filename varchar(256) not null,
+   fk_metadata bigint not null,
+   primary key (id)
+);
 
 -- portfolio
 create table o_pf_binder (
@@ -3042,6 +3095,8 @@ alter table o_qual_generator ENGINE = InnoDB;
 alter table o_qual_generator_config ENGINE = InnoDB;
 alter table o_qual_generator_to_org ENGINE = InnoDB;
 alter table o_qual_analysis_presentation ENGINE = InnoDB;
+alter table o_vfs_metadata ENGINE = InnoDB;
+alter table o_vfs_thumbnail ENGINE = InnoDB;
 alter table o_sms_message_log ENGINE = InnoDB;
 alter table o_feed ENGINE = InnoDB;
 alter table o_feed_item ENGINE = InnoDB;
@@ -3531,6 +3586,17 @@ alter table o_eva_form_session add constraint eva_sess_to_form_idx foreign key (
 alter table o_eva_form_response add constraint eva_resp_to_sess_idx foreign key (fk_session) references o_eva_form_session (id);
 create index idx_eva_resp_report_idx on o_eva_form_response (fk_session, e_responseidentifier, e_no_response);
 
+-- vfs metadata
+alter table o_vfs_metadata add constraint fmeta_to_author_idx foreign key (fk_locked_identity) references o_bs_identity (id);
+alter table o_vfs_metadata add constraint fmeta_to_lockid_idx foreign key (fk_author) references o_bs_identity (id);
+alter table o_vfs_metadata add constraint fmeta_to_lic_type_idx foreign key (fk_license_type) references o_lic_license_type (id);
+alter table o_vfs_metadata add constraint fmeta_to_parent_idx foreign key (fk_parent) references o_vfs_metadata (id);
+create index f_m_path_keys_idx on o_vfs_metadata (f_m_path_keys(100));
+create index f_m_rel_path_idx on o_vfs_metadata (f_relative_path(255));
+create index f_m_filename_idx on o_vfs_metadata (f_filename(255));
+
+alter table o_vfs_thumbnail add constraint fthumb_to_meta_idx foreign key (fk_metadata) references o_vfs_metadata (id);
+
 -- quality management
 alter table o_qual_data_collection add constraint qual_dc_to_gen_idx foreign key (fk_generator) references o_qual_generator (id);
 create index idx_dc_status_idx on o_qual_data_collection (q_status);
diff --git a/src/main/resources/database/oracle/alter_13_2_x_to_13_3_0.sql b/src/main/resources/database/oracle/alter_13_2_x_to_13_3_0.sql
new file mode 100644
index 0000000000000000000000000000000000000000..999ca3814b5ce1f71998d01b1265d5b38dcd3699
--- /dev/null
+++ b/src/main/resources/database/oracle/alter_13_2_x_to_13_3_0.sql
@@ -0,0 +1,69 @@
+create table o_vfs_metadata (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_uuid varchar(64) not null,
+   f_filename varchar(256) not null,
+   f_relative_path varchar(2048) not null,
+   f_directory number default 0,
+   f_lastmodified timestamp not null,
+   f_size number(20) default 0,
+   f_uri varchar(2000) not null,
+   f_uri_protocol varchar(16) not null,
+   f_cannot_thumbnails number default 0,
+   f_download_count number(20) default 0,
+   f_comment varchar(32000),
+   f_title varchar(2000),
+   f_publisher varchar(2000),
+   f_creator varchar(2000),
+   f_source varchar(2000),
+   f_city varchar(256),
+   f_pages varchar(2000),
+   f_language varchar(16),
+   f_url varchar(1024),
+   f_pub_month varchar(16),
+   f_pub_year varchar(16),
+   f_license_type_name varchar(256),
+   f_license_text CLOB,
+   f_licensor varchar(4000),
+   f_locked_date timestamp,
+   f_locked number default 0,
+   f_m_path_keys varchar(1024),
+   fk_locked_identity number(20),
+   fk_license_type number(20),
+   fk_author number(20),
+   fk_parent number(20),
+   primary key (id)
+);
+
+alter table o_vfs_metadata add constraint fmeta_to_author_idx foreign key (fk_locked_identity) references o_bs_identity (id);
+create index idx_fmeta_to_author_idx on o_vfs_metadata (fk_locked_identity);
+alter table o_vfs_metadata add constraint fmeta_to_lockid_idx foreign key (fk_author) references o_bs_identity (id);
+create index idx_fmeta_to_lockid_idx on o_vfs_metadata (fk_author);
+alter table o_vfs_metadata add constraint fmeta_to_lic_type_idx foreign key (fk_license_type) references o_lic_license_type (id);
+create index idx_fmeta_to_lic_type_idx on o_vfs_metadata (fk_license_type);
+alter table o_vfs_metadata add constraint fmeta_to_parent_idx foreign key (fk_parent) references o_vfs_metadata (id);
+create index idx_fmeta_to_parent_idx on o_vfs_metadata (fk_parent);
+create index f_m_path_keys_idx on o_vfs_metadata (f_m_path_keys);
+create index f_m_rel_path_idx on o_vfs_metadata (f_relative_path);
+create index f_m_filename_idx on o_vfs_metadata (f_filename);
+
+
+create table o_vfs_thumbnail (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_size number(20) default 0 not null,
+   f_max_width number(20) default 0 not null,
+   f_max_height number(20) default 0 not null,
+   f_final_width number(20) default 0 not null,
+   f_final_height number(20) default 0 not null,
+   f_fill number default 0 not null,
+   f_filename varchar(256) not null,
+   fk_metadata number(20) not null,
+   primary key (id)
+);
+
+alter table o_vfs_thumbnail add constraint fthumb_to_meta_idx foreign key (fk_metadata) references o_vfs_metadata (id);
+create index idx_fthumb_to_meta_idx on o_vfs_thumbnail (fk_metadata);
+
diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql
index 560fa0cc9ed7fce1c39da149087c2e122866d87f..4824214ded791b0410144bd45d6a294a5a694a6f 100644
--- a/src/main/resources/database/oracle/setupDatabase.sql
+++ b/src/main/resources/database/oracle/setupDatabase.sql
@@ -1610,6 +1610,60 @@ create table o_qti_assessment_marks (
    primary key (id)
 );
 
+-- vfs
+create table o_vfs_metadata (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_uuid varchar(64) not null,
+   f_filename varchar(256) not null,
+   f_relative_path varchar(2048) not null,
+   f_directory number default 0,
+   f_lastmodified timestamp not null,
+   f_size number(20) default 0,
+   f_uri varchar(2000) not null,
+   f_uri_protocol varchar(16) not null,
+   f_cannot_thumbnails number default 0,
+   f_download_count number(20) default 0,
+   f_comment varchar(32000),
+   f_title varchar(2000),
+   f_publisher varchar(2000),
+   f_creator varchar(2000),
+   f_source varchar(2000),
+   f_city varchar(256),
+   f_pages varchar(2000),
+   f_language varchar(16),
+   f_url varchar(1024),
+   f_pub_month varchar(16),
+   f_pub_year varchar(16),
+   f_license_type_name varchar(256),
+   f_license_text CLOB,
+   f_licensor varchar(4000),
+   f_locked_date timestamp,
+   f_locked number default 0,
+   f_m_path_keys varchar(1024),
+   fk_locked_identity number(20),
+   fk_license_type number(20),
+   fk_author number(20),
+   fk_parent number(20),
+   primary key (id)
+);
+
+create table o_vfs_thumbnail (
+   id number(20) generated always as identity,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_size number(20) default 0 not null,
+   f_max_width number(20) default 0 not null,
+   f_max_height number(20) default 0 not null,
+   f_final_width number(20) default 0 not null,
+   f_final_height number(20) default 0 not null,
+   f_fill number default 0 not null,
+   f_filename varchar(256) not null,
+   fk_metadata number(20) not null,
+   primary key (id)
+);
+
 -- portfolio
 create table o_pf_binder (
    id number(20) GENERATED ALWAYS AS IDENTITY,
@@ -3558,6 +3612,22 @@ create index idx_qti_marks_to_centry_idx on o_qti_assessment_marks (fk_reference
 alter table o_qti_assessment_marks add constraint qti_marks_to_identity_idx foreign key (fk_identity) references o_bs_identity (id);
 create index idx_qti_marks_to_identity_idx on o_qti_assessment_marks (fk_identity);
 
+-- vfs
+alter table o_vfs_metadata add constraint fmeta_to_author_idx foreign key (fk_locked_identity) references o_bs_identity (id);
+create index idx_fmeta_to_author_idx on o_vfs_metadata (fk_locked_identity);
+alter table o_vfs_metadata add constraint fmeta_to_lockid_idx foreign key (fk_author) references o_bs_identity (id);
+create index idx_fmeta_to_lockid_idx on o_vfs_metadata (fk_author);
+alter table o_vfs_metadata add constraint fmeta_to_lic_type_idx foreign key (fk_license_type) references o_lic_license_type (id);
+create index idx_fmeta_to_lic_type_idx on o_vfs_metadata (fk_license_type);
+alter table o_vfs_metadata add constraint fmeta_to_parent_idx foreign key (fk_parent) references o_vfs_metadata (id);
+create index idx_fmeta_to_parent_idx on o_vfs_metadata (fk_parent);
+create index f_m_path_keys_idx on o_vfs_metadata (f_m_path_keys);
+create index f_m_rel_path_idx on o_vfs_metadata (f_relative_path);
+create index f_m_filename_idx on o_vfs_metadata (f_filename);
+
+alter table o_vfs_thumbnail add constraint fthumb_to_meta_idx foreign key (fk_metadata) references o_vfs_metadata (id);
+create index idx_fthumb_to_meta_idx on o_vfs_thumbnail (fk_metadata);
+
 -- portfolio
 alter table o_pf_binder add constraint pf_binder_resource_idx foreign key (fk_olatresource_id) references o_olatresource (resource_id);
 create index idx_pf_binder_resource_idx on o_pf_binder (fk_olatresource_id);
diff --git a/src/main/resources/database/postgresql/alter_13_2_x_to_13_3_0.sql b/src/main/resources/database/postgresql/alter_13_2_x_to_13_3_0.sql
new file mode 100644
index 0000000000000000000000000000000000000000..08ccac477dd9feabf2455492558181e5d5f374a4
--- /dev/null
+++ b/src/main/resources/database/postgresql/alter_13_2_x_to_13_3_0.sql
@@ -0,0 +1,70 @@
+-- vfs metadata
+create table o_vfs_metadata (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_uuid varchar(64) not null,
+   f_filename varchar(256) not null,
+   f_relative_path varchar(2048) not null,
+   f_directory bool default false,
+   f_lastmodified timestamp not null,
+   f_size bigint default 0,
+   f_uri varchar(2000) not null,
+   f_uri_protocol varchar(16) not null,
+   f_cannot_thumbnails bool default false,
+   f_download_count bigint default 0,
+   f_comment varchar(32000),
+   f_title varchar(2000),
+   f_publisher varchar(2000),
+   f_creator varchar(2000),
+   f_source varchar(2000),
+   f_city varchar(256),
+   f_pages varchar(2000),
+   f_language varchar(16),
+   f_url varchar(1024),
+   f_pub_month varchar(16),
+   f_pub_year varchar(16),
+   f_license_type_name varchar(256),
+   f_license_text text,
+   f_licensor varchar(4000),
+   f_locked_date timestamp,
+   f_locked bool default false,
+   f_m_path_keys varchar(1024),
+   fk_locked_identity bigint,
+   fk_license_type bigint,
+   fk_author bigint,
+   fk_parent bigint,
+   primary key (id)
+);
+
+alter table o_vfs_metadata add constraint fmeta_to_author_idx foreign key (fk_locked_identity) references o_bs_identity (id);
+create index idx_fmeta_to_author_idx on o_vfs_metadata (fk_locked_identity);
+alter table o_vfs_metadata add constraint fmeta_to_lockid_idx foreign key (fk_author) references o_bs_identity (id);
+create index idx_fmeta_to_lockid_idx on o_vfs_metadata (fk_author);
+alter table o_vfs_metadata add constraint fmeta_to_lic_type_idx foreign key (fk_license_type) references o_lic_license_type (id);
+create index idx_fmeta_to_lic_type_idx on o_vfs_metadata (fk_license_type);
+alter table o_vfs_metadata add constraint fmeta_to_parent_idx foreign key (fk_parent) references o_vfs_metadata (id);
+create index idx_fmeta_to_parent_idx on o_vfs_metadata (fk_parent);
+create index f_m_path_keys_idx on o_vfs_metadata (f_m_path_keys);
+create index f_m_rel_path_idx on o_vfs_metadata (f_relative_path);
+create index f_m_filename_idx on o_vfs_metadata (f_filename);
+
+
+create table o_vfs_thumbnail (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_size bigint default 0 not null,
+   f_max_width bigint default 0 not null,
+   f_max_height bigint default 0 not null,
+   f_final_width bigint default 0 not null,
+   f_final_height bigint default 0 not null,
+   f_fill bool default false not null,
+   f_filename varchar(256) not null,
+   fk_metadata bigint not null,
+   primary key (id)
+);
+
+alter table o_vfs_thumbnail add constraint fthumb_to_meta_idx foreign key (fk_metadata) references o_vfs_metadata (id);
+create index idx_fthumb_to_meta_idx on o_vfs_thumbnail (fk_metadata);
+
diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql
index 3352ab6ae2e8ac8d1b02cee3d425bcbc28f60bd1..f04b654639ab0c8a047c0fc88f2b659c9bb2e28e 100644
--- a/src/main/resources/database/postgresql/setupDatabase.sql
+++ b/src/main/resources/database/postgresql/setupDatabase.sql
@@ -1573,6 +1573,60 @@ create table o_qti_assessment_marks (
    primary key (id)
 );
 
+-- vfs metadata
+create table o_vfs_metadata (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_uuid varchar(64) not null,
+   f_filename varchar(256) not null,
+   f_relative_path varchar(2048) not null,
+   f_directory bool default false,
+   f_lastmodified timestamp not null,
+   f_size bigint default 0,
+   f_uri varchar(2000) not null,
+   f_uri_protocol varchar(16) not null,
+   f_cannot_thumbnails bool default false,
+   f_download_count bigint default 0,
+   f_comment varchar(32000),
+   f_title varchar(2000),
+   f_publisher varchar(2000),
+   f_creator varchar(2000),
+   f_source varchar(2000),
+   f_city varchar(256),
+   f_pages varchar(2000),
+   f_language varchar(16),
+   f_url varchar(1024),
+   f_pub_month varchar(16),
+   f_pub_year varchar(16),
+   f_license_type_name varchar(256),
+   f_license_text text,
+   f_licensor varchar(4000),
+   f_locked_date timestamp,
+   f_locked bool default false,
+   f_m_path_keys varchar(1024),
+   fk_locked_identity bigint,
+   fk_license_type bigint,
+   fk_author bigint,
+   fk_parent bigint,
+   primary key (id)
+);
+
+create table o_vfs_thumbnail (
+   id bigserial,
+   creationdate timestamp not null,
+   lastmodified timestamp not null,
+   f_size bigint default 0 not null,
+   f_max_width bigint default 0 not null,
+   f_max_height bigint default 0 not null,
+   f_final_width bigint default 0 not null,
+   f_final_height bigint default 0 not null,
+   f_fill bool default false not null,
+   f_filename varchar(256) not null,
+   fk_metadata bigint not null,
+   primary key (id)
+);
+
 -- portfolio
 create table o_pf_binder (
    id bigserial,
@@ -3519,6 +3573,22 @@ create index idx_user_pfpage_idx on o_pf_page_user_infos (fk_identity_id);
 alter table o_pf_page_user_infos add constraint page_pfpage_idx foreign key (fk_page_id) references o_pf_page (id);
 create index idx_page_pfpage_idx on o_pf_page_user_infos (fk_page_id);
 
+-- vfs metadata
+alter table o_vfs_metadata add constraint fmeta_to_author_idx foreign key (fk_locked_identity) references o_bs_identity (id);
+create index idx_fmeta_to_author_idx on o_vfs_metadata (fk_locked_identity);
+alter table o_vfs_metadata add constraint fmeta_to_lockid_idx foreign key (fk_author) references o_bs_identity (id);
+create index idx_fmeta_to_lockid_idx on o_vfs_metadata (fk_author);
+alter table o_vfs_metadata add constraint fmeta_to_lic_type_idx foreign key (fk_license_type) references o_lic_license_type (id);
+create index idx_fmeta_to_lic_type_idx on o_vfs_metadata (fk_license_type);
+alter table o_vfs_metadata add constraint fmeta_to_parent_idx foreign key (fk_parent) references o_vfs_metadata (id);
+create index idx_fmeta_to_parent_idx on o_vfs_metadata (fk_parent);
+create index f_m_path_keys_idx on o_vfs_metadata (f_m_path_keys);
+create index f_m_rel_path_idx on o_vfs_metadata (f_relative_path);
+create index f_m_filename_idx on o_vfs_metadata (f_filename);
+
+alter table o_vfs_thumbnail add constraint fthumb_to_meta_idx foreign key (fk_metadata) references o_vfs_metadata (id);
+create index idx_fthumb_to_meta_idx on o_vfs_thumbnail (fk_metadata);
+
 -- evaluation form
 alter table o_eva_form_survey add constraint eva_surv_to_surv_idx foreign key (fk_series_previous) references o_eva_form_survey (id);
 create unique index idx_eva_surv_ores_idx on o_eva_form_survey (e_resid, e_resname, e_sub_ident);
diff --git a/src/test/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAOTest.java b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..27d55c988d59b3edf6280d34618641ce57b37510
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSMetadataDAOTest.java
@@ -0,0 +1,113 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.util.Date;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.manager.VFSMetadataDAO;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 11 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class VFSMetadataDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private VFSMetadataDAO vfsMetadataDao;
+	
+	@Test
+	public void createMetadata() {
+		String uuid = UUID.randomUUID().toString();
+		String relativePath = "/bcroot/hello/world/";
+		String filename = "image.jpg";
+		String uri = "file:///Users/frentix/Documents/bcroot/hello/world/image.jpg";
+		String uriProtocol = "file";
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, relativePath, filename, new Date(), 10l, false, uri, uriProtocol, null);
+		dbInstance.commitAndCloseSession();
+		
+		Assert.assertNotNull(metadata);
+		Assert.assertNotNull(metadata.getKey());
+		Assert.assertNotNull(metadata.getCreationDate());
+		Assert.assertNotNull(metadata.getLastModified());
+		Assert.assertEquals(uuid, metadata.getUuid());
+		Assert.assertEquals(relativePath, metadata.getRelativePath());
+		Assert.assertEquals(filename, metadata.getFilename());
+		Assert.assertFalse(metadata.isDirectory());
+		Assert.assertEquals(uri, metadata.getUri());
+		Assert.assertEquals(uriProtocol, metadata.getProtocol());
+	}
+	
+	@Test
+	public void getMetadata_uuid() {
+		String uuid = UUID.randomUUID().toString();
+		String relativePath = "/bcroot/hello/world/";
+		String filename = "image.jpg";
+		String uri = "file:///Users/frentix/Documents/bcroot/hello/world/image.jpg";
+		String uriProtocol = "file";
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, relativePath, filename, new Date(), 15l, false, uri, uriProtocol, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(metadata);
+		
+		VFSMetadata loadedMetadata = vfsMetadataDao.getMetadata(uuid);
+		Assert.assertEquals(metadata, loadedMetadata);
+		Assert.assertEquals(metadata.getKey(), loadedMetadata.getKey());
+		Assert.assertEquals(uuid, metadata.getUuid());
+		Assert.assertEquals(relativePath, metadata.getRelativePath());
+		Assert.assertEquals(filename, metadata.getFilename());
+		Assert.assertFalse(metadata.isDirectory());
+		Assert.assertEquals(uri, metadata.getUri());
+		Assert.assertEquals(uriProtocol, metadata.getProtocol());
+	}
+	
+	@Test
+	public void getMetadata_path() {
+		String uuid = UUID.randomUUID().toString();
+		String relativePath = "/bcroot/hello/world/";
+		String filename = uuid + ".jpg";
+		String uri = "file:///Users/frentix/Documents/bcroot/hello/world/image.jpg";
+		String uriProtocol = "file";
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, relativePath, filename, new Date(), 18l, false, uri, uriProtocol, null);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(metadata);
+		
+		VFSMetadata loadedMetadata = vfsMetadataDao.getMetadata(relativePath, filename, false);
+		Assert.assertEquals(metadata, loadedMetadata);
+		Assert.assertEquals(metadata.getKey(), loadedMetadata.getKey());
+		Assert.assertEquals(uuid, metadata.getUuid());
+		Assert.assertEquals(relativePath, metadata.getRelativePath());
+		Assert.assertEquals(filename, metadata.getFilename());
+		Assert.assertEquals(18l, metadata.getFileSize());
+		Assert.assertFalse(metadata.isDirectory());
+		Assert.assertEquals(uri, metadata.getUri());
+		Assert.assertEquals(uriProtocol, metadata.getProtocol());
+	}
+
+}
diff --git a/src/test/java/org/olat/core/commons/modules/bc/meta/MetaInfoFactoryTest.java b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceTest.java
similarity index 63%
rename from src/test/java/org/olat/core/commons/modules/bc/meta/MetaInfoFactoryTest.java
rename to src/test/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceTest.java
index f0e91d0745571e4427f18199848d467a3425aefc..8ecf0d1ad2ae02b34ce3f3d1d81fb830b88326f4 100644
--- a/src/test/java/org/olat/core/commons/modules/bc/meta/MetaInfoFactoryTest.java
+++ b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSRepositoryServiceTest.java
@@ -17,16 +17,15 @@
  * frentix GmbH, http://www.frentix.com
  * <p>
  */
-package org.olat.core.commons.modules.bc.meta;
+package org.olat.core.commons.services.vfs.manager;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import java.io.File;
-import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.UUID;
 
+import org.apache.commons.io.IOUtils;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -35,45 +34,58 @@ import org.olat.core.commons.services.license.License;
 import org.olat.core.commons.services.license.LicenseService;
 import org.olat.core.commons.services.license.LicenseType;
 import org.olat.core.commons.services.license.manager.LicenseCleaner;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
-import org.olat.core.util.FileUtils;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
-import org.olat.core.util.vfs.VFSTest;
-import org.olat.core.util.vfs.meta.MetaInfo;
-import org.olat.core.util.vfs.meta.MetaInfoFactory;
-import org.olat.core.util.vfs.meta.MetaInfoFileImpl;
 import org.olat.test.OlatTestCase;
 import org.springframework.beans.factory.annotation.Autowired;
 
 /**
  * 
- * Initial date: 05.03.2018<br>
- * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ * Initial date: 12 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class MetaInfoFactoryTest extends OlatTestCase {
+public class VFSRepositoryServiceTest extends OlatTestCase {
 	
-	private static final OLog log = Tracing.createLoggerFor(MetaInfoFactoryTest.class);
-	private static final String VFS_META_DIR = "/vfsmetatest";
+	private static final OLog log = Tracing.createLoggerFor(VFSRepositoryServiceTest.class);
+	private static final String VFS_TEST_DIR = "/vfsrepotest";
 	
 	@Autowired
 	private DB dbInstance;
 	@Autowired
-	private MetaInfoFactory metaInfoFactory;
-	@Autowired
 	private LicenseService licenseService;
 	@Autowired
 	private LicenseCleaner licenseCleaner;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	@Before
 	public void cleanUp() {
 		licenseCleaner.deleteAll();
 	}
 	
+	@Test
+	public void getMetadataFor_file() {
+		VFSLeaf leaf = createFile();
+		
+		// create metadata
+		VFSMetadata metadata = vfsRepositoryService.getMetadataFor(leaf);
+		dbInstance.commitAndCloseSession();
+		Assert.assertNotNull(metadata);
+		Assert.assertNotNull(metadata.getKey());
+		Assert.assertNotNull(metadata.getCreationDate());
+		Assert.assertNotNull(metadata.getLastModified());
+		Assert.assertNotNull(metadata.getFileLastModified());
+		Assert.assertEquals(leaf.getName(), metadata.getFilename());
+		Assert.assertFalse(metadata.isDirectory());	
+	}
+	
 	@Test
 	public void shouldLoadExistingLicenseType() {
 		String typeName = "name";
@@ -82,34 +94,35 @@ public class MetaInfoFactoryTest extends OlatTestCase {
 		dbInstance.commitAndCloseSession();
 		String licensor = "licensor";
 		String name = licenseType.getName();
-		File file = new File("");
-		MetaInfo meta = new MetaInfoFileImpl(file);
+		
+		VFSLeaf file = createFile();
+		VFSMetadata meta = vfsRepositoryService.getMetadataFor(file);
 		meta.setLicenseTypeName(name);
 		meta.setLicensor(licensor);
-		
-		License license = metaInfoFactory.getLicense(meta);
+		License license = vfsRepositoryService.getLicense(meta);
 
 		assertThat(license.getLicensor()).isEqualTo(licensor);
 		LicenseType loadedLicenseType = license.getLicenseType();
 		assertThat(loadedLicenseType).isEqualTo(licenseType);
 	}
-
+	
 	@Test
 	public void shouldCreateNonExistingLicenseType() {
 		String typeName = "name";
 		LicenseType licenseType = licenseService.createLicenseType(typeName);
 		licenseType = licenseService.saveLicenseType(licenseType);
 		dbInstance.commitAndCloseSession();
+		
 		String licensor = "licensor";
 		String name = "new";
 		String text = "text";
-		File file = new File("");
-		MetaInfo meta = new MetaInfoFileImpl(file);
+		VFSLeaf file = createFile();
+		VFSMetadata meta = vfsRepositoryService.getMetadataFor(file);
+
 		meta.setLicenseTypeName(name);
 		meta.setLicensor(licensor);
 		meta.setLicenseText(text);
-		
-		License license = metaInfoFactory.getLicense(meta);
+		License license = vfsRepositoryService.getLicense(meta);
 
 		assertThat(license.getLicensor()).isEqualTo(licensor);
 		LicenseType loadedLicenseType = license.getLicenseType();
@@ -123,36 +136,45 @@ public class MetaInfoFactoryTest extends OlatTestCase {
 	@Test
 	public void readWriteBinary() {
 		String filename = UUID.randomUUID() + ".txt";
-		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_META_DIR, null);
+		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_TEST_DIR, null);
 		VFSLeaf leaf = testContainer.createChildLeaf(filename);
 		Assert.assertEquals(VFSConstants.YES, leaf.canMeta());
-		prepareFile(leaf);
+		copyTestTxt(leaf);
 		
-		MetaInfo metaInfo = leaf.getMetaInfo();
+		VFSMetadata metaInfo = leaf.getMetaInfo();
 		metaInfo.setComment("A little comment");
-		metaInfo.write();
+		vfsRepositoryService.updateMetadata(metaInfo);
 		
-		byte[] binaryData = metaInfo.readBinary();
+		byte[] binaryData = MetaInfoReader.toBinaries(metaInfo);
 		Assert.assertNotNull(binaryData);
 		Assert.assertTrue(binaryData.length > 0);
 		
 		
 		String secondFilename = UUID.randomUUID() + ".txt";
 		VFSLeaf secondLeaf = testContainer.createChildLeaf(secondFilename);
-		prepareFile(secondLeaf);
+		copyTestTxt(secondLeaf);
 
-		MetaInfo secondMetaInfo = leaf.getMetaInfo();
-		secondMetaInfo.writeBinary(binaryData);
+		VFSMetadata secondMetaInfo = leaf.getMetaInfo();
+		vfsRepositoryService.copyBinaries(secondMetaInfo, binaryData);
 		String comment = secondMetaInfo.getComment();
 		Assert.assertEquals("A little comment", comment);
 	}
 	
-	private void prepareFile(VFSLeaf file) {
+	private VFSLeaf createFile() {
+		String filename = UUID.randomUUID() + ".txt";
+		VFSContainer testContainer = VFSManager.olatRootContainer(VFS_TEST_DIR, null);
+		VFSLeaf firstLeaf = testContainer.createChildLeaf(filename);
+		copyTestTxt(firstLeaf);
+		return firstLeaf;
+	}
+	
+	private int copyTestTxt(VFSLeaf file) {
 		try(OutputStream out = file.getOutputStream(false);
-				InputStream in = VFSTest.class.getResourceAsStream("test.txt")) {
-			FileUtils.cpio(in, out, "");
-		} catch(IOException e) {
+				InputStream in = VFSRepositoryServiceTest.class.getResourceAsStream("test.txt")) {
+			return IOUtils.copy(in, out);
+		} catch(Exception e) {
 			log.error("", e);
+			return -1;
 		}
 	}
 }
diff --git a/src/test/java/org/olat/core/commons/services/vfs/manager/VFSThumbnailDAOTest.java b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSThumbnailDAOTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..afaf2c6814432a75c9f82265a3f37616b0a78d75
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSThumbnailDAOTest.java
@@ -0,0 +1,161 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.commons.persistence.DB;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSThumbnailMetadata;
+import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * 
+ * Initial date: 14 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class VFSThumbnailDAOTest extends OlatTestCase {
+	
+	@Autowired
+	private DB dbInstance;
+	@Autowired
+	private VFSMetadataDAO vfsMetadataDao;
+	@Autowired
+	private VFSThumbnailDAO vfsThumbnailDao;
+	
+	@Test
+	public void createMetadata() {
+		String uuid = UUID.randomUUID().toString();
+		String filename = uuid.concat(".jpg");
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, "vfsrepo2test/world/", filename, new Date(), 1l, false, "file:///uri", "file", null);
+		VFSThumbnailMetadata thumbnail = vfsThumbnailDao.createThumbnailMetadata(metadata, "._oo_th_", 1l, true, 200, 150, 120, 80);
+		dbInstance.commitAndCloseSession();
+		
+		Assert.assertNotNull(thumbnail);
+		Assert.assertNotNull(thumbnail.getKey());
+		Assert.assertNotNull(thumbnail.getCreationDate());
+		Assert.assertNotNull(thumbnail.getLastModified());
+		Assert.assertTrue(thumbnail.isFill());
+		Assert.assertEquals(200, thumbnail.getMaxWidth());
+		Assert.assertEquals(150, thumbnail.getMaxHeight());
+		Assert.assertEquals(120, thumbnail.getFinalWidth());
+		Assert.assertEquals(80, thumbnail.getFinalHeight());
+	}
+	
+	@Test
+	public void loadByKey() {
+		String uuid = UUID.randomUUID().toString();
+		String filename = uuid.concat(".jpg");
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, "vfsrepo2test/world/", filename, new Date(), 1l, false, "file:///uri", "file", null);
+		VFSThumbnailMetadata thumbnail = vfsThumbnailDao.createThumbnailMetadata(metadata, "._ooth_", 1l, true, 200, 150, 120, 80);
+		dbInstance.commitAndCloseSession();
+		
+		VFSThumbnailMetadata reloadedThumbnail = vfsThumbnailDao.loadByKey(thumbnail.getKey());
+		
+		Assert.assertNotNull(reloadedThumbnail);
+		Assert.assertNotNull(reloadedThumbnail.getKey());
+		Assert.assertNotNull(reloadedThumbnail.getCreationDate());
+		Assert.assertNotNull(reloadedThumbnail.getLastModified());
+		Assert.assertTrue(reloadedThumbnail.isFill());
+		Assert.assertEquals(thumbnail, reloadedThumbnail);
+		Assert.assertEquals(metadata, reloadedThumbnail.getOwner());
+		Assert.assertEquals("._ooth_", reloadedThumbnail.getFilename());
+		Assert.assertEquals(200, reloadedThumbnail.getMaxWidth());
+		Assert.assertEquals(150, reloadedThumbnail.getMaxHeight());
+		Assert.assertEquals(120, reloadedThumbnail.getFinalWidth());
+		Assert.assertEquals(80, reloadedThumbnail.getFinalHeight());
+	}
+	
+	@Test
+	public void loadByMetadata() {
+		String uuid = UUID.randomUUID().toString();
+		String filename = uuid.concat(".png");
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, "vfsrepo2test/world/", filename, new Date(), 1l, false, "file:///uri", "file", null);
+		VFSThumbnailMetadata thumbnail = vfsThumbnailDao.createThumbnailMetadata(metadata, "._ooth_", 1l, true, 200, 150, 120, 80);
+		dbInstance.commitAndCloseSession();
+		
+		List<VFSThumbnailMetadata> thumbnails = vfsThumbnailDao.loadByMetadata(metadata);
+
+		Assert.assertNotNull(thumbnails);
+		Assert.assertEquals(1, thumbnails.size());
+		Assert.assertEquals(thumbnail, thumbnails.get(0));
+	}
+	
+	@Test
+	public void findThumbnail() {
+		String uuid = UUID.randomUUID().toString();
+		String filename = uuid.concat(".jpg");
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, "vfsrepo2test/world/", filename, new Date(), 1l, false, "file:///uri", "file", null);
+		VFSThumbnailMetadata thumbnail = vfsThumbnailDao.createThumbnailMetadata(metadata, "._ooth_", 1l, true, 200, 150, 120, 80);
+		dbInstance.commitAndCloseSession();
+		
+		VFSThumbnailMetadata reloadedThumbnail = vfsThumbnailDao.findThumbnail("vfsrepo2test/world/", filename, true, 200, 150);
+
+		Assert.assertNotNull(reloadedThumbnail);
+		Assert.assertNotNull(reloadedThumbnail.getKey());
+		Assert.assertNotNull(reloadedThumbnail.getCreationDate());
+		Assert.assertNotNull(reloadedThumbnail.getLastModified());
+		Assert.assertEquals(thumbnail, reloadedThumbnail);
+		Assert.assertTrue(reloadedThumbnail.isFill());
+		Assert.assertEquals(metadata, reloadedThumbnail.getOwner());
+		Assert.assertEquals("._ooth_", reloadedThumbnail.getFilename());
+		Assert.assertEquals(200, reloadedThumbnail.getMaxWidth());
+		Assert.assertEquals(150, reloadedThumbnail.getMaxHeight());
+		Assert.assertEquals(120, reloadedThumbnail.getFinalWidth());
+		Assert.assertEquals(80, reloadedThumbnail.getFinalHeight());
+	}
+	
+	@Test
+	public void findThumbnails() {
+		String uuid = UUID.randomUUID().toString();
+		String filename = uuid.concat(".jpg");
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, "vfsrepo2test/world/", filename, new Date(), 1l, false, "file:///uri", "file", null);
+		VFSThumbnailMetadata thumbnail = vfsThumbnailDao.createThumbnailMetadata(metadata, "._ooth_", 1l, true, 200, 150, 120, 80);
+		dbInstance.commitAndCloseSession();
+		
+		List<VFSThumbnailMetadata> thumbnails = vfsThumbnailDao.findThumbnails("vfsrepo2test/world/", filename);
+
+		Assert.assertNotNull(thumbnails);
+		Assert.assertEquals(1, thumbnails.size());
+		Assert.assertEquals(thumbnail, thumbnails.get(0));
+	}
+	
+	@Test
+	public void findThumbnail_byMetadata() {
+		String uuid = UUID.randomUUID().toString();
+		String filename = uuid.concat(".jpg");
+		VFSMetadata metadata = vfsMetadataDao.createMetadata(uuid, "vfsrepo2test/world/", filename, new Date(), 1l, false, "file:///uri", "file", null);
+		VFSThumbnailMetadata thumbnail = vfsThumbnailDao.createThumbnailMetadata(metadata, "._ooth_", 1l, true, 200, 150, 120, 80);
+		VFSThumbnailMetadata largeThumbnail = vfsThumbnailDao.createThumbnailMetadata(metadata, "._ooth_", 1l, true, 400, 300, 120, 80);
+		dbInstance.commitAndCloseSession();
+		
+		VFSThumbnailMetadata reloadedThumbnail = vfsThumbnailDao.findThumbnail(metadata, true, 200, 150);
+
+		Assert.assertNotNull(reloadedThumbnail);
+		Assert.assertEquals(thumbnail, reloadedThumbnail);
+		Assert.assertNotEquals(largeThumbnail, reloadedThumbnail);
+	}
+}
diff --git a/src/test/java/org/olat/core/commons/services/vfs/manager/VFSXStreamTest.java b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSXStreamTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8190fa3d22313bd03b9b60ed2f7f9bd63350801
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/vfs/manager/VFSXStreamTest.java
@@ -0,0 +1,57 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.core.commons.services.vfs.manager;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.olat.core.logging.OLATRuntimeException;
+import org.olat.core.logging.OLog;
+import org.olat.core.logging.Tracing;
+import org.olat.core.util.vfs.version.VersionsFileImpl;
+import org.olat.test.OlatTestCase;
+
+/**
+ * 
+ * Initial date: 13 mars 2019<br>
+ * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
+ *
+ */
+public class VFSXStreamTest extends OlatTestCase {
+	
+	private static final OLog log = Tracing.createLoggerFor(VFSXStreamTest.class);
+	
+	@Test
+	public void readOldVersions() throws IOException {
+		VersionsFileImpl versions = null;
+		try(InputStream in = VFSXStreamTest.class.getResourceAsStream("house.versions.jpg.xml")) {
+			versions = (VersionsFileImpl)VFSXStream.read(in);
+			
+		} catch(IOException | OLATRuntimeException e) {
+			log.error("", e);
+			throw e;
+		}
+		
+		Assert.assertNotNull(versions);
+	}
+
+}
diff --git a/src/test/java/org/olat/core/commons/services/vfs/manager/house.versions.jpg.xml b/src/test/java/org/olat/core/commons/services/vfs/manager/house.versions.jpg.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1e505ffd21d6d7b6ab4f68b45047a7c681f991c8
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/vfs/manager/house.versions.jpg.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<versions>
+  <versioned>true</versioned>
+  <author>kanu</author>
+  <creator>kanu</creator>
+  <revisionNr>3</revisionNr>
+  <comment>The old one</comment>
+  <revisions>
+    <revision>
+      <author>kanu</author>
+      <name>house.jpg</name>
+      <uuid>cf39a5d5-4180-41b4-b600-35d9a727cc5e</uuid>
+      <lastModified>1547659812000</lastModified>
+      <revisionNr>1</revisionNr>
+      <filename>fafb0367-4cbe-41b7-9457-56d69b85c7d3_house.jpg</filename>
+      <metadata class="metadata">
+        <uuid>e3d63dc8595942b2801d0af9c1f6182a</uuid>
+        <authorIdentKey>720898</authorIdentKey>
+        <comment>A very cute house</comment>
+        <title>An house</title>
+        <publisher>Still me</publisher>
+        <creator>Me too</creator>
+        <source>Always me</source>
+        <city>Where I lived</city>
+        <pages>68</pages>
+        <language>French</language>
+        <url>https://blog.cyberiacafe.ch</url>
+        <pubMonth>12</pubMonth>
+        <pubYear>2018</pubYear>
+        <licenseTypeKey>10</licenseTypeKey>
+        <licenseTypeName>CC BY-NC-ND</licenseTypeName>
+        <licenseText>https://creativecommons.org/licenses/by-nc-nd/4.0/</licenseText>
+        <licensor>Me</licensor>
+        <downloadCount>2</downloadCount>
+        <locked>false</locked>
+        <originFile>/HotCoffee/olatdatas/openolat_110_postgresql/bcroot/homes/kanu/public/house.jpg</originFile>
+        <metaFile>/HotCoffee/olatdatas/openolat_110_postgresql/bcroot/.meta/homes/kanu/public/house.jpg.xml</metaFile>
+        <cannotGenerateThumbnail>false</cannotGenerateThumbnail>
+        <thumbnails/>
+      </metadata>
+    </revision>
+    <revision>
+      <author>kanu</author>
+      <comment>A pink one</comment>
+      <name>house.jpg</name>
+      <uuid>acba63ce-de13-4759-bf85-a26f1833eca1</uuid>
+      <lastModified>1552487917000</lastModified>
+      <revisionNr>2</revisionNr>
+      <filename>c4087cf5-0ed6-4ed8-a97c-1317c963a0a8_house.jpg</filename>
+      <metadata class="metadata">
+        <uuid>e3d63dc8595942b2801d0af9c1f6182a</uuid>
+        <authorIdentKey>720898</authorIdentKey>
+        <comment></comment>
+        <licenseTypeKey>1</licenseTypeKey>
+        <licenseTypeName>no.license</licenseTypeName>
+        <downloadCount>2</downloadCount>
+        <locked>false</locked>
+        <originFile>/HotCoffee/olatdatas/openolat_110_postgresql/bcroot/homes/kanu/public/house.jpg</originFile>
+        <metaFile>/HotCoffee/olatdatas/openolat_110_postgresql/bcroot/.meta/homes/kanu/public/house.jpg.xml</metaFile>
+        <cannotGenerateThumbnail>false</cannotGenerateThumbnail>
+        <thumbnails/>
+      </metadata>
+    </revision>
+  </revisions>
+</versions>
\ No newline at end of file
diff --git a/src/test/java/org/olat/core/commons/services/vfs/manager/test.txt b/src/test/java/org/olat/core/commons/services/vfs/manager/test.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5ab2f8a4323abafb10abb68657d9d39f1a775057
--- /dev/null
+++ b/src/test/java/org/olat/core/commons/services/vfs/manager/test.txt
@@ -0,0 +1 @@
+Hello
\ No newline at end of file
diff --git a/src/test/java/org/olat/core/util/vfs/VFSTest.java b/src/test/java/org/olat/core/util/vfs/VFSTest.java
index f5df2a971834a891e748ccb98c7e86df957fc8dd..c1f37c1a3f0a2129dde792f59228d4df1063ea06 100644
--- a/src/test/java/org/olat/core/util/vfs/VFSTest.java
+++ b/src/test/java/org/olat/core/util/vfs/VFSTest.java
@@ -26,11 +26,13 @@ import java.util.UUID;
 
 import org.junit.Assert;
 import org.junit.Test;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
 import org.olat.core.util.FileUtils;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.test.OlatTestCase;
+import org.springframework.beans.factory.annotation.Autowired;
 
 
 /**
@@ -45,6 +47,9 @@ public class VFSTest extends OlatTestCase {
 	
 	private static final String VFS_TEST_DIR = "/vfstest";
 	
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
+	
 	/**
 	 * Test the copyFrom method (inclusive copy of metadata)
 	 */
@@ -56,10 +61,10 @@ public class VFSTest extends OlatTestCase {
 		Assert.assertEquals(VFSConstants.YES, firstLeaf.canMeta());
 		prepareFile(firstLeaf);
 		
-		MetaInfo metaInfo = firstLeaf.getMetaInfo();
+		VFSMetadata metaInfo = firstLeaf.getMetaInfo();
 		metaInfo.setComment("A comment");
 		metaInfo.setCreator("Me");
-		Assert.assertTrue(metaInfo.write());
+		Assert.assertNotNull(vfsRepositoryService.updateMetadata(metaInfo));
 		
 		VFSContainer targetContainer = VFSManager.olatRootContainer(VFS_TEST_DIR + "/vfstarger" + UUID.randomUUID(), null);
 		Assert.assertEquals(VFSConstants.YES, targetContainer.canMeta());
@@ -71,7 +76,7 @@ public class VFSTest extends OlatTestCase {
 		VFSLeaf copiedLeaf = (VFSLeaf)copiedItem;
 		Assert.assertEquals(VFSConstants.YES, copiedLeaf.canMeta());
 		
-		MetaInfo copiedMetaInfo = copiedLeaf.getMetaInfo();
+		VFSMetadata copiedMetaInfo = copiedLeaf.getMetaInfo();
 		Assert.assertEquals("A comment", copiedMetaInfo.getComment());
 		Assert.assertEquals("Me", copiedMetaInfo.getCreator());
 	}
@@ -84,10 +89,10 @@ public class VFSTest extends OlatTestCase {
 		Assert.assertEquals(VFSConstants.YES, firstLeaf.canMeta());
 		prepareFile(firstLeaf);
 		
-		MetaInfo metaInfo = firstLeaf.getMetaInfo();
+		VFSMetadata metaInfo = firstLeaf.getMetaInfo();
 		metaInfo.setComment("my old comment");
 		metaInfo.setCreator("Always Me");
-		Assert.assertTrue(metaInfo.write());
+		Assert.assertNotNull(vfsRepositoryService.updateMetadata(metaInfo));
 		
 		String newName = UUID.randomUUID() + ".txt";
 		VFSStatus renamedStatus = firstLeaf.rename(newName);
@@ -97,7 +102,7 @@ public class VFSTest extends OlatTestCase {
 		Assert.assertTrue(renamedItem instanceof VFSLeaf);
 		VFSLeaf renamedLeaf = (VFSLeaf)renamedItem;
 		
-		MetaInfo renamedMetaInfo = renamedLeaf.getMetaInfo();
+		VFSMetadata renamedMetaInfo = renamedLeaf.getMetaInfo();
 		Assert.assertEquals("my old comment", renamedMetaInfo.getComment());
 		Assert.assertEquals("Always Me", renamedMetaInfo.getCreator());
 	}
diff --git a/src/test/java/org/olat/core/util/vfs/version/VersionManagerTest.java b/src/test/java/org/olat/core/util/vfs/version/VersionManagerTest.java
index 746ba91333cbccb45b04813c57c5cc3240db8c73..e54b907d65ca157ffa9e125c9f19c2da11fa3f93 100644
--- a/src/test/java/org/olat/core/util/vfs/version/VersionManagerTest.java
+++ b/src/test/java/org/olat/core/util/vfs/version/VersionManagerTest.java
@@ -40,6 +40,8 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.olat.core.CoreSpringFactory;
+import org.olat.core.commons.services.vfs.VFSMetadata;
+import org.olat.core.commons.services.vfs.VFSRepositoryService;
 import org.olat.core.id.Identity;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
@@ -47,7 +49,6 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.olat.core.util.vfs.filters.SystemItemFilter;
-import org.olat.core.util.vfs.meta.MetaInfo;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatTestCase;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -71,6 +72,8 @@ public class VersionManagerTest extends OlatTestCase {
 	private VersionsFileManager versionsManager;
 	@Autowired
 	private SimpleVersionConfig versioningConfigurator;
+	@Autowired
+	private VFSRepositoryService vfsRepositoryService;
 	
 	@Before
 	public void setUp() throws Exception {
@@ -118,10 +121,7 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/test", null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		
@@ -153,7 +153,7 @@ public class VersionManagerTest extends OlatTestCase {
 		
 		VFSRevision revision0 = revisions.get(0);
 		//we don't set an author for the original file
-		assertEquals("-", revision0.getAuthor());
+		assertNull(null, revision0.getAuthor());
 		VFSRevision revision1 = revisions.get(1);
 		assertEquals(id2.getName(), revision1.getAuthor());
 		VFSRevision revision2 = revisions.get(2);
@@ -175,10 +175,7 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/test_" + UUID.randomUUID(), null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		
@@ -212,10 +209,7 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/test_" + UUID.randomUUID(), null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		
@@ -239,10 +233,7 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/ver-" + UUID.randomUUID(), null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		
@@ -291,10 +282,7 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/ver-" + UUID.randomUUID(), null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		
@@ -355,10 +343,7 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/ver-" + UUID.randomUUID(), null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = new ByteArrayInputStream("Hello original".getBytes());
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		
@@ -424,19 +409,16 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/test2", null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		assertEquals(VFSConstants.YES, file.canMeta());
 		
 		//set the author
-		MetaInfo metaInfo = file.getMetaInfo();
+		VFSMetadata metaInfo = file.getMetaInfo();
 		metaInfo.setAuthor(id1);
 		metaInfo.setCreator(id1.getName());
-		metaInfo.write();
+		metaInfo = vfsRepositoryService.updateMetadata(metaInfo);
 		
 		//save a first version -> id2
 		Versionable versionedFile1 = (Versionable)file;
@@ -483,19 +465,16 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/test2", null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		assertEquals(VFSConstants.YES, file.canMeta());
 		
 		//set the author
-		MetaInfo metaInfo = file.getMetaInfo();
+		VFSMetadata metaInfo = file.getMetaInfo();
 		metaInfo.setAuthor(id1);
 		metaInfo.setCreator(id1.getName());
-		metaInfo.write();
+		metaInfo = vfsRepositoryService.updateMetadata(metaInfo);
 		
 		//save a first version -> id2
 		Versionable versionedFile1 = (Versionable)file;
@@ -550,19 +529,16 @@ public class VersionManagerTest extends OlatTestCase {
 		VFSContainer rootTest = VFSManager.olatRootContainer("/test2", null);
 		String filename = getRandomName();
 		VFSLeaf file = rootTest.createChildLeaf(filename);
-		OutputStream out = file.getOutputStream(false);
-		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
-		int byteCopied = IOUtils.copy(in, out);
-		in.close();
+		int byteCopied = copyTestTxt(file);
 		assertFalse(byteCopied == 0);
 		assertTrue(file instanceof Versionable);
 		assertEquals(VFSConstants.YES, file.canMeta());
 		
 		//set the author
-		MetaInfo metaInfo = file.getMetaInfo();
+		VFSMetadata metaInfo = file.getMetaInfo();
 		metaInfo.setAuthor(id1);
 		metaInfo.setCreator(id1.getName());
-		metaInfo.write();
+		metaInfo = vfsRepositoryService.updateMetadata(metaInfo);
 		
 		//save a first version of file A -> id2
 		Versionable versionedFile1 = (Versionable)file;
@@ -618,6 +594,15 @@ public class VersionManagerTest extends OlatTestCase {
 		assertEquals(id2.getName(), versions.getAuthor());
 	}
 	
+	private int copyTestTxt(VFSLeaf file) {
+		try(OutputStream out = file.getOutputStream(false);
+				InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt")) {
+			return IOUtils.copy(in, out);
+		} catch(IOException e) {
+			return -1;
+		}
+	}
+	
 	private String getRandomName() {
 		return UUID.randomUUID().toString().replace("-", "");
 	}
diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java
index 6c76a725a30822e7253d2fb1f22cb2813ad2fb58..55285c10e93983e6b4f0e74d095eb6a0a21c1571 100644
--- a/src/test/java/org/olat/test/AllTestsJunit4.java
+++ b/src/test/java/org/olat/test/AllTestsJunit4.java
@@ -97,8 +97,11 @@ import org.junit.runners.Suite;
 	org.olat.commons.coordinate.cluster.jms.JMSTest.class,
 	org.olat.commons.coordinate.cluster.lock.LockTest.class,
 	org.olat.commons.coordinate.CoordinatorTest.class,
-	org.olat.core.commons.modules.bc.meta.MetaInfoFactoryTest.class,
 	org.olat.core.commons.services.csp.manager.CSPManagerTest.class,
+	org.olat.core.commons.services.vfs.manager.VFSMetadataDAOTest.class,
+	org.olat.core.commons.services.vfs.manager.VFSRepositoryServiceTest.class,
+	org.olat.core.commons.services.vfs.manager.VFSThumbnailDAOTest.class,
+	org.olat.core.commons.services.vfs.manager.VFSXStreamTest.class,
 	org.olat.core.commons.services.help.ConfluenceHelperTest.class,
 	org.olat.core.commons.services.help.spi.ConfluenceLinkSPITest.class,
 	org.olat.core.commons.services.license.manager.LicenseTypeActivationDAOTest.class,
diff --git a/src/test/java/org/olat/test/VFSJavaIOFile.java b/src/test/java/org/olat/test/VFSJavaIOFile.java
index fe27d9a44db30e5ae09ab1b3ff547416ba46d186..771a98f66caf4c2899b8530a0e49b03b2dd94376 100644
--- a/src/test/java/org/olat/test/VFSJavaIOFile.java
+++ b/src/test/java/org/olat/test/VFSJavaIOFile.java
@@ -27,6 +27,7 @@ import java.io.OutputStream;
 import java.nio.file.Path;
 
 import org.olat.core.commons.modules.bc.FolderConfig;
+import org.olat.core.commons.services.vfs.VFSMetadata;
 import org.olat.core.util.vfs.JavaIOItem;
 import org.olat.core.util.vfs.VFSConstants;
 import org.olat.core.util.vfs.VFSContainer;
@@ -34,7 +35,6 @@ import org.olat.core.util.vfs.VFSItem;
 import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSStatus;
 import org.olat.core.util.vfs.callbacks.VFSSecurityCallback;
-import org.olat.core.util.vfs.meta.MetaInfo;
 
 /**
  * An implementation of the VFSLEaf for a pure java.io.File with
@@ -154,7 +154,7 @@ public class VFSJavaIOFile implements VFSLeaf, JavaIOItem {
 	}
 
 	@Override
-	public MetaInfo getMetaInfo() {
+	public VFSMetadata getMetaInfo() {
 		return null;
 	}