feat: introduce idCache for pCloud

This commit is contained in:
Manuel Jenny 2021-03-16 21:12:56 +01:00
parent 7dc9456d97
commit 81ee67b378
No known key found for this signature in database
GPG Key ID: 1C80FE62B2BEAA18
7 changed files with 242 additions and 64 deletions

View File

@ -30,8 +30,8 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository<PC
private final PCloudCloud cloud;
public PCloudCloudContentRepository(PCloudCloud cloud, Context context) {
super(new Intercepted(cloud, context));
public PCloudCloudContentRepository(PCloudCloud cloud, Context context, PCloudIdCache idCache) {
super(new Intercepted(cloud, context, idCache));
this.cloud = cloud;
}
@ -57,8 +57,8 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository<PC
private final PCloudImpl cloud;
public Intercepted(PCloudCloud cloud, Context context) {
this.cloud = new PCloudImpl(cloud, context);
public Intercepted(PCloudCloud cloud, Context context, PCloudIdCache idCache) {
this.cloud = new PCloudImpl(cloud, context, idCache);
}
public PCloudFolder root(PCloudCloud cloud) {
@ -160,7 +160,7 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository<PC
try {
return cloud.write(uploadFile, data, progressAware, replace, size);
} catch (ApiError | IOException e) {
if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) {
if (e instanceof ApiError && ((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) {
throw new NoSuchCloudFileException(uploadFile.getName());
}
throw new FatalBackendException(e);
@ -172,7 +172,7 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository<PC
try {
cloud.read(file, data, progressAware);
} catch (ApiError | IOException e) {
if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) {
if (e instanceof ApiError && ((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) {
throw new NoSuchCloudFileException(file.getName());
}
throw new FatalBackendException(e);
@ -184,7 +184,7 @@ class PCloudCloudContentRepository extends InterceptingCloudContentRepository<PC
try {
cloud.delete(node);
} catch (ApiError | IOException e) {
if (((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) {
if (e instanceof ApiError && ((ApiError)e).errorCode() == PCloudApiErrorCodes.FILE_NOT_FOUND.getValue()) {
throw new NoSuchCloudFileException(node.getName());
}
throw new FatalBackendException(e);

View File

@ -16,10 +16,12 @@ import static org.cryptomator.domain.CloudType.PCLOUD;
public class PCloudCloudContentRepositoryFactory implements CloudContentRepositoryFactory {
private final Context context;
private final PCloudIdCache idCache;
@Inject
public PCloudCloudContentRepositoryFactory(Context context) {
public PCloudCloudContentRepositoryFactory(Context context, PCloudIdCache idCache) {
this.context = context;
this.idCache = idCache;
}
@Override
@ -29,7 +31,7 @@ public class PCloudCloudContentRepositoryFactory implements CloudContentReposito
@Override
public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) {
return new PCloudCloudContentRepository((PCloudCloud) cloud, context);
return new PCloudCloudContentRepository((PCloudCloud) cloud, context, idCache);
}
}

View File

@ -8,8 +8,7 @@ import org.cryptomator.util.Optional;
class PCloudCloudNodeFactory {
public static PCloudFile from(PCloudFolder parent, RemoteFile metadata) {
public static PCloudFile file(PCloudFolder parent, RemoteFile metadata) {
return new PCloudFile(parent, metadata.fileId(), metadata.name(), getNodePath(parent, metadata.name()), Optional.ofNullable(metadata.size()), Optional.ofNullable(metadata.lastModified()));
}
@ -17,23 +16,39 @@ class PCloudCloudNodeFactory {
return new PCloudFile(parent, null, name, path, size, Optional.empty());
}
public static PCloudFolder from(PCloudFolder parent, RemoteFolder metadata) {
return new PCloudFolder(parent, metadata.folderId(), metadata.name(), getNodePath(parent, metadata.name()));
public static PCloudFile file(PCloudFolder parent, String name, Optional<Long> 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<Long> 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());
}
}

View File

@ -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<String, NodeInfo> cache;
@Inject
PCloudIdCache() {
cache = new LruCache<>(1000);
}
public NodeInfo get(String path) {
return cache.get(path);
}
<T extends PCloudIdCloudNode> 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;
}
}
}

View File

@ -0,0 +1,9 @@
package org.cryptomator.data.cloud.pcloud;
import org.cryptomator.domain.CloudNode;
interface PCloudIdCloudNode extends CloudNode {
Long getId();
}

View File

@ -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<Long> size) {
public PCloudFile file(PCloudFolder parent, String name, Optional<Long> 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<RemoteEntry> 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<RemoteEntry> 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<RemoteEntry> 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<PCloudNode> list(CloudFolder folder) throws ApiError, IOException {
public List<PCloudNode> list(PCloudFolder folder) throws ApiError, IOException {
List<PCloudNode> 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<RemoteEntry> 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<UploadState> progressAware, boolean replace, long size) throws ApiError, IOException, CloudNodeAlreadyExistsException {
public PCloudFile write(PCloudFile file, DataSource data, final ProgressAware<UploadState> 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<UploadState> 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<DownloadState> 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 {

View File

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