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 +