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();
}
}