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

View File

@ -16,10 +16,12 @@ import static org.cryptomator.domain.CloudType.PCLOUD;
public class PCloudCloudContentRepositoryFactory implements CloudContentRepositoryFactory { public class PCloudCloudContentRepositoryFactory implements CloudContentRepositoryFactory {
private final Context context; private final Context context;
private final PCloudIdCache idCache;
@Inject @Inject
public PCloudCloudContentRepositoryFactory(Context context) { public PCloudCloudContentRepositoryFactory(Context context, PCloudIdCache idCache) {
this.context = context; this.context = context;
this.idCache = idCache;
} }
@Override @Override
@ -29,7 +31,7 @@ public class PCloudCloudContentRepositoryFactory implements CloudContentReposito
@Override @Override
public CloudContentRepository cloudContentRepositoryFor(Cloud cloud) { 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 { 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())); 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()); return new PCloudFile(parent, null, name, path, size, Optional.empty());
} }
public static PCloudFolder from(PCloudFolder parent, RemoteFolder metadata) { public static PCloudFile file(PCloudFolder parent, String name, Optional<Long> size) {
return new PCloudFolder(parent, metadata.folderId(), metadata.name(), getNodePath(parent, metadata.name())); return new PCloudFile(parent, null, name, getNodePath(parent, name), size, Optional.empty());
} }
private static String getNodePath(PCloudFolder parent, String name) { public static PCloudFile file(PCloudFolder folder, String name, Optional<Long> size, String path, Long fileId) {
return parent.getPath() + "/" + name; 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) { public static PCloudFolder folder(PCloudFolder parent, String name, String path) {
return new PCloudFolder(parent, null, name, 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) { public static PCloudNode from(PCloudFolder parent, RemoteEntry metadata) {
if (metadata instanceof RemoteFile) { if (metadata instanceof RemoteFile) {
return from(parent, (RemoteFile) metadata); return file(parent, metadata.asFile());
} else { } 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.data.util.CopyStream;
import org.cryptomator.domain.CloudFile; import org.cryptomator.domain.CloudFile;
import org.cryptomator.domain.CloudFolder;
import org.cryptomator.domain.CloudNode; import org.cryptomator.domain.CloudNode;
import org.cryptomator.domain.PCloudCloud; import org.cryptomator.domain.PCloudCloud;
import org.cryptomator.domain.exception.BackendException;
import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException; import org.cryptomator.domain.exception.CloudNodeAlreadyExistsException;
import org.cryptomator.domain.exception.NoSuchCloudFileException;
import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException; import org.cryptomator.domain.exception.authentication.NoAuthenticationProvidedException;
import org.cryptomator.domain.usecases.ProgressAware; import org.cryptomator.domain.usecases.ProgressAware;
import org.cryptomator.domain.usecases.cloud.DataSource; import org.cryptomator.domain.usecases.cloud.DataSource;
@ -43,16 +44,20 @@ import okio.Source;
import static org.cryptomator.domain.usecases.cloud.Progress.progress; import static org.cryptomator.domain.usecases.cloud.Progress.progress;
class PCloudImpl { class PCloudImpl {
private final PCloudIdCache idCache;
private final PCloudClientFactory clientFactory = new PCloudClientFactory(); private final PCloudClientFactory clientFactory = new PCloudClientFactory();
private final PCloudCloud cloud; private final PCloudCloud cloud;
private final RootPCloudFolder root; private final RootPCloudFolder root;
private final Context context; private final Context context;
PCloudImpl(PCloudCloud cloud, Context context) { PCloudImpl(PCloudCloud cloud, Context context, PCloudIdCache idCache) {
if (cloud.accessToken() == null) { if (cloud.accessToken() == null) {
throw new NoAuthenticationProvidedException(cloud); throw new NoAuthenticationProvidedException(cloud);
} }
this.cloud = cloud; this.cloud = cloud;
this.idCache = idCache;
this.root = new RootPCloudFolder(cloud); this.root = new RootPCloudFolder(cloud);
this.context = context; this.context = context;
} }
@ -83,36 +88,87 @@ class PCloudImpl {
return folder; return folder;
} }
public PCloudFile file(CloudFolder folder, String name) { public PCloudFile file(PCloudFolder parent, String name) {
return file(folder, name, Optional.empty()); 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( // return PCloudCloudNodeFactory.file( //
(PCloudFolder) folder, // parent, //
name, // name, //
size, // size, //
folder.getPath() + '/' + name); parent.getPath() + '/' + name);
} }
public PCloudFolder folder(CloudFolder folder, String name) { public PCloudFolder folder(PCloudFolder parent, String name) {
return PCloudCloudNodeFactory.folder( // if (parent.getId() == null) {
(PCloudFolder) folder, // return PCloudCloudNodeFactory.folder(parent, name);
name, // }
folder.getPath() + '/' + 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 { try {
if (node instanceof PCloudFolder) { 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; return true;
} else { } else {
client().stat(((PCloudFile)node).getPath()).execute(); RemoteFile remoteFile = client().stat(node.getPath()).execute();
idCache.add(PCloudCloudNodeFactory.file(node.getParent(), remoteFile));
return true; return true;
} }
} catch (ApiError e) { } catch (ApiError e) {
if (e.errorCode() == PCloudApiErrorCodes.DIRECTORY_DOES_NOT_EXIST.getValue() 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.INVALID_FILE_OR_FOLDER_NAME.getValue()
|| e.errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()) { || e.errorCode() == PCloudApiErrorCodes.FILE_OR_FOLDER_NOT_FOUND.getValue()) {
return false; 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<>(); List<PCloudNode> result = new ArrayList<>();
Long folderId = ((PCloudFolder)folder).getId(); Long folderId = folder.getId();
RemoteFolder listFolderResult; RemoteFolder listFolderResult;
if (folderId == null) { if (folderId == null) {
listFolderResult = client().listFolder(folder.getPath()).execute(); listFolderResult = client().listFolder(folder.getPath()).execute();
} else { } else {
listFolderResult = client() // listFolderResult = client() //
.listFolder(((PCloudFolder) folder).getId()) // .listFolder(folder.getId()) //
.execute(); .execute();
} }
List<RemoteEntry> entryMetadata = listFolderResult.children(); List<RemoteEntry> entryMetadata = listFolderResult.children();
for (RemoteEntry metadata : entryMetadata) { for (RemoteEntry metadata : entryMetadata) {
result.add(PCloudCloudNodeFactory.from( // result.add(PCloudCloudNodeFactory.from(folder, metadata));
(PCloudFolder) folder, //
metadata));
} }
return result; return result;
} }
public PCloudFolder create(CloudFolder folder) throws ApiError, IOException { public PCloudFolder create(PCloudFolder folder) throws ApiError, IOException {
RemoteFolder createFolderResult = client() // RemoteFolder createdFolder = client() //
.createFolder(((PCloudFolder)folder.getParent()).getId(), folder.getName()) // .createFolder(folder.getParent().getId(), folder.getName()) //
.execute(); .execute();
return idCache.cache( //
return PCloudCloudNodeFactory.from( // PCloudCloudNodeFactory.folder(folder.getParent(), createdFolder));
(PCloudFolder) folder.getParent(), //
createFolderResult.asFolder());
} }
public CloudNode move(CloudNode source, CloudNode target) throws ApiError, IOException { public CloudNode move(PCloudNode source, PCloudNode target) throws ApiError, BackendException, IOException {
RemoteEntry relocationResult; RemoteEntry relocationResult;
if (source instanceof PCloudFolder) { if (exists(target)) {
relocationResult = client().moveFolder(((PCloudFolder) source).getId(), ((PCloudFolder) target).getId()).execute(); throw new CloudNodeAlreadyExistsException(target.getName());
} else {
relocationResult = client().moveFile(((PCloudFile) source).getId(), ((PCloudFolder) target).getId()).execute();
} }
return PCloudCloudNodeFactory.from( // if (source instanceof PCloudFolder) {
(PCloudFolder) target.getParent(), // relocationResult = client().moveFolder(source.getId(), target.getId()).execute();
relocationResult); } 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) { if (exists(file) && !replace) {
throw new CloudNodeAlreadyExistsException("CloudNode already exists and replace is false"); 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))); progressAware.onProgress(Progress.started(UploadState.upload(file)));
UploadOptions uploadOptions = UploadOptions.DEFAULT; UploadOptions uploadOptions = UploadOptions.DEFAULT;
if (replace) { if (replace) {
@ -181,9 +241,7 @@ class PCloudImpl {
progressAware.onProgress(Progress.completed(UploadState.upload(file))); progressAware.onProgress(Progress.completed(UploadState.upload(file)));
return PCloudCloudNodeFactory.from( // return idCache.cache(PCloudCloudNodeFactory.file(file.getParent(), uploadedFile));
file.getParent(), //
uploadedFile);
} }
private RemoteFile uploadFile(final PCloudFile file, DataSource data, final ProgressAware<UploadState> progressAware, UploadOptions uploadOptions, final long size) // 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() // return client() //
.createFile(file.getParent().getId(), file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) // .createFile(parentFolderId, file.getName(), pCloudDataSource, new Date(), listener, uploadOptions) //
.execute(); .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 { public void read(CloudFile file, OutputStream data, final ProgressAware<DownloadState> progressAware) throws ApiError, IOException {
progressAware.onProgress(Progress.started(DownloadState.download(file))); progressAware.onProgress(Progress.started(DownloadState.download(file)));
Long fileId = ((PCloudFile)file).getId(); Long fileId = ((PCloudFile)file).getId();
if (fileId == null) { if (fileId == null) {
fileId = client().stat(file.getPath()).execute().fileId(); fileId = idCache.get(file.getPath()).getId();
} }
FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute(); FileLink fileLink = client().createFileLink(fileId, DownloadOptions.DEFAULT).execute();
@ -241,15 +312,15 @@ class PCloudImpl {
progressAware.onProgress(Progress.completed(DownloadState.download(file))); 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) { if (node instanceof PCloudFolder) {
client() // client() //
.deleteFolder(((PCloudFolder) node).getId()).execute(); .deleteFolder(node.getId()).execute();
} else { } else {
client() // client() //
.deleteFile(((PCloudFile) node).getId()).execute(); .deleteFile(node.getId()).execute();
} }
idCache.remove(node);
} }
public String currentAccount() throws ApiError, IOException { public String currentAccount() throws ApiError, IOException {

View File

@ -2,8 +2,12 @@ package org.cryptomator.data.cloud.pcloud;
import org.cryptomator.domain.CloudNode; import org.cryptomator.domain.CloudNode;
interface PCloudNode extends CloudNode { interface PCloudNode extends PCloudIdCloudNode {
@Override
Long getId(); Long getId();
@Override
PCloudFolder getParent();
} }