From 81ee67b378a61e14bd802da2980bf4777af675c4 Mon Sep 17 00:00:00 2001 From: Manuel Jenny Date: Tue, 16 Mar 2021 21:12:56 +0100 Subject: [PATCH] feat: introduce idCache for pCloud --- .../pcloud/PCloudCloudContentRepository.java | 14 +- .../PCloudCloudContentRepositoryFactory.java | 6 +- .../cloud/pcloud/PCloudCloudNodeFactory.java | 31 +++- .../data/cloud/pcloud/PCloudIdCache.java | 77 +++++++++ .../data/cloud/pcloud/PCloudIdCloudNode.java | 9 + .../data/cloud/pcloud/PCloudImpl.java | 163 +++++++++++++----- .../data/cloud/pcloud/PCloudNode.java | 6 +- 7 files changed, 242 insertions(+), 64 deletions(-) create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java create mode 100644 data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java index 0f5f6190..3b311232 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudCloudContentRepository.java @@ -30,8 +30,8 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository size) { + return new PCloudFile(parent, null, name, getNodePath(parent, name), size, Optional.empty()); } - private static String getNodePath(PCloudFolder parent, String name) { - return parent.getPath() + "/" + name; + public static PCloudFile file(PCloudFolder folder, String name, Optional size, String path, Long fileId) { + return new PCloudFile(folder, fileId, name, path, size, Optional.empty()); + } + + public static PCloudFolder folder(PCloudFolder parent, RemoteFolder metadata) { + return new PCloudFolder(parent, metadata.folderId(), metadata.name(), getNodePath(parent, metadata.name())); } public static PCloudFolder folder(PCloudFolder parent, String name, String path) { return new PCloudFolder(parent, null, name, path); } + public static PCloudFolder folder(PCloudFolder parent, String name) { + return new PCloudFolder(parent, null, name, getNodePath(parent, name)); + } + + public static PCloudFolder folder(PCloudFolder parent, String name, String path, Long folderId) { + return new PCloudFolder(parent, folderId, name, path); + } + + public static String getNodePath(PCloudFolder parent, String name) { + return parent.getPath() + "/" + name; + } + public static PCloudNode from(PCloudFolder parent, RemoteEntry metadata) { if (metadata instanceof RemoteFile) { - return from(parent, (RemoteFile) metadata); + return file(parent, metadata.asFile()); } else { - return from(parent, (RemoteFolder) metadata); + return folder(parent, metadata.asFolder()); } } diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java new file mode 100644 index 00000000..7c22b0cd --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCache.java @@ -0,0 +1,77 @@ +package org.cryptomator.data.cloud.pcloud; + +import android.util.LruCache; + +import org.cryptomator.domain.CloudFolder; + +import javax.inject.Inject; + +class PCloudIdCache { + + private final LruCache cache; + + @Inject + PCloudIdCache() { + cache = new LruCache<>(1000); + } + + public NodeInfo get(String path) { + return cache.get(path); + } + + T cache(T value) { + add(value); + return value; + } + + public void add(PCloudIdCloudNode node) { + add(node.getPath(), new NodeInfo(node)); + } + + private void add(String path, NodeInfo info) { + cache.put(path, info); + } + + public void remove(PCloudIdCloudNode node) { + remove(node.getPath()); + } + + private void remove(String path) { + removeChildren(path); + cache.remove(path); + } + + private void removeChildren(String path) { + String prefix = path + '/'; + for (String key : cache.snapshot().keySet()) { + if (key.startsWith(prefix)) { + cache.remove(key); + } + } + } + + static class NodeInfo { + + private final Long id; + private final boolean isFolder; + + private NodeInfo(PCloudIdCloudNode node) { + this(node.getId(), node instanceof CloudFolder); + } + + NodeInfo(Long id, boolean isFolder) { + this.id = id; + this.isFolder = isFolder; + } + + public Long getId() { + return id; + } + + public boolean isFolder() { + return isFolder; + } + + } + +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java new file mode 100644 index 00000000..3490cb53 --- /dev/null +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudIdCloudNode.java @@ -0,0 +1,9 @@ +package org.cryptomator.data.cloud.pcloud; + +import org.cryptomator.domain.CloudNode; + +interface PCloudIdCloudNode extends CloudNode { + + Long getId(); + +} diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java index 58f9fd17..695c77c5 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudImpl.java @@ -16,10 +16,11 @@ import com.pcloud.sdk.UserInfo; import org.cryptomator.data.util.CopyStream; import org.cryptomator.domain.CloudFile; -import org.cryptomator.domain.CloudFolder; import org.cryptomator.domain.CloudNode; import org.cryptomator.domain.PCloudCloud; +import org.cryptomator.domain.exception.BackendException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; +import org.cryptomator.domain.exception.NoSuchCloudFileException; import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException; import org.cryptomator.domain.usecases.ProgressAware; import org.cryptomator.domain.usecases.cloud.DataSource; @@ -43,16 +44,20 @@ import okio.Source; import static org.cryptomator.domain.usecases.cloud.Progress.progress; class PCloudImpl { + + private final PCloudIdCache idCache; + private final PCloudClientFactory clientFactory = new PCloudClientFactory(); private final PCloudCloud cloud; private final RootPCloudFolder root; private final Context context; - PCloudImpl(PCloudCloud cloud, Context context) { + PCloudImpl(PCloudCloud cloud, Context context, PCloudIdCache idCache) { if (cloud.accessToken() == null) { throw new NoAuthenticationProvidedException(cloud); } this.cloud = cloud; + this.idCache = idCache; this.root = new RootPCloudFolder(cloud); this.context = context; } @@ -83,36 +88,87 @@ class PCloudImpl { return folder; } - public PCloudFile file(CloudFolder folder, String name) { - return file(folder, name, Optional.empty()); + public PCloudFile file(PCloudFolder parent, String name) { + return file(parent, name, Optional.empty()); } - public PCloudFile file(CloudFolder folder, String name, Optional size) { + public PCloudFile file(PCloudFolder parent, String name, Optional size) { + if (parent.getId() == null) { + return PCloudCloudNodeFactory.file(parent, name, size); + } + String path = PCloudCloudNodeFactory.getNodePath(parent, name); + PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); + if (nodeInfo != null && !nodeInfo.isFolder()) { + return PCloudCloudNodeFactory.file(parent, name, size, path, nodeInfo.getId()); + } + + Optional file = findEntry(parent.getId(), name, false); + if (file.isPresent()) { + return idCache.cache(PCloudCloudNodeFactory.file(parent, file.get().asFile())); + } + return PCloudCloudNodeFactory.file( // - (PCloudFolder) folder, // + parent, // name, // size, // - folder.getPath() + '/' + name); + parent.getPath() + '/' + name); } - public PCloudFolder folder(CloudFolder folder, String name) { - return PCloudCloudNodeFactory.folder( // - (PCloudFolder) folder, // - name, // - folder.getPath() + '/' + name); + public PCloudFolder folder(PCloudFolder parent, String name) { + if (parent.getId() == null) { + return PCloudCloudNodeFactory.folder(parent, name); + } + String path = PCloudCloudNodeFactory.getNodePath(parent, name); + PCloudIdCache.NodeInfo nodeInfo = idCache.get(path); + if (nodeInfo != null && nodeInfo.isFolder()) { + return PCloudCloudNodeFactory.folder( // + parent, // + name, // + path, // + nodeInfo.getId()); + } + + Optional folder = findEntry(parent.getId(), name, true); + if (folder.isPresent()) { + return idCache.cache(PCloudCloudNodeFactory.folder(parent, folder.get().asFolder())); + } + return PCloudCloudNodeFactory.folder(parent, name, parent.getPath() + '/' + name); } - public boolean exists(CloudNode node) throws ApiError, IOException { + private Optional findEntry(Long folderId, String name, boolean isFolder) { + try { + RemoteFolder remoteFolder = client().listFolder(folderId).execute(); + for (RemoteEntry remoteEntry : remoteFolder.children()) { + if (isFolder) { + if (remoteEntry.isFolder() && remoteEntry.name().equals(name)) { + return Optional.of(remoteEntry); + } + } else { + if (remoteEntry.isFile() && remoteEntry.name().equals(name)) { + return Optional.of(remoteEntry); + } + } + } + return Optional.empty(); + } catch(ApiError | IOException ex) { + return Optional.empty(); + } + } + + public boolean exists(PCloudNode node) throws ApiError, IOException { try { if (node instanceof PCloudFolder) { - client().listFolder(((PCloudFolder) node).getPath()).execute(); + RemoteFolder remoteFolder = client().listFolder(node.getPath()).execute(); + idCache.add(PCloudCloudNodeFactory.folder(node.getParent(), remoteFolder)); return true; } else { - client().stat(((PCloudFile)node).getPath()).execute(); + RemoteFile remoteFile = client().stat(node.getPath()).execute(); + idCache.add(PCloudCloudNodeFactory.file(node.getParent(), remoteFile)); return true; } } catch (ApiError e) { if (e.errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() + || e.errorCode() == PCloudApiErrorCodes.COMPONENT_OF_PARENT_DIRECTORY_DOES_NOT_EXIST.getValue() || e.errorCode() == PCloudApiErrorCodes.INVALID_FILE_OR_FOLDER_NAME.getValue() || e.errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()) { return false; @@ -121,56 +177,60 @@ class PCloudImpl { } } - public List list(CloudFolder folder) throws ApiError, IOException { + public List list(PCloudFolder folder) throws ApiError, IOException { List result = new ArrayList<>(); - Long folderId = ((PCloudFolder)folder).getId(); + Long folderId = folder.getId(); RemoteFolder listFolderResult; if (folderId == null) { listFolderResult = client().listFolder(folder.getPath()).execute(); } else { listFolderResult = client() // - .listFolder(((PCloudFolder) folder).getId()) // + .listFolder(folder.getId()) // .execute(); } List entryMetadata = listFolderResult.children(); for (RemoteEntry metadata : entryMetadata) { - result.add(PCloudCloudNodeFactory.from( // - (PCloudFolder) folder, // - metadata)); + result.add(PCloudCloudNodeFactory.from(folder, metadata)); } return result; } - public PCloudFolder create(CloudFolder folder) throws ApiError, IOException { - RemoteFolder createFolderResult = client() // - .createFolder(((PCloudFolder)folder.getParent()).getId(), folder.getName()) // + public PCloudFolder create(PCloudFolder folder) throws ApiError, IOException { + RemoteFolder createdFolder = client() // + .createFolder(folder.getParent().getId(), folder.getName()) // .execute(); - - return PCloudCloudNodeFactory.from( // - (PCloudFolder) folder.getParent(), // - createFolderResult.asFolder()); + return idCache.cache( // + PCloudCloudNodeFactory.folder(folder.getParent(), createdFolder)); } - public CloudNode move(CloudNode source, CloudNode target) throws ApiError, IOException { + public CloudNode move(PCloudNode source, PCloudNode target) throws ApiError, BackendException, IOException { RemoteEntry relocationResult; - if (source instanceof PCloudFolder) { - relocationResult = client().moveFolder(((PCloudFolder) source).getId(), ((PCloudFolder) target).getId()).execute(); - } else { - relocationResult = client().moveFile(((PCloudFile) source).getId(), ((PCloudFolder) target).getId()).execute(); + if (exists(target)) { + throw new CloudNodeAlreadyExistsException(target.getName()); } - return PCloudCloudNodeFactory.from( // - (PCloudFolder) target.getParent(), // - relocationResult); + if (source instanceof PCloudFolder) { + relocationResult = client().moveFolder(source.getId(), target.getId()).execute(); + } else { + relocationResult = client().moveFile(source.getId(), target.getId()).execute(); + } + + idCache.remove(source); + return PCloudCloudNodeFactory.from(target.getParent(), relocationResult); } - public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) throws ApiError, IOException, CloudNodeAlreadyExistsException { + public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware progressAware, boolean replace, long size) + throws ApiError, BackendException, IOException { if (exists(file) && !replace) { throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); } + if (file.getParent().getId() == null) { + throw new NoSuchCloudFileException(String.format("The parent folder of %s doesn't have a folderId. The file would remain in root folder", file.getPath())); + } + progressAware.onProgress(Progress.started(UploadState.upload(file))); UploadOptions uploadOptions = UploadOptions.DEFAULT; if (replace) { @@ -181,9 +241,7 @@ class PCloudImpl { progressAware.onProgress(Progress.completed(UploadState.upload(file))); - return PCloudCloudNodeFactory.from( // - file.getParent(), // - uploadedFile); + return idCache.cache(PCloudCloudNodeFactory.file(file.getParent(), uploadedFile)); } private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware progressAware, UploadOptions uploadOptions, final long size) // @@ -208,17 +266,30 @@ class PCloudImpl { } }; + Long parentFolderId = file.getParent().getId(); + if (parentFolderId == null) { + parentFolderId = idCache.get(file.getParent().getPath()).getId(); + } + return client() // - .createFile(file.getParent().getId(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // + .createFile(parentFolderId, file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // .execute(); } +// private Long getFolderId(PCloudFolder folder) { +// try { +// return client().listFolder(folder.getPath()).execute().folderId(); +// } catch(ApiError | IOException e) { +// return null; +// } +// } + public void read(CloudFile file, OutputStream data, final ProgressAware progressAware) throws ApiError, IOException { progressAware.onProgress(Progress.started(DownloadState.download(file))); Long fileId = ((PCloudFile)file).getId(); if (fileId == null) { - fileId = client().stat(file.getPath()).execute().fileId(); + fileId = idCache.get(file.getPath()).getId(); } FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); @@ -241,15 +312,15 @@ class PCloudImpl { progressAware.onProgress(Progress.completed(DownloadState.download(file))); } - public void delete(CloudNode node) throws ApiError, IOException { + public void delete(PCloudNode node) throws ApiError, IOException { if (node instanceof PCloudFolder) { client() // - .deleteFolder(((PCloudFolder) node).getId()).execute(); + .deleteFolder(node.getId()).execute(); } else { client() // - .deleteFile(((PCloudFile) node).getId()).execute(); + .deleteFile(node.getId()).execute(); } - + idCache.remove(node); } public String currentAccount() throws ApiError, IOException { diff --git a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java index c9b03334..af04d938 100644 --- a/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java +++ b/data/src/main/java/org/cryptomator/data/cloud/pcloud/PCloudNode.java @@ -2,8 +2,12 @@ package org.cryptomator.data.cloud.pcloud; import org.cryptomator.domain.CloudNode; -interface PCloudNode extends CloudNode { +interface PCloudNode extends PCloudIdCloudNode { + @Override Long getId(); + @Override + PCloudFolder getParent(); + }