diff --git a/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj b/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj
index 8646b9f2..d6377c99 100644
--- a/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj
+++ b/src/JavaFileStorageBindings/JavaFileStorageBindings.csproj
@@ -81,6 +81,9 @@
False
+
+
+
@@ -150,4 +153,4 @@
-
\ No newline at end of file
+
diff --git a/src/KeePass.sln b/src/KeePass.sln
index a2837f93..845d2961 100644
--- a/src/KeePass.sln
+++ b/src/KeePass.sln
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Net.FtpClient.Androi
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamsungPass", "SamsungPass\Xamarin.SamsungPass\SamsungPass\SamsungPass.csproj", "{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCloudBindings", "PCloudBindings\PCloudBindings.csproj", "{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -283,6 +285,16 @@ Global
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.Debug|Win32.Build.0 = Debug|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.Debug|x64.Build.0 = Debug|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.Release|Win32.Build.0 = Release|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.Release|x64.Build.0 = Release|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|Any CPU.ActiveCfg = ReleaseNoNet|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|Any CPU.Build.0 = ReleaseNoNet|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|Win32.Build.0 = ReleaseNoNet|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
+ {48574278-4779-4B3A-A9E4-9CF1BC285D0B}.ReleaseNoNet|x64.Build.0 = ReleaseNoNet|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/Kp2aBusinessLogic/Io/PCloudFileStorage.cs b/src/Kp2aBusinessLogic/Io/PCloudFileStorage.cs
new file mode 100644
index 00000000..9f7449ef
--- /dev/null
+++ b/src/Kp2aBusinessLogic/Io/PCloudFileStorage.cs
@@ -0,0 +1,23 @@
+using Android.Content;
+#if !EXCLUDE_JAVAFILESTORAGE
+
+namespace keepass2android.Io
+{
+ public partial class PCloudFileStorage: JavaFileStorage
+ {
+ private const string ClientId = "CkRWTQXY6Lm";
+
+ public PCloudFileStorage(Context ctx, IKp2aApp app) :
+ base(new Keepass2android.Javafilestorage.PCloudFileStorage(ctx, ClientId), app)
+ {
+ }
+
+
+ public override bool UserShouldBackup
+ {
+ get { return false; }
+ }
+ }
+
+}
+#endif
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
index de62cce7..cec263bb 100644
--- a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
+++ b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
@@ -117,6 +117,7 @@
+
@@ -215,4 +216,4 @@
-->
-
+
\ No newline at end of file
diff --git a/src/PCloudBindings/Additions/AboutAdditions.txt b/src/PCloudBindings/Additions/AboutAdditions.txt
new file mode 100644
index 00000000..08caee33
--- /dev/null
+++ b/src/PCloudBindings/Additions/AboutAdditions.txt
@@ -0,0 +1,48 @@
+Additions allow you to add arbitrary C# to the generated classes
+before they are compiled. This can be helpful for providing convenience
+methods or adding pure C# classes.
+
+== Adding Methods to Generated Classes ==
+
+Let's say the library being bound has a Rectangle class with a constructor
+that takes an x and y position, and a width and length size. It will look like
+this:
+
+public partial class Rectangle
+{
+ public Rectangle (int x, int y, int width, int height)
+ {
+ // JNI bindings
+ }
+}
+
+Imagine we want to add a constructor to this class that takes a Point and
+Size structure instead of 4 ints. We can add a new file called Rectangle.cs
+with a partial class containing our new method:
+
+public partial class Rectangle
+{
+ public Rectangle (Point location, Size size) :
+ this (location.X, location.Y, size.Width, size.Height)
+ {
+ }
+}
+
+At compile time, the additions class will be added to the generated class
+and the final assembly will a Rectangle class with both constructors.
+
+
+== Adding C# Classes ==
+
+Another thing that can be done is adding fully C# managed classes to the
+generated library. In the above example, let's assume that there isn't a
+Point class available in Java or our library. The one we create doesn't need
+to interact with Java, so we'll create it like a normal class in C#.
+
+By adding a Point.cs file with this class, it will end up in the binding library:
+
+public class Point
+{
+ public int X { get; set; }
+ public int Y { get; set; }
+}
\ No newline at end of file
diff --git a/src/PCloudBindings/Jars/AboutJars.txt b/src/PCloudBindings/Jars/AboutJars.txt
new file mode 100644
index 00000000..c359b62f
--- /dev/null
+++ b/src/PCloudBindings/Jars/AboutJars.txt
@@ -0,0 +1,24 @@
+This directory is for Android .jars.
+
+There are 2 types of jars that are supported:
+
+== Input Jar ==
+
+This is the jar that bindings should be generated for.
+
+For example, if you were binding the Google Maps library, this would
+be Google's "maps.jar".
+
+Set the build action for these jars in the properties page to "InputJar".
+
+
+== Reference Jars ==
+
+These are jars that are referenced by the input jar. C# bindings will
+not be created for these jars. These jars will be used to resolve
+types used by the input jar.
+
+NOTE: Do not add "android.jar" as a reference jar. It will be added automatically
+based on the Target Framework selected.
+
+Set the build action for these jars in the properties page to "ReferenceJar".
\ No newline at end of file
diff --git a/src/PCloudBindings/Jars/pcloud-sdk-android-1.0.1.aar b/src/PCloudBindings/Jars/pcloud-sdk-android-1.0.1.aar
new file mode 100644
index 00000000..29c5015b
Binary files /dev/null and b/src/PCloudBindings/Jars/pcloud-sdk-android-1.0.1.aar differ
diff --git a/src/PCloudBindings/Jars/pcloud-sdk-java-core-1.0.1.jar b/src/PCloudBindings/Jars/pcloud-sdk-java-core-1.0.1.jar
new file mode 100644
index 00000000..96a99ec5
Binary files /dev/null and b/src/PCloudBindings/Jars/pcloud-sdk-java-core-1.0.1.jar differ
diff --git a/src/PCloudBindings/PCloudBindings.csproj b/src/PCloudBindings/PCloudBindings.csproj
new file mode 100644
index 00000000..bc9deba9
--- /dev/null
+++ b/src/PCloudBindings/PCloudBindings.csproj
@@ -0,0 +1,75 @@
+
+
+
+ Debug
+ AnyCPU
+ {2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}
+ {6322E8A7-5C46-4E8C-8B19-448B7BC95DC1};{E1126D83-ADAB-4E4F-81F7-4B0A645A68C7}
+ Library
+ Properties
+ PCloudBindings
+ PCouldBindings
+ 512
+ True
+ v8.1
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 0
+ None
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ false
+
+
+ bin\ReleaseNoNet\
+ TRACE
+ true
+ pdbonly
+ AnyCPU
+ prompt
+ MinimumRecommendedRules.ruleset
+ false
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/PCloudBindings/Properties/AssemblyInfo.cs b/src/PCloudBindings/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..b4493d7e
--- /dev/null
+++ b/src/PCloudBindings/Properties/AssemblyInfo.cs
@@ -0,0 +1,30 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Android.App;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PCloudBindings")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PCloudBindings")]
+[assembly: AssemblyCopyright("Copyright © 2013")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/src/PCloudBindings/Transforms/EnumFields.xml b/src/PCloudBindings/Transforms/EnumFields.xml
new file mode 100644
index 00000000..22959957
--- /dev/null
+++ b/src/PCloudBindings/Transforms/EnumFields.xml
@@ -0,0 +1,14 @@
+
+
+
\ No newline at end of file
diff --git a/src/PCloudBindings/Transforms/EnumMethods.xml b/src/PCloudBindings/Transforms/EnumMethods.xml
new file mode 100644
index 00000000..49216c61
--- /dev/null
+++ b/src/PCloudBindings/Transforms/EnumMethods.xml
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/src/PCloudBindings/Transforms/Metadata.xml b/src/PCloudBindings/Transforms/Metadata.xml
new file mode 100644
index 00000000..2587ddc4
--- /dev/null
+++ b/src/PCloudBindings/Transforms/Metadata.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/java/JavaFileStorage/app/build.gradle b/src/java/JavaFileStorage/app/build.gradle
index 602be402..789f4131 100644
--- a/src/java/JavaFileStorage/app/build.gradle
+++ b/src/java/JavaFileStorage/app/build.gradle
@@ -35,6 +35,8 @@ dependencies {
compile('com.onedrive.sdk:onedrive-sdk-android:1.2.0') {
transitive = false
}
+ compile 'com.pcloud.sdk:java-core:1.0.1'
+ compile 'com.pcloud.sdk:android:1.0.1'
compile 'com.google.code.gson:gson:2.3.1'
compile 'com.microsoft.services.msa:msa-auth:0.8.6'
compile 'com.microsoft.aad:adal:1.14.0'
diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/JavaFileStorage.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/JavaFileStorage.java
index 0dca7e9d..49019bfd 100644
--- a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/JavaFileStorage.java
+++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/JavaFileStorage.java
@@ -132,7 +132,7 @@ public class FileEntry {
public boolean checkForFileChangeFast(String path, String previousFileVersion) throws Exception;
- public String getCurrentFileVersionFast(String path);
+ public String getCurrentFileVersionFast(String path) throws Exception;
public InputStream openFileForRead(String path) throws Exception;
@@ -157,4 +157,4 @@ public class FileEntry {
public void onActivityResult(FileStorageSetupActivity activity, int requestCode, int resultCode, Intent data);
public void onRequestPermissionsResult(FileStorageSetupActivity activity, int requestCode, String[] permissions, int[] grantResults);
-}
\ No newline at end of file
+}
diff --git a/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/PCloudFileStorage.java b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/PCloudFileStorage.java
new file mode 100644
index 00000000..45e064dc
--- /dev/null
+++ b/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/PCloudFileStorage.java
@@ -0,0 +1,402 @@
+package keepass2android.javafilestorage;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.regex.Pattern;
+
+import com.pcloud.sdk.ApiClient;
+import com.pcloud.sdk.ApiError;
+import com.pcloud.sdk.Authenticators;
+import com.pcloud.sdk.AuthorizationActivity;
+import com.pcloud.sdk.AuthorizationResult;
+import com.pcloud.sdk.Call;
+import com.pcloud.sdk.DataSource;
+import com.pcloud.sdk.PCloudSdk;
+import com.pcloud.sdk.RemoteEntry;
+import com.pcloud.sdk.RemoteFile;
+import com.pcloud.sdk.RemoteFolder;
+
+/**
+ * FileStorage implementation for PCloud provider.
+ * https://www.pcloud.com/
+ */
+public class PCloudFileStorage extends JavaFileStorageBase
+{
+ final static private int PCLOUD_AUTHORIZATION_REQUEST_CODE = 1001845497;
+
+ final static private String SHARED_PREF_NAME = "PCLOUD";
+ final static private String SHARED_PREF_AUTH_TOKEN = "AUTH_TOKEN";
+
+ private final Context ctx;
+
+ private ApiClient apiClient;
+ private String clientId;
+
+ public PCloudFileStorage(Context ctx, String clientId) {
+ this.ctx = ctx;
+ this.clientId = clientId;
+ this.apiClient = createApiClientFromSharedPrefs();
+ }
+
+ @Override
+ public boolean requiresSetup(String path) {
+ return true;
+ }
+
+ @Override
+ public void startSelectFile(FileStorageSetupInitiatorActivity activity, boolean isForSave, int requestCode) {
+ String path = getProtocolId() + "://";
+ activity.startSelectFileProcess(path, isForSave, requestCode);
+ }
+
+ @Override
+ public void prepareFileUsage(Context appContext, String path) throws Throwable {
+ if (!isConnected()) {
+ throw new UserInteractionRequiredException();
+ }
+ }
+
+ @Override
+ public void prepareFileUsage(FileStorageSetupInitiatorActivity activity, String path, int requestCode, boolean alwaysReturnSuccess) {
+ if (isConnected()) {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_PATH, path);
+ activity.onImmediateResult(requestCode, RESULT_FILEUSAGE_PREPARED, intent);
+ } else {
+ activity.startFileUsageProcess(path, requestCode, alwaysReturnSuccess);
+ }
+ }
+
+ @Override
+ public String getProtocolId() {
+ return "pcloud";
+ }
+
+ @Override
+ public String getDisplayName(String path) {
+ return path;
+ }
+
+ @Override
+ public String getFilename(String path) {
+ return path.substring(path.lastIndexOf("/") + 1);
+ }
+
+ @Override
+ public boolean checkForFileChangeFast(String path, String previousFileVersion) throws Exception {
+ if (previousFileVersion == null || "".equals(previousFileVersion)) {
+ return false;
+ }
+
+ path = this.cleanPath(path);
+
+ RemoteFile remoteFile = this.getRemoteFileByPath(path);
+
+ return !remoteFile.hash().equals(previousFileVersion);
+ }
+
+ @Override
+ public String getCurrentFileVersionFast(String path) throws Exception {
+ path = this.cleanPath(path);
+
+ RemoteFile remoteFile = this.getRemoteFileByPath(path);
+
+ return remoteFile.hash();
+ }
+
+ @Override
+ public InputStream openFileForRead(String path) throws Exception {
+ path = this.cleanPath(path);
+
+ RemoteFile remoteFile = this.getRemoteFileByPath(path);
+
+ return remoteFile.byteStream();
+ }
+
+ @Override
+ public void uploadFile(String path, byte[] data, boolean writeTransactional) throws Exception {
+ path = this.cleanPath(path);
+
+ DataSource dataSource = DataSource.create(data);
+ String filename = path.substring(path.lastIndexOf("/") + 1);
+ String filePath = path.substring(0, path.lastIndexOf("/") + 1);
+ RemoteFolder remoteFolder = this.getRemoteFolderByPath(filePath);
+
+ try {
+ this.apiClient.createFile(remoteFolder, filename, dataSource).execute();
+ } catch (ApiError e) {
+ throw convertApiError(e);
+ }
+ }
+
+ @Override
+ public String createFolder(String parentPath, String newDirName) throws Exception {
+ String parentPathWithoutProtocol = this.cleanPath(parentPath);
+
+ RemoteFolder remoteFolder = this.getRemoteFolderByPath(parentPathWithoutProtocol);
+
+ try {
+ this.apiClient.createFolder(remoteFolder, newDirName).execute();
+ } catch (ApiError e) {
+ throw convertApiError(e);
+ }
+
+ return this.createFilePath(parentPath, newDirName);
+ }
+
+ @Override
+ public String createFilePath(String parentPath, String newFileName) throws Exception {
+ return (
+ this.getProtocolId() + "://" +
+ this.cleanPath(parentPath) +
+ ("".equals(newFileName) ? "" : "/") + newFileName
+ );
+ }
+
+ @Override
+ public List listFiles(String parentPath) throws Exception {
+ parentPath = this.cleanPath(parentPath);
+
+ ArrayList fileEntries = new ArrayList<>();
+
+ RemoteFolder remoteFolder = this.getRemoteFolderByPath(parentPath);
+
+ for (RemoteEntry remoteEntry : remoteFolder.children()) {
+ fileEntries.add(this.convertRemoteEntryToFileEntry(remoteEntry, parentPath));
+ }
+
+ return fileEntries;
+ }
+
+ @Override
+ public FileEntry getFileEntry(String path) throws Exception {
+ path = this.cleanPath(path);
+
+ RemoteEntry remoteEntry = this.getRemoteEntryByPath(path);
+
+ return this.convertRemoteEntryToFileEntry(
+ remoteEntry,
+ path.substring(0, path.lastIndexOf("/"))
+ );
+ }
+
+ @Override
+ public void delete(String path) throws Exception {
+ path = this.cleanPath(path);
+
+ RemoteEntry remoteEntry = this.getRemoteFileByPath(path);
+
+ try {
+ this.apiClient.delete(remoteEntry).execute();
+ } catch (ApiError e) {
+ throw convertApiError(e);
+ }
+ }
+
+ @Override
+ public void onCreate(FileStorageSetupActivity activity, Bundle savedInstanceState) {
+
+ }
+
+ @Override
+ public void onResume(FileStorageSetupActivity activity) {
+ if (activity.getProcessName().equals(PROCESS_NAME_SELECTFILE)) {
+ activity.getState().putString(EXTRA_PATH, activity.getPath());
+ }
+
+ if (this.isConnected()) {
+ finishActivityWithSuccess(activity);
+ } else if (!activity.getState().getBoolean("hasStartedAuth", false)) {
+ Activity castedActivity = (Activity)activity;
+ Intent authIntent = AuthorizationActivity.createIntent(castedActivity, this.clientId);
+ castedActivity.startActivityForResult(authIntent, PCLOUD_AUTHORIZATION_REQUEST_CODE);
+ activity.getState().putBoolean("hasStartedAuth", true);
+ }
+
+ }
+
+ @Override
+ public void onActivityResult(FileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) {
+ if (requestCode == PCLOUD_AUTHORIZATION_REQUEST_CODE && data != null) {
+ activity.getState().putBoolean("hasStartedAuth", false);
+ AuthorizationResult result = (AuthorizationResult)(
+ data.getSerializableExtra(AuthorizationActivity.KEY_AUTHORIZATION_RESULT)
+ );
+ this.handleAuthResult(activity, result, data);
+ }
+ }
+
+ private void handleAuthResult(FileStorageSetupActivity activity, AuthorizationResult authorizationResult,
+ Intent data) {
+ if (authorizationResult == AuthorizationResult.ACCESS_GRANTED) {
+ String authToken = data.getStringExtra(AuthorizationActivity.KEY_ACCESS_TOKEN);
+ setAuthToken(authToken);
+ finishActivityWithSuccess(activity);
+ } else {
+ Activity castedActivity = (Activity)activity;
+ Intent resultData = new Intent();
+ resultData.putExtra(EXTRA_ERROR_MESSAGE, "Authentication failed.");
+ castedActivity.setResult(Activity.RESULT_CANCELED, resultData);
+ castedActivity.finish();
+ }
+ }
+
+ @Override
+ public void onStart(FileStorageSetupActivity activity) {
+
+ }
+
+ private ApiClient createApiClientFromSharedPrefs() {
+ SharedPreferences prefs = this.ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
+ String authToken = prefs.getString(SHARED_PREF_AUTH_TOKEN, null);
+ return this.createApiClient(authToken);
+ }
+
+ private ApiClient createApiClient(String authToken) {
+ if (authToken == null) {
+ return null;
+ }
+
+ return (
+ PCloudSdk.newClientBuilder()
+ .authenticator(Authenticators.newOAuthAuthenticator(authToken))
+ .create()
+ );
+ }
+
+ private boolean isConnected() {
+ return (this.apiClient != null);
+ }
+
+ private void clearAuthToken() {
+ this.apiClient = null;
+ SharedPreferences prefs = this.ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.clear();
+ edit.apply();
+ }
+
+ private void setAuthToken(String authToken) {
+ this.apiClient = this.createApiClient(authToken);
+ SharedPreferences prefs = this.ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor edit = prefs.edit();
+ edit.putString(SHARED_PREF_AUTH_TOKEN, authToken);
+ edit.apply();
+ }
+
+ private String cleanPath(String path) {
+ return (
+ "/" + path.replaceAll("^(" + Pattern.quote(this.getProtocolId()) + "://)?/*", "")
+ );
+ }
+
+ private RemoteFile getRemoteFileByPath(String path) throws Exception {
+ RemoteEntry remoteEntry = this.getRemoteEntryByPath(path);
+
+ try {
+ return remoteEntry.asFile();
+ } catch (IllegalStateException e) {
+ throw new FileNotFoundException(e.toString());
+ }
+ }
+
+ private RemoteFolder getRemoteFolderByPath(String path) throws Exception {
+ RemoteEntry remoteEntry = this.getRemoteEntryByPath(path);
+
+ try {
+ return remoteEntry.asFolder();
+ } catch (IllegalStateException e) {
+ throw new FileNotFoundException(e.toString());
+ }
+ }
+
+ private RemoteEntry getRemoteEntryByPath(String path) throws Exception {
+ Call call = this.apiClient.listFolder(RemoteFolder.ROOT_FOLDER_ID, true);
+
+ RemoteFolder folder;
+ try {
+ folder = call.execute();
+ } catch (ApiError apiError) {
+ throw convertApiError(apiError);
+ }
+
+ if ("/".equals(path)) {
+ return folder;
+ }
+
+ String[] fileNames = path.substring(1).split("/");
+ RemoteFolder currentFolder = folder;
+ Iterator fileNamesIterator = Arrays.asList(fileNames).iterator();
+ while (true) {
+ String fileName = fileNamesIterator.next();
+
+ Iterator entryIterator = currentFolder.children().iterator();
+ while (true) {
+ RemoteEntry remoteEntry;
+ try {
+ remoteEntry = entryIterator.next();
+ } catch (NoSuchElementException e) {
+ throw new FileNotFoundException(e.toString());
+ }
+
+ if (currentFolder.folderId() == remoteEntry.parentFolderId() && fileName.equals(remoteEntry.name())) {
+ if (!fileNamesIterator.hasNext()) {
+ return remoteEntry;
+ }
+
+ try {
+ currentFolder = remoteEntry.asFolder();
+ } catch (IllegalStateException e) {
+ throw new FileNotFoundException(e.toString());
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ private Exception convertApiError(ApiError e) {
+ String strErrorCode = String.valueOf(e.errorCode());
+ if (strErrorCode.startsWith("1") || "2000".equals(strErrorCode)) {
+ this.clearAuthToken();
+ return new UserInteractionRequiredException("Unlinked from PCloud! User must re-link.", e);
+ } else if (strErrorCode.startsWith("2")) {
+ return new FileNotFoundException(e.toString());
+ }
+
+ return e;
+ }
+
+ private FileEntry convertRemoteEntryToFileEntry(RemoteEntry remoteEntry, String parentPath) {
+ FileEntry fileEntry = new FileEntry();
+ fileEntry.canRead = true;
+ fileEntry.canWrite = true;
+ fileEntry.path = (
+ this.getProtocolId() + "://" +
+ ("/".equals(parentPath) ? "" : parentPath) +
+ "/" + remoteEntry.name()
+ );
+ fileEntry.displayName = remoteEntry.name();
+ fileEntry.isDirectory = !remoteEntry.isFile();
+ fileEntry.lastModifiedTime = remoteEntry.lastModified().getTime();
+
+ if (remoteEntry.isFile()) {
+ fileEntry.sizeInBytes = remoteEntry.asFile().size();
+ }
+
+ return fileEntry;
+ }
+}
diff --git a/src/keepass2android/Resources/drawable-mdpi/ic_storage_pcloud.png b/src/keepass2android/Resources/drawable-mdpi/ic_storage_pcloud.png
new file mode 100644
index 00000000..816e9940
Binary files /dev/null and b/src/keepass2android/Resources/drawable-mdpi/ic_storage_pcloud.png differ
diff --git a/src/keepass2android/Resources/drawable-xhdpi/ic_storage_pcloud.png b/src/keepass2android/Resources/drawable-xhdpi/ic_storage_pcloud.png
new file mode 100644
index 00000000..ff67d750
Binary files /dev/null and b/src/keepass2android/Resources/drawable-xhdpi/ic_storage_pcloud.png differ
diff --git a/src/keepass2android/Resources/values/strings.xml b/src/keepass2android/Resources/values/strings.xml
index 986cb092..50293d90 100644
--- a/src/keepass2android/Resources/values/strings.xml
+++ b/src/keepass2android/Resources/values/strings.xml
@@ -528,6 +528,7 @@
Dropbox (KP2A folder)
If you do not want to give KP2A access to your full Dropbox, you may select this option. It will request only access to the folder Apps/Keepass2Android. This is especially suited when creating a new database. If you already have a database, click this option to create the folder, then place your file inside the folder (from your PC) and then select this option again for opening the file.
Google Drive
+ PCloud
OneDrive
SFTP (SSH File Transfer)
System file picker
diff --git a/src/keepass2android/app/App.cs b/src/keepass2android/app/App.cs
index f7448d20..16c05191 100644
--- a/src/keepass2android/app/App.cs
+++ b/src/keepass2android/app/App.cs
@@ -45,6 +45,7 @@ using KeePassLib.Utility;
#if !NoNet
using Keepass2android.Javafilestorage;
using GoogleDriveFileStorage = keepass2android.Io.GoogleDriveFileStorage;
+using PCloudFileStorage = keepass2android.Io.PCloudFileStorage;
#endif
namespace keepass2android
{
@@ -588,6 +589,7 @@ namespace keepass2android
new SftpFileStorage(this),
new NetFtpFileStorage(Application.Context, this),
new WebDavFileStorage(this),
+ new PCloudFileStorage(Application.Context, this),
//new LegacyWebDavStorage(this),
//new LegacyFtpStorage(this),
#endif
diff --git a/src/keepass2android/keepass2android.csproj b/src/keepass2android/keepass2android.csproj
index 4202a813..0f6672ba 100644
--- a/src/keepass2android/keepass2android.csproj
+++ b/src/keepass2android/keepass2android.csproj
@@ -488,6 +488,7 @@
+
@@ -831,6 +832,10 @@
{48574278-4779-4B3A-A9E4-9CF1BC285D0B}
JavaFileStorageBindings
+
+ {2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}
+ PCloudBindings
+
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}
KeePassLib2Android
@@ -1146,6 +1151,9 @@
+
+
+
@@ -1909,4 +1917,4 @@
-
\ No newline at end of file
+