package automately.core.file; import automately.core.data.User; import automately.core.data.comparators.FileComparator; import automately.core.data.predicates.KeyStartsWithPredicate; import automately.core.file.nio.UserFilePath; import automately.core.file.nio.UserFileSystem; import automately.core.file.nio.UserFileSystemProvider; import com.hazelcast.core.ILock; import com.hazelcast.core.IMap; import com.hazelcast.query.*; import io.jsync.app.core.Cluster; import io.jsync.buffer.Buffer; import io.jsync.json.JsonObject; import java.net.URI; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; public class VirtualFileSystem { private static Cluster cluster = null; private static IMap<String, VirtualFile> files = null; private static IMap<String, User> users = null; private static IMap<String, Object> publicFileTokens = null; private static VirtualFileStore fileStore = null; private static Map<String, UserFileSystem> userFileSystems = new ConcurrentHashMap<>(); private VirtualFileSystem() { } protected static void initialize(Cluster cluster) { VirtualFileSystem.cluster = cluster; fileStore = VirtualFileService.getFileStore(); files = cluster.data().persistentMap("files"); users = cluster.data().persistentMap("users"); // Old tokens are located at "files.public.tokens" publicFileTokens = cluster.data().persistentMap("files.public.tokens2"); } public static VirtualFileStore getFileStore(){ return fileStore; } public static UserFileSystem getUserFileSystem(String userToken) { User user = users.get(userToken); if(user == null){ throw new RuntimeException("The user with the token \"" + userToken + "\" does not exist in the cluster."); } return getUserFileSystem(user); } public static UserFileSystem getUserFileSystem(User user) { checkInitialized(); if(user == null){ throw new NullPointerException("The user cannot be null!"); } UserFileSystem cachedFs = userFileSystems.get(user.token()); if(cachedFs == null){ if(!users.containsKey(user.token())){ throw new RuntimeException("The user with the token \"" + user.token() + "\" does not exist in the cluster."); } UserFileSystemProvider provider = UserFileSystemProvider.create(user); cachedFs = provider.getFileSystem(URI.create("/")); userFileSystems.put(user.token(), cachedFs); } return cachedFs; } @Deprecated public static Buffer readFileData(VirtualFile file) { checkInitialized(); Buffer tmpData = fileStore.readRawData(file); if (tmpData == null) { tmpData = new Buffer(); // We want the data to empty by default } return tmpData; } @Deprecated public static VirtualFile writeFileData(VirtualFile file, byte[] data) { return writeFileData(file, new Buffer(data)); } @Deprecated public static VirtualFile writeFileData(VirtualFile file, Buffer data) { checkInitialized(); ILock fileLock = cluster.hazelcast().getLock("fs.file.lock." + file.token()); // We cannot update this file if it is locked. if(fileLock.isLocked()){ throw new RuntimeException("The file \"" + file.token() + "\" is locked!"); } file.size = data.length(); file.updated = new Date(); files.set(file.token(), file); fileStore.writeRawData(file, data); return file; } public static boolean deleteUserFile(User user, VirtualFile file){ return deleteUserFile(user, file, false); } public static boolean deleteUserFile(User user, VirtualFile file, boolean purgeDirectory) { checkInitialized(); if (user == null) { throw new IllegalArgumentException(new NullPointerException("The user cannot be null.")); } if (file == null) { throw new IllegalArgumentException(new NullPointerException("The file cannot be null.")); } if(files.containsKey(file.token())){ ILock fileLock = cluster.hazelcast().getLock("fs.file.lock." + file.token()); // We cannot delete this file if it is locked. if(fileLock.isLocked()){ return false; } if(file.isDirectory){ Collection<VirtualFile> existingFiles = files.values(Predicates.and(Predicates.equal("userToken", user.token()), Predicates.equal("pathAlias", file.pathAlias + file.name))); if(existingFiles.size() > 0){ if(!purgeDirectory){ throw new RuntimeException("It looks like the file \"" + file.token() + "\" is a directory and it is not empty."); } // Make sure we delete all files // stored within this directory existingFiles.forEach(file1 -> deleteUserFile(user, file1)); } } files.remove(file.token()); fileStore.deleteFile(file); return true; } return false; } public static boolean deleteUserFile(User user, String fileTokenOrPath) { checkInitialized(); if (user == null) { throw new IllegalArgumentException(new NullPointerException("The user cannot be null.")); } if (fileTokenOrPath == null || fileTokenOrPath.isEmpty()) { throw new IllegalArgumentException(new NullPointerException("The file cannot be empty.")); } VirtualFile file = getUserFile(user, fileTokenOrPath); return file != null && deleteUserFile(user, file); } public static boolean containsUserFile(User user, String fullPathOrToken) { return containsUserFile(user, fullPathOrToken, false); } public static boolean containsUserFile(User user, String fullPathOrToken, boolean publicOnly) { return getUserFiles(user, fullPathOrToken, 0, 1, false, publicOnly, true).size() > 0; } public static VirtualFile getUserFile(User user, String fullPathOrToken) { return getUserFile(user, fullPathOrToken, false); } public static VirtualFile getUserFile(User user, String fullPathOrToken, boolean publicOnly) { Iterator<VirtualFile> iterator = getUserFiles(user, fullPathOrToken, 0, 1, false, false, true).iterator(); if(iterator.hasNext()){ return iterator.next(); } return null; } public static Collection<VirtualFile> getUserFiles(User user) { return getUserFiles(user, null, 0, 10, false, false, true); } public static Collection<VirtualFile> getUserFiles(User user, String path) { return getUserFiles(user, path, 0, 0, true, false, true); } public static Collection<VirtualFile> getUserFiles(User user, String path, boolean publicOnly) { return getUserFiles(user, path, 0, 0, true, publicOnly, true); } public static Collection<VirtualFile> getUserFiles(User user, String pathOrToken, int page, int count, boolean recursive, boolean publicOnly, boolean noPaging) { checkInitialized(); List<Predicate> predicateList = new LinkedList<>(); predicateList.add(Predicates.equal("userToken", user.token())); try { if(pathOrToken != null && !pathOrToken.isEmpty()){ UserFileSystem fileSystem = getUserFileSystem(user); UserFilePath realpath = fileSystem.getPath(pathOrToken).toAbsolutePath().normalize(); String fileName = realpath.getFileName().toString(); String pathAlias = realpath.getParent().toPathAlias(); if(!pathAlias.isEmpty()){ if(recursive){ predicateList.add(new KeyStartsWithPredicate("pathAlias", pathAlias)); } else { predicateList.add(Predicates.equal("pathAlias", pathAlias)); } } if(!fileName.isEmpty()){ predicateList.add(Predicates.equal("name", fileName)); } } } catch (Exception ignored){ } if(publicOnly){ predicateList.add(Predicates.equal("isPublic", true)); } Predicate userFilesPredicate = Predicates.or( Predicates.and( Predicates.equal("userToken", user.token()), Predicates.equal("token", pathOrToken) ), Predicates.and(predicateList.toArray(new Predicate[predicateList.size()])) ); if(noPaging){ return files.values(userFilesPredicate); } int defaultPage = 0; if (page > -1) { defaultPage = page; } if (count > 100) count = 100; int defaultCount = 10; // Max Count is always 100 if (count > 0) { defaultCount = count; } // This predicate uses the previous one.. and then sorts the posts by date... // IMPORTANT apparently can't lambda PagingPredicate pagingPredicate = new PagingPredicate(userFilesPredicate, new FileComparator(), defaultCount); Collection<VirtualFile> values = files.values(pagingPredicate); if (defaultCount > pagingPredicate.getPage()) { while (defaultPage > pagingPredicate.getPage()) { pagingPredicate.nextPage(); } values = files.values(pagingPredicate); } return values; } private static void checkInitialized() { if (cluster == null || files == null) { throw new RuntimeException("VirtualFileSystem has not been initialized!"); } } public static boolean validatePublicFileToken(VirtualFile file, String publicFileToken) { return validatePublicFileToken(file.token(), publicFileToken); } public static boolean validatePublicFileToken(String fileToken, String publicFileToken) { if (publicFileTokens.containsKey(publicFileToken)) { Object token = publicFileTokens.get(publicFileToken); if (token instanceof JsonObject) { JsonObject decoded = (JsonObject) token; if (decoded.containsField("fileToken") && decoded.getString("fileToken").equals(fileToken)) { if (decoded.containsField("expires")) { long expires = decoded.getLong("expires"); if ((new Date()).getTime() > expires) { return true; } // Remove it since it is expired publicFileTokens.remove(publicFileToken); return false; } return true; } } else if (token instanceof VirtualFileToken) { VirtualFileToken decoded = (VirtualFileToken) token; if (decoded.fileToken.equals(fileToken)) { if ((new Date()).getTime() > decoded.expires.getTime()) { return true; } publicFileTokens.remove(publicFileToken); } } } return false; } public static void deletePublicFileToken(String publicFileToken) { publicFileTokens.remove(publicFileToken); } public static String generatePublicFileToken(VirtualFile file, long expiresInMinutes) { checkInitialized(); VirtualFileToken newToken = new VirtualFileToken(); newToken.fileToken = file.token(); newToken.userToken = file.userToken; newToken.expires = new Date((new Date()).getTime() + TimeUnit.MINUTES.toMillis(expiresInMinutes)); publicFileTokens.set(newToken.token(), newToken, expiresInMinutes, TimeUnit.MINUTES); return newToken.token(); } }