Merge branch 'master' into release-1.7.0
authorDavid A. Velasco <dvelasco@solidgear.es>
Tue, 3 Feb 2015 11:46:23 +0000 (12:46 +0100)
committerDavid A. Velasco <dvelasco@solidgear.es>
Tue, 3 Feb 2015 11:46:23 +0000 (12:46 +0100)
194 files changed:
.gitignore
.gitmodules
.travis.yml
SETUP.md
actionbarsherlock [deleted submodule]
build.gradle [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.jar [new file with mode: 0644]
gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
gradlew [new file with mode: 0755]
gradlew.bat [new file with mode: 0644]
libs/actionbarsherlock_lib/build.gradle [new file with mode: 0644]
libs/actionbarsherlock_lib/library-4.1.0.aar [new file with mode: 0644]
oc_jb_workaround/build.gradle [new file with mode: 0644]
oc_jb_workaround/gradle/wrapper/gradle-wrapper.jar [new file with mode: 0644]
oc_jb_workaround/gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
oc_jb_workaround/gradlew [new file with mode: 0755]
oc_jb_workaround/gradlew.bat [new file with mode: 0644]
oc_jb_workaround/project.properties
owncloud-android-library
pom.xml
res/drawable-hdpi/copy_link.png
res/drawable-hdpi/ic_favorite.png
res/drawable-hdpi/shared_with_me.png
res/drawable-hdpi/sharedlink.png
res/drawable-ldpi/copy_link.png
res/drawable-mdpi/copy_link.png
res/drawable-mdpi/ic_favorite.png
res/drawable-mdpi/shared_with_me.png
res/drawable-mdpi/sharedlink.png
res/drawable-xhdpi/copy_link.png
res/drawable-xhdpi/file.png [new file with mode: 0644]
res/drawable-xhdpi/file_doc.png [new file with mode: 0644]
res/drawable-xhdpi/file_image.png [new file with mode: 0644]
res/drawable-xhdpi/file_movie.png [new file with mode: 0644]
res/drawable-xhdpi/file_pdf.png [new file with mode: 0644]
res/drawable-xhdpi/file_ppt.png [new file with mode: 0644]
res/drawable-xhdpi/file_sound.png [new file with mode: 0644]
res/drawable-xhdpi/file_xls.png [new file with mode: 0644]
res/drawable-xhdpi/file_zip.png [new file with mode: 0644]
res/drawable-xhdpi/folder_public.png [new file with mode: 0644]
res/drawable-xhdpi/ic_favorite.png
res/drawable-xhdpi/ic_menu_archive.png [new file with mode: 0644]
res/drawable-xhdpi/shared_with_me.png
res/drawable-xhdpi/shared_with_me_folder.png [new file with mode: 0644]
res/drawable-xhdpi/sharedlink.png
res/drawable/downloading_file_indicator.png
res/layout-land/account_setup.xml
res/layout/account_setup.xml
res/layout/grid_image.xml [new file with mode: 0644]
res/layout/grid_item.xml [new file with mode: 0644]
res/layout/list_footer.xml [new file with mode: 0644]
res/layout/list_fragment.xml
res/layout/list_item.xml
res/layout/preference_widget_radiobutton.xml [new file with mode: 0644]
res/layout/uploader_list_item_layout.xml
res/values-af-rZA/strings.xml
res/values-ar/strings.xml
res/values-az/strings.xml
res/values-be/strings.xml
res/values-bg-rBG/strings.xml
res/values-bn-rBD/strings.xml
res/values-bs/strings.xml
res/values-ca/strings.xml
res/values-cs-rCZ/strings.xml
res/values-cy-rGB/strings.xml
res/values-da/strings.xml
res/values-de-rDE/strings.xml
res/values-de/strings.xml
res/values-el/strings.xml
res/values-en-rGB/strings.xml
res/values-eo/strings.xml
res/values-es-rAR/strings.xml
res/values-es-rCL/strings.xml
res/values-es-rMX/strings.xml
res/values-es/strings.xml
res/values-et-rEE/strings.xml
res/values-eu/strings.xml
res/values-fa/strings.xml
res/values-fi-rFI/strings.xml
res/values-fi/strings.xml [new file with mode: 0644]
res/values-fr/strings.xml
res/values-gl/strings.xml
res/values-he/strings.xml
res/values-hr/strings.xml
res/values-hu-rHU/strings.xml
res/values-ia/strings.xml
res/values-id/strings.xml
res/values-is/strings.xml
res/values-it/strings.xml
res/values-ja-rJP/strings.xml
res/values-ka-rGE/strings.xml
res/values-km/strings.xml
res/values-kn/strings.xml
res/values-ko/strings.xml
res/values-lb/strings.xml
res/values-lo/strings.xml [new file with mode: 0644]
res/values-lt-rLT/strings.xml
res/values-lv/strings.xml
res/values-mk/strings.xml
res/values-mn/strings.xml
res/values-ms-rMY/strings.xml
res/values-my/strings.xml
res/values-nb-rNO/strings.xml
res/values-nl/strings.xml
res/values-nn-rNO/strings.xml
res/values-oc/strings.xml
res/values-pa/strings.xml
res/values-pl/strings.xml
res/values-pt-rBR/strings.xml
res/values-pt-rPT/strings.xml
res/values-ro/strings.xml
res/values-ru/strings.xml
res/values-si-rLK/strings.xml
res/values-sk-rSK/strings.xml
res/values-sl/strings.xml
res/values-sq/strings.xml
res/values-sr-rSP/strings.xml
res/values-sr/strings.xml
res/values-sv/strings.xml
res/values-ta-rLK/strings.xml
res/values-th-rTH/strings.xml
res/values-tr/strings.xml
res/values-ug/strings.xml
res/values-uk/strings.xml
res/values-ur-rPK/strings.xml
res/values-vi/strings.xml
res/values-yo/strings.xml [new file with mode: 0644]
res/values-zh-rCN/strings.xml
res/values-zh-rHK/strings.xml
res/values-zh-rTW/strings.xml
res/values/colors.xml
res/values/dims.xml
res/values/strings.xml
res/xml/preferences.xml
settings.gradle [new file with mode: 0644]
setup_env.bat
setup_env.sh
src/com/owncloud/android/authentication/AuthenticatorActivity.java
src/com/owncloud/android/datamodel/FileDataStorageManager.java
src/com/owncloud/android/datamodel/OCFile.java
src/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
src/com/owncloud/android/db/DbHandler.java
src/com/owncloud/android/db/ProviderMeta.java
src/com/owncloud/android/files/FileMenuFilter.java
src/com/owncloud/android/files/FileOperationsHelper.java
src/com/owncloud/android/files/services/FileDownloader.java
src/com/owncloud/android/files/services/FileUploader.java
src/com/owncloud/android/files/services/IndexedForest.java [new file with mode: 0644]
src/com/owncloud/android/operations/CreateShareOperation.java
src/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java
src/com/owncloud/android/operations/DownloadFileOperation.java
src/com/owncloud/android/operations/GetSharesForFileOperation.java
src/com/owncloud/android/operations/RefreshFolderOperation.java [new file with mode: 0644]
src/com/owncloud/android/operations/RenameFileOperation.java
src/com/owncloud/android/operations/SynchronizeFileOperation.java
src/com/owncloud/android/operations/SynchronizeFolderOperation.java
src/com/owncloud/android/providers/FileContentProvider.java
src/com/owncloud/android/services/OperationsService.java
src/com/owncloud/android/services/SyncFolderHandler.java [new file with mode: 0644]
src/com/owncloud/android/syncadapter/FileSyncAdapter.java
src/com/owncloud/android/ui/ExtendedListView.java [deleted file]
src/com/owncloud/android/ui/LongClickableCheckBoxPreference.java [deleted file]
src/com/owncloud/android/ui/RadioButtonPreference.java [new file with mode: 0644]
src/com/owncloud/android/ui/SquareImageView.java [new file with mode: 0644]
src/com/owncloud/android/ui/SquareLinearLayout.java [new file with mode: 0644]
src/com/owncloud/android/ui/activity/ComponentsGetter.java
src/com/owncloud/android/ui/activity/FileActivity.java
src/com/owncloud/android/ui/activity/FileDisplayActivity.java
src/com/owncloud/android/ui/activity/FolderPickerActivity.java
src/com/owncloud/android/ui/activity/LogHistoryActivity.java
src/com/owncloud/android/ui/activity/Preferences.java
src/com/owncloud/android/ui/adapter/FileListListAdapter.java
src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
src/com/owncloud/android/ui/dialog/RemoveFileDialogFragment.java
src/com/owncloud/android/ui/dialog/RenameFileDialogFragment.java
src/com/owncloud/android/ui/fragment/ExtendedListFragment.java
src/com/owncloud/android/ui/fragment/FileDetailFragment.java
src/com/owncloud/android/ui/fragment/LocalFileListFragment.java
src/com/owncloud/android/ui/fragment/OCFileListFragment.java
src/com/owncloud/android/ui/preview/FileDownloadFragment.java
src/com/owncloud/android/ui/preview/PreviewImageActivity.java
src/com/owncloud/android/ui/preview/PreviewImageFragment.java
src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java
src/com/owncloud/android/utils/BitmapUtils.java
src/com/owncloud/android/utils/DisplayUtils.java
src/com/owncloud/android/utils/ErrorMessageAdapter.java
src/com/owncloud/android/utils/FileStorageUtils.java
src/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java [new file with mode: 0644]
src/third_parties/in/srain/cube/lapache-2.0.txt [new file with mode: 0644]
third_party/android-support-library/android-support-v4.jar
user_manual/Makefile [new file with mode: 0644]
user_manual/conf.py
user_manual/make.bat [new file with mode: 0644]
user_manual/ocdoc [new submodule]

index 9b9bd8e..8346dbf 100644 (file)
@@ -10,6 +10,8 @@
 
 # generated files
 bin/
+build/
+*.iml
 gen/
 target/
 
@@ -30,5 +32,11 @@ oc_framework/proguard-project.txt
 oc_framework-test-project/proguard-project.txt
 tests/proguard-project.txt
 
-# Should not be commited inside this repo:
-actionbarsherlock/
\ No newline at end of file
+# Android Studio and Gradle specific entries
+.gradle
+.idea
+*.iml
+build
+
+# Actionbarsherlock is now ignored since scripts takes care of init the sub-modules.
+actionbarsherlock
\ No newline at end of file
index f0ca872..38f0f58 100644 (file)
@@ -1,6 +1,9 @@
-[submodule "actionbarsherlock"]
-       path = actionbarsherlock
-       url = git://github.com/JakeWharton/ActionBarSherlock.git
+
 [submodule "owncloud-android-library"]
        path = owncloud-android-library
        url = git://github.com/owncloud/android-library.git
+       branch = develop
+[submodule "ocdoc"]
+       path = user_manual/ocdoc
+       url = https://github.com/owncloud/documentation
+       branch = master
index 50b73d1..d7e46cb 100644 (file)
@@ -1,22 +1,14 @@
 language: android
 android:
   components:
+    - platform-tools
+    - tools
     - build-tools-20.0.0
     - android-19
-    - android-17
-    - android-14
-    - extra-android-support
-  licenses:
-    - 'android-sdk-license-5be876d5'
-    - 'android-sdk-license-598b93a6'
-  
-jdk: oraclejdk7
-
 before_install:
   - rm pom.xml
-  - ./setup_env.sh
-
 script:
+  - ./setup_env.sh ant
   - ant clean
   - ant debug
 
index 3953903..a9e2a93 100644 (file)
--- a/SETUP.md
+++ b/SETUP.md
@@ -1,13 +1,11 @@
-  
+
 If you want to start help developing ownCloud please follow the [contribution guidelines][0] and observe these instructions.
 
 If you have any problems, start again with 1) and work your way down. If something still does not work as described here, please open a new issue describing exactly what you did, what happened, and what should have happened.
-  
-### 1) Fork and download android/develop repository:
 
-NOTE: Android SDK with platforms 8, 14 and 19 (and maybe others) need to be installed.
-      You must have the Android SDK 'tools/', and 'platforms-tools/' folders in your environment path variable.
-      "git" need to be installed and in your environment path variable.
+### 1. Fork and download android/develop repository:
+
+NOTE: You must have git in your environment path variable to perform the next operations.
 
 * Navigate to https://github.com/owncloud/android, click fork.
 * Clone your new repo: "git clone git@github.com:YOURGITHUBNAME/android.git"
@@ -16,23 +14,28 @@ NOTE: Android SDK with platforms 8, 14 and 19 (and maybe others) need to be inst
 * Pull changes from your develop branch: "git pull origin develop"
 * Make official ownCloud repo known as upstream: "git remote add upstream git@github.com:owncloud/android.git"
 * Make sure to get the latest changes from official android/develop branch: "git pull upstream develop"
-* Complete the setup of project properties and resolve pending dependencies running "setup_env.bat" or "./setup_env.sh" .
 
-At this point you can continue using different tools to build the project. Sections 2a), 2b), and 2c) describe some of the existing alternatives.
+At this point you can continue using different tools to build the project. Section 2, 3, 4, 5 and 6 describe some of the existing alternatives.
+
+### 2. Building with Ant:
 
-### 2a) Building with Ant:
-  
 NOTE: You must have the Android SDK 'tools/', and 'platforms-tools/' folders in your environment path variable.
 
-* Run "ant clean" .
+* Resolve necessary dependencies running:
+    - Windows: "setup_env.bat ant"
+    - Mac OS/Linux: "./setup_env.sh ant"
+* Run "ant clean".
 * Run "ant debug" to generate a debuggable version of the ownCloud app.
 
-### 2b) Building with console/maven:
+### 3. Building with console/maven:
 
 NOTE: You must have mvn (version >= 3.1.1) in your environment path. Current Android 'platforms-tools' need to be installed.
 
-Download/install Android plugin for Maven, install owncloud-android-library, then build ownCloud with mvn:
+Download/install Android plugin for Maven, then build ownCloud with mvn:
 
+* Resolve necessary dependencies running:
+    - Windows: "setup_env.bat maven"
+    - Mac OS/Linux: "./setup_env.sh maven"
 * cd ..
 * git clone https://github.com/mosabua/maven-android-sdk-deployer.git
 * cd maven-android-sdk-deployer
@@ -41,43 +44,65 @@ Download/install Android plugin for Maven, install owncloud-android-library, the
 * mvn install
 * cd ..
 
-Now you can create ownCloud APK using "mvn package"
+Now you can create ownCloud APK using "mvn package" and find it as ownCloud.apk under the target
+
+### 4. Building with Gradle:
 
-### 2c) Building with Eclipse:
+* Complete the setup of project properties running:
+    - Windows: "setup_env.bat gradle"
+    - Mac OS/Linux: "./setup_env.sh gradle"
+* Open a terminal and go to the "android" directory that contains the repository.
+* Run the 'clean' and 'build' tasks using the Gradle wrapper provided:
+    - Windows: gradlew.bat clean build
+    - Mac OS/Linux: ./gradlew clean build
+* You can find the generated apk file in android/build/outputs/apk
+
+### 5. Building with Eclipse:
 
 NOTE: You must have the Android SDK 'tools/', and 'platforms-tools/' folders in your environment path variable.
 
-* Complete the setup of project properties and resolve pending dependencies running "setup_env.bat" or "./setup_env.sh" .
-* Open Eclipse and create new "Android Project from Existing Code". Choose android/actionbarsherlock/library as root.
+* Resolve necessary dependencies running:
+    - Windows: "setup_env.bat ant"
+    - Mac OS/Linux: "./setup_env.sh ant"
+    * Open Eclipse and create new "Android Project from Existing Code". Choose android/actionbarsherlock/library as root.
 * Clean project and compile.
-* If any error appear, check the project properties; in the 'Android' section, API Level should be greater or equal than 14.
-* If "error loading libz.so.1" appears, try "sudo apt-get install lib32z1"
+* If any error appears, check the project properties; in the 'Android' section, API Level should be greater or equal than 14.
 * Make sure android/actionbarsherlock/library/bin/library.jar was created.
-* Create a new "Android Project from Existing Code". Choose android/owncloud-android-library as root. (test and sample clients are not required.)
+* Create a new "Android Project from Existing Code". Choose android/owncloud-android-library as root.
 * Clean project and compile.
-* If any error appear, check the project properties; in the 'Android' section, API Level should be 19 or greater.
-* Make sure 'android/owncloud-android-library/bin/owncloud android library.jar' was created.
+* If any error appears, check the project properties; in the 'Android' section, API Level should be 19 or greater.
+* Make sure android/owncloud-android-library/bin/classes.jar was created.
 * Import ownCloud Android project.
 * Clean project and compile.
 * If any error appears, check the project properties of owncloud-android project; in the 'Android' section:
-  - API Level should be 19 or greater.
-  - Two library projects should appear referred in the bottom square: actionbarsherlock/library and owncloud-android-library. Add them if needed. 
+    - API Level should be 19 or greater.
+    - Two library projects should appear referred in the bottom square: actionbarsherlock/library and owncloud-android-library. Add them if needed.
 * After those actions you should be good to go. HAVE FUN!
 
 NOTE: Even though API level is set to 19, APK also runs on older devices because in AndroidManifest.xml minSdkVersion is set to 8.
 
-### 3) Create pull request:
-  
+### 6. Building with Android Studio:
+
+* Complete the setup of project properties running:
+    - Windows: "setup_env.bat gradle"
+    - Mac OS/Linux: "./setup_env.sh gradle"
+* The first time you want to open the project in Android Studio, select 'Import Project...' and choose the file "android/settings.gradle".
+  Android Studio will then create the '.iml' files it needs. If you ever close the project but the files are still there, you just select
+  'Open Project...'. The file chooser will show an Android face as the folder icon, which you can select to reopen the project.
+* To build the project, follow the guidelines shown on [4. Building with Gradle][2].
+
+### 7. Create pull request:
+
 NOTE: You must sign the [Contributor Agreement][1] before your changes can be accepted!
 
 * Commit your changes locally: "git commit -a"
-* Push your changes to your Github repo: "git push"
+* Push your changes to your GitHub repo: "git push"
 * Browse to https://github.com/YOURGITHUBNAME/android/pulls and issue pull request
 * Click "Edit" and set "base:develop"
 * Again, click "Edit" and set "compare:develop"
 * Enter description and send pull request.
 
-### 4) Create another pull request:
+### 8. Create another pull request:
 
 To make sure your new pull request does not contain commits which are already contained in previous PRs, create a new branch which is a clone of upstream/develop.
 
@@ -85,8 +110,9 @@ To make sure your new pull request does not contain commits which are already co
 * git checkout -b my_new_develop_branch upstream/develop
 * If you want to rename that branch later: "git checkout -b my_new_develop_branch_with_new_name"
 * Push branch to server: "git push -u origin name_of_local_develop_branch"
-* Use Github to issue PR
+* Use GitHub to issue PR
 
 
 [0]: https://github.com/owncloud/android/blob/master/CONTRIBUTING.md
 [1]: http://owncloud.org/about/contributor-agreement/
+[2]: https://github.com/owncloud/android/blob/master/SETUP.md#4-building-with-gradle
diff --git a/actionbarsherlock b/actionbarsherlock
deleted file mode 160000 (submodule)
index 9598f2b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 9598f2bb2ceed4a834cd5586a903f270ca4c0ccc
diff --git a/build.gradle b/build.gradle
new file mode 100644 (file)
index 0000000..57ec86e
--- /dev/null
@@ -0,0 +1,78 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.0.0'
+    }
+}
+
+apply plugin: 'com.android.application'
+
+
+repositories {
+    mavenCentral()
+
+    flatDir {
+        dirs 'libs'
+    }
+}
+
+artifacts.add("default", file('libs/actionbarsherlock.aar'))
+
+dependencies {
+    compile name: 'touch-image-view'
+    compile 'com.android.support:support-v4:19.1.0'
+    compile project('libs/actionbarsherlock_lib')
+    compile project(':owncloud-android-library')
+    compile 'com.jakewharton:disklrucache:2.0.2'
+}
+
+android {
+    compileSdkVersion 19
+    buildToolsVersion "20.0.0"
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            resources.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+            assets.srcDirs = ['res']
+        }
+
+        // Move the tests to tests/java, tests/res, etc...
+        instrumentTest.setRoot('tests')
+
+        // Move the build types to build-types/<type>
+        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
+        // This moves them out of them default location under src/<type>/... which would
+        // conflict with src/ being used by the main source set.
+        // Adding new build types or product flavors should be accompanied
+        // by a similar customization.
+        debug.setRoot('build-types/debug')
+        release.setRoot('build-types/release')
+    }
+
+    android {
+        lintOptions {
+            abortOnError false
+        }
+    }
+
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
+    
+    productFlavors {
+    }
+
+    packagingOptions {
+        exclude 'META-INF/LICENSE.txt'
+    }
+}
+
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644 (file)
index 0000000..3d0dee6
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644 (file)
index 0000000..5a1c438
--- /dev/null
@@ -0,0 +1,6 @@
+#Sun Jan 18 17:01:43 CET 2015
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755 (executable)
index 0000000..91a7e26
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644 (file)
index 0000000..aec9973
--- /dev/null
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off\r
+@rem ##########################################################################\r
+@rem\r
+@rem  Gradle startup script for Windows\r
+@rem\r
+@rem ##########################################################################\r
+\r
+@rem Set local scope for the variables with windows NT shell\r
+if "%OS%"=="Windows_NT" setlocal\r
+\r
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r
+set DEFAULT_JVM_OPTS=\r
+\r
+set DIRNAME=%~dp0\r
+if "%DIRNAME%" == "" set DIRNAME=.\r
+set APP_BASE_NAME=%~n0\r
+set APP_HOME=%DIRNAME%\r
+\r
+@rem Find java.exe\r
+if defined JAVA_HOME goto findJavaFromJavaHome\r
+\r
+set JAVA_EXE=java.exe\r
+%JAVA_EXE% -version >NUL 2>&1\r
+if "%ERRORLEVEL%" == "0" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:findJavaFromJavaHome\r
+set JAVA_HOME=%JAVA_HOME:"=%\r
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe\r
+\r
+if exist "%JAVA_EXE%" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:init\r
+@rem Get command-line arguments, handling Windowz variants\r
+\r
+if not "%OS%" == "Windows_NT" goto win9xME_args\r
+if "%@eval[2+2]" == "4" goto 4NT_args\r
+\r
+:win9xME_args\r
+@rem Slurp the command line arguments.\r
+set CMD_LINE_ARGS=\r
+set _SKIP=2\r
+\r
+:win9xME_args_slurp\r
+if "x%~1" == "x" goto execute\r
+\r
+set CMD_LINE_ARGS=%*\r
+goto execute\r
+\r
+:4NT_args\r
+@rem Get arguments from the 4NT Shell from JP Software\r
+set CMD_LINE_ARGS=%$\r
+\r
+:execute\r
+@rem Setup the command line\r
+\r
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar\r
+\r
+@rem Execute Gradle\r
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r
+\r
+:end\r
+@rem End local scope for the variables with windows NT shell\r
+if "%ERRORLEVEL%"=="0" goto mainEnd\r
+\r
+:fail\r
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r
+rem the _cmd.exe /c_ return code!\r
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1\r
+exit /b 1\r
+\r
+:mainEnd\r
+if "%OS%"=="Windows_NT" endlocal\r
+\r
+:omega\r
diff --git a/libs/actionbarsherlock_lib/build.gradle b/libs/actionbarsherlock_lib/build.gradle
new file mode 100644 (file)
index 0000000..58172e2
--- /dev/null
@@ -0,0 +1,2 @@
+configurations.create("default")
+artifacts.add("default", file('library-4.1.0.aar'))
diff --git a/libs/actionbarsherlock_lib/library-4.1.0.aar b/libs/actionbarsherlock_lib/library-4.1.0.aar
new file mode 100644 (file)
index 0000000..f340e01
Binary files /dev/null and b/libs/actionbarsherlock_lib/library-4.1.0.aar differ
diff --git a/oc_jb_workaround/build.gradle b/oc_jb_workaround/build.gradle
new file mode 100644 (file)
index 0000000..882a090
--- /dev/null
@@ -0,0 +1,42 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:0.13.0'
+    }
+}
+apply plugin: 'com.android.library'
+
+dependencies {
+    compile fileTree(dir: 'libs', include: '*.jar')
+}
+
+android {
+    compileSdkVersion 19
+    buildToolsVersion "20.0.0"
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            resources.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+            assets.srcDirs = ['assets']
+        }
+
+        // Move the tests to tests/java, tests/res, etc...
+        instrumentTest.setRoot('tests')
+
+        // Move the build types to build-types/<type>
+        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
+        // This moves them out of them default location under src/<type>/... which would
+        // conflict with src/ being used by the main source set.
+        // Adding new build types or product flavors should be accompanied
+        // by a similar customization.
+        debug.setRoot('build-types/debug')
+        release.setRoot('build-types/release')
+    }
+}
diff --git a/oc_jb_workaround/gradle/wrapper/gradle-wrapper.jar b/oc_jb_workaround/gradle/wrapper/gradle-wrapper.jar
new file mode 100644 (file)
index 0000000..8c0fb64
Binary files /dev/null and b/oc_jb_workaround/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/oc_jb_workaround/gradle/wrapper/gradle-wrapper.properties b/oc_jb_workaround/gradle/wrapper/gradle-wrapper.properties
new file mode 100644 (file)
index 0000000..1e61d1f
--- /dev/null
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
diff --git a/oc_jb_workaround/gradlew b/oc_jb_workaround/gradlew
new file mode 100755 (executable)
index 0000000..91a7e26
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/oc_jb_workaround/gradlew.bat b/oc_jb_workaround/gradlew.bat
new file mode 100644 (file)
index 0000000..aec9973
--- /dev/null
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off\r
+@rem ##########################################################################\r
+@rem\r
+@rem  Gradle startup script for Windows\r
+@rem\r
+@rem ##########################################################################\r
+\r
+@rem Set local scope for the variables with windows NT shell\r
+if "%OS%"=="Windows_NT" setlocal\r
+\r
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r
+set DEFAULT_JVM_OPTS=\r
+\r
+set DIRNAME=%~dp0\r
+if "%DIRNAME%" == "" set DIRNAME=.\r
+set APP_BASE_NAME=%~n0\r
+set APP_HOME=%DIRNAME%\r
+\r
+@rem Find java.exe\r
+if defined JAVA_HOME goto findJavaFromJavaHome\r
+\r
+set JAVA_EXE=java.exe\r
+%JAVA_EXE% -version >NUL 2>&1\r
+if "%ERRORLEVEL%" == "0" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:findJavaFromJavaHome\r
+set JAVA_HOME=%JAVA_HOME:"=%\r
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe\r
+\r
+if exist "%JAVA_EXE%" goto init\r
+\r
+echo.\r
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r
+echo.\r
+echo Please set the JAVA_HOME variable in your environment to match the\r
+echo location of your Java installation.\r
+\r
+goto fail\r
+\r
+:init\r
+@rem Get command-line arguments, handling Windowz variants\r
+\r
+if not "%OS%" == "Windows_NT" goto win9xME_args\r
+if "%@eval[2+2]" == "4" goto 4NT_args\r
+\r
+:win9xME_args\r
+@rem Slurp the command line arguments.\r
+set CMD_LINE_ARGS=\r
+set _SKIP=2\r
+\r
+:win9xME_args_slurp\r
+if "x%~1" == "x" goto execute\r
+\r
+set CMD_LINE_ARGS=%*\r
+goto execute\r
+\r
+:4NT_args\r
+@rem Get arguments from the 4NT Shell from JP Software\r
+set CMD_LINE_ARGS=%$\r
+\r
+:execute\r
+@rem Setup the command line\r
+\r
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar\r
+\r
+@rem Execute Gradle\r
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r
+\r
+:end\r
+@rem End local scope for the variables with windows NT shell\r
+if "%ERRORLEVEL%"=="0" goto mainEnd\r
+\r
+:fail\r
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r
+rem the _cmd.exe /c_ return code!\r
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1\r
+exit /b 1\r
+\r
+:mainEnd\r
+if "%OS%"=="Windows_NT" endlocal\r
+\r
+:omega\r
index a3ee5ab..4ab1256 100644 (file)
@@ -11,4 +11,4 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-17
+target=android-19
index 0030d82..e87f5f2 160000 (submodule)
@@ -1 +1 @@
-Subproject commit 0030d82ee8156eb82224eaea810e4bf6010feef1
+Subproject commit e87f5f25ad91950d47ec9b6fa01401360cd7ec8d
diff --git a/pom.xml b/pom.xml
index 6a8cf6a..538df03 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xmlns="http://maven.apache.org/POM/4.0.0"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 
     <modelVersion>4.0.0</modelVersion>
     <groupId>com.owncloud.android</groupId>
@@ -13,7 +14,7 @@
         <owncloud.version>1.5.1-SNAPSHOT</owncloud.version>
         <java-version>1.6</java-version>
         <!-- Given by maven-android-sdk-deployer -->
-        <google.android-version>4.4.2_r3</google.android-version>
+        <google.android-version>4.4.2_r4</google.android-version>
         <!-- Usually the latest Android API -->
         <google.android-api>19</google.android-api>
         <actionbarsherlock-version>4.2.0</actionbarsherlock-version>
         <developerConnection>scm:git:git@github.com:owncloud/android.git</developerConnection>
         <url>https://github.com/owncloud/android</url>
     </scm>
-    
+
+
+    <!--<repositories>-->
+        <!--<repository>-->
+            <!--<id>android-support</id>-->
+            <!--<url>file://${env.ANDROID_HOME}/extras/android/m2repository</url>-->
+        <!--</repository>-->
+    <!--</repositories>-->
+
     <dependencies>
+
         <!-- Dirty trick, but it works. TouchImageView library is not available as Maven project. -->
         <dependency>
             <groupId>touch-image-view.jar</groupId>
             <version>19.1.0</version>
         </dependency>-->
         <!-- Instead we need to include the exact same version -->
+
+        <dependency>
+            <groupId>com.jakewharton</groupId>
+            <artifactId>disklrucache</artifactId>
+            <version>2.0.2</version>
+        </dependency>
+
         <dependency>
             <groupId>android-support-v4.jar</groupId>
             <artifactId>android-support-v4.jar</artifactId>
             <scope>system</scope>
             <systemPath>${basedir}/third_party/android-support-library/android-support-v4.jar</systemPath>
         </dependency>
-        
-        <dependency>
-            <groupId>android</groupId>
-            <artifactId>android</artifactId>
-            <version>${google.android-version}</version>
-            <scope>provided</scope>
-        </dependency>
+
 
         <dependency>
             <groupId>com.actionbarsherlock</groupId>
-            <artifactId>actionbarsherlock</artifactId>
+            <artifactId>library</artifactId>
             <version>${actionbarsherlock-version}</version>
-            <scope>provided</scope>
+            <type>apklib</type>
         </dependency>
 
         <dependency>
-            <groupId>com.actionbarsherlock</groupId>
-            <artifactId>actionbarsherlock</artifactId>
-            <version>${actionbarsherlock-version}</version>
-            <type>apklib</type>
+            <groupId>android</groupId>
+            <artifactId>android</artifactId>
+            <version>${google.android-version}</version>
+            <scope>provided</scope>
         </dependency>
 
+        <!--<dependency>-->
+            <!--<groupId>android.support</groupId>-->
+            <!--<artifactId>compatibility-v4</artifactId>-->
+            <!--<version>19.1.0</version>-->
+        <!--</dependency>-->
+
+
         <!-- MUST BE INSTALLED FIRST: cd owncloud-android-library; mvn install -->
         <dependency>
-         <groupId>com.owncloud.android</groupId>
-         <artifactId>owncloud-android-library</artifactId>
-         <version>${owncloud.version}</version>
-      </dependency>
+            <groupId>com.owncloud.android</groupId>
+            <artifactId>owncloud-android-library</artifactId>
+            <version>${owncloud.version}</version>
+        </dependency>
 
     </dependencies>
 
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.0</version>
+                <version>3.1</version>
                 <configuration>
                     <source>${java-version}</source>
                     <target>${java-version}</target>
             <plugin>
                 <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                 <artifactId>android-maven-plugin</artifactId>
-                <version>3.8.0</version>
+                <version>3.8.2</version>
                 <configuration>
                     <sdk>
                         <path>${env.ANDROID_HOME}</path>
index 35df55f..0c58f65 100644 (file)
Binary files a/res/drawable-hdpi/copy_link.png and b/res/drawable-hdpi/copy_link.png differ
index 1cb4d85..d95f724 100644 (file)
Binary files a/res/drawable-hdpi/ic_favorite.png and b/res/drawable-hdpi/ic_favorite.png differ
index 222172a..9ec18ce 100644 (file)
Binary files a/res/drawable-hdpi/shared_with_me.png and b/res/drawable-hdpi/shared_with_me.png differ
index a3c42a9..f4279f3 100644 (file)
Binary files a/res/drawable-hdpi/sharedlink.png and b/res/drawable-hdpi/sharedlink.png differ
index b3caf52..7384309 100644 (file)
Binary files a/res/drawable-ldpi/copy_link.png and b/res/drawable-ldpi/copy_link.png differ
index 4e2af28..6bac9b9 100644 (file)
Binary files a/res/drawable-mdpi/copy_link.png and b/res/drawable-mdpi/copy_link.png differ
index dead474..487b89d 100644 (file)
Binary files a/res/drawable-mdpi/ic_favorite.png and b/res/drawable-mdpi/ic_favorite.png differ
index 8300eac..0b17c76 100644 (file)
Binary files a/res/drawable-mdpi/shared_with_me.png and b/res/drawable-mdpi/shared_with_me.png differ
index 772838a..1d27294 100644 (file)
Binary files a/res/drawable-mdpi/sharedlink.png and b/res/drawable-mdpi/sharedlink.png differ
index c69eb05..45acfc4 100644 (file)
Binary files a/res/drawable-xhdpi/copy_link.png and b/res/drawable-xhdpi/copy_link.png differ
diff --git a/res/drawable-xhdpi/file.png b/res/drawable-xhdpi/file.png
new file mode 100644 (file)
index 0000000..d249e5f
Binary files /dev/null and b/res/drawable-xhdpi/file.png differ
diff --git a/res/drawable-xhdpi/file_doc.png b/res/drawable-xhdpi/file_doc.png
new file mode 100644 (file)
index 0000000..a8c10c8
Binary files /dev/null and b/res/drawable-xhdpi/file_doc.png differ
diff --git a/res/drawable-xhdpi/file_image.png b/res/drawable-xhdpi/file_image.png
new file mode 100644 (file)
index 0000000..84b2803
Binary files /dev/null and b/res/drawable-xhdpi/file_image.png differ
diff --git a/res/drawable-xhdpi/file_movie.png b/res/drawable-xhdpi/file_movie.png
new file mode 100644 (file)
index 0000000..bd84c72
Binary files /dev/null and b/res/drawable-xhdpi/file_movie.png differ
diff --git a/res/drawable-xhdpi/file_pdf.png b/res/drawable-xhdpi/file_pdf.png
new file mode 100644 (file)
index 0000000..e272562
Binary files /dev/null and b/res/drawable-xhdpi/file_pdf.png differ
diff --git a/res/drawable-xhdpi/file_ppt.png b/res/drawable-xhdpi/file_ppt.png
new file mode 100644 (file)
index 0000000..c898895
Binary files /dev/null and b/res/drawable-xhdpi/file_ppt.png differ
diff --git a/res/drawable-xhdpi/file_sound.png b/res/drawable-xhdpi/file_sound.png
new file mode 100644 (file)
index 0000000..82fbc7b
Binary files /dev/null and b/res/drawable-xhdpi/file_sound.png differ
diff --git a/res/drawable-xhdpi/file_xls.png b/res/drawable-xhdpi/file_xls.png
new file mode 100644 (file)
index 0000000..2e98d6c
Binary files /dev/null and b/res/drawable-xhdpi/file_xls.png differ
diff --git a/res/drawable-xhdpi/file_zip.png b/res/drawable-xhdpi/file_zip.png
new file mode 100644 (file)
index 0000000..28130ec
Binary files /dev/null and b/res/drawable-xhdpi/file_zip.png differ
diff --git a/res/drawable-xhdpi/folder_public.png b/res/drawable-xhdpi/folder_public.png
new file mode 100644 (file)
index 0000000..7680712
Binary files /dev/null and b/res/drawable-xhdpi/folder_public.png differ
index c187f0c..8a777a4 100644 (file)
Binary files a/res/drawable-xhdpi/ic_favorite.png and b/res/drawable-xhdpi/ic_favorite.png differ
diff --git a/res/drawable-xhdpi/ic_menu_archive.png b/res/drawable-xhdpi/ic_menu_archive.png
new file mode 100644 (file)
index 0000000..3ee6028
Binary files /dev/null and b/res/drawable-xhdpi/ic_menu_archive.png differ
index 3879663..ef7779c 100644 (file)
Binary files a/res/drawable-xhdpi/shared_with_me.png and b/res/drawable-xhdpi/shared_with_me.png differ
diff --git a/res/drawable-xhdpi/shared_with_me_folder.png b/res/drawable-xhdpi/shared_with_me_folder.png
new file mode 100644 (file)
index 0000000..060728c
Binary files /dev/null and b/res/drawable-xhdpi/shared_with_me_folder.png differ
index 9ef8f3e..11f8afa 100644 (file)
Binary files a/res/drawable-xhdpi/sharedlink.png and b/res/drawable-xhdpi/sharedlink.png differ
index 7c49554..e735542 100644 (file)
Binary files a/res/drawable/downloading_file_indicator.png and b/res/drawable/downloading_file_indicator.png differ
index 88b1ab3..68625ef 100644 (file)
@@ -32,7 +32,7 @@
         android:orientation="horizontal" >\r
         \r
                <ImageView\r
-                       android:id="@+id/imageView1"\r
+                       android:id="@+id/thumbnail"\r
                        android:layout_width="0dp"\r
                        android:layout_height="wrap_content"\r
                        android:layout_weight="1"\r
index e3321f9..b2973a0 100644 (file)
@@ -35,7 +35,7 @@
         android:padding="8dp" >\r
 \r
         <ImageView\r
-            android:id="@+id/imageView1"\r
+            android:id="@+id/thumbnail"\r
             android:layout_width="match_parent"\r
             android:layout_height="wrap_content"\r
             android:layout_marginBottom="10dp"\r
diff --git a/res/layout/grid_image.xml b/res/layout/grid_image.xml
new file mode 100644 (file)
index 0000000..8bccde6
--- /dev/null
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!--\r
+  ownCloud Android client application\r
+  Copyright (C) 2014 ownCloud Inc.\r
+\r
+  This program is free software: you can redistribute it and/or modify\r
+  it under the terms of the GNU General Public License version 2,\r
+  as published by the Free Software Foundation.\r
+\r
+  This program is distributed in the hope that it will be useful,\r
+  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+  GNU General Public License for more details.\r
+\r
+  You should have received a copy of the GNU General Public License\r
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
+  \r
+-->\r
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/ListItemLayout"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:layout_gravity="center_horizontal"\r
+    android:background="@drawable/list_selector"\r
+    android:gravity="center_horizontal"\r
+    android:orientation="vertical" >\r
+\r
+    <FrameLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content" >\r
+\r
+        <com.owncloud.android.ui.SquareImageView\r
+            android:id="@+id/thumbnail"\r
+            android:layout_width="match_parent"\r
+            android:layout_height="match_parent"\r
+            android:paddingLeft="10dp"\r
+            android:paddingRight="10dp"\r
+            android:scaleType="centerCrop"\r
+            android:src="@drawable/ic_menu_archive"/>\r
+\r
+        <LinearLayout\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_gravity="top|right"\r
+            android:orientation="vertical"\r
+            android:layout_margin="4dp">\r
+\r
+            <ImageView\r
+                android:id="@+id/sharedIcon"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="center"\r
+                android:layout_marginBottom="4dp"\r
+                android:src="@drawable/sharedlink" />\r
+\r
+            <ImageView\r
+                android:id="@+id/sharedWithMeIcon"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="center"\r
+                android:layout_marginTop="4dp"\r
+                android:src="@drawable/shared_with_me"\r
+                android:visibility="invisible" />\r
+        </LinearLayout>\r
+\r
+        <ImageView\r
+            android:id="@+id/localFileIndicator"\r
+            android:layout_width="@dimen/file_icon_size"\r
+            android:layout_height="@dimen/file_icon_size"\r
+            android:layout_gravity="bottom|right"\r
+            android:layout_marginTop="4dp"\r
+            android:layout_marginBottom="4dp"\r
+            android:layout_marginRight="4dp"\r
+            android:src="@drawable/local_file_indicator" />\r
+\r
+        <ImageView\r
+            android:id="@+id/favoriteIcon"\r
+            android:layout_width="15dp"\r
+            android:layout_height="15dp"\r
+            android:layout_gravity="bottom|right"\r
+            android:layout_marginBottom="4dp"\r
+            android:layout_marginRight="4dp"\r
+            android:src="@drawable/ic_favorite" />\r
+    </FrameLayout>\r
+\r
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/grid_item.xml b/res/layout/grid_item.xml
new file mode 100644 (file)
index 0000000..b494d48
--- /dev/null
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!--\r
+  ownCloud Android client application\r
+  Copyright (C) 2014 ownCloud Inc.\r
+\r
+  This program is free software: you can redistribute it and/or modify\r
+  it under the terms of the GNU General Public License version 2,\r
+  as published by the Free Software Foundation.\r
+\r
+  This program is distributed in the hope that it will be useful,\r
+  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+  GNU General Public License for more details.\r
+\r
+  You should have received a copy of the GNU General Public License\r
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
+  \r
+-->\r
+<com.owncloud.android.ui.SquareLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
+    android:id="@+id/ListItemLayout"\r
+    android:layout_width="match_parent"\r
+    android:layout_height="match_parent"\r
+    android:layout_gravity="center_horizontal"\r
+    android:background="@drawable/list_selector"\r
+    android:gravity="center"\r
+    android:orientation="vertical" >\r
+\r
+    <FrameLayout\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content"\r
+        android:layout_gravity="center_horizontal" >\r
+\r
+        <ImageView\r
+            android:id="@+id/thumbnail"\r
+            android:layout_width="72dp"\r
+            android:layout_height="72dp"\r
+            android:layout_gravity="center_horizontal"\r
+            android:layout_marginLeft="10dp"\r
+            android:layout_marginRight="10dp"\r
+            android:src="@drawable/ic_menu_archive" />\r
+\r
+        <LinearLayout\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_gravity="top|right"\r
+            android:orientation="vertical"\r
+            android:layout_margin="2dp">\r
+\r
+            <ImageView\r
+                android:id="@+id/sharedIcon"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="center"\r
+                android:layout_marginBottom="2dp"\r
+                android:src="@drawable/sharedlink" />\r
+\r
+            <ImageView\r
+                android:id="@+id/sharedWithMeIcon"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="center"\r
+                android:layout_marginTop="2dp"\r
+                android:src="@drawable/shared_with_me"\r
+                android:visibility="invisible" />\r
+        </LinearLayout>\r
+\r
+        <ImageView\r
+            android:id="@+id/localFileIndicator"\r
+            android:layout_width="@dimen/file_icon_size"\r
+            android:layout_height="@dimen/file_icon_size"\r
+            android:layout_gravity="bottom|right"\r
+            android:layout_marginTop="2dp"\r
+            android:layout_marginRight="2dp"\r
+            android:src="@drawable/local_file_indicator" />\r
+\r
+        <ImageView\r
+            android:id="@+id/favoriteIcon"\r
+            android:layout_width="15dp"\r
+            android:layout_height="15dp"\r
+            android:layout_gravity="bottom|right"\r
+            android:layout_marginBottom="2dp"\r
+            android:layout_marginRight="2dp"\r
+            android:src="@drawable/ic_favorite" />\r
+\r
+\r
+\r
+    </FrameLayout>\r
+\r
+    <TextView\r
+        android:id="@+id/Filename"\r
+        android:layout_width="match_parent"\r
+        android:layout_height="wrap_content"\r
+        android:layout_marginLeft="4dp"\r
+        android:layout_marginRight="4dp"\r
+        android:ellipsize="middle"\r
+        android:gravity="center_horizontal"\r
+        android:singleLine="true"\r
+        android:text="TextView"\r
+        android:textColor="@color/textColor"\r
+        android:textSize="16dip" />\r
+\r
+</com.owncloud.android.ui.SquareLinearLayout>
\ No newline at end of file
diff --git a/res/layout/list_footer.xml b/res/layout/list_footer.xml
new file mode 100644 (file)
index 0000000..a49874d
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="56dp"
+    android:layout_gravity="center_horizontal"
+    android:gravity="center_horizontal"
+    android:orientation="vertical"
+    android:showDividers="none" >
+
+    <TextView
+        android:id="@+id/footerText"
+        android:layout_width="match_parent"
+        android:layout_height="56dp"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:textColor="@color/setup_text_hint" />
+
+</LinearLayout> 
\ No newline at end of file
index 4236d07..f14714e 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- 
+<!--
   ownCloud Android client application
 
   Copyright (C) 2012  Bartek Przybylski
-  Copyright (C) 2012-2013 ownCloud Inc.
+  Copyright (C) 2012-2015 ownCloud Inc.
 
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2,
 
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- -->
+-->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-       android:layout_width="0dp"
-       android:layout_height="match_parent"
-       android:layout_weight="1" >
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_weight="1" >
 
     <android.support.v4.widget.SwipeRefreshLayout
         android:id="@+id/swipe_refresh_files"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" >
-
-        <com.owncloud.android.ui.ExtendedListView
+        android:layout_height="match_parent" 
+        android:layout_weight="1"
+        android:footerDividersEnabled="false" > 
+        
+        <third_parties.in.srain.cube.GridViewWithHeaderAndFooter
             android:id="@+id/list_root"
             android:layout_width="match_parent"
-            android:layout_height="match_parent" />
-        
-    </android.support.v4.widget.SwipeRefreshLayout>
+            android:layout_height="match_parent"
+            android:columnWidth="100dp"
+            android:gravity="center"
+            android:horizontalSpacing="2dp"
+            android:stretchMode="columnWidth"
+            android:verticalSpacing="2dp"
+            android:visibility="visible" />
 
+    </android.support.v4.widget.SwipeRefreshLayout>
+       
     <android.support.v4.widget.SwipeRefreshLayout
         android:id="@+id/swipe_refresh_files_emptyView"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:visibility="gone" >
-
-        <ScrollView
-            android:layout_width="match_parent"
-            android:layout_height="match_parent" >
-
-                   <TextView
-                               android:id="@+id/empty_list_view"
-                               android:layout_width="match_parent"
-                               android:layout_height="wrap_content"
-                               android:gravity="center_vertical|center_horizontal"
-                               android:text="@string/empty"
-                                       android:layout_gravity="center"
-                               android:visibility="visible" />
-
-        </ScrollView>
+            <ScrollView
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" >
+                <TextView
+                    android:id="@+id/empty_list_view"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center"
+                    android:gravity="center_vertical|center_horizontal"
+                    android:text="@string/empty"
+                    android:visibility="visible" />
+            </ScrollView>
     </android.support.v4.widget.SwipeRefreshLayout>
-</FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
index c6c7b92..0ea3bce 100644 (file)
  -->\r
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"\r
     android:id="@+id/ListItemLayout"\r
-    android:layout_width="fill_parent"\r
+    android:layout_width="match_parent"\r
     android:background="@drawable/list_selector"\r
-    android:orientation="horizontal"\r
+    android:orientation="vertical"\r
     android:layout_height="56dp">\r
 \r
-    <FrameLayout\r
-        android:layout_width="56dp"\r
-        android:layout_height="56dp"\r
-        android:focusable="false"\r
-        android:focusableInTouchMode="false">\r
-\r
-        <ImageView\r
-            android:id="@+id/imageView2"\r
-            android:layout_width="@dimen/file_icon_size"\r
-            android:layout_height="@dimen/file_icon_size"\r
-            android:layout_gravity="center_vertical"\r
-            android:layout_marginLeft="22dp"\r
-            android:src="@drawable/local_file_indicator" />\r
-\r
-        <ImageView\r
-            android:id="@+id/imageView1"\r
-            android:layout_width="@dimen/file_icon_size"\r
-            android:layout_height="@dimen/file_icon_size"\r
-            android:layout_gravity="center_vertical"\r
-            android:layout_marginLeft="9dp"\r
-            android:src="@drawable/ic_menu_archive" />\r
-\r
-        <ImageView\r
-            android:id="@+id/imageView3"\r
-            android:layout_width="wrap_content"\r
-            android:layout_height="wrap_content"\r
-            android:layout_gravity="bottom|right"\r
-            android:layout_marginBottom="10dp"\r
-            android:layout_marginRight="2dp"\r
-            android:src="@drawable/ic_favorite" />\r
-    </FrameLayout>\r
-\r
     <LinearLayout\r
-        android:layout_width="0dp"\r
+        android:layout_width="match_parent"\r
         android:layout_height="match_parent"\r
-        android:layout_weight="1"\r
-        android:gravity="center_vertical"\r
-        android:orientation="vertical" >\r
-\r
-        <TextView\r
-            android:id="@+id/Filename"\r
-            android:layout_width="wrap_content"\r
-            android:layout_height="wrap_content"\r
-            android:layout_gravity="center_vertical"\r
-            android:layout_marginLeft="4dp"\r
-            android:layout_marginRight="4dp"\r
-            android:ellipsize="middle"\r
-            android:singleLine="true"\r
-            android:text="TextView"\r
-            android:textColor="#303030"\r
-            android:textSize="16dip" />\r
+        android:orientation="horizontal">\r
+\r
+        <FrameLayout\r
+            android:layout_width="56dp"\r
+            android:layout_height="56dp"\r
+            android:focusable="false"\r
+            android:focusableInTouchMode="false">\r
+\r
+            <ImageView\r
+                android:id="@+id/localFileIndicator"\r
+                android:layout_width="@dimen/file_icon_size"\r
+                android:layout_height="@dimen/file_icon_size"\r
+                android:layout_gravity="center_vertical"\r
+                android:layout_marginLeft="22dp"\r
+                android:src="@drawable/local_file_indicator" />\r
+\r
+            <ImageView\r
+                android:id="@+id/thumbnail"\r
+                android:layout_width="@dimen/file_icon_size"\r
+                android:layout_height="@dimen/file_icon_size"\r
+                android:layout_gravity="center_vertical"\r
+                android:layout_marginLeft="9dp"\r
+                android:src="@drawable/ic_menu_archive" />\r
+\r
+            <ImageView\r
+                android:id="@+id/favoriteIcon"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="bottom|right"\r
+                android:layout_marginBottom="10dp"\r
+                android:layout_marginRight="2dp"\r
+                android:src="@drawable/ic_favorite" />\r
+        </FrameLayout>\r
 \r
         <LinearLayout\r
-            android:layout_width="match_parent"\r
-            android:layout_height="wrap_content"\r
-            android:layout_marginLeft="4dp"\r
-            android:layout_marginRight="4dp"\r
-            android:weightSum="1">\r
+            android:layout_width="0dp"\r
+            android:layout_height="match_parent"\r
+            android:layout_weight="1"\r
+            android:gravity="center_vertical"\r
+            android:orientation="vertical" >\r
 \r
             <TextView\r
-                android:id="@+id/last_mod"\r
+                android:id="@+id/Filename"\r
                 android:layout_width="wrap_content"\r
                 android:layout_height="wrap_content"\r
+                android:layout_gravity="center_vertical"\r
+                android:layout_marginLeft="4dp"\r
+                android:layout_marginRight="4dp"\r
+                android:ellipsize="middle"\r
+                android:singleLine="true"\r
                 android:text="TextView"\r
-                android:layout_weight=".5"\r
-                android:textColor="@color/list_item_lastmod_and_filesize_text"\r
-                android:textSize="12dip"/>\r
+                android:textColor="#303030"\r
+                android:textSize="16dip" />\r
 \r
-            <TextView\r
-                android:id="@+id/file_size"\r
-                android:layout_width="wrap_content"\r
+            <LinearLayout\r
+                android:layout_width="match_parent"\r
                 android:layout_height="wrap_content"\r
-                android:gravity="right"\r
-                android:text="TextView"\r
-                android:textColor="@color/list_item_lastmod_and_filesize_text"\r
-                android:layout_weight=".5"\r
-                android:textSize="12dip"/>\r
+                android:layout_marginLeft="4dp"\r
+                android:layout_marginRight="4dp"\r
+                android:weightSum="1">\r
+\r
+                <TextView\r
+                    android:id="@+id/last_mod"\r
+                    android:layout_width="wrap_content"\r
+                    android:layout_height="wrap_content"\r
+                    android:text="TextView"\r
+                    android:layout_weight=".5"\r
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"\r
+                    android:textSize="12dip"/>\r
+\r
+                <TextView\r
+                    android:id="@+id/file_size"\r
+                    android:layout_width="wrap_content"\r
+                    android:layout_height="wrap_content"\r
+                    android:gravity="right"\r
+                    android:text="TextView"\r
+                    android:textColor="@color/list_item_lastmod_and_filesize_text"\r
+                    android:layout_weight=".5"\r
+                    android:textSize="12dip"/>\r
+\r
+            </LinearLayout>\r
 \r
         </LinearLayout>\r
 \r
-    </LinearLayout>\r
+        <LinearLayout\r
+            android:layout_width="25dp"\r
+            android:layout_height="match_parent"\r
+            android:gravity="center_vertical"\r
+            android:orientation="vertical">\r
 \r
-    <LinearLayout\r
-        android:layout_width="25dp"\r
-        android:layout_height="match_parent"\r
-        android:gravity="center_vertical"\r
-        android:orientation="vertical">\r
-\r
-    <ImageView\r
-        android:id="@+id/sharedIcon"\r
-        android:layout_width="wrap_content"\r
-        android:layout_height="wrap_content"\r
-        android:layout_gravity="center"\r
-        android:layout_marginLeft="4dp"\r
-        android:layout_marginBottom="4dp"\r
-        android:layout_marginRight="4dp"\r
-        android:src="@drawable/sharedlink" />\r
-\r
-    <ImageView\r
-        android:id="@+id/sharedWithMeIcon"\r
-        android:layout_width="wrap_content"\r
-        android:layout_height="wrap_content"\r
-        android:layout_gravity="center"\r
-        android:layout_marginLeft="4dp"\r
-        android:layout_marginRight="4dp"\r
-        android:layout_marginTop="4dp"\r
-        android:src="@drawable/shared_with_me" />\r
+            <ImageView\r
+                android:id="@+id/sharedIcon"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="center"\r
+                android:layout_marginLeft="4dp"\r
+                android:layout_marginBottom="4dp"\r
+                android:layout_marginRight="4dp"\r
+                android:src="@drawable/sharedlink" />\r
+\r
+            <ImageView\r
+                android:id="@+id/sharedWithMeIcon"\r
+                android:layout_width="wrap_content"\r
+                android:layout_height="wrap_content"\r
+                android:layout_gravity="center"\r
+                android:layout_marginLeft="4dp"\r
+                android:layout_marginRight="4dp"\r
+                android:layout_marginTop="4dp"\r
+                android:src="@drawable/shared_with_me"\r
+                android:visibility="invisible" />\r
 \r
+        </LinearLayout>\r
+\r
+        <ImageView\r
+            android:id="@+id/custom_checkbox"\r
+            android:layout_width="wrap_content"\r
+            android:layout_height="wrap_content"\r
+            android:layout_gravity="center_vertical"\r
+            android:layout_marginLeft="4dp"\r
+            android:layout_marginRight="4dp"\r
+            android:gravity=""\r
+            android:src="@android:drawable/checkbox_off_background" />\r
     </LinearLayout>\r
 \r
-    <ImageView\r
-        android:id="@+id/custom_checkbox"\r
-        android:layout_width="wrap_content"\r
-        android:layout_height="wrap_content"\r
-        android:layout_gravity="center_vertical"\r
-        android:layout_marginLeft="4dp"\r
-        android:layout_marginRight="4dp"\r
-        android:gravity=""\r
-        android:src="@android:drawable/checkbox_off_background" />\r
+    <View\r
+        android:layout_width="match_parent"\r
+        android:layout_height="1dp"\r
+        android:background="@color/list_divider_background"></View>\r
 \r
 </LinearLayout>\r
diff --git a/res/layout/preference_widget_radiobutton.xml b/res/layout/preference_widget_radiobutton.xml
new file mode 100644 (file)
index 0000000..85f8f60
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2006 The Android Open Source Project
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
+inside android.R.layout.preference. -->
+<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
+android:id="@+android:id/checkbox"
+android:layout_width="wrap_content"
+android:layout_height="wrap_content"
+android:layout_gravity="center"
+android:focusable="false"
+android:clickable="false" />
\ No newline at end of file
index 1cb9361..08f9cee 100644 (file)
@@ -30,7 +30,7 @@
         android:layout_gravity="center_vertical|center"
         android:layout_margin="4dp"
         android:src="@drawable/ic_menu_archive" 
-        android:id="@+id/imageView1" />
+        android:id="@+id/thumbnail" />
     
     <TextView 
         android:text="TextView" 
index 4b024b7..634d267 100644 (file)
@@ -7,8 +7,11 @@
   <string name="auth_username">Gebruikersnaam</string>
   <string name="auth_password">Wagwoord</string>
   <string name="file_list_seconds_ago">sekondes gelede</string>
+  <string name="common_yes">Ja</string>
+  <string name="common_no">Nee</string>
   <string name="common_ok">OK</string>
   <string name="common_cancel">Kanseleer</string>
+  <string name="common_error">Fout</string>
   <string name="empty"></string>
-  <string name="move_choose_button_text">Kies</string>
+  <string name="folder_picker_choose_button_text">Kies</string>
 </resources>
index 276af9b..d8d4eb0 100644 (file)
   <string name="file_list_seconds_ago">منذ ثواني</string>
   <string name="file_list_empty">لا يوجد شيء هنا. إرفع بعض الملفات!</string>
   <string name="file_list_loading">جاري التحميل ...</string>
+  <string name="file_list_folder">مجلد</string>
+  <string name="file_list_folders">مجلدات</string>
+  <string name="file_list_file">ملف</string>
+  <string name="file_list_files">ملفات</string>
   <string name="filedetails_select_file">اضغظ على الملف ليتم عرض خيارات أكثر</string>
   <string name="filedetails_size">الحجم :</string>
   <string name="filedetails_type">النوع :</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">حسابات</string>
   <string name="saml_authentication_wrong_pass">كلمة مرور خاطئة</string>
-  <string name="move_choose_button_text">اختيار</string>
+  <string name="folder_picker_choose_button_text">اختيار</string>
   <string name="prefs_category_security">الأمان</string>
+  <string name="shared_subject_header">مُشارك</string>
 </resources>
index d3a9060..3f91b6d 100644 (file)
@@ -220,4 +220,5 @@ Aşağıda göstərilən %5$s-də olan daxili və xarici fayl(lar) link edilmiş
   <string name="prefs_category_accounts">Hesablar</string>
   <string name="prefs_add_account">Hesab əlavə et</string>
   <string name="saml_authentication_wrong_pass">Yalnış şifrə</string>
+  <string name="shared_subject_header">yayımlanmış</string>
 </resources>
index c0dd55b..0fa7ba3 100644 (file)
@@ -9,5 +9,5 @@
   <string name="common_ok">Добра</string>
   <string name="common_error">Памылка</string>
   <string name="empty"></string>
-  <string name="move_choose_button_text">Выбар</string>
+  <string name="folder_picker_choose_button_text">Выбар</string>
 </resources>
index ff47c31..1fd8462 100644 (file)
   <string name="file_list_empty">Тук няма нищо. Качете нещо!</string>
   <string name="file_list_loading">Зареждане...</string>
   <string name="local_file_list_empty">Няма файлове в тази папка.</string>
+  <string name="file_list_folder">папка</string>
+  <string name="file_list_folders">папки</string>
+  <string name="file_list_file">файл</string>
+  <string name="file_list_files">файл</string>
   <string name="filedetails_select_file">Натисни върху файл, за да видиш допълнителна информация.</string>
   <string name="filedetails_size">Размер:</string>
   <string name="filedetails_type">Тип:</string>
   <string name="auth_redirect_non_secure_connection_title">Сигурна връзка е пренасочена по несигурен път.</string>
   <string name="actionbar_logger">Доклади</string>
   <string name="log_send_history_button">Изпрати История</string>
-  <string name="log_mail_subject">ownCloud Android доклади</string>
-  <string name="log_progress_dialog_text">Зареждане на информация...</string>
   <string name="saml_authentication_required_text">Нужна е идентификация</string>
   <string name="saml_authentication_wrong_pass">Грешна парола</string>
   <string name="actionbar_move">Премести</string>
   <string name="file_list_empty_moving">Тук няма нищо. Можеш да добавиш папка!</string>
-  <string name="move_choose_button_text">Избери</string>
+  <string name="folder_picker_choose_button_text">Избери</string>
   <string name="move_file_not_found">Неуспешно преместване. Моля, провери дали файла съществува.</string>
   <string name="move_file_invalid_into_descendent">Не е възможно да преместиш папка в нейна под папка.</string>
   <string name="move_file_invalid_overwrite">Файлът вече съществува в отдалечената папка.</string>
   <string name="forbidden_permissions_move">за да преместиш този файл</string>
   <string name="prefs_category_instant_uploading">Незабавно качване</string>
   <string name="prefs_category_security">Сигурност</string>
+  <string name="shared_subject_header">споделен</string>
 </resources>
index f0f2300..a2861ba 100644 (file)
   <string name="saml_authentication_wrong_pass">ভুল কুটশব্দ</string>
   <string name="actionbar_move">সরাও</string>
   <string name="file_list_empty_moving">এখানে কিছু নেই। একটি ফোল্ডার যোগ করতে পারেন!</string>
-  <string name="move_choose_button_text">বেছে নিন</string>
+  <string name="folder_picker_choose_button_text">বেছে নিন</string>
   <string name="move_file_not_found">সরাতে ব্যার্থ হলো। ফাইলটি রয়েছে কিনা দেখুন।</string>
   <string name="prefs_category_security">নিরাপত্তা</string>
+  <string name="shared_subject_header">ভাগাভাগিকৃত</string>
 </resources>
index 4c2f869..9ab386c 100644 (file)
@@ -1,7 +1,34 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
+  <string name="actionbar_upload">Učitaj</string>
+  <string name="actionbar_upload_files">Datoteke</string>
   <string name="actionbar_mkdir">Nova fascikla</string>
+  <string name="actionbar_settings">Postavke</string>
+  <string name="actionbar_send_file">Pošalji</string>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
+  <string name="prefs_category_more">Više</string>
+  <string name="prefs_help">Pomoć</string>
+  <string name="auth_username">Korisničko ime</string>
+  <string name="auth_password">Lozinka</string>
+  <string name="sync_string_files">Datoteke</string>
+  <string name="uploader_btn_upload_text">Učitaj</string>
+  <string name="filedetails_download">Preuzmite</string>
+  <string name="action_share_file">Podijelite vezu</string>
+  <string name="common_yes">Da</string>
+  <string name="common_no">Ne</string>
+  <string name="common_ok">Ok</string>
+  <string name="common_cancel_upload">Prekini učitavanje</string>
+  <string name="common_cancel">Odustani</string>
+  <string name="common_error">Greška</string>
+  <string name="common_error_unknown">Nepoznata greška</string>
+  <string name="change_password">Promijeni lozinku</string>
+  <string name="create_account">Kreiraj račun</string>
+  <string name="common_rename">Preimenuj</string>
+  <string name="activity_chooser_send_file_title">Pošalji</string>
   <string name="empty"></string>
+  <string name="saml_authentication_required_text">Potrebna autentifikacija</string>
+  <string name="saml_authentication_wrong_pass">Pogrešna lozinka</string>
+  <string name="folder_picker_choose_button_text">Izaberite</string>
+  <string name="prefs_category_security">Sigurnost</string>
 </resources>
index 1908508..9dcb1c6 100644 (file)
   <string name="actionbar_settings">Configuració</string>
   <string name="actionbar_see_details">Detalls</string>
   <string name="actionbar_send_file">Envia</string>
+  <string name="actionbar_sort">Ordena</string>
+  <string name="actionbar_sort_title">Ordena per</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Z</item>
+    <item>Més nou - Més antic</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">General</string>
   <string name="file_list_empty">Res per aquí. Pugeu alguna cosa!</string>
   <string name="file_list_loading">Carregant...</string>
   <string name="local_file_list_empty">No hi ha arxius a aquesta carpeta</string>
+  <string name="file_list_folder">carpeta</string>
+  <string name="file_list_folders">carpetes</string>
+  <string name="file_list_file">fitxer</string>
+  <string name="file_list_files">fitxers</string>
   <string name="filedetails_select_file">Feu clic en un fitxer per mostrar informació addicional.</string>
   <string name="filedetails_size">Mida:</string>
   <string name="filedetails_type">Tipus:</string>
   <string name="prefs_add_account">Afegeix compte</string>
   <string name="saml_authentication_required_text">Es requereix autenticació</string>
   <string name="saml_authentication_wrong_pass">Contrasenya incorrecta</string>
-  <string name="move_choose_button_text">Escull</string>
+  <string name="folder_picker_choose_button_text">Escull</string>
   <string name="prefs_category_security">Seguretat</string>
+  <string name="shared_subject_header">compartit</string>
 </resources>
index 08f37b6..cca3f49 100644 (file)
   <string name="file_list_empty">Žádný obsah. Nahrajte něco!</string>
   <string name="file_list_loading">Načítám...</string>
   <string name="local_file_list_empty">V tomto adresáři nejsou žádné soubory.</string>
+  <string name="file_list_folder">adresář</string>
+  <string name="file_list_folders">adresáře</string>
+  <string name="file_list_file">soubor</string>
+  <string name="file_list_files">soubory</string>
   <string name="filedetails_select_file">Více informací získáte klepnutím na soubor.</string>
   <string name="filedetails_size">Velikost:</string>
   <string name="filedetails_type">Typ:</string>
@@ -277,13 +281,14 @@ správce systému.</string>
   <string name="auth_redirect_non_secure_connection_title">Bezpečné spojení je přesměrováno na nezabezpečenou trasu.</string>
   <string name="actionbar_logger">Logy</string>
   <string name="log_send_history_button">Odeslat historii</string>
-  <string name="log_mail_subject">Logy aplikace ownCloud pro Android</string>
-  <string name="log_progress_dialog_text">Načítám data...</string>
+  <string name="log_send_no_mail_app">Nebyla nalezena žádná aplikace pro odesílání logů. Nainstalujte poštovní aplikaci!</string>
+  <string name="log_send_mail_subject">%1$s logy aplikace pro Android</string>
+  <string name="log_progress_dialog_text">Načítání dat…</string>
   <string name="saml_authentication_required_text">Vyžadováno přihlášení</string>
   <string name="saml_authentication_wrong_pass">Nesprávné heslo</string>
   <string name="actionbar_move">Přesunout</string>
   <string name="file_list_empty_moving">Zde nic není. Můžete přidat adresář!</string>
-  <string name="move_choose_button_text">Vybrat</string>
+  <string name="folder_picker_choose_button_text">Vybrat</string>
   <string name="move_file_not_found">Nelze přesunout. Zkontrolujte prosím že soubor existuje</string>
   <string name="move_file_invalid_into_descendent">Není možné adresář přesunout do vlastního podadresáře</string>
   <string name="move_file_invalid_overwrite">Soubor již v cílovém adresáři existuje</string>
@@ -291,4 +296,9 @@ správce systému.</string>
   <string name="forbidden_permissions_move">pro přesun tohoto souboru</string>
   <string name="prefs_category_instant_uploading">Okamžitá odesílání</string>
   <string name="prefs_category_security">Zabezpečení</string>
+  <string name="prefs_instant_video_upload_path_title">Cesta pro nahrávání videí</string>
+  <string name="download_folder_failed_content">Stažení adresáře %1$s nemohlo být dokončeno</string>
+  <string name="shared_subject_header">sdílené</string>
+  <string name="with_you_subject_header">s vámi</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 97ef067..eccb3fd 100644 (file)
   <string name="activity_chooser_send_file_title">Anfon</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Cyfrifon</string>
-  <string name="move_choose_button_text">Dewisiwch</string>
+  <string name="folder_picker_choose_button_text">Dewisiwch</string>
 </resources>
index df31bdc..f4d5761 100644 (file)
   <string name="file_list_empty">Her er tomt. Upload noget!</string>
   <string name="file_list_loading">Indlæser...</string>
   <string name="local_file_list_empty">Der er ingen filer i denne mappe.</string>
+  <string name="file_list_folder">mappe</string>
+  <string name="file_list_folders">mapper</string>
+  <string name="file_list_file">fil</string>
+  <string name="file_list_files">filer</string>
   <string name="filedetails_select_file">Tryk på en fil for at vise yderligere information.</string>
   <string name="filedetails_size">Størelse:</string>
   <string name="filedetails_type">Type:</string>
   <string name="auth_redirect_non_secure_connection_title">Sikker forbindelse videredirigeres til en usikker rute.</string>
   <string name="actionbar_logger">Logge</string>
   <string name="log_send_history_button">Send historik</string>
-  <string name="log_mail_subject">App-logregistreringer for ownCloud Android</string>
-  <string name="log_progress_dialog_text">Indlæser data...</string>
+  <string name="log_send_no_mail_app">Der blev ikke fundet apps, der kan sende logge. Installér mail-app\'en!</string>
+  <string name="log_send_mail_subject">%1$s Android-app - logge</string>
+  <string name="log_progress_dialog_text">Indlæser data ...</string>
   <string name="saml_authentication_required_text">Godkendelse påkrævet</string>
   <string name="saml_authentication_wrong_pass">Forkert kodeord</string>
   <string name="actionbar_move">Flyt</string>
   <string name="file_list_empty_moving">Der er intet her. Du kan tilføje en mappe!</string>
-  <string name="move_choose_button_text">Vælg</string>
+  <string name="folder_picker_choose_button_text">Vælg</string>
   <string name="move_file_not_found">Kan ikke flytte. Tjek venligst om filen findes</string>
   <string name="move_file_invalid_into_descendent">Det er ikke muligt at flytte en mappe til en undermappe</string>
   <string name="move_file_invalid_overwrite">Filen findes allerede i destinationsmappen</string>
   <string name="forbidden_permissions_move">til at flytte denne fil</string>
   <string name="prefs_category_instant_uploading">Øjeblikkelige uploads</string>
   <string name="prefs_category_security">Sikkerhed</string>
+  <string name="prefs_instant_video_upload_path_title">Sti til videoupload</string>
+  <string name="download_folder_failed_content">Download af %1$s mappe kunne ikke fuldføres</string>
+  <string name="shared_subject_header">delt</string>
+  <string name="with_you_subject_header">med dig</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 2669130..eb15d29 100644 (file)
   <string name="file_list_empty">Alles leer. Laden Sie etwas hoch!</string>
   <string name="file_list_loading">Ladevorgang …</string>
   <string name="local_file_list_empty">Es befinden sich keine Dateien in diesem Ordner.</string>
+  <string name="file_list_folder">Ordner</string>
+  <string name="file_list_folders">Ordner</string>
+  <string name="file_list_file">Datei</string>
+  <string name="file_list_files">Dateien</string>
   <string name="filedetails_select_file">Klicken Sie auf eine Datei für weitere Informationen.</string>
   <string name="filedetails_size">Größe:</string>
   <string name="filedetails_type">Art:</string>
   <string name="auth_redirect_non_secure_connection_title">Die gesicherte Verbindung wird auf eine unsichere Route weitergeleitet.</string>
   <string name="actionbar_logger">Protokolle</string>
   <string name="log_send_history_button">Verlauf senden</string>
-  <string name="log_mail_subject">Protokolle der ownCloud-Android-App</string>
-  <string name="log_progress_dialog_text">Daten werden geladen …</string>
+  <string name="log_send_no_mail_app">Keine App zum Versenden der Meldungen gefunden. Bitte installieren Sie die Mail-App!</string>
+  <string name="log_send_mail_subject">%1$s Android-App Meldungen</string>
+  <string name="log_progress_dialog_text">Lade Daten…</string>
   <string name="saml_authentication_required_text">Legitimierung benötigt</string>
   <string name="saml_authentication_wrong_pass">Falsches Passwort</string>
   <string name="actionbar_move">Verschieben</string>
   <string name="file_list_empty_moving">Nichts vorhanden. Sie können einen Ordner hinzufügen!</string>
-  <string name="move_choose_button_text">Auswählen</string>
+  <string name="folder_picker_choose_button_text">Auswählen</string>
   <string name="move_file_not_found">Verschieben nicht möglich. Bitte überprüfen Sie, ob die Datei existiert</string>
   <string name="move_file_invalid_into_descendent">Es ist nicht möglich einen Ordner eine Ebene tiefer zu verschieben</string>
   <string name="move_file_invalid_overwrite">Die Datei ist bereits im Zielordner vorhanden</string>
   <string name="forbidden_permissions_move">um diese Datei zu verschieben</string>
   <string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
   <string name="prefs_category_security">Sicherheit</string>
+  <string name="prefs_instant_video_upload_path_title">Verzeichnis zum Hochladen der Videos</string>
+  <string name="download_folder_failed_content">Herunterladen des %1$s - Ordners konnte nicht abgeschlossen werden</string>
+  <string name="shared_subject_header">geteilt</string>
+  <string name="with_you_subject_header">Mit Ihnen</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 45f078d..af0e804 100644 (file)
   <string name="file_list_empty">Alles leer. Lade etwas hoch!</string>
   <string name="file_list_loading">Ladevorgang …</string>
   <string name="local_file_list_empty">Es befinden sich keine Dateien in diesem Ordner.</string>
+  <string name="file_list_folder">Ordner</string>
+  <string name="file_list_folders">Ordner</string>
+  <string name="file_list_file">Datei</string>
+  <string name="file_list_files">Dateien</string>
   <string name="filedetails_select_file">Klicken Sie auf eine Datei für weitere Informationen.</string>
   <string name="filedetails_size">Größe:</string>
   <string name="filedetails_type">Art:</string>
   <string name="auth_redirect_non_secure_connection_title">Die gesicherte Verbindung wird auf eine unsichere Route weitergeleitet.</string>
   <string name="actionbar_logger">Protokolle</string>
   <string name="log_send_history_button">Verlauf senden</string>
-  <string name="log_mail_subject">Protokolle der ownCloud-Android-App</string>
-  <string name="log_progress_dialog_text">Daten werden geladen …</string>
+  <string name="log_send_no_mail_app">Keine App zum Versenden der Meldungen gefunden. Bitte installiere die Mail-App!</string>
+  <string name="log_send_mail_subject">%1$s Android-App Meldungen</string>
+  <string name="log_progress_dialog_text">Lade Daten…</string>
   <string name="saml_authentication_required_text">Legitimierung benötigt</string>
   <string name="saml_authentication_wrong_pass">Falsches Passwort</string>
   <string name="actionbar_move">Verschieben</string>
   <string name="file_list_empty_moving">Nichts vorhanden. Du kannst einen Ordner hinzufügen!</string>
-  <string name="move_choose_button_text">Auswählen</string>
+  <string name="folder_picker_choose_button_text">Auswählen</string>
   <string name="move_file_not_found">Verschieben nicht möglich. Prüfe, dass die Datei existiert</string>
   <string name="move_file_invalid_into_descendent">Es ist nicht möglich, einen Ordner in einen seiner Unterordner zu verschieben</string>
   <string name="move_file_invalid_overwrite">Die Datei ist bereits im Zielordner vorhanden</string>
   <string name="forbidden_permissions_move">um diese Datei zu verschieben</string>
   <string name="prefs_category_instant_uploading">Sofortiges Hochladen</string>
   <string name="prefs_category_security">Sicherheit</string>
+  <string name="prefs_instant_video_upload_path_title">Verzeichnis zum Hochladen der Videos</string>
+  <string name="download_folder_failed_content">Herunterladen des %1$s - Ordners konnte nicht abgeschlossen werden</string>
+  <string name="shared_subject_header">geteilt</string>
+  <string name="with_you_subject_header">Mit Dir</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 6a304ca..6faffba 100644 (file)
   <string name="actionbar_settings">Ρυθμίσεις</string>
   <string name="actionbar_see_details">Λεπτομέρειες</string>
   <string name="actionbar_send_file">Αποστολή</string>
+  <string name="actionbar_sort">Ταξινόμηση</string>
+  <string name="actionbar_sort_title">Ταξινόμηση κατά</string>
   <string-array name="actionbar_sortby">
-    <item>A-Z</item>
+    <item>A-Ω</item>
     <item>Νεότερο - Παλαιότερο</item>
   </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
@@ -36,6 +38,8 @@
   <string name="prefs_recommend">Προτείνετε σε φίλο</string>
   <string name="prefs_feedback">Σχόλια </string>
   <string name="prefs_imprint">Αποτύπωμα</string>
+  <string name="prefs_remember_last_share_location">Αποθήκευση σημείου διαμοιρασμού</string>
+  <string name="prefs_remember_last_upload_location_summary">Αποθήκευση τελευταίου σημείου διαμοιρασμού μεταφόρτωσης</string>
   <string name="recommend_subject">Δοκιμάστε %1$s στο κινητό σας!</string>
   <string name="recommend_text">Θα ήθελα να σε προσκαλέσω να χρησιμοποιήσεις το %1$s στο κινητό σου!\nΛήψη εδώ: %2$s</string>
   <string name="auth_check_server">Έλεγχος Διακομιστή</string>
   <string name="uploader_error_forbidden_content">Ο %1$s δεν επιτρέπεται να έχει πρόσβαση στο κοινόχρηστο περιεχόμενο</string>
   <string name="uploader_info_uploading">Μεταφόρτωση</string>
   <string name="file_list_seconds_ago">δευτερόλεπτα πριν</string>
-  <string name="file_list_empty">Î\94εν Ï\85Ï\80άÏ\81Ï\87ει Ï\84ίÏ\80οÏ\84α ÎµÎ´Ï\8e. Î\91νεβάστε κάτι!</string>
-  <string name="file_list_loading">Φόρτωση ...</string>
+  <string name="file_list_empty">Î\94εν Ï\85Ï\80άÏ\81Ï\87ει Ï\84ίÏ\80οÏ\84α ÎµÎ´Ï\8e. Î\9cεÏ\84αÏ\86οÏ\81Ï\84Ï\8eστε κάτι!</string>
+  <string name="file_list_loading">Φόρτωση...</string>
   <string name="local_file_list_empty">Δεν υπάρχουν αρχεία σε αυτό τον φάκελο.</string>
+  <string name="file_list_folder">φάκελος</string>
+  <string name="file_list_folders">φάκελοι</string>
+  <string name="file_list_file">αρχείο</string>
+  <string name="file_list_files">αρχεία</string>
   <string name="filedetails_select_file">Αγγίξτε κάποιο αρχείο για να προβάλετε περισσότερες πληροφορίες.</string>
   <string name="filedetails_size">Μέγεθος:</string>
   <string name="filedetails_type">Τύπος:</string>
                διαχειριστή σας.</string>
   <string name="share_link_file_no_exist">Αδύνατη η κοινή χρήση. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει</string>
   <string name="share_link_file_error">Ένα σφάλμα προέκυψε κατά την προσπάθεια διαμοιρασμού αυτού του αρχείου ή φακέλου</string>
-  <string name="unshare_link_file_no_exist">Αδύνατη η διακοπή κοινής χρήσης.  Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει</string>
+  <string name="unshare_link_file_no_exist">Αδύνατη η διακοπή κοινής χρήσης.  Παρακαλώ ελέγξτε αν το αρχείο υπάρχει</string>
   <string name="unshare_link_file_error">Ένα σφάλμα προέκυψε κατά τη διάρκεια ακύρωσης διαμοιρασμού αυτού του αρχείου ή φακέλου</string>
   <string name="activity_chooser_send_file_title">Αποστολή</string>
   <string name="copy_link">Αντιγραφή συνδέσμου</string>
   <string name="forbidden_permissions_rename">για να μετονομάσετε αυτό το αρχείο</string>
   <string name="forbidden_permissions_delete">για να διαγράψετε αυτό το αρχείο</string>
   <string name="share_link_forbidden_permissions">για να μοιραστείτε αυτό το αρχείο</string>
-  <string name="unshare_link_forbidden_permissions">για Î½Î± Î¼Î· Î¼Î¿Î¹Ï\81αÏ\83Ï\84είÏ\84ε Î±Ï\85Ï\84Ï\8c Ï\84ο Î±Ï\81Ï\87είο</string>
+  <string name="unshare_link_forbidden_permissions">για Î½Î± Î´Î¹Î±ÎºÏ\8cÏ\88εÏ\84ε Ï\84ο Î´Î¹Î±Î¼Î¿Î¹Ï\81αÏ\83μÏ\8c Î±Ï\85Ï\84οÏ\8d Ï\84οÏ\85 Î±Ï\81Ï\87είοÏ\85</string>
   <string name="forbidden_permissions_create">για να δημιουργήσετε το αρχείο</string>
-  <string name="uploader_upload_forbidden_permissions">για να μεταφορτώσετε σε αυτό τον κατάλογο</string>
+  <string name="uploader_upload_forbidden_permissions">για να μεταφορτώσετε σε αυτό το φάκελο</string>
   <string name="downloader_download_file_not_found">Αυτό το αρχείο δεν είναι πια διαθέσιμο στο διακομιστή</string>
   <string name="prefs_category_accounts">Λογαριασμοί</string>
   <string name="prefs_add_account">Προσθήκη λογαριασμού</string>
   <string name="auth_redirect_non_secure_connection_title">Ασφαλής σύνδεση ανακατευθύνεται σε μια μη ασφαλή διαδρομή.</string>
   <string name="actionbar_logger">Αρχεία καταγραφών</string>
-  <string name="log_send_history_button">Αποστολή ιστορικού</string>
-  <string name="log_mail_subject">αρχεία καταγραφής της εφαρμογής ownCloud Android</string>
+  <string name="log_send_history_button">Αποστολή Ιστορικού</string>
+  <string name="log_send_no_mail_app">Δεν εντοπίστηκε εφαρμογή αποστολής αναφορών συστήματος. Εγκαταστήστε την εφαρμογή Ηλ. Ταχυδρομείου!!</string>
+  <string name="log_send_mail_subject">%1$s αναφορές της εφαρμογής Android</string>
   <string name="log_progress_dialog_text">Φόρτωση δεδομένων....</string>
   <string name="saml_authentication_required_text">Απαιτείται πιστοποίηση</string>
-  <string name="saml_authentication_wrong_pass">Εσφαλμένο συνθηματικό</string>
+  <string name="saml_authentication_wrong_pass">Εσφαλμένος κωδικός πρόσβασης</string>
   <string name="actionbar_move">Μετακίνηση</string>
   <string name="file_list_empty_moving">Δεν υπάρχει τίποτα εδώ. Μπορείτε να προσθέσετε ένα φάκελο!</string>
-  <string name="move_choose_button_text">Επιλέξτε</string>
-  <string name="move_file_not_found">Αδύνατη η μετακίνηση. Παρακαλώ ελέγξτε αν ο φάκελος υπάρχει</string>
+  <string name="folder_picker_choose_button_text">Επιλέξτε</string>
+  <string name="move_file_not_found">Αδύνατη η μετακίνηση. Παρακαλώ ελέγξτε αν το αρχείο υπάρχει</string>
   <string name="move_file_invalid_into_descendent">Δεν είναι δυνατό να μετακινηθεί ο φάκελος σε έναν απογονικό</string>
   <string name="move_file_invalid_overwrite">Το αρχείο υπάρχει ήδη στο φάκελο προορισμού</string>
   <string name="move_file_error">Ένα σφάλμα προέκυψε κατά την προσπάθεια μετακίνησης αυτού του αρχείου ή φακέλου</string>
   <string name="forbidden_permissions_move">για μετακίνηση αυτού του αρχείου</string>
   <string name="prefs_category_instant_uploading">Στιγμιαίες Μεταφορτώσεις</string>
   <string name="prefs_category_security">Ασφάλεια</string>
+  <string name="prefs_instant_video_upload_path_title">Διαδρομή Μεταφόρτωσης Βίντεο</string>
+  <string name="download_folder_failed_content">Η λήψη του φακέλου %1$s δεν ολοκληρώθηκε με επιτυχία.</string>
+  <string name="shared_subject_header">μοιρασμένο </string>
+  <string name="with_you_subject_header">με εσένα</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 94bf042..5288af4 100644 (file)
   <string name="file_list_empty">Nothing in here. Upload something!</string>
   <string name="file_list_loading">Loading...</string>
   <string name="local_file_list_empty">There are no files in this folder.</string>
+  <string name="file_list_folder">folder</string>
+  <string name="file_list_folders">folders</string>
+  <string name="file_list_file">file</string>
+  <string name="file_list_files">files</string>
   <string name="filedetails_select_file">Tap on a file to display additional information.</string>
   <string name="filedetails_size">Size:</string>
   <string name="filedetails_type">Type:</string>
   <string name="auth_redirect_non_secure_connection_title">Secure connection is redirected to an unsecured route.</string>
   <string name="actionbar_logger">Logs</string>
   <string name="log_send_history_button">Send History</string>
-  <string name="log_mail_subject">ownCloud Android app logs</string>
-  <string name="log_progress_dialog_text">Loading data...</string>
+  <string name="log_send_no_mail_app">No app for sending logs found. Install mail app!</string>
+  <string name="log_send_mail_subject">%1$s Android app logs</string>
+  <string name="log_progress_dialog_text">Loading data…</string>
   <string name="saml_authentication_required_text">Authentication required</string>
   <string name="saml_authentication_wrong_pass">Incorrect password</string>
   <string name="actionbar_move">Move</string>
   <string name="file_list_empty_moving">Nothing in here. You can add a folder!</string>
-  <string name="move_choose_button_text">Choose</string>
+  <string name="folder_picker_choose_button_text">Choose</string>
   <string name="move_file_not_found">Unable to move. Please check whether the file exists</string>
   <string name="move_file_invalid_into_descendent">It is not possible to move a folder into a descendant</string>
   <string name="move_file_invalid_overwrite">The file exists already in the destination folder</string>
   <string name="forbidden_permissions_move">to move this file</string>
   <string name="prefs_category_instant_uploading">Instant Uploads</string>
   <string name="prefs_category_security">Security</string>
+  <string name="prefs_instant_video_upload_path_title">Upload Video Path</string>
+  <string name="download_folder_failed_content">Download of %1$s folder could not be completed</string>
+  <string name="shared_subject_header">shared</string>
+  <string name="with_you_subject_header">with you</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 6b15f10..4c604f0 100644 (file)
   <string name="uploader_info_uploading">Alŝutante</string>
   <string name="file_list_seconds_ago">sekundoj antaŭe</string>
   <string name="file_list_empty">Nenio estas ĉi tie. Alŝutu ion!</string>
+  <string name="file_list_folder">dosierujo</string>
+  <string name="file_list_folders">dosierujoj</string>
+  <string name="file_list_file">dosiero</string>
+  <string name="file_list_files">dosieroj</string>
   <string name="filedetails_size">Grando:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="filedetails_created">Kreita je:</string>
   <string name="prefs_category_accounts">Kontoj</string>
   <string name="saml_authentication_required_text">Aŭtentiĝo nepras</string>
   <string name="saml_authentication_wrong_pass">Malĝusta pasvorto</string>
-  <string name="move_choose_button_text">Elekti</string>
+  <string name="folder_picker_choose_button_text">Elekti</string>
   <string name="prefs_category_security">Sekuro</string>
+  <string name="shared_subject_header">kunhavigita</string>
 </resources>
index eeb51c3..6100a40 100644 (file)
   <string name="file_list_empty">No hay nada. ¡Subí contenido!</string>
   <string name="file_list_loading">Cargando...</string>
   <string name="local_file_list_empty">No existen archivos en esta carpeta.</string>
+  <string name="file_list_folder">carpeta</string>
+  <string name="file_list_folders">carpetas</string>
+  <string name="file_list_file">archivo</string>
+  <string name="file_list_files">archivos</string>
   <string name="filedetails_select_file">Pulsá sobre un archivo para mostrar información adicional.</string>
   <string name="filedetails_size">Tamaño:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="prefs_category_accounts">Cuentas</string>
   <string name="saml_authentication_required_text">Autentificación requerida</string>
   <string name="saml_authentication_wrong_pass">Clave incorrecta</string>
-  <string name="move_choose_button_text">Elegir</string>
+  <string name="folder_picker_choose_button_text">Elegir</string>
   <string name="prefs_category_security">Seguridad</string>
+  <string name="shared_subject_header">compartido</string>
 </resources>
index 8f2457d..16485ea 100644 (file)
   <string name="error__upload__local_file_not_copied">%1$s no pudo ser copiado a la carpeta local %2$s</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Cuentas</string>
-  <string name="move_choose_button_text">Choose</string>
+  <string name="folder_picker_choose_button_text">Choose</string>
 </resources>
index 3606025..5758463 100644 (file)
   <string name="empty"></string>
   <string name="prefs_category_accounts">Cuentas</string>
   <string name="saml_authentication_wrong_pass">Contraseña incorrecta</string>
-  <string name="move_choose_button_text">Seleccionar</string>
+  <string name="folder_picker_choose_button_text">Seleccionar</string>
   <string name="prefs_category_security">Seguridad</string>
+  <string name="shared_subject_header">compartido</string>
 </resources>
index 93e83c2..a3d335f 100644 (file)
@@ -46,7 +46,7 @@
   <string name="auth_host_url">Dirección del servidor https://…</string>
   <string name="auth_username">Nombre de usuario</string>
   <string name="auth_password">Contraseña</string>
-  <string name="auth_register">Nuevo para %1$s?</string>
+  <string name="auth_register">¿Nuevo en %1$s?</string>
   <string name="sync_string_files">Archivos</string>
   <string name="setup_btn_connect">Conectar</string>
   <string name="uploader_btn_upload_text">Subir</string>
   <string name="file_list_empty">No hay nada aquí. ¡Suba algo!</string>
   <string name="file_list_loading">Cargando...</string>
   <string name="local_file_list_empty">No hay archivos en esta carpeta.</string>
+  <string name="file_list_folder">carpeta</string>
+  <string name="file_list_folders">carpetas</string>
+  <string name="file_list_file">archivo</string>
+  <string name="file_list_files">archivos</string>
   <string name="filedetails_select_file">Pulsa sobre un archivo para mostrar información adicional.</string>
   <string name="filedetails_size">Tamaño:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="ssl_validator_header">La identidad del sitio no puede ser verificada</string>
   <string name="ssl_validator_reason_cert_not_trusted">- El certificado del servidor no es de confianza</string>
   <string name="ssl_validator_reason_cert_expired">- El certificado del servidor expiró</string>
-  <string name="ssl_validator_reason_cert_not_yet_valid">- El certificado del servidor es de una fecha que aún no llega</string>
+  <string name="ssl_validator_reason_cert_not_yet_valid">- El certificado del servidor es de una fecha que aún no ha llegado</string>
   <string name="ssl_validator_reason_hostname_not_verified">- La URL no coincide con el nombre de dominio del certificado</string>
   <string name="ssl_validator_question">¿Confías de todas formas en este certificado?</string>
   <string name="ssl_validator_not_saved">El certificado no pudo ser guardado</string>
   <string name="prefs_category_accounts">Cuentas</string>
   <string name="prefs_add_account">Agregar cuenta</string>
   <string name="auth_redirect_non_secure_connection_title">La conexión segura está siendo desviada por una ruta insegura.</string>
-  <string name="actionbar_logger">Logs</string>
+  <string name="actionbar_logger">Registros</string>
   <string name="log_send_history_button">Enviar historial</string>
-  <string name="log_mail_subject">Logs de las apps ownCloud Android</string>
+  <string name="log_send_no_mail_app">No se ha encontrado una app para enviar logs. Instale la app mail!</string>
+  <string name="log_send_mail_subject">Se han encontrado %1$s logs de la app Android</string>
   <string name="log_progress_dialog_text">Cargando datos...</string>
   <string name="saml_authentication_required_text">Se necesita autenticación</string>
   <string name="saml_authentication_wrong_pass">Contraseña incorrecta</string>
   <string name="actionbar_move">Mover</string>
   <string name="file_list_empty_moving">Aquí no hay nada. ¡Puede agregar una carpeta!</string>
-  <string name="move_choose_button_text">Seleccionar</string>
+  <string name="folder_picker_choose_button_text">Seleccionar</string>
   <string name="move_file_not_found">No se puede mover. Revise si el archivo existe</string>
   <string name="move_file_invalid_into_descendent">No se puede mover una carpeta dentro de una de SUS subcarpetas.</string>
   <string name="move_file_invalid_overwrite">El archivo ya existe en la carpeta de destino</string>
   <string name="forbidden_permissions_move">para mover este archivo</string>
   <string name="prefs_category_instant_uploading">Subidas instantáneas</string>
   <string name="prefs_category_security">Seguridad</string>
+  <string name="prefs_instant_video_upload_path_title">Guardar videos subidos en la carpeta:</string>
+  <string name="download_folder_failed_content">Descarga de la carpeta %1$s no ha podido ser completada</string>
+  <string name="shared_subject_header">compartido</string>
+  <string name="with_you_subject_header">contigo</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 75f28a7..84636cc 100644 (file)
   <string name="file_list_empty">Siin pole midagi. Lae midagi üles!</string>
   <string name="file_list_loading">Laen ...</string>
   <string name="local_file_list_empty">Kaustas pole faile.</string>
+  <string name="file_list_folder">kaust</string>
+  <string name="file_list_folders">kausta</string>
+  <string name="file_list_file">fail</string>
+  <string name="file_list_files">faili</string>
   <string name="filedetails_select_file">Lisainfo vaatamiseks vajuta failile.</string>
   <string name="filedetails_size">Suurus:</string>
   <string name="filedetails_type">Tüüp:</string>
@@ -282,13 +286,11 @@ Allpool on loend kohalikest failidest ning serveris asuvatest failidest %5$s, mi
   <string name="auth_redirect_non_secure_connection_title">Turvaline ühendus suunatakse läbi turvamata ühenduse.</string>
   <string name="actionbar_logger">Logid</string>
   <string name="log_send_history_button">Saada ajalugu</string>
-  <string name="log_mail_subject">ownCloud Android rakenduse logid</string>
-  <string name="log_progress_dialog_text">Andmete laadimine...</string>
   <string name="saml_authentication_required_text">Autentimine on vajalik</string>
   <string name="saml_authentication_wrong_pass">Vale parool</string>
   <string name="actionbar_move">Tõsta ümber</string>
   <string name="file_list_empty_moving">Siin pole midagi. Sa võid lisada kausta!</string>
-  <string name="move_choose_button_text">Vali</string>
+  <string name="folder_picker_choose_button_text">Vali</string>
   <string name="move_file_not_found">Liigutamine ebaõnnestus. Palun kontrolli, kas fail on olemas</string>
   <string name="move_file_invalid_into_descendent">Kausta ei saa liigutada selle alamkausta</string>
   <string name="move_file_invalid_overwrite">See fail on juba sihtkaustas olemas</string>
@@ -296,4 +298,5 @@ Allpool on loend kohalikest failidest ning serveris asuvatest failidest %5$s, mi
   <string name="forbidden_permissions_move">selle faili liigutamiseks</string>
   <string name="prefs_category_instant_uploading">Kohesed üleslaadimised</string>
   <string name="prefs_category_security">Turvalisus</string>
+  <string name="shared_subject_header">jagatud</string>
 </resources>
index 7480e32..6b96f28 100644 (file)
   <string name="file_list_empty">Ez dago ezer. Igo zerbait!</string>
   <string name="file_list_loading">Kargatzen...</string>
   <string name="local_file_list_empty">Ez dago fitxategirik karpeta honetan.</string>
+  <string name="file_list_folder">karpeta</string>
+  <string name="file_list_folders">Karpetak</string>
+  <string name="file_list_file">fitxategia</string>
+  <string name="file_list_files">fitxategiak</string>
   <string name="filedetails_select_file">Sakatu fitxategi baten gainean informazio gehiago lortzeko</string>
   <string name="filedetails_size">Tamaina:</string>
   <string name="filedetails_type">Mota:</string>
@@ -267,12 +271,15 @@ Mesedez, baimendu berriz</string>
   <string name="downloader_download_file_not_found">Fitxategia jadanik ez dago eskuragarri zerbitzarian</string>
   <string name="prefs_category_accounts">Kontuak</string>
   <string name="prefs_add_account">Gehitu kontua</string>
-  <string name="log_mail_subject">ownCloud Android programaren egunerokoak</string>
+  <string name="log_send_mail_subject">%1$s Android aplikazioaren egunerokoak</string>
   <string name="log_progress_dialog_text">Datuak kargatzen...</string>
   <string name="saml_authentication_required_text">Autentikazioa beharrezkoa</string>
   <string name="saml_authentication_wrong_pass">Pasahitz okerra</string>
   <string name="actionbar_move">Mugitu</string>
-  <string name="move_choose_button_text">Aukeratu</string>
+  <string name="folder_picker_choose_button_text">Aukeratu</string>
   <string name="prefs_category_instant_uploading">Berehalako Igoerak</string>
   <string name="prefs_category_security">Segurtasuna</string>
+  <string name="shared_subject_header">konpartitua</string>
+  <string name="with_you_subject_header">zurekin</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index d11e513..142e71b 100644 (file)
   <string name="file_list_empty">اینجا هیچ چیز نیست.</string>
   <string name="file_list_loading">درحال بارگذاری...</string>
   <string name="local_file_list_empty">هیچ فایلی در این پوشه نیست.</string>
+  <string name="file_list_folder">پوشه</string>
+  <string name="file_list_folders">پوشه ها</string>
+  <string name="file_list_file">پرونده</string>
+  <string name="file_list_files">پرونده ها</string>
   <string name="filedetails_select_file">روی هر فایل کلیک کنید تا اطلاعات اضافی نمایش داده شود.</string>
   <string name="filedetails_size">اندازه</string>
   <string name="filedetails_type">نوع:</string>
   <string name="prefs_category_accounts">حساب‌ها</string>
   <string name="saml_authentication_required_text">احراز هویت مورد نیاز است</string>
   <string name="saml_authentication_wrong_pass">رمز عبور اشتباه است</string>
-  <string name="move_choose_button_text">انتخاب کردن</string>
+  <string name="folder_picker_choose_button_text">انتخاب کردن</string>
   <string name="prefs_category_security">امنیت</string>
+  <string name="shared_subject_header">اشتراک گذاشته شده</string>
 </resources>
index 3d77e6b..5273257 100644 (file)
   <string name="file_list_empty">Täällä ei ole mitään. Lähetä tänne jotakin!</string>
   <string name="file_list_loading">Ladataan...</string>
   <string name="local_file_list_empty">Tässä kansiossa ei ole tiedostoja</string>
+  <string name="file_list_folder">kansio</string>
+  <string name="file_list_folders">kansiota</string>
+  <string name="file_list_file">tiedosto</string>
+  <string name="file_list_files">tiedostoa</string>
   <string name="filedetails_select_file">Napauta tiedostoa nähdäksesi lisätietoja.</string>
   <string name="filedetails_size">Koko:</string>
   <string name="filedetails_type">Tyyppi:</string>
@@ -71,6 +75,7 @@
   <string name="filedetails_sync_file">Päivitä tiedosto</string>
   <string name="filedetails_renamed_in_upload_msg">Tiedoston nimeksi muutettiin %1$s siirron yhteydessä</string>
   <string name="action_share_file">Jaa linkki</string>
+  <string name="action_unshare_file">Poista linkin jako</string>
   <string name="common_yes">Kyllä</string>
   <string name="common_no">Ei</string>
   <string name="common_ok">OK</string>
   <string name="pincode_wrong">Väärä sovelluksen PIN</string>
   <string name="pincode_removed">Sovelluksen PIN poistettu</string>
   <string name="pincode_stored">Sovelluksen PIN-koodi tallennettu</string>
+  <string name="media_notif_ticker">%1$s-musiikkisoitin</string>
   <string name="media_state_playing">%1$s (toistetaan)</string>
   <string name="media_state_loading">%1$s (ladataan)</string>
   <string name="media_err_nothing_to_play">Mediatiedostoa ei löytynyt</string>
   <string name="media_err_no_account">Tiliä ei määritetty</string>
   <string name="media_err_not_in_owncloud">Tiedosto ei ole kelvollisella tilillä</string>
+  <string name="media_err_unsupported">Mediakoodekki ei ole tuettu</string>
   <string name="media_err_io">Mediatiedoston luku ei onnistunut</string>
+  <string name="media_err_malformed">Mediatiedostoa ei ole koodattu kelvollisesti</string>
   <string name="media_err_timeout">Aikakatkaisu toistoa yrittäessä</string>
   <string name="media_err_invalid_progressive_playback">Mediatiedostoa ei voi suoratoistaa</string>
   <string name="media_err_security_ex">Turvallisuusvirhe yrittäessä toistaa kohdetta %1$s</string>
   <string name="auth_testing_connection">Testataan yhteyttä...</string>
   <string name="auth_not_configured_title">Väärin tehdyt palvelin-asetukset</string>
   <string name="auth_account_not_new">Laitteella on jo tili samalle käyttäjälle ja palvelimelle</string>
+  <string name="auth_account_not_the_same">Syötetty käyttäjä ei täsmää tämän tilin käyttäjän kanssa</string>
   <string name="auth_unknown_error_title">Tuntematon virhe</string>
   <string name="auth_unknown_host_title">Isäntää ei löydy</string>
   <string name="auth_incorrect_path_title">Palvelin-instanssia ei löydetty</string>
   <string name="auth_redirect_non_secure_connection_title">Salattu yhteys on ohjattu uudelleen salaamatonta reittiä pitkin.</string>
   <string name="actionbar_logger">Lokit</string>
   <string name="log_send_history_button">Lähetä historia</string>
-  <string name="log_mail_subject">ownCloudin Android-sovelluksen lokit</string>
-  <string name="log_progress_dialog_text">Ladataan tietoja...</string>
+  <string name="log_send_no_mail_app">Lokien lähettämistä varten ei löytynyt sovellusta. Asenna sähköpostisovellus!</string>
+  <string name="log_progress_dialog_text">Ladataan tietoja</string>
   <string name="saml_authentication_required_text">Tunnistautuminen vaaditaan</string>
   <string name="saml_authentication_wrong_pass">Väärä salasana</string>
   <string name="actionbar_move">Siirrä</string>
   <string name="file_list_empty_moving">Täällä ei ole mitään. Voit lisätä kansion!</string>
-  <string name="move_choose_button_text">Valitse</string>
+  <string name="folder_picker_choose_button_text">Valitse</string>
   <string name="move_file_not_found">Siirto ei onnistu. Tarkista, ettei tiedostoa ole jo olemassa</string>
   <string name="move_file_invalid_overwrite">Tiedosto on jo olemassa kohdekansiossa</string>
   <string name="move_file_error">Tämän tiedoston tai kansion siirtoa yrittäessä tapahtui virhe</string>
   <string name="prefs_category_instant_uploading">Välittömät lähetykset</string>
   <string name="prefs_category_security">Tietoturva</string>
+  <string name="shared_subject_header">jaettu</string>
+  <string name="with_you_subject_header">kanssasi</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644 (file)
index 0000000..5244404
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+  <string name="actionbar_upload">Lähetä</string>
+  <string name="actionbar_open_with">Avaa sovelluksessa</string>
+  <string name="actionbar_mkdir">Luo kansio</string>
+  <string name="actionbar_settings">Asetukset</string>
+  <!--TODO re-enable when server-side folder size calculation is available   
+       <item>Biggest - Smallest</item>-->
+  <string name="prefs_category_more">Lisää</string>
+  <string name="prefs_manage_accounts">Hallitse tilejä</string>
+  <string name="prefs_pincode">Sovelluksen PIN</string>
+  <string name="prefs_help">Apua</string>
+  <string name="auth_host_url">http://esimerkki.fi/palvelu/</string>
+  <string name="auth_username">Käyttäjätunnus</string>
+  <string name="auth_password">Salasana</string>
+  <string name="uploader_btn_upload_text">Lähetä</string>
+  <string name="file_list_empty">Ei sisältöä. Siirrä tänne jotakin!</string>
+  <string name="file_list_loading">Ladataan...</string>
+  <string name="file_list_folder">kansio</string>
+  <string name="file_list_folders">kansiot</string>
+  <string name="file_list_file">tiedosto</string>
+  <string name="file_list_files">Tiedostot</string>
+  <string name="action_share_file">Jaa linkki</string>
+  <string name="action_unshare_file">Poista linkin jako</string>
+  <string name="common_yes">KYLLÄ</string>
+  <string name="common_no">EI</string>
+  <string name="common_ok">Ok</string>
+  <string name="common_cancel">Peruuta</string>
+  <string name="common_error">Virhe</string>
+  <string name="pincode_wrong">Virheellinen PIN</string>
+  <string name="pincode_removed">Sovelluksen PIN poistettu</string>
+  <string name="pincode_stored">Sovelluksen PIN tallennettu</string>
+  <string name="auth_account_not_the_same">Syötetty käyttäjä ei täsmää tämän tilin käyttäjän kanssa</string>
+  <string name="auth_unsupported_auth_method">Palvelin ei tue tätä autentikointitapaa</string>
+  <string name="common_rename">Nimeä uudelleen</string>
+  <string name="ssl_validator_label_L">Sijainti:</string>
+  <string name="conflict_overwrite">Ylikirjoita</string>
+  <string name="copy_link">Kopioi linkki</string>
+  <string name="empty"></string>
+  <string name="actionbar_move">Siirrä</string>
+  <string name="folder_picker_choose_button_text">Valitse</string>
+  <string name="move_file_invalid_overwrite">Tiedosto on jo kohdekansiossa.</string>
+</resources>
index 4548ee8..3b3b347 100644 (file)
@@ -22,7 +22,7 @@
   <string name="prefs_category_general">Général</string>
   <string name="prefs_category_more">Plus</string>
   <string name="prefs_accounts">Comptes</string>
-  <string name="prefs_manage_accounts">Gestion des comptes utilisateur</string>
+  <string name="prefs_manage_accounts">Gestion des comptes</string>
   <string name="prefs_pincode">Code de sécurité</string>
   <string name="prefs_pincode_summary">Protéger l\'accès à l\'application</string>
   <string name="prefs_instant_upload">Envoi instantané des photos</string>
@@ -64,6 +64,10 @@ Téléchargez-le ici : %2$s</string>
   <string name="file_list_empty">Il n\'y a rien ici ! Envoyez donc quelque chose :)</string>
   <string name="file_list_loading">Chargement…</string>
   <string name="local_file_list_empty">Aucun fichier n\'est présent dans ce dossier.</string>
+  <string name="file_list_folder">dossier</string>
+  <string name="file_list_folders">dossiers</string>
+  <string name="file_list_file">fichier</string>
+  <string name="file_list_files">fichiers</string>
   <string name="filedetails_select_file">Effleurez un fichier pour afficher les informations complémentaires</string>
   <string name="filedetails_size">Taille :</string>
   <string name="filedetails_type">Type :</string>
@@ -108,10 +112,10 @@ Téléchargez-le ici : %2$s</string>
   <string name="common_choose_account">Choisissez un compte</string>
   <string name="sync_fail_ticker">La synchronisation a échoué</string>
   <string name="sync_fail_ticker_unauthorized">Échec de la synchronisation, vous devez vous reconnecter à nouveau</string>
-  <string name="sync_fail_content">La synchronisation de %1$s ne peut pas être complétée</string>
-  <string name="sync_fail_content_unauthorized">Mot de passe invalide pour %1$s</string>
+  <string name="sync_fail_content">La synchronisation de %1$s n\'a pu être terminée</string>
+  <string name="sync_fail_content_unauthorized">Mot de passe non valide pour %1$s</string>
   <string name="sync_conflicts_in_favourites_ticker">Des conflits ont été trouvés</string>
-  <string name="sync_conflicts_in_favourites_content">%1$d fichiers à garder synchronisés n\'ont put être synchronisé</string>
+  <string name="sync_conflicts_in_favourites_content">%1$d fichiers à garder synchronisés n\'ont pu être synchronisés</string>
   <string name="sync_fail_in_favourites_ticker">La synchronisation des fichiers a échoué</string>
   <string name="sync_fail_in_favourites_content">Le contenu de %1$d fichiers n\'a pu être synchronisé (%2$d conflits)</string>
   <string name="sync_foreign_files_forgotten_ticker">Certains fichiers locaux ont été oubliés</string>
@@ -140,14 +144,14 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="media_notif_ticker">%1$s lecteur de musique</string>
   <string name="media_state_playing">%1$s (lecture)</string>
   <string name="media_state_loading">%1$s (chargement)</string>
-  <string name="media_event_done">%1$s pourcentage de lecture finie</string>
+  <string name="media_event_done">Lecture de %1$s terminée</string>
   <string name="media_err_nothing_to_play">Fichier média introuvable </string>
   <string name="media_err_no_account">Aucun compte n\'a été trouvé</string>
   <string name="media_err_not_in_owncloud">Le fichier n\'est pas dans un compte valide</string>
   <string name="media_err_unsupported">Le codec de ce média n\'est pas pris en charge </string>
   <string name="media_err_io">Le fichier média ne peut pas être lu</string>
   <string name="media_err_malformed">Le fichier média n\'est pas correctement encodé</string>
-  <string name="media_err_timeout">Délai dépassé pour la lecture du morceau.</string>
+  <string name="media_err_timeout">Délai dépassé pour la lecture du morceau</string>
   <string name="media_err_invalid_progressive_playback">Le fichier média ne peut pas être diffusé</string>
   <string name="media_err_unknown">Le fichier média ne peut être joué avec le lecteur standard</string>
   <string name="media_err_security_ex">Erreur de sécurité à la lecture de %1$s</string>
@@ -169,7 +173,7 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="auth_unknown_host_title">Impossible de trouver l\'hôte</string>
   <string name="auth_incorrect_path_title">Aucune instance du serveur n\'a été trouvée</string>
   <string name="auth_timeout_title">Le serveur met trop longtemps à répondre</string>
-  <string name="auth_incorrect_address_title">Adresse invalide</string>
+  <string name="auth_incorrect_address_title">Adresse non valide</string>
   <string name="auth_ssl_general_error_title">Échec de l\'initialisation SSL</string>
   <string name="auth_ssl_unverified_server_title">Impossible de vérifier l\'identité du serveur SSL</string>
   <string name="auth_bad_oc_version_title">La version du serveur n\'est pas reconnue</string>
@@ -178,7 +182,7 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="auth_unauthorized">Nom d\'utilisateur ou mot de passe incorrect</string>
   <string name="auth_oauth_error">Échec d\'autorisation</string>
   <string name="auth_oauth_error_access_denied">Accès refusé par le serveur d\'autorisation</string>
-  <string name="auth_wtf_reenter_URL">État inattendu ; veuillez entrer à nouveau l\'URL du serveur</string>
+  <string name="auth_wtf_reenter_URL">État inattendu ; veuillez saisir à nouveau l\'URL du serveur</string>
   <string name="auth_expired_oauth_token_toast">Votre autorisation a expiré. Merci de vous authentifier à nouveau</string>
   <string name="auth_expired_basic_auth_toast">Veuillez saisir le mot de passe courant</string>
   <string name="auth_expired_saml_sso_token_toast">Votre session a expiré. Merci de vous reconnecter</string>
@@ -194,23 +198,23 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="confirmation_remove_alert">Voulez-vous vraiment supprimer %1$s ?</string>
   <string name="confirmation_remove_folder_alert">Voulez-vous vraiment supprimer %1$s et son contenu ?</string>
   <string name="confirmation_remove_local">Local seulement</string>
-  <string name="confirmation_remove_folder_local">Le contenu local uniquement</string>
+  <string name="confirmation_remove_folder_local">Contenu local uniquement</string>
   <string name="confirmation_remove_remote">Effacer du serveur</string>
-  <string name="confirmation_remove_remote_and_local">Les deux distant et local</string>
+  <string name="confirmation_remove_remote_and_local">Distant et local</string>
   <string name="remove_success_msg">Suppression effectuée avec succès</string>
   <string name="remove_fail_msg">Suppression impossible</string>
   <string name="rename_dialog_title">Entrez un nouveau nom</string>
   <string name="rename_local_fail_msg">La version locale ne peut être renommée, veuillez réessayer avec un nom différent</string>
   <string name="rename_server_fail_msg">Renommage impossible</string>
   <string name="sync_file_fail_msg">Le fichier distant n\'a pu être vérifié</string>
-  <string name="sync_file_nothing_to_do_msg">Le contenu des fichiers est déjà synchronisé</string>
+  <string name="sync_file_nothing_to_do_msg">Le contenu du fichier est déjà synchronisé</string>
   <string name="create_dir_fail_msg">Le dossier n\'a pas pu être créé</string>
   <string name="filename_forbidden_characters">Caractères interdits : / \\ &lt; &gt; : \" | ? *</string>
   <string name="filename_empty">Le nom du fichier ne peut pas être vide</string>
   <string name="wait_a_moment">Veuillez patienter</string>
   <string name="filedisplay_unexpected_bad_get_content">Problème inattendu. Veuillez essayer une autre application pour la sélection du fichier</string>
   <string name="filedisplay_no_file_selected">Aucun fichier sélectionné</string>
-  <string name="activity_chooser_title">Envoyer un lien à…</string>
+  <string name="activity_chooser_title">Envoyer le lien vers…</string>
   <string name="oauth_check_onoff">Connexion avec oAuth2</string>
   <string name="oauth_login_connection">Connexion au serveur oAuth2…</string>
   <string name="ssl_validator_header">L\'identité du site ne peut être vérifiée</string>
@@ -282,13 +286,14 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="auth_redirect_non_secure_connection_title">La connexion sécurisée est redirigée via une route non-sécurisée.</string>
   <string name="actionbar_logger">Journaux</string>
   <string name="log_send_history_button">Envoyer l\'historique</string>
-  <string name="log_mail_subject">Journaux de l\'application Android ownCloud</string>
+  <string name="log_send_no_mail_app">Aucune application trouvée pour envoyer les logs. Installez une application de courriel !</string>
+  <string name="log_send_mail_subject">Journaux de l\'application Android %1$s</string>
   <string name="log_progress_dialog_text">Chargement des données...</string>
   <string name="saml_authentication_required_text">Authentification requise</string>
   <string name="saml_authentication_wrong_pass">Mot de passe incorrect</string>
   <string name="actionbar_move">Déplacer</string>
   <string name="file_list_empty_moving">Il n\'y a rien ici. Vous pouvez ajouter un dossier !</string>
-  <string name="move_choose_button_text">Choisir</string>
+  <string name="folder_picker_choose_button_text">Choisir</string>
   <string name="move_file_not_found">Impossible de déplacer. Vérifiez que le fichier existe</string>
   <string name="move_file_invalid_into_descendent">Il n\'est pas possible de déplacer un dossier vers un de ses descendants</string>
   <string name="move_file_invalid_overwrite">Le fichier existe déjà dans le dossier de destination</string>
@@ -296,4 +301,9 @@ Ci-dessous la liste des fichiers locaux, et les fichiers distants dans %5$s auxq
   <string name="forbidden_permissions_move">de déplacer ce fichier</string>
   <string name="prefs_category_instant_uploading">Envoi instantané</string>
   <string name="prefs_category_security">Sécurité</string>
+  <string name="prefs_instant_video_upload_path_title">Répertoire d\'envoi des vidéos</string>
+  <string name="download_folder_failed_content">Le téléchargement du dossier %1$s n\'a pas pu être achevé complètement</string>
+  <string name="shared_subject_header">partagé(e)</string>
+  <string name="with_you_subject_header">avec vous</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index d278332..616d482 100644 (file)
   <string name="actionbar_settings">Preferencias</string>
   <string name="actionbar_see_details">Detalles</string>
   <string name="actionbar_send_file">Enviar</string>
+  <string name="actionbar_sort">Ordenar</string>
+  <string name="actionbar_sort_title">Ordenar por</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Z</item>
+    <item>Máis novo - Máis antigo</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">Xeral</string>
   <string name="prefs_category_more">Máis</string>
   <string name="prefs_accounts">Contas</string>
-  <string name="prefs_manage_accounts">Xestionar as contas</string>
+  <string name="prefs_manage_accounts">Administrar as contas</string>
   <string name="prefs_pincode">PIN da aplicación</string>
-  <string name="prefs_pincode_summary">Protexe o seu cliente</string>
+  <string name="prefs_pincode_summary">Protexa o seu cliente</string>
   <string name="prefs_instant_upload">Envío instantáneo de fotos</string>
   <string name="prefs_instant_upload_summary">Enviar instantaneamente as fotos tiradas coa cámara</string>
   <string name="prefs_instant_video_upload">Envío instantáneo de vídeos</string>
   <string name="prefs_recommend">Recomendar a un amigo</string>
   <string name="prefs_feedback">Comentarios</string>
   <string name="prefs_imprint">Impresión</string>
+  <string name="prefs_remember_last_share_location">Lembrar a localización da compartición</string>
+  <string name="prefs_remember_last_upload_location_summary">Lembrar a localización do envío da última compartición</string>
   <string name="recommend_subject">Tente %1$s no seu teléfono intelixente!</string>
+  <string name="recommend_text">Quero convidalo a empregar %1$s no seu teléfono intelixente!
+Descárgueo de aquí: %2$s</string>
   <string name="auth_check_server">Comprobar o servidor</string>
   <string name="auth_host_url">Enderezo do servidor https://…</string>
   <string name="auth_username">Nome de usuario</string>
   <string name="file_list_empty">Aquí non hai nada. Envíe algo!</string>
   <string name="file_list_loading">Cargando...</string>
   <string name="local_file_list_empty">Non hai ficheiros neste cartafol.</string>
+  <string name="file_list_folder">cartafol</string>
+  <string name="file_list_folders">cartafoles</string>
+  <string name="file_list_file">ficheiro</string>
+  <string name="file_list_files">ficheiros</string>
   <string name="filedetails_select_file">Prema nun ficheiro para que amose a información adicional.</string>
   <string name="filedetails_size">Tamaño:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="sync_fail_in_favourites_content">Non foi posíbel sincronizar o contido de %1$d ficheiros (%2$d conflitos)</string>
   <string name="sync_foreign_files_forgotten_ticker">Algúns ficheiros locais foron esquecidos</string>
   <string name="sync_foreign_files_forgotten_content">Non é posíbel copiar %1$d ficheiros do cartafol %2$s en</string>
+  <string name="sync_foreign_files_forgotten_explanation">Desde a versión 1.3.16, os ficheiros enviados desde este dispositivo cópianse no cartafol local %1$s para evitar a perda de datos cando se sincroniza un ficheiro con varias contas.\n\nPor mor deste cambio, todos os ficheiros enviados coas versións anteriores desta aplicación cópianse no cartafol %2$s. Porén, un erro impediu a finalización desta operación durante a sincronización da conta. É posíbel deixar o(s) ficheiro(s) como está(n) e retirar a ligazón a %3$s, ou mover o(s) ficheiro(s) ao directorio %1$s e manter a ligazón a %4$s.\n\nA seguir enuméranse o(s) ficheiro(s) local(is), e o(s) ficheiro(s) remoto(s) en %5$s co(s) que estaba(n) ligado(s).</string>
   <string name="sync_current_folder_was_removed">O cartafol %1$s xa non existe</string>
   <string name="foreign_files_move">Mover todo</string>
   <string name="foreign_files_success">Foron movidos todos os ficheiros</string>
   <string name="preview_image_description">Vista previa da imaxe</string>
   <string name="preview_image_error_unknown_format">Esta imaxe non pode ser amosada</string>
   <string name="error__upload__local_file_not_copied">Non foi posíbel copiar %1$s no cartafol local %2$s</string>
+  <string name="prefs_instant_upload_path_title">Enviar a ruta</string>
   <string name="share_link_no_support_share_api">O seu servidor non ten activada a opción de compartir. Póñase en contacto co
                administrador.</string>
+  <string name="share_link_file_no_exist">Non foi posíbel compartir. Comprobe que existe o ficheiro</string>
   <string name="share_link_file_error">Produciuse un erro ao tentar compartir este ficheiro ou cartafol.</string>
+  <string name="unshare_link_file_no_exist">Non foi posíbel deixar de compartir. Comprobe que existe o ficheiro</string>
   <string name="unshare_link_file_error">Produciuse un erro ao tentar deixar de compartir este ficheiro ou cartafol</string>
   <string name="activity_chooser_send_file_title">Enviar</string>
   <string name="copy_link">Copiar a ligazón</string>
   <string name="downloader_download_file_not_found">O ficheiro xa non está dispoñíbel no servidor</string>
   <string name="prefs_category_accounts">Contas</string>
   <string name="prefs_add_account">Engadir unha conta</string>
+  <string name="auth_redirect_non_secure_connection_title">A conexión segura está a ser redirixida a unha ruta non segura.</string>
+  <string name="actionbar_logger">Rexistros</string>
+  <string name="log_send_history_button">Enviar o historial</string>
+  <string name="log_send_no_mail_app">Non se atopou unha aplicación para enviar os rexistros. Instale unha aplicación de correo!</string>
+  <string name="log_send_mail_subject">Rexistros da aplicación %1$s Android</string>
+  <string name="log_progress_dialog_text">Cargando os datos...</string>
   <string name="saml_authentication_required_text">Requírese autenticación</string>
   <string name="saml_authentication_wrong_pass">Contrasinal incorrecto</string>
   <string name="actionbar_move">Mover</string>
-  <string name="move_choose_button_text">Escoller</string>
+  <string name="file_list_empty_moving">Aquí non hai nada. Pode engadir un cartafol!</string>
+  <string name="folder_picker_choose_button_text">Escoller</string>
+  <string name="move_file_not_found">Non é posíbel movelo. Comprobe se existe o ficheiro</string>
+  <string name="move_file_invalid_into_descendent">Non é posíbel mover un cartafol cara un descendente</string>
+  <string name="move_file_invalid_overwrite">Este ficheiro xa existe no cartafol de destino</string>
+  <string name="move_file_error">Produciuse un erro ao tentar mover este ficheiro ou cartafol.</string>
+  <string name="forbidden_permissions_move">para mover este ficheiro</string>
+  <string name="prefs_category_instant_uploading">Envío instantáneo</string>
   <string name="prefs_category_security">Seguranza</string>
+  <string name="prefs_instant_video_upload_path_title">Enviar a ruta do vídeo</string>
+  <string name="download_folder_failed_content">A descarga do cartafol %1$s non se puido completar</string>
+  <string name="shared_subject_header">compartido</string>
+  <string name="with_you_subject_header">con vostede</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 4599f2f..120ca55 100644 (file)
   <string name="file_list_empty">אין כאן שום דבר. אולי ברצונך להעלות משהו?</string>
   <string name="file_list_loading">בטעינה...</string>
   <string name="local_file_list_empty">אין קבצים בתיקייה זו:</string>
+  <string name="file_list_folder">תקיה</string>
+  <string name="file_list_folders">תקיות</string>
+  <string name="file_list_file">קובץ</string>
+  <string name="file_list_files">קבצים</string>
   <string name="filedetails_select_file">יש לגעת בקובץ כדי להציג פרטים נוספים.</string>
   <string name="filedetails_size">גודל:</string>
   <string name="filedetails_type">סוג:</string>
   <string name="downloader_download_file_not_found">הקובץ אינו זמין יותר על השרת</string>
   <string name="prefs_category_accounts">חשבונות</string>
   <string name="prefs_add_account">הוספת חשבון</string>
-  <string name="move_choose_button_text">בחירה</string>
+  <string name="folder_picker_choose_button_text">בחירה</string>
   <string name="prefs_category_security">אבטחה</string>
 </resources>
index c9bf237..eb8bf88 100644 (file)
@@ -1,15 +1,34 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
+  <string name="about_android">%1$s Android aplikacija</string>
+  <string name="about_version">verzija %1$s</string>
+  <string name="actionbar_sync">Osvježi račun</string>
   <string name="actionbar_upload">Učitaj</string>
+  <string name="actionbar_upload_from_apps">Sadržaj iz drugih aplikacija</string>
   <string name="actionbar_upload_files">Datoteke</string>
+  <string name="actionbar_open_with">Otvori sa</string>
   <string name="actionbar_mkdir">Nova mapa</string>
   <string name="actionbar_settings">Postavke</string>
+  <string name="actionbar_see_details">Detalji</string>
   <string name="actionbar_send_file">Pošaljite</string>
+  <string name="actionbar_sort">Sortiraj</string>
+  <string name="actionbar_sort_title">Sortiraj po</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Z</item>
+    <item>Najnoviji- Stariji</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">Općenito</string>
   <string name="prefs_category_more">više</string>
   <string name="prefs_accounts">Korisnićki računi</string>
+  <string name="prefs_manage_accounts">Upravljaj računima</string>
+  <string name="prefs_pincode">PIN aplikacije</string>
+  <string name="prefs_pincode_summary">Zaštit svog klijenta</string>
+  <string name="prefs_instant_upload">Trenutni upload slika</string>
+  <string name="prefs_instant_upload_summary">Trenutni upload slika snimljenih kamerom</string>
+  <string name="prefs_instant_video_upload">Trenutni upload videa</string>
+  <string name="prefs_instant_video_upload_summary">Trenutni upload videa snimljen kamerom</string>
   <string name="prefs_help">Pomoć</string>
   <string name="auth_username">Korisničko ime</string>
   <string name="auth_password">Lozinka</string>
   <string name="uploader_wrn_no_account_quit_btn_text">Izlaz</string>
   <string name="file_list_seconds_ago">prije par sekundi</string>
   <string name="file_list_empty">Nema ničega u ovoj mapi. Pošalji nešto!</string>
+  <string name="file_list_folder">mapa</string>
+  <string name="file_list_folders">mape</string>
+  <string name="file_list_file">datoteka</string>
+  <string name="file_list_files">datoteke</string>
   <string name="filedetails_download">Preuzimanje</string>
   <string name="action_share_file">Podijelite vezu</string>
   <string name="common_yes">Da</string>
   <string name="auth_trying_to_login">Trying to login…</string>
   <string name="common_rename">Promjeni ime</string>
   <string name="common_remove">Makni</string>
+  <string name="ssl_validator_btn_details_see">Detalji</string>
   <string name="activity_chooser_send_file_title">Pošaljite</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Korisnićki računi</string>
   <string name="saml_authentication_required_text">Potrebna autentikacija</string>
   <string name="saml_authentication_wrong_pass">Pogrešna lozinka</string>
-  <string name="move_choose_button_text">Izaberi</string>
+  <string name="folder_picker_choose_button_text">Odaberite</string>
   <string name="prefs_category_security">Sigurnost</string>
 </resources>
index 0e64683..e8d1111 100644 (file)
   <string name="file_list_empty">Itt nincs semmi. Töltsön fel valamit!</string>
   <string name="file_list_loading">Betöltés...</string>
   <string name="local_file_list_empty">Nincs fájl ebben a mappában.</string>
+  <string name="file_list_folder">mappa</string>
+  <string name="file_list_folders">mappák</string>
+  <string name="file_list_file">fájl</string>
+  <string name="file_list_files">fájlok</string>
   <string name="filedetails_select_file">Érintsen meg egy fájlt a további információkért.</string>
   <string name="filedetails_size">Méret:</string>
   <string name="filedetails_type">Tipus:</string>
   <string name="clipboard_text_copied">Bemásolva a vágólapra</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Fiókok</string>
+  <string name="prefs_add_account">Fiók hozzáadása</string>
+  <string name="actionbar_logger">Naplók</string>
   <string name="saml_authentication_required_text">Felhasználóazonosítás szükséges</string>
   <string name="saml_authentication_wrong_pass">Hibás jelszó</string>
-  <string name="move_choose_button_text">Válasszon</string>
+  <string name="actionbar_move">Mozgatás</string>
+  <string name="folder_picker_choose_button_text">Válasszon</string>
   <string name="prefs_category_security">Biztonság</string>
+  <string name="shared_subject_header">Megosztott</string>
 </resources>
index 598d99a..1f8c5b4 100644 (file)
@@ -29,5 +29,5 @@
   <string name="uploader_info_dirname">Nomine de dossier</string>
   <string name="activity_chooser_send_file_title">Invia</string>
   <string name="empty"></string>
-  <string name="move_choose_button_text">Seliger</string>
+  <string name="folder_picker_choose_button_text">Seliger</string>
 </resources>
index 1434642..eb2df51 100644 (file)
   <string name="file_list_empty">Tidak ada apa-apa di sini. Unggah sesuatu!</string>
   <string name="file_list_loading">Memuat...</string>
   <string name="local_file_list_empty">Tidak ada satupun berkas dalam folder ini.</string>
+  <string name="file_list_folder">folder</string>
+  <string name="file_list_folders">folder-folder</string>
+  <string name="file_list_file">berkas</string>
+  <string name="file_list_files">berkas-berkas</string>
   <string name="filedetails_select_file">Sentuh pada berkas untuk menampilkan informasi tambahan</string>
   <string name="filedetails_size">Ukuran:</string>
   <string name="filedetails_type">Tipe:</string>
   <string name="auth_redirect_non_secure_connection_title">Sambungan aman dialihkan ke rute yang tidak aman.</string>
   <string name="actionbar_logger">Log</string>
   <string name="log_send_history_button">Kirim Riwayat</string>
-  <string name="log_mail_subject">Log apl ownCloud Android</string>
-  <string name="log_progress_dialog_text">Memuat data...</string>
   <string name="saml_authentication_required_text">Diperlukan otentikasi</string>
   <string name="saml_authentication_wrong_pass">Sandi salah</string>
   <string name="actionbar_move">Pindah</string>
   <string name="file_list_empty_moving">Tdak ada apapun disini. Anda dapat menambahkan sebuah folder!</string>
-  <string name="move_choose_button_text">Pilih</string>
+  <string name="folder_picker_choose_button_text">Pilih</string>
   <string name="move_file_not_found">Tidak dapat memindahkan. Silakan periksa apakah berkas ada</string>
   <string name="move_file_invalid_into_descendent">Tidak mungkin untuk memindahkan folder kedalam turunannya</string>
   <string name="move_file_invalid_overwrite">Berkas sudah ada didalam folder tujuan</string>
   <string name="forbidden_permissions_move">untuk memindahkan berkas ini</string>
   <string name="prefs_category_instant_uploading">Unggah Cepat</string>
   <string name="prefs_category_security">Keamanan</string>
+  <string name="shared_subject_header">dibagikan</string>
 </resources>
index 1e14885..2811b4c 100644 (file)
@@ -27,5 +27,5 @@
   <string name="common_remove">Fjarlægja</string>
   <string name="activity_chooser_send_file_title">Senda</string>
   <string name="empty"></string>
-  <string name="move_choose_button_text">Veldu</string>
+  <string name="folder_picker_choose_button_text">Veldu</string>
 </resources>
index 5bb7982..9cca628 100644 (file)
@@ -35,7 +35,7 @@
   <string name="prefs_log_summary_history">Mostra i log registrati</string>
   <string name="prefs_log_delete_history_button">Elimina la cronologia</string>
   <string name="prefs_help">Aiuto</string>
-  <string name="prefs_recommend">Consiglia ad un amico</string>
+  <string name="prefs_recommend">Consiglia a un amico</string>
   <string name="prefs_feedback">Segnalazioni</string>
   <string name="prefs_imprint">Imprint</string>
   <string name="prefs_remember_last_share_location">Ricorda la posizione della condivisione</string>
   <string name="file_list_empty">Non c\'è niente qui. Carica qualcosa!</string>
   <string name="file_list_loading">Caricamento in corso...</string>
   <string name="local_file_list_empty">Non ci sono file in questa cartella.</string>
+  <string name="file_list_folder">cartella</string>
+  <string name="file_list_folders">cartelle</string>
+  <string name="file_list_file">file</string>
+  <string name="file_list_files">file</string>
   <string name="filedetails_select_file">Tocca un file per visualizzare informazioni aggiuntive.</string>
   <string name="filedetails_size">Dimensione:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="auth_redirect_non_secure_connection_title">La connessione sicura è rediretta su un percorso non sicuro.</string>
   <string name="actionbar_logger">Registri</string>
   <string name="log_send_history_button">Invia cronologia</string>
-  <string name="log_mail_subject">Registri applicazione ownCloud Android</string>
-  <string name="log_progress_dialog_text">Caricamento dati...</string>
+  <string name="log_send_no_mail_app">Non è stata trovata alcuna applicazione per l\'invio dei registri. Installa l\'applicazione mail!</string>
+  <string name="log_send_mail_subject">Registri applicazione %1$s Android</string>
+  <string name="log_progress_dialog_text">Caricamento dati in corso...</string>
   <string name="saml_authentication_required_text">Autenticazione richiesta</string>
   <string name="saml_authentication_wrong_pass">Password errata</string>
   <string name="actionbar_move">Sposta</string>
   <string name="file_list_empty_moving">Qui non c\'è niente. Puoi aggiungere una cartella.</string>
-  <string name="move_choose_button_text">Scegli</string>
+  <string name="folder_picker_choose_button_text">Scegli</string>
   <string name="move_file_not_found">Impossibile spostare. Assicurati che il file esista</string>
   <string name="move_file_invalid_into_descendent">Impossibile spostare una cartella in una cartella inferiore</string>
   <string name="move_file_invalid_overwrite">Il file esiste già nella cartella di destinazione</string>
   <string name="forbidden_permissions_move">per spostare questo file</string>
   <string name="prefs_category_instant_uploading">Caricamenti istantanei</string>
   <string name="prefs_category_security">Protezione</string>
+  <string name="prefs_instant_video_upload_path_title">Percorso di caricamento video</string>
+  <string name="download_folder_failed_content">Lo scaricamento della cartella %1$s non può essere completato</string>
+  <string name="shared_subject_header">condiviso</string>
+  <string name="with_you_subject_header">con te</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index f6e43e2..1f4e320 100644 (file)
   <string name="file_list_empty">ここには何もありません。何かアップロードしてください。</string>
   <string name="file_list_loading">読込中 ...</string>
   <string name="local_file_list_empty">このフォルダーにはファイルがありません。</string>
+  <string name="file_list_folder">フォルダー</string>
+  <string name="file_list_folders">フォルダー</string>
+  <string name="file_list_file">ファイル</string>
+  <string name="file_list_files">ファイル</string>
   <string name="filedetails_select_file">ファイルをタップすると追加情報が表示されます。</string>
   <string name="filedetails_size">サイズ:</string>
   <string name="filedetails_type">タイプ:</string>
   <string name="auth_redirect_non_secure_connection_title">暗号化接続は非暗号化接続にリダイレクトされました。</string>
   <string name="actionbar_logger">ログ</string>
   <string name="log_send_history_button">ログを送信</string>
-  <string name="log_mail_subject">ownCloud Android アプリログ</string>
+  <string name="log_send_no_mail_app">ログを送信するアプリが見つかりませんでした。メールアプリをインストールしてください。</string>
+  <string name="log_send_mail_subject">%1$s アンドロイドアプリログ</string>
   <string name="log_progress_dialog_text">読込中 ...</string>
   <string name="saml_authentication_required_text">認証を必要とする</string>
   <string name="saml_authentication_wrong_pass">無効なパスワード</string>
   <string name="actionbar_move">移動</string>
-  <string name="file_list_empty_moving">ファイルが有りません。フォルダを追加してください。</string>
-  <string name="move_choose_button_text">選択</string>
+  <string name="file_list_empty_moving">何もありません。フォルダーを追加してください。</string>
+  <string name="folder_picker_choose_button_text">選択</string>
   <string name="move_file_not_found">移動できません。ファイルがあるか確認してください。</string>
-  <string name="move_file_invalid_into_descendent">ã\83\95ã\82©ã\83«ã\83\80ã\82\92å­\90ã\83\95ã\82©ã\83«ã\83\80へ移動することはできません。</string>
-  <string name="move_file_invalid_overwrite">そのファイルは、宛先フォルダに既に存在しています。</string>
+  <string name="move_file_invalid_into_descendent">ã\83\95ã\82©ã\83«ã\83\80ã\83¼ã\82\92å­\90ã\83\95ã\82©ã\83«ã\83\80ã\83¼へ移動することはできません。</string>
+  <string name="move_file_invalid_overwrite">そのファイルは宛先フォルダーにすでに存在します。</string>
   <string name="move_file_error">このファイルまたはフォルダーを移動する際にエラーが発生しました</string>
   <string name="forbidden_permissions_move">このファイルを移動</string>
   <string name="prefs_category_instant_uploading">自動アップロード</string>
   <string name="prefs_category_security">セキュリティ</string>
+  <string name="prefs_instant_video_upload_path_title">動画のアップロードパス</string>
+  <string name="download_folder_failed_content">%1$s のフォルダのダウンロードが完了しませんでした。</string>
+  <string name="shared_subject_header">共有中</string>
+  <string name="with_you_subject_header">あなたと</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index f0efc92..7836c23 100644 (file)
   <string name="clipboard_text_copied">კოპირებულია კლიპბორდში</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">ანგარიში</string>
-  <string name="move_choose_button_text">არჩევა</string>
+  <string name="folder_picker_choose_button_text">არჩევა</string>
   <string name="prefs_category_security">უსაფრთხოება</string>
 </resources>
index 75c68d7..535f76b 100644 (file)
@@ -1,17 +1,27 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
+  <string name="about_android">%1$s កម្មវិធីអានដ្រយ</string>
+  <string name="about_version">ជំនាន់ %1$s</string>
   <string name="actionbar_upload">ផ្ទុក​ឡើង</string>
   <string name="actionbar_upload_files">ឯកសារ</string>
+  <string name="actionbar_open_with">បើកជាមួយ</string>
   <string name="actionbar_mkdir">ថត​ថ្មី</string>
   <string name="actionbar_settings">ការកំណត់</string>
   <string name="actionbar_see_details">ព័ត៌មាន​លម្អិត</string>
   <string name="actionbar_send_file">ផ្ញើ</string>
+  <string name="actionbar_sort">តម្រៀប</string>
+  <string name="actionbar_sort_title">តម្រៀបដោយ</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Z</item>
+    <item>ថ្មីបំផុត-ចាស់បំផុត</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">ទូទៅ</string>
   <string name="prefs_category_more">ច្រើន​ទៀត</string>
   <string name="prefs_accounts">គណនី</string>
   <string name="prefs_manage_accounts">គ្រប់គ្រង​គណនី</string>
+  <string name="prefs_pincode">ភីន​កូដ កម្មវិធី</string>
   <string name="prefs_help">ជំនួយ</string>
   <string name="auth_username">ឈ្មោះ​អ្នកប្រើ</string>
   <string name="auth_password">ពាក្យសម្ងាត់</string>
@@ -41,6 +51,7 @@
   <string name="common_error">កំហុស</string>
   <string name="common_loading">កំពុងដំណើរការ</string>
   <string name="common_error_unknown">មិន​ស្គាល់​កំហុស</string>
+  <string name="about_title">អំពី</string>
   <string name="change_password">ប្តូរ​ពាក្យសម្ងាត់</string>
   <string name="delete_account">លប់គណនី</string>
   <string name="create_account">បង្កើតគណនី</string>
@@ -69,6 +80,9 @@
   <string name="fd_keep_in_sync">រក្សាឯកសាររហូតដល់កាលបរិច្ឆេទ</string>
   <string name="common_rename">ប្ដូរ​ឈ្មោះ</string>
   <string name="common_remove">ដកចេញ</string>
+  <string name="confirmation_remove_local">ទីកន្លែងតែមួយ</string>
+  <string name="confirmation_remove_remote">ដកចេញពី​សឺវឺ</string>
+  <string name="confirmation_remove_remote_and_local">បញ្ជារ និងទីតាំង</string>
   <string name="remove_success_msg">ការដកយកចេញបានជោគជ័យ</string>
   <string name="remove_fail_msg">ការដកយកចេញបានបរាជ័យ</string>
   <string name="rename_dialog_title">បញ្ចូលឈ្មោះថ្មី</string>
@@ -85,6 +99,6 @@
   <string name="empty"></string>
   <string name="prefs_category_accounts">គណនី</string>
   <string name="saml_authentication_wrong_pass">ខុស​ពាក្យ​សម្ងាត់</string>
-  <string name="move_choose_button_text">ជ្រើស</string>
+  <string name="folder_picker_choose_button_text">ជ្រើស</string>
   <string name="prefs_category_security">សុវត្ថិភាព</string>
 </resources>
index 69623e1..f55428e 100644 (file)
@@ -1,6 +1,35 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
+  <string name="actionbar_upload">ಪೇರಿಸು</string>
+  <string name="actionbar_upload_files">ಕಡತಗಳು</string>
+  <string name="actionbar_mkdir">ಹೊಸ ಕಡತಕೋಶ</string>
+  <string name="actionbar_settings">ಆಯ್ಕೆ</string>
+  <string name="actionbar_send_file">ಕಳುಹಿಸಿ</string>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
+  <string name="prefs_category_more">ಇನ್ನಷ್ಟು</string>
+  <string name="prefs_help">ಸಹಾಯ</string>
+  <string name="prefs_imprint">ಮುದ್ರೆ</string>
+  <string name="auth_username">ಬಳಕೆಯ ಹೆಸರು</string>
+  <string name="auth_password">ಗುಪ್ತ ಪದ</string>
+  <string name="sync_string_files">ಕಡತಗಳು</string>
+  <string name="uploader_btn_upload_text">ಪೇರಿಸು</string>
+  <string name="filedetails_download">ಪ್ರತಿಯನ್ನು ಸ್ಥಳೀಯವಾಗಿ ಉಳಿಸಿಕೊಳ್ಳಿ</string>
+  <string name="action_share_file">ಸಂಪರ್ಕ ಕೊಂಡಿಯನ್ನು  ಹಂಚಿಕೊಳ್ಳಬಹುದು</string>
+  <string name="common_yes">ಹೌದು</string>
+  <string name="common_no">ಇಲ್ಲ</string>
+  <string name="common_ok">ಸರಿ</string>
+  <string name="common_cancel_upload">ವರ್ಗಾವಣೆ ರದ್ದು ಮಾಡಿ</string>
+  <string name="common_cancel">ರದ್ದು</string>
+  <string name="common_error">ತಪ್ಪಾಗಿದೆ</string>
+  <string name="common_error_unknown">ಗೊತ್ತಿಲ್ಲದ ದೋಷ</string>
+  <string name="change_password">ಗುಪ್ತ ಪದವನ್ನು ಬದಲಾಯಿಸಿ</string>
+  <string name="common_rename">ಮರುಹೆಸರಿಸು</string>
+  <string name="common_remove">ತೆಗೆದುಹಾಕಿ</string>
+  <string name="activity_chooser_send_file_title">ಕಳುಹಿಸಿ</string>
   <string name="empty"></string>
+  <string name="saml_authentication_required_text">ದೃಢೀಕರಣ ಅಗತ್ಯವಿದೆ</string>
+  <string name="saml_authentication_wrong_pass">ದುರ್ಬಲ ಗುಪ್ತಪದ</string>
+  <string name="folder_picker_choose_button_text">ಆಯ್ಕೆ</string>
+  <string name="prefs_category_security">ಭದ್ರತೆ</string>
 </resources>
index a298ff5..e6cbb50 100644 (file)
@@ -6,29 +6,42 @@
   <string name="actionbar_upload">업로드</string>
   <string name="actionbar_upload_from_apps">다른 앱의 콘텐츠</string>
   <string name="actionbar_upload_files">파일</string>
-  <string name="actionbar_open_with">로 열기</string>
+  <string name="actionbar_open_with">ë\8b¤ì\9d\8cì\9c¼ë¡\9c ì\97´ê¸°</string>
   <string name="actionbar_mkdir">새 폴더</string>
   <string name="actionbar_settings">설정</string>
-  <string name="actionbar_see_details">ì\84¸ë¶\80ë\82´ì\9a©</string>
+  <string name="actionbar_see_details">ì\9e\90ì\84¸í\95\9c ì \95ë³´</string>
   <string name="actionbar_send_file">보내기</string>
+  <string name="actionbar_sort">정렬</string>
+  <string name="actionbar_sort_title">정렬 순서</string>
+  <string-array name="actionbar_sortby">
+    <item>가나다</item>
+    <item>최신 - 이전</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">일반</string>
-  <string name="prefs_category_more">더 중요함</string>
+  <string name="prefs_category_more">더 보기</string>
   <string name="prefs_accounts">계정</string>
   <string name="prefs_manage_accounts">계정 관리</string>
   <string name="prefs_pincode">앱 암호</string>
   <string name="prefs_pincode_summary">내 클라이언트 보호</string>
-  <string name="prefs_log_title">로깅 허용</string>
-  <string name="prefs_log_summary">이건 로그 문제에 사용됩니다</string>
+  <string name="prefs_instant_upload">사진 즉시 업로드</string>
+  <string name="prefs_instant_upload_summary">카메라로 찍은 사진 즉시 업로드</string>
+  <string name="prefs_instant_video_upload">동영상 즉시 업로드</string>
+  <string name="prefs_instant_video_upload_summary">카메라로 찍은 동영상 즉시 업로드</string>
+  <string name="prefs_log_title">로그 기록 사용</string>
+  <string name="prefs_log_summary">문제점을 기록하는 데 사용됩니다</string>
   <string name="prefs_log_title_history">로그 기록</string>
   <string name="prefs_log_summary_history">여기서 기록된 로그를 보여줍니다</string>
-  <string name="prefs_log_delete_history_button">역사 삭제하기</string>
+  <string name="prefs_log_delete_history_button">과거 기록 삭제</string>
   <string name="prefs_help">도움말</string>
-  <string name="prefs_recommend">친구들에게 권하기</string>
+  <string name="prefs_recommend">친구에게 추천하기</string>
   <string name="prefs_feedback">피드백</string>
-  <string name="prefs_imprint">임프린트</string>
-  <string name="recommend_subject">%1$s 을 스마트폰에서 사용해보세요!</string>
+  <string name="prefs_imprint">법적 고지</string>
+  <string name="prefs_remember_last_share_location">공유 위치 기억하기</string>
+  <string name="prefs_remember_last_upload_location_summary">마지막 공유 업로드 위치 기억하기</string>
+  <string name="recommend_subject">%1$s을(를) 스마트폰에서 사용해 보세요!</string>
+  <string name="recommend_text">%1$s을(를) 스마트폰에서 사용해 보는 것을 추천합니다!\n다운로드 링크: %2$s</string>
   <string name="auth_check_server">서버 확인</string>
   <string name="auth_host_url">서버 주소 https://…</string>
   <string name="auth_username">사용자 이름</string>
@@ -37,6 +50,7 @@
   <string name="sync_string_files">파일</string>
   <string name="setup_btn_connect">접속</string>
   <string name="uploader_btn_upload_text">업로드</string>
+  <string name="uploader_top_message">업로드 폴더 선택:</string>
   <string name="uploader_wrn_no_account_title">계정 없음</string>
   <string name="uploader_wrn_no_account_text">이 장치에 %1$s 계정이 없습니다. 먼저 계정을 설정하십시오.</string>
   <string name="uploader_wrn_no_account_setup_btn_text">설정</string>
   <string name="uploader_wrn_no_content_text">받은 콘텐츠가 없습니다. 업로드할 항목이 없습니다.</string>
   <string name="uploader_error_forbidden_content">%1$s에서 공유된 콘텐츠에 접근할 수 없습니다</string>
   <string name="uploader_info_uploading">업로드 중</string>
-  <string name="file_list_seconds_ago">ì´\88 ì \84</string>
+  <string name="file_list_seconds_ago">ì´\88 ì§\80ë\82¨</string>
   <string name="file_list_empty">내용이 없습니다. 업로드할 수 있습니다!</string>
+  <string name="file_list_loading">불러오는 중...</string>
+  <string name="local_file_list_empty">이 폴더에 파일이 없습니다.</string>
+  <string name="file_list_folder">폴더</string>
+  <string name="file_list_folders">폴더</string>
+  <string name="file_list_file">파일</string>
+  <string name="file_list_files">파일</string>
   <string name="filedetails_select_file">파일을 누르면 추가 정보가 표시됩니다.</string>
   <string name="filedetails_size">크기:</string>
   <string name="filedetails_type">종류:</string>
   <string name="filedetails_created">만든 날짜:</string>
   <string name="filedetails_modified">수정한 날짜:</string>
   <string name="filedetails_download">다운로드</string>
-  <string name="filedetails_sync_file">파일 새로고침</string>
+  <string name="filedetails_sync_file">파일 새로 고침</string>
   <string name="filedetails_renamed_in_upload_msg">업로드 중 파일 이름을 %1$s(으)로 변경하였습니다</string>
   <string name="action_share_file">링크 공유</string>
+  <string name="action_unshare_file">링크 공유 해제</string>
   <string name="common_yes">예</string>
   <string name="common_no">아니요</string>
   <string name="common_ok">확인</string>
@@ -65,7 +86,7 @@
   <string name="common_save_exit">저장하고 끝내기</string>
   <string name="common_error">오류</string>
   <string name="common_loading">불러오는 중...</string>
-  <string name="common_error_unknown">알없는 오류</string>
+  <string name="common_error_unknown">알 수 없는 오류</string>
   <string name="about_title">정보</string>
   <string name="change_password">암호 변경</string>
   <string name="delete_account">계정 삭제</string>
   <string name="uploader_upload_succeeded_ticker">업로드 성공</string>
   <string name="uploader_upload_succeeded_content_single">%1$s을(를) 업로드하였습니다</string>
   <string name="uploader_upload_failed_ticker">업로드 실패</string>
-  <string name="uploader_upload_failed_content_single">%1$s을(를) 업로드할 수 없었습니다</string>
+  <string name="uploader_upload_failed_content_single">%1$s을(를) 업로드할 수 없습니다</string>
+  <string name="uploader_upload_failed_credentials_error">업로드가 실패하였습니다. 다시 로그인하십시오</string>
   <string name="downloader_download_in_progress_ticker">다운로드 중...</string>
   <string name="downloader_download_in_progress_content">%1$d%% %2$s 다운로드 중</string>
   <string name="downloader_download_succeeded_ticker">다운로드 성공</string>
   <string name="downloader_download_succeeded_content">%1$s을(를) 다운로드하였습니다</string>
   <string name="downloader_download_failed_ticker">다운로드 실패</string>
-  <string name="downloader_download_failed_content">%1$sì\9d\84(를) ë\8b¤ì\9a´ë¡\9cë\93\9cí\95  ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+  <string name="downloader_download_failed_content">%1$s을(를) 다운로드할 수 없습니다</string>
   <string name="downloader_not_downloaded_yet">아직 다운로드 되지 않았습니다</string>
+  <string name="downloader_download_failed_credentials_error">다운로드가 실패하였습니다. 다시 로그인하십시오</string>
   <string name="common_choose_account">계정 선택</string>
   <string name="sync_fail_ticker">동기화 실패</string>
+  <string name="sync_fail_ticker_unauthorized">동기화가 실패하였습니다. 다시 로그인하십시오</string>
   <string name="sync_fail_content">%1$s와(과) 동기화할 수 없었습니다</string>
-  <string name="sync_fail_content_unauthorized">%1$sì\97\90 ë\8c\80í\95\9c ë¹\84ë°\80ë²\88í\98¸ê°\80 í\8b\80립니다</string>
+  <string name="sync_fail_content_unauthorized">%1$sì\9d\98 ì\95\94í\98¸ê°\80 ì\98¬ë°\94르ì§\80 ì\95\8aì\8aµ니다</string>
   <string name="sync_conflicts_in_favourites_ticker">충돌하는 항목 발견됨</string>
-  <string name="sync_conflicts_in_favourites_content">ë\8f\99기í\99\94ë\90\9c í\8c\8cì\9d¼ ì¤\91 %1$dê°\9c를 ë\8f\99기í\99\94í\95  ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+  <string name="sync_conflicts_in_favourites_content">동기화된 파일 중 %1$d개를 동기화할 수 없습니다</string>
   <string name="sync_fail_in_favourites_ticker">파일을 동기화할 수 없었습니다</string>
-  <string name="sync_fail_in_favourites_content">파일 %1$d개의 내용을 동기화할 수 없었습니다 (충돌 %2$d개)</string>
-  <string name="sync_foreign_files_forgotten_ticker">몇몇 로컬 파일이 사라졌습니다.</string>
-  <string name="sync_current_folder_was_removed">%1$s 폴더가 존재하지 않습니다.</string>
-  <string name="foreign_files_move">모두 옮김</string>
-  <string name="foreign_files_success">모든 파일 옮김</string>
-  <string name="foreign_files_fail">몇몇 파일을 옮기지 못했습니다.</string>
+  <string name="sync_fail_in_favourites_content">파일 %1$d개의 내용을 동기화할 수 없습니다 (충돌 %2$d개)</string>
+  <string name="sync_foreign_files_forgotten_ticker">일부 로컬 파일이 사라졌습니다.</string>
+  <string name="sync_foreign_files_forgotten_content">폴더 %2$s의 파일 중 %1$d개를 복사할 수 없습니다</string>
+  <string name="sync_foreign_files_forgotten_explanation">버전 1.3.16부터는 하나의 파일이 여러 계정과 동기화될 때 데이터 손실을 막기 위해서 이 장치에서 업로드된 파일은 로컬 폴더 %1$s(으)로 복사됩니다.\n\n이 변경 사항 때문에 이 앱의 이전 버전에서 업로드된 모든 파일은 폴더 %2$s(으)로 복사되었습니다. 계정 동기화 중 오류가 발생하여 이 작업이 중단되었습니다. 파일을 그대로 둔 다음 %3$s(으)로 향한 링크를 삭제하거나, 파일을 직접 폴더 %1$s(으)로 이동한 다음 %4$s(으)로 향한 링크를 그대로 두십시오.\n\n아래 목록은 로컬 파일과 링크가 걸려 있는 %5$s에 있는 원격 파일입니다.</string>
+  <string name="sync_current_folder_was_removed">폴더 %1$s이(가) 더 이상 존재하지 않습니다.</string>
+  <string name="foreign_files_move">모두 이동</string>
+  <string name="foreign_files_success">모든 파일 이동됨</string>
+  <string name="foreign_files_fail">몇몇 파일을 이동할 수 없음</string>
   <string name="foreign_files_local_text">로컬: %1$s</string>
   <string name="foreign_files_remote_text">원격: %1$s</string>
+  <string name="upload_query_move_foreign_files">선택한 파일을 폴더 %1$s(으)로 복사할 공간이 부족합니다. 파일을 이동하시겠습니까?</string>
   <string name="pincode_enter_pin_code">앱 암호를 입력하십시오</string>
   <string name="pincode_configure_your_pin">앱 암호를 입력하십시오</string>
   <string name="pincode_configure_your_pin_explanation">앱을 시작할 때마다 암호를 물어봅니다</string>
   <string name="pincode_removed">앱 암호가 삭제되었습니다</string>
   <string name="pincode_stored">앱 암호가 저장되었습니다</string>
   <string name="media_notif_ticker">%1$s 음악 재생기</string>
-  <string name="media_state_playing">%1$s (재생중)</string>
+  <string name="media_state_playing">%1$s (재생 중)</string>
   <string name="media_state_loading">%1$s (불러오는 중)</string>
   <string name="media_event_done">%1$s 재생 완료됨</string>
-  <string name="media_err_nothing_to_play">미디어 파일을 찾을수 없습니다</string>
+  <string name="media_err_nothing_to_play">미디어 파일을 찾을 수 음</string>
   <string name="media_err_no_account">준비된 계정이 없습니다</string>
   <string name="media_err_not_in_owncloud">유효한 계정의 파일이 아닙니다</string>
   <string name="media_err_unsupported">지원하지 않는 미디어 코덱</string>
-  <string name="media_err_io">미디어 파일을 읽을수 </string>
+  <string name="media_err_io">미디어 파일을 읽을 수 없음</string>
   <string name="media_err_malformed">미디어 파일이 제대로 인코드 되지 않았습니다</string>
   <string name="media_err_timeout">재생 시도 중 시간이 초과됨</string>
-  <string name="media_err_invalid_progressive_playback">미디어 파일을 스트리밍 할수 없습니다</string>
-  <string name="media_err_unknown">내장된 미디어 플레이어에서는 이 미디어 파일을 재생할수 없습니다</string>
-  <string name="media_err_security_ex">%1$s 를 재생하는 중에 보안오류가 발생함</string>
-  <string name="media_err_io_ex">%1$s 를 재생하는 중에 입력 에러가 발생함</string>
-  <string name="media_err_unexpected">%1$s 를 재생하던 중에 알수 없는 오류가 발생함</string>
-  <string name="media_rewind_description">되감기 버튼</string>
-  <string name="media_play_pause_description">재생 혹은 일시정지 버튼</string>
-  <string name="media_forward_description">빨리감기 버튼</string>
-  <string name="auth_trying_to_login">로그인 중...</string>
+  <string name="media_err_invalid_progressive_playback">미디어 파일을 스트리밍 할 수 없습니다</string>
+  <string name="media_err_unknown">내장된 미디어 플레이어에서 이 미디어 파일을 재생할 수 없습니다</string>
+  <string name="media_err_security_ex">%1$s을(를) 재생하는 중 보안 오류가 발생함</string>
+  <string name="media_err_io_ex">%1$s을(를) 재생하는 중 입력 오류가 발생함</string>
+  <string name="media_err_unexpected">%1$s을(를) 재생하는 중 알 수 없는 오류가 발생함</string>
+  <string name="media_rewind_description">되감기 단추</string>
+  <string name="media_play_pause_description">재생 혹은 일시 정지 단추</string>
+  <string name="media_forward_description">빨리감기 단추</string>
+  <string name="auth_getting_authorization">인증 정보 가져오는 중...</string>
+  <string name="auth_trying_to_login">로그인 시도 중...</string>
   <string name="auth_no_net_conn_title">네트워크에 연결할 수 없습니다</string>
   <string name="auth_nossl_plain_ok_title">암호화된 연결을 사용할 수 없습니다.</string>
   <string name="auth_connection_established">연결됨</string>
   <string name="auth_testing_connection">연결 테스트 중...</string>
   <string name="auth_not_configured_title">서버 설정이 잘못됨</string>
   <string name="auth_account_not_new">같은 사용자와 서버에 대한 계정이 이미 존재합니다</string>
-  <string name="auth_account_not_the_same">ì\9e\85ë ¥ë\90\9c ì\82¬ì\9a©ì\9e\90ê°\80 ì\9d´ ê³\84ì \95ì\9d\98 ì\82¬ì\9a©ì\9e\90ì\99\80 ì\9d¼ì¹\98í\95\98ì§\80 ì\95\8aì\9d\8c</string>
+  <string name="auth_account_not_the_same">ì\9e\85ë ¥ë\90\9c ì\82¬ì\9a©ì\9e\90ê°\80 ì\9d´ ê³\84ì \95ì\9d\98 ì\82¬ì\9a©ì\9e\90ì\99\80 ì\9d¼ì¹\98í\95\98ì§\80 ì\95\8aì\8aµë\8b\88ë\8b¤</string>
   <string name="auth_unknown_error_title">알 수 없는 오류가 발생하였습니다!</string>
   <string name="auth_unknown_host_title">호스트를 찾을 수 없음</string>
   <string name="auth_incorrect_path_title">서버 인스턴스를 찾을 수 없음</string>
-  <string name="auth_timeout_title">ì\84\9cë²\84 ì\9d\91ë\8bµ ì\8b\9cê°\84ì\9d´ ì´\88ê³¼ë\90\98ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+  <string name="auth_timeout_title">ì\84\9cë²\84 ì\9d\91ë\8bµ ì\8b\9cê°\84ì\9d´ ì´\88ê³¼ë\90¨</string>
   <string name="auth_incorrect_address_title">잘못된 URL</string>
   <string name="auth_ssl_general_error_title">SSL 초기화 오류</string>
   <string name="auth_ssl_unverified_server_title">SSL 서버의 신원을 확인할수 없습니다</string>
   <string name="auth_bad_oc_version_title">확인할 수 없는 서버 버전</string>
   <string name="auth_wrong_connection_title">연결을 수립할 수 없음</string>
   <string name="auth_secure_connection">암호화된 연결 사용 중</string>
-  <string name="auth_unauthorized">잘못된 로그인/암호</string>
-  <string name="auth_oauth_error">권한부여가 성공적으로 이뤄지지 않았습니다</string>
-  <string name="auth_oauth_error_access_denied">권한 서버로 부터 접근이 거부되었습니다</string>
-  <string name="auth_wtf_reenter_URL">뜻밖의 상태; 다시 서버 주소를 입력해주십시오</string>
-  <string name="auth_expired_oauth_token_toast">인증이 만료되었습니다. 다시 인증해주세요</string>
-  <string name="auth_expired_basic_auth_toast">현재 암호를 </string>
-  <string name="auth_expired_saml_sso_token_toast">세션이 만료되었습니다. 다시 접속해주세요</string>
-  <string name="auth_connecting_auth_server">ì\9d¸ì¦\9d ì\84\9cë²\84ì\97\90 ì \91ì\86\8d하는 중...</string>
+  <string name="auth_unauthorized">잘못된 사용자 이름 및 암호</string>
+  <string name="auth_oauth_error">인증 실패</string>
+  <string name="auth_oauth_error_access_denied">인증 서버 접근 거부됨</string>
+  <string name="auth_wtf_reenter_URL">예상하지 못한 상태입니다. 서버 URL을 다시 입력해 주십시오</string>
+  <string name="auth_expired_oauth_token_toast">인증이 만료되었습니다. 다시 인증해 주십시오</string>
+  <string name="auth_expired_basic_auth_toast">현재 암호를 입력해 주십시오</string>
+  <string name="auth_expired_saml_sso_token_toast">세션이 만료되었습니다. 다시 접속해 주십시오</string>
+  <string name="auth_connecting_auth_server">ì\9d¸ì¦\9d ì\84\9cë²\84ì\97\90 ì\97°ê²°하는 중...</string>
   <string name="auth_unsupported_auth_method">서버에서 이 인증 방법을 지원하지 않습니다.</string>
-  <string name="auth_unsupported_multiaccount">%1$s 에서는 다중 계정을 지원하지 않습니다</string>
+  <string name="auth_unsupported_multiaccount">%1$s에서 다중 계정을 지원하지 않습니다</string>
+  <string name="auth_fail_get_user_name">서버에서 올바른 사용자 ID를 반환하지 않았습니다. 관리자에게 연락하십시오
+       </string>
+  <string name="auth_can_not_auth_against_server">이 서버에 인증할 수 없음</string>
   <string name="fd_keep_in_sync">파일을 최신 정보로 유지</string>
   <string name="common_rename">이름 바꾸기</string>
   <string name="common_remove">삭제</string>
+  <string name="confirmation_remove_alert">%1$s을(를) 삭제하시겠습니까?</string>
+  <string name="confirmation_remove_folder_alert">%1$s 및 포함된 내용을 삭제하시겠습니까?</string>
   <string name="confirmation_remove_local">로컬만</string>
   <string name="confirmation_remove_folder_local">로컬 콘텐츠만</string>
   <string name="confirmation_remove_remote">서버에서 삭제</string>
   <string name="confirmation_remove_remote_and_local">서버와 로컬 모두</string>
-  <string name="remove_success_msg">ì\84±ê³µì \81ì\9c¼ë¡\9c ì\82­ì \9cí\95\98ì\98\80ì\8aµë\8b\88ë\8b¤</string>
-  <string name="remove_fail_msg">ì\82­ì \9cí\95  ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+  <string name="remove_success_msg">ì\84±ê³µì \81ì\9c¼ë¡\9c ì\82­ì \9cí\95¨</string>
+  <string name="remove_fail_msg">ì\82­ì \9cí\95  ì\88\98 ì\97\86ì\9d\8c</string>
   <string name="rename_dialog_title">새 이름 입력</string>
   <string name="rename_local_fail_msg">로컬 파일의 이름을 변경할 수 없습니다. 다른 이름을 입력하십시오</string>
-  <string name="rename_server_fail_msg">이름을 변경할 수 없었습니다</string>
-  <string name="sync_file_fail_msg">원격 파일을 확인할 수 없었습니다</string>
-  <string name="sync_file_nothing_to_do_msg">파일 내용이 이미 동기화되었습니다</string>
-  <string name="filename_forbidden_characters">사용할수 없는 문자들: / \\ &lt; &gt; : \" | ? *</string>
+  <string name="rename_server_fail_msg">이름을 변경할 수 없음</string>
+  <string name="sync_file_fail_msg">원격 파일을 확인할 수 없음</string>
+  <string name="sync_file_nothing_to_do_msg">파일 내용이 이미 동기화됨</string>
+  <string name="create_dir_fail_msg">폴더를 만들 수 없음</string>
+  <string name="filename_forbidden_characters">사용할 수 없는 문자: / \\ &lt; &gt; : \" | ? *</string>
+  <string name="filename_empty">파일 이름이 비어 있을 수 없음</string>
   <string name="wait_a_moment">잠시 기다려 주십시오</string>
   <string name="filedisplay_unexpected_bad_get_content">예상하지 못한 오류입니다. 다른 앱에서 파일을 선택하십시오</string>
   <string name="filedisplay_no_file_selected">선택한 파일 없음</string>
+  <string name="activity_chooser_title">다음으로 링크 보내기...</string>
   <string name="oauth_check_onoff">oAuth2로 로그인하기</string>
-  <string name="oauth_login_connection">oAuth2 서버에 연결중...</string>
-  <string name="ssl_validator_header">ì\82¬ì\9d´í\8a¸ ì\9d¸ì¦\9dì\84\9c를 í\99\95ì\9d¸í\95  ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+  <string name="oauth_login_connection">oAuth2 서버에 연결 중...</string>
+  <string name="ssl_validator_header">사이트 인증서를 확인할 수 없습니다</string>
   <string name="ssl_validator_reason_cert_not_trusted">- 서버 인증서를 신뢰할 수 없습니다</string>
   <string name="ssl_validator_reason_cert_expired">- 서버 인증서가 만료되었습니다</string>
   <string name="ssl_validator_reason_cert_not_yet_valid">- 서버 인증서의 유효 기간이 시작되지 않았습니다</string>
   <string name="ssl_validator_reason_hostname_not_verified">- 인증서의 URL과 입력한 URL이 일치하지 않습니다</string>
   <string name="ssl_validator_question">이 인증서를 신뢰하시겠습니까?</string>
-  <string name="ssl_validator_not_saved">ì\9d¸ì¦\9dì\84\9c를 ì \80ì\9e¥í\95  ì\88\98 ì\97\86ì\97\88ì\8aµë\8b\88ë\8b¤</string>
+  <string name="ssl_validator_not_saved">인증서를 저장할 수 없습니다</string>
   <string name="ssl_validator_btn_details_see">자세히</string>
   <string name="ssl_validator_btn_details_hide">숨기기</string>
   <string name="ssl_validator_label_subject">발급 대상:</string>
   <string name="ssl_validator_label_validity_to">끝:</string>
   <string name="ssl_validator_label_signature">서명:</string>
   <string name="ssl_validator_label_signature_algorithm">알고리즘:</string>
-  <string name="placeholder_sentence">이것은 플레이스홀더입니다</string>
+  <string name="ssl_validator_null_cert">인증서를 표시할 수 없습니다.</string>
+  <string name="ssl_validator_no_info_about_error">- 오류에 대한 정보가 없습니다</string>
+  <string name="placeholder_sentence">이것은 자리 비움자입니다</string>
   <string name="placeholder_filename">placeholder.txt</string>
   <string name="placeholder_filetype">PNG 그림</string>
   <string name="placeholder_filesize">389 KB</string>
   <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
   <string name="placeholder_media_time">12:23:45</string>
-  <string name="instant_upload_on_wifi">WiFi 사용 중일때만 사진 업로드</string>
+  <string name="instant_upload_on_wifi">Wi-Fi 사용 중일때만 사진 업로드</string>
+  <string name="instant_video_upload_on_wifi">Wi-Fi 사용 중일때만 동영상 업로드</string>
   <string name="instant_upload_path">/InstantUpload</string>
   <string name="conflict_title">업데이트 충돌</string>
   <string name="conflict_message">원격 파일 %s이(가) 로컬 파일과 동기화되지 않았습니다. 계속 진행하면 서버에 있는 파일을 덮어씁니다.</string>
   <string name="conflict_keep_both">모두 저장</string>
   <string name="conflict_overwrite">덮어쓰기</string>
   <string name="conflict_dont_upload">업로드하지 않음</string>
-  <string name="preview_image_description">그림 미리보기</string>
+  <string name="preview_image_description">사진 미리 보기</string>
+  <string name="preview_image_error_unknown_format">이 사진을 미리 볼 수 없습니다</string>
+  <string name="error__upload__local_file_not_copied">%1$s을(를) 로컬 폴더 %2$s(으)로 복사할 수 없습니다</string>
+  <string name="prefs_instant_upload_path_title">업로드 경로</string>
+  <string name="share_link_no_support_share_api">서버에서 공유가 비활성화되어 있습니다. 관리자에게 연락하십시오.</string>
+  <string name="share_link_file_no_exist">공유할 수 없습니다. 파일이 있는지 확인하십시오</string>
+  <string name="share_link_file_error">이 파일이나 폴더를 공유하는 중 오류 발생</string>
+  <string name="unshare_link_file_no_exist">공유를 해제할 수 없습니다. 파일이 있는지 확인하십시오</string>
+  <string name="unshare_link_file_error">이 파일이나 폴더의 공유를 해제하는 중 오류 발생</string>
   <string name="activity_chooser_send_file_title">보내기</string>
-  <string name="copy_link">링크 복사</string>
+  <string name="copy_link">링크 주소 복사</string>
   <string name="clipboard_text_copied">클립보드로 복사됨</string>
+  <string name="error_cant_bind_to_operations_service">치명적 오류: 작업을 진행할 수 없음</string>
+  <string name="network_error_socket_exception">서버에 연결하는 중 오류가 발생하였습니다.</string>
+  <string name="network_error_socket_timeout_exception">서버를 기다리는 중 오류가 발생하였습니다. 작업이 진행되지 않았을 수도 있습니다</string>
+  <string name="network_error_connect_timeout_exception">서버를 기다리는 중 오류가 발생하였습니다. 작업이 진행되지 않았을 수도 있습니다</string>
+  <string name="network_host_not_available">서버를 사용할 수 없어서 작업을 진행할 수 없습니다</string>
   <string name="empty"></string>
+  <string name="forbidden_permissions">%s 권한이 없습니다</string>
+  <string name="forbidden_permissions_rename">이 파일의 이름을 바꿀</string>
+  <string name="forbidden_permissions_delete">이 파일을 삭제할</string>
+  <string name="share_link_forbidden_permissions">이 파일을 공유할</string>
+  <string name="unshare_link_forbidden_permissions">이 파일의 공유를 해제할</string>
+  <string name="forbidden_permissions_create">파일을 생성할</string>
+  <string name="uploader_upload_forbidden_permissions">이 폴더에 업로드할</string>
+  <string name="downloader_download_file_not_found">이 파일을 서버에서 더 이상 사용할 수 없습니다</string>
   <string name="prefs_category_accounts">계정</string>
+  <string name="prefs_add_account">계정 추가</string>
+  <string name="auth_redirect_non_secure_connection_title">보안 연결이 안전하지 않은 경로로 넘어갑니다.</string>
+  <string name="actionbar_logger">로그</string>
+  <string name="log_send_history_button">과거 기록 보내기</string>
+  <string name="log_send_no_mail_app">로그를 보낼 앱이 없습니다. 메일 앱을 설치하십시오!</string>
+  <string name="log_send_mail_subject">%1$s Android 앱 로그</string>
+  <string name="log_progress_dialog_text">데이터 불러오는 중...</string>
   <string name="saml_authentication_required_text">인증 필요함</string>
   <string name="saml_authentication_wrong_pass">잘못된 암호</string>
-  <string name="move_choose_button_text">선택</string>
+  <string name="actionbar_move">이동</string>
+  <string name="file_list_empty_moving">항목이 없습니다. 폴더를 추가할 수 있습니다!</string>
+  <string name="folder_picker_choose_button_text">선택</string>
+  <string name="move_file_not_found">이동할 수 없습니다. 파일이 존재하는 지 확인하십시오</string>
+  <string name="move_file_invalid_into_descendent">폴더를 하위 폴더 아래로 이동할 수 없습니다</string>
+  <string name="move_file_invalid_overwrite">파일이 이미 대상 폴더에 존재합니다</string>
+  <string name="move_file_error">이 파일이나 폴더를 이동하는 중 오류가 발생하였습니다</string>
+  <string name="forbidden_permissions_move">이 파일을 이동할</string>
+  <string name="prefs_category_instant_uploading">즉시 업로드</string>
   <string name="prefs_category_security">보안</string>
+  <string name="prefs_instant_video_upload_path_title">동영상 업로드 경로</string>
+  <string name="download_folder_failed_content">%1$s 폴더를 다운로드할 수 없습니다</string>
+  <string name="shared_subject_header">공유됨</string>
+  <string name="with_you_subject_header">나와</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 545c842..826c0a2 100644 (file)
   <string name="uploader_info_uploading">Eroplueden</string>
   <string name="file_list_seconds_ago">Sekonnen hir</string>
   <string name="file_list_empty">Hei ass näischt. Lued eppes rop!</string>
+  <string name="file_list_folder">Dossier</string>
+  <string name="file_list_folders">Dossieren</string>
+  <string name="file_list_file">Datei</string>
+  <string name="file_list_files">Dateien</string>
   <string name="filedetails_size">Gréisst:</string>
   <string name="filedetails_type">Typ:</string>
   <string name="filedetails_created">Erstallt:</string>
@@ -87,5 +91,5 @@
   <string name="activity_chooser_send_file_title">Schécken</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Accounten</string>
-  <string name="move_choose_button_text">Auswielen</string>
+  <string name="folder_picker_choose_button_text">Auswielen</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
new file mode 100644 (file)
index 0000000..69623e1
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+  <!--TODO re-enable when server-side folder size calculation is available   
+       <item>Biggest - Smallest</item>-->
+  <string name="empty"></string>
+</resources>
index 2f45be1..b8c3bb1 100644 (file)
   <string name="actionbar_settings">Nustatymai</string>
   <string name="actionbar_see_details">Informacija</string>
   <string name="actionbar_send_file">Siųsti</string>
+  <string name="actionbar_sort">Rikiuoti</string>
+  <string name="actionbar_sort_title">Rikiuoti pagal</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Z</item>
+    <item>Naujausi - Seniausi</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">Bendras</string>
@@ -32,6 +38,8 @@
   <string name="prefs_recommend">Rekomenduoti draugui</string>
   <string name="prefs_feedback">Atsiliepimai</string>
   <string name="prefs_imprint">Imprint</string>
+  <string name="prefs_remember_last_share_location">Prisiminti bendrinimo vietą</string>
+  <string name="prefs_remember_last_upload_location_summary">Prisiminti paskutinio bendrinimo įkėlimo vietą</string>
   <string name="recommend_subject">Išbandykite %1$s savo išmaniajame telefone!</string>
   <string name="auth_check_server">Patikrinti Serverį</string>
   <string name="auth_host_url">Serverio adresas </string>
   <string name="file_list_empty">Čia tuščia. Įkelkite ką nors!</string>
   <string name="file_list_loading">Įkeliama ...</string>
   <string name="local_file_list_empty">Šiame aplanke nėra failų.</string>
+  <string name="file_list_folder">katalogas</string>
+  <string name="file_list_folders">katalogai</string>
+  <string name="file_list_file">failas</string>
+  <string name="file_list_files">failai</string>
   <string name="filedetails_select_file">Palieskite failą, kad parodyti papildomą informaciją.</string>
   <string name="filedetails_size">Dydis:</string>
   <string name="filedetails_type">Tipas:</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Paskyros</string>
   <string name="saml_authentication_wrong_pass">Neteisingas slaptažodis</string>
-  <string name="move_choose_button_text">Pasirinkite</string>
+  <string name="folder_picker_choose_button_text">Pasirinkite</string>
   <string name="prefs_category_security">Saugumas</string>
+  <string name="shared_subject_header">bendrinamas</string>
 </resources>
index 39ea5bf..0895ca9 100644 (file)
   <string name="uploader_info_uploading">Augšupielādē</string>
   <string name="file_list_seconds_ago">sekundes atpakaļ</string>
   <string name="file_list_empty">Te vēl nekas nav. Rīkojies, sāc augšupielādēt!</string>
+  <string name="file_list_folder">mape</string>
+  <string name="file_list_folders">mapes</string>
+  <string name="file_list_file">fails</string>
+  <string name="file_list_files">faili</string>
   <string name="filedetails_select_file">Uzsitiet uz datnes, lai redzētu papildinformāciju.</string>
   <string name="filedetails_size">Izmērs:</string>
   <string name="filedetails_type">Tips:</string>
   <string name="activity_chooser_send_file_title">Sūtīt</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Konti</string>
-  <string name="move_choose_button_text">Izvēlieties</string>
+  <string name="saml_authentication_wrong_pass">Nepareiza parole</string>
+  <string name="folder_picker_choose_button_text">Izvēlieties</string>
   <string name="prefs_category_security">Drošība</string>
 </resources>
index 5333aae..23c3611 100644 (file)
   <string name="uploader_wrn_no_account_quit_btn_text">Прекини</string>
   <string name="file_list_seconds_ago">пред секунди</string>
   <string name="file_list_empty">Тука нема ништо. Снимете нешто!</string>
+  <string name="file_list_folder">фолдер</string>
+  <string name="file_list_folders">фолдери</string>
+  <string name="file_list_file">датотека</string>
+  <string name="file_list_files">датотеки</string>
   <string name="filedetails_size">Големина:</string>
   <string name="filedetails_type">Тип:</string>
   <string name="filedetails_created">Создадено:</string>
   <string name="prefs_category_accounts">Сметки</string>
   <string name="saml_authentication_required_text">Потребна е автентификација</string>
   <string name="saml_authentication_wrong_pass">Погрешна лозинка</string>
-  <string name="move_choose_button_text">Избери</string>
+  <string name="folder_picker_choose_button_text">Избери</string>
   <string name="prefs_category_security">Безбедност</string>
+  <string name="shared_subject_header">споделен</string>
 </resources>
index 69623e1..b4185fd 100644 (file)
@@ -1,6 +1,17 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
+  <string name="actionbar_upload">Байршуулах</string>
+  <string name="actionbar_upload_files">Файлууд</string>
+  <string name="actionbar_settings">Тохиргоо</string>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
+  <string name="prefs_category_general">Ерөнхий</string>
+  <string name="auth_username">Хэрэглэгчийн нэр</string>
+  <string name="auth_password">Нууц үг</string>
+  <string name="sync_string_files">Файлууд</string>
+  <string name="uploader_btn_upload_text">Байршуулах</string>
+  <string name="create_account">Аккаунт үүсгэх</string>
+  <string name="common_remove">Устгах</string>
   <string name="empty"></string>
+  <string name="prefs_category_security">Аюулгүй байдал</string>
 </resources>
index 808ab65..9e24a59 100644 (file)
   <string name="uploader_wrn_no_account_quit_btn_text">Berhenti</string>
   <string name="uploader_info_uploading">Memuatnaik</string>
   <string name="file_list_empty">Tiada apa-apa di sini. Muat naik sesuatu!</string>
+  <string name="file_list_folder">direktori</string>
+  <string name="file_list_folders">direktori</string>
+  <string name="file_list_file">fail</string>
+  <string name="file_list_files">fail</string>
   <string name="filedetails_size">Saiz</string>
   <string name="filedetails_type">Jenis</string>
   <string name="filedetails_created">Telah dibina:</string>
index 7ec9005..b016f79 100644 (file)
@@ -14,5 +14,5 @@
   <string name="common_ok">အိုကေ</string>
   <string name="common_cancel">ပယ်ဖျက်မည်</string>
   <string name="empty"></string>
-  <string name="move_choose_button_text">ရွေးချယ်</string>
+  <string name="folder_picker_choose_button_text">ရွေးချယ်</string>
 </resources>
index 1132651..859c7c9 100644 (file)
   <string name="actionbar_settings">Innstillinger</string>
   <string name="actionbar_see_details">Detaljer</string>
   <string name="actionbar_send_file">Send</string>
+  <string name="actionbar_sort">Sorter</string>
+  <string name="actionbar_sort_title">Sorter på</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Z</item>
+    <item>Nyeste - Eldste</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">Generelt</string>
@@ -32,6 +38,8 @@
   <string name="prefs_recommend">Anbefal til en venn</string>
   <string name="prefs_feedback">Tilbakemelding</string>
   <string name="prefs_imprint">Avtrykk</string>
+  <string name="prefs_remember_last_share_location">Husk delt plassering</string>
+  <string name="prefs_remember_last_upload_location_summary">Husk sist delt plassering for opplasting</string>
   <string name="recommend_subject">Prøv %1$s på smarttelefonen din!</string>
   <string name="recommend_text">Jeg ønsker å invitere deg til å bruke %1$s på smarttelefonen din!\nLast ned her: %2$s</string>
   <string name="auth_check_server">Sjekk server</string>
   <string name="file_list_empty">Ingenting her. Last opp noe!</string>
   <string name="file_list_loading">Laster...</string>
   <string name="local_file_list_empty">Det er ingen filer i denne mappen.</string>
+  <string name="file_list_folder">mappe</string>
+  <string name="file_list_folders">mapper</string>
+  <string name="file_list_file">fil</string>
+  <string name="file_list_files">filer</string>
   <string name="filedetails_select_file">Trykk på en fil for å vise ekstra informasjon.</string>
   <string name="filedetails_size">Størrelse:</string>
   <string name="filedetails_type">Type:</string>
   <string name="preview_image_description">Bildeforhåndsvisning</string>
   <string name="preview_image_error_unknown_format">Dette bildet kan ikke vises</string>
   <string name="error__upload__local_file_not_copied">%1$s kunne ikke kopieres til lokal mappe %2$s</string>
+  <string name="prefs_instant_upload_path_title">Sti til opplasting</string>
   <string name="share_link_no_support_share_api">Beklager, deling er ikke skrudd på for din tjener. Ta kontakt med
                administratoren.</string>
   <string name="share_link_file_no_exist">Kan ikke dele. Sjekk om filen eksisterer.</string>
   <string name="downloader_download_file_not_found">Filen finnes ikke på serveren lenger</string>
   <string name="prefs_category_accounts">Kontoer</string>
   <string name="prefs_add_account">Legg til en konto</string>
+  <string name="auth_redirect_non_secure_connection_title">Sikker forbindelse er omdirigert til en usikker rute.</string>
   <string name="actionbar_logger">Logger</string>
   <string name="log_send_history_button">Send historikk</string>
-  <string name="log_mail_subject">logger for ownCloud Android app</string>
+  <string name="log_send_no_mail_app">Ingen app for sending av logger funnet. Installer epost-app!</string>
+  <string name="log_send_mail_subject">%1$s Android app logger</string>
   <string name="log_progress_dialog_text">Laster data...</string>
   <string name="saml_authentication_required_text">Autentisering kreves</string>
   <string name="saml_authentication_wrong_pass">Feil passord</string>
   <string name="actionbar_move">Flytt</string>
   <string name="file_list_empty_moving">Ingenting her. Du kan legge til en mappe!</string>
-  <string name="move_choose_button_text">Velg</string>
+  <string name="folder_picker_choose_button_text">Velg</string>
   <string name="move_file_not_found">Kan ikke flytte. Sjekk om filen eksisterer.</string>
   <string name="move_file_invalid_into_descendent">Det er ikke mulig å flytte en mappe inn i sin egen undermappe</string>
   <string name="move_file_invalid_overwrite">Filen finnes allerede i målmappen</string>
   <string name="move_file_error">En feil oppstod ved flytting av denne filen eller mappen</string>
   <string name="forbidden_permissions_move">å flytte denne filen</string>
+  <string name="prefs_category_instant_uploading">Umiddelbare opplastinger</string>
   <string name="prefs_category_security">Sikkerhet</string>
+  <string name="prefs_instant_video_upload_path_title">Sti til video-opplasting</string>
+  <string name="shared_subject_header">delt</string>
+  <string name="with_you_subject_header">med deg</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index e0bcaa4..482a279 100644 (file)
@@ -64,6 +64,10 @@ Download hier: %2$s</string>
   <string name="file_list_empty">Er bevindt zich hier niets. Upload een bestand!</string>
   <string name="file_list_loading">Laden ...</string>
   <string name="local_file_list_empty">Er staan geen bestanden in deze map.</string>
+  <string name="file_list_folder">map</string>
+  <string name="file_list_folders">mappen</string>
+  <string name="file_list_file">bestand</string>
+  <string name="file_list_files">bestanden</string>
   <string name="filedetails_select_file">Druk op een bestand om extra informatie weer te geven</string>
   <string name="filedetails_size">Grootte:</string>
   <string name="filedetails_type">Type:</string>
@@ -281,13 +285,14 @@ Hieronder staan de lokale bestanden en de externe bestanden in %5$s waar ze naar
   <string name="auth_redirect_non_secure_connection_title">De beveiligde verbinding is omgeleid naar een onveilige route.</string>
   <string name="actionbar_logger">Logs</string>
   <string name="log_send_history_button">Verstuur geschiedenis</string>
-  <string name="log_mail_subject">ownCloud Android app logs</string>
+  <string name="log_send_no_mail_app">Geen app voor versturen van logs gevonden. Installeer de mail app!</string>
+  <string name="log_send_mail_subject">%1$s Android app logs</string>
   <string name="log_progress_dialog_text">Laden data...</string>
   <string name="saml_authentication_required_text">Authenticatie vereist</string>
   <string name="saml_authentication_wrong_pass">Onjuist wachtwoord</string>
   <string name="actionbar_move">verplaatsen</string>
   <string name="file_list_empty_moving">Niets hier. U kunt een map toevoegen!</string>
-  <string name="move_choose_button_text">Kies</string>
+  <string name="folder_picker_choose_button_text">Kies</string>
   <string name="move_file_not_found">Kan niet verplaatsen. Ga na of het bestand wel bestaat</string>
   <string name="move_file_invalid_into_descendent">De map kan niet naar een onderliggende map worden verplaatst</string>
   <string name="move_file_invalid_overwrite">Het bestand bestaat al in de doelmap</string>
@@ -295,4 +300,9 @@ Hieronder staan de lokale bestanden en de externe bestanden in %5$s waar ze naar
   <string name="forbidden_permissions_move">om dit bestand te verplaatsen</string>
   <string name="prefs_category_instant_uploading">Directe uploads</string>
   <string name="prefs_category_security">Beveiliging</string>
+  <string name="prefs_instant_video_upload_path_title">Upload Video Pad</string>
+  <string name="download_folder_failed_content">Download van %1$s map kon niet worden voltooid</string>
+  <string name="shared_subject_header">gedeeld</string>
+  <string name="with_you_subject_header">met u</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 9cf7ac6..930ff33 100644 (file)
   <string name="empty"></string>
   <string name="prefs_category_accounts">Kontoar</string>
   <string name="saml_authentication_wrong_pass">Feil passord</string>
-  <string name="move_choose_button_text">Vel</string>
+  <string name="folder_picker_choose_button_text">Vel</string>
   <string name="prefs_category_security">Tryggleik</string>
 </resources>
index e6a7eae..5d11e00 100644 (file)
   <string name="uploader_info_uploading">Al amontcargar</string>
   <string name="file_list_seconds_ago">segonda a</string>
   <string name="file_list_empty">Pas res dedins. Amontcarga qualquaren</string>
+  <string name="file_list_folder">Dorsièr</string>
+  <string name="file_list_folders">Dorsièrs</string>
+  <string name="file_list_file">fichièr</string>
+  <string name="file_list_files">fichièrs</string>
   <string name="filedetails_size">Talha :</string>
   <string name="filedetails_type">Tipe :</string>
   <string name="filedetails_created">Creat :</string>
@@ -44,5 +48,5 @@
   <string name="common_rename">Torna nomenar</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Comptes</string>
-  <string name="move_choose_button_text">Causís</string>
+  <string name="folder_picker_choose_button_text">Causís</string>
 </resources>
index bf5abd6..05d612d 100644 (file)
   <string name="activity_chooser_send_file_title">ਭੇਜੋ</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">ਅਕਾਊਂਟ</string>
-  <string name="move_choose_button_text">ਚੁਣੋ</string>
+  <string name="folder_picker_choose_button_text">ਚੁਣੋ</string>
 </resources>
index a5a7b7b..cc0f5b4 100644 (file)
@@ -38,6 +38,7 @@
   <string name="prefs_recommend">Poleć znajomemu</string>
   <string name="prefs_feedback">Wsparcie</string>
   <string name="prefs_imprint">Stopka</string>
+  <string name="prefs_remember_last_share_location">Zapamiętaj położenie udostępnienia</string>
   <string name="recommend_subject">Wypróbuj %1$s na swoim smartphonie!</string>
   <string name="recommend_text">Chciałbym zaprosić Cię do używania %1$s na swoim smartfonie!\nŚciągnij tutaj: %2$s</string>
   <string name="auth_check_server">Sprawdź serwer</string>
   <string name="file_list_empty">Pusto. Wyślij coś!</string>
   <string name="file_list_loading">Ładowanie...</string>
   <string name="local_file_list_empty">Nie ma plików w tym folderze.</string>
+  <string name="file_list_folder">folder</string>
+  <string name="file_list_folders">foldery</string>
+  <string name="file_list_file">plik</string>
+  <string name="file_list_files">pliki</string>
   <string name="filedetails_select_file">Dotknij plik aby wyświetlić dodatkowe informacje</string>
   <string name="filedetails_size">Rozmiar:</string>
   <string name="filedetails_type">Typ:</string>
   <string name="auth_redirect_non_secure_connection_title">Bezpieczne połączenie jest przekierowywane przez niezabezpieczone trasy.</string>
   <string name="actionbar_logger">Logi</string>
   <string name="log_send_history_button">Wyślij historię</string>
-  <string name="log_mail_subject">Logi aplikacji ownCloud Android</string>
+  <string name="log_send_mail_subject">%1$s Logi aplikacji Android</string>
   <string name="log_progress_dialog_text">Ładuję dane...</string>
   <string name="saml_authentication_required_text">Wymagana autoryzacja</string>
   <string name="saml_authentication_wrong_pass">Złe hasło</string>
   <string name="actionbar_move">Przenieś</string>
   <string name="file_list_empty_moving">Nic tu nie ma. Możesz dodać folder!</string>
-  <string name="move_choose_button_text">Wybierz</string>
+  <string name="folder_picker_choose_button_text">Wybierz</string>
   <string name="move_file_not_found">Nie można przenieść. Proszę sprawdzić, czy plik istnieje</string>
   <string name="move_file_invalid_into_descendent">Nie jest możliwe przeniesienie folderu do potomka</string>
   <string name="move_file_invalid_overwrite">Plik istnieje już w folderze docelowym</string>
   <string name="forbidden_permissions_move">aby przenieść ten plik</string>
   <string name="prefs_category_instant_uploading">Automatyczne wysyłanie</string>
   <string name="prefs_category_security">Bezpieczeństwo</string>
+  <string name="prefs_instant_video_upload_path_title">Katalog wysyłania dla wideo</string>
+  <string name="shared_subject_header">współdzielone</string>
 </resources>
index 512cfe5..5d015e8 100644 (file)
   <string name="file_list_empty">Nada aqui. Envie alguma coisa!</string>
   <string name="file_list_loading">Carregando...</string>
   <string name="local_file_list_empty">Não existe nenhum arquivo nesta pasta.</string>
+  <string name="file_list_folder">pasta</string>
+  <string name="file_list_folders">pastas</string>
+  <string name="file_list_file">arquivo</string>
+  <string name="file_list_files">arquivos</string>
   <string name="filedetails_select_file">Toque em um arquivo para mostrar informações adicionais.</string>
   <string name="filedetails_size">Tamanho:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="auth_redirect_non_secure_connection_title">Conexão segura esta redirecionada para uma rota não segura.</string>
   <string name="actionbar_logger">Logs</string>
   <string name="log_send_history_button">Enviar Histórico</string>
-  <string name="log_mail_subject">Logs do aplicativo ownCloud Android</string>
-  <string name="log_progress_dialog_text">Carregamento de dados...</string>
+  <string name="log_send_no_mail_app">Não foi encontrado nenhum app para envio de logs. Instale o mail app!</string>
+  <string name="log_send_mail_subject">%1$s logs do Android app</string>
+  <string name="log_progress_dialog_text">Carregando dados...</string>
   <string name="saml_authentication_required_text">Autenticação é requerida</string>
   <string name="saml_authentication_wrong_pass">Senha incorreta</string>
   <string name="actionbar_move">Mover</string>
   <string name="file_list_empty_moving">Nada aqui. Você pode adicionar uma pasta!</string>
-  <string name="move_choose_button_text">Escolher</string>
+  <string name="folder_picker_choose_button_text">Escolher</string>
   <string name="move_file_not_found">Não é possível mover. Por favor verifique se o arquivo existe</string>
   <string name="move_file_invalid_into_descendent">Não é possível mover a pasta para uma descendente</string>
   <string name="move_file_invalid_overwrite">O arquivo já existe na pasta de destino</string>
   <string name="forbidden_permissions_move">mover este arquivo</string>
   <string name="prefs_category_instant_uploading">Envios Instantâneos</string>
   <string name="prefs_category_security">Segurança</string>
+  <string name="prefs_instant_video_upload_path_title">Enviar o Caminho do Vídeo</string>
+  <string name="download_folder_failed_content">Baixar %1$s da pasta não pode ser completado</string>
+  <string name="shared_subject_header">compartilhado</string>
+  <string name="with_you_subject_header">com você</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 072a085..efadb60 100644 (file)
@@ -40,8 +40,8 @@
   <string name="prefs_imprint">Imprint</string>
   <string name="prefs_remember_last_share_location">Lembrar localização de partilha</string>
   <string name="prefs_remember_last_upload_location_summary">Lembrar da última localização de envio de partilha</string>
-  <string name="recommend_subject">Experimente %1$s no seu smartphone!</string>
-  <string name="recommend_text">Quero convidar-te a usares %1$s no teu smartphone!\nFaz download aqui: %2$s</string>
+  <string name="recommend_subject">Test %1$s no seu smartphone!</string>
+  <string name="recommend_text">Eu quero convidar-te para usares %1$s no teu smartphone!\nTransfere  aqui: %2$s</string>
   <string name="auth_check_server">Verificar Servidor</string>
   <string name="auth_host_url">Endereço do servidor https://..</string>
   <string name="auth_username">Nome de Utilizador</string>
@@ -52,7 +52,7 @@
   <string name="uploader_btn_upload_text">Enviar</string>
   <string name="uploader_top_message">Escolha a pasta de envio:</string>
   <string name="uploader_wrn_no_account_title">A conta não foi encontrada</string>
-  <string name="uploader_wrn_no_account_text">Não tem nenhuma conta  %1$s no seu dispositivo. Por favor, configure primeiro uma conta.</string>
+  <string name="uploader_wrn_no_account_text">Não existe nenhuma conta %1$s no seu dispositivo. Por favor, configure primeiro uma conta.</string>
   <string name="uploader_wrn_no_account_setup_btn_text">Configurar</string>
   <string name="uploader_wrn_no_account_quit_btn_text">Sair</string>
   <string name="uploader_wrn_no_content_title">Sem conteúdo para enviar</string>
   <string name="uploader_error_forbidden_content">O  %1$s não está autorizado a aceder aos ficheiro partilhados.</string>
   <string name="uploader_info_uploading">A enviar</string>
   <string name="file_list_seconds_ago">segundos atrás</string>
-  <string name="file_list_empty">Vazio. Envie alguma coisa!</string>
+  <string name="file_list_empty">Aqui não existe nada. Envie alguma coisa!</string>
   <string name="file_list_loading">A carregar ...</string>
   <string name="local_file_list_empty">Não existem ficheiros nesta pasta.</string>
-  <string name="filedetails_select_file">Clique no ficheiro para visualizar informação adicional.</string>
+  <string name="file_list_folder">pasta</string>
+  <string name="file_list_folders">pastas</string>
+  <string name="file_list_file">ficheiro</string>
+  <string name="file_list_files">ficheiros</string>
+  <string name="filedetails_select_file">Toque num ficheiro para visualizar a informação adicional.</string>
   <string name="filedetails_size">Tamanho:</string>
   <string name="filedetails_type">Tipo:</string>
   <string name="filedetails_created">Criado:</string>
   <string name="filedetails_modified">Modificado:</string>
   <string name="filedetails_download">Transferir</string>
   <string name="filedetails_sync_file">Atualizar ficheiro</string>
-  <string name="filedetails_renamed_in_upload_msg">O nome do ficheiro foi alterado para %1$s durante o envio.</string>
+  <string name="filedetails_renamed_in_upload_msg">O ficheiro foi renomeado para %1$s durante o envio.</string>
   <string name="action_share_file">Partilhar a hiperligação</string>
-  <string name="action_unshare_file">Deixar de partilhar a ligação</string>
+  <string name="action_unshare_file">Deixar de partilhar a hiperligação</string>
   <string name="common_yes">Sim</string>
   <string name="common_no">Não</string>
-  <string name="common_ok">OK</string>
+  <string name="common_ok">ACEITAR</string>
   <string name="common_cancel_download">Cancelar a transferência</string>
   <string name="common_cancel_upload">Cancelar o envio</string>
   <string name="common_cancel">Cancelar</string>
   <string name="upload_chooser_title">Enviar de ...</string>
   <string name="uploader_info_dirname">Nome da pasta</string>
   <string name="uploader_upload_in_progress_ticker">A enviar ...</string>
-  <string name="uploader_upload_in_progress_content">A enviar %1$d%% , %2$s completo.</string>
-  <string name="uploader_upload_succeeded_ticker">Carregado com sucesso</string>
-  <string name="uploader_upload_succeeded_content_single">%1$s foi carregado com sucesso</string>
-  <string name="uploader_upload_failed_ticker">Carregamento falhou</string>
-  <string name="uploader_upload_failed_content_single">O envio do ficheiro %1$s não foi concluído.</string>
+  <string name="uploader_upload_in_progress_content">%1$d%%  A enviar %2$s</string>
+  <string name="uploader_upload_succeeded_ticker">Envio bem sucedido</string>
+  <string name="uploader_upload_succeeded_content_single">%1$s foi enviado com sucesso</string>
+  <string name="uploader_upload_failed_ticker">Não foi possível enviar</string>
+  <string name="uploader_upload_failed_content_single">Não foi possível concluir o envio de %1$s.</string>
   <string name="uploader_upload_failed_credentials_error">Falha no carregamento, é necessário fazer novo login</string>
-  <string name="downloader_download_in_progress_ticker">A transferir ...</string>
-  <string name="downloader_download_in_progress_content">%1$d%% A decarregar %2$s</string>
-  <string name="downloader_download_succeeded_ticker">Descarga com sucesso</string>
-  <string name="downloader_download_succeeded_content">%1$s foi descarregado com sucesso</string>
-  <string name="downloader_download_failed_ticker">Descarga falhou</string>
-  <string name="downloader_download_failed_content">O descarregamento %1$s não foi possível descarregar</string>
-  <string name="downloader_not_downloaded_yet">Não transferido</string>
-  <string name="downloader_download_failed_credentials_error">Falha no download, é necessário fazer login </string>
+  <string name="downloader_download_in_progress_ticker">A transferir...</string>
+  <string name="downloader_download_in_progress_content">%1$d%% A transferir %2$s</string>
+  <string name="downloader_download_succeeded_ticker">Transferência bem sucedida</string>
+  <string name="downloader_download_succeeded_content">%1$s foi transferido com sucesso</string>
+  <string name="downloader_download_failed_ticker">Transferência falhada</string>
+  <string name="downloader_download_failed_content">Não foi possível concluir a transferência de %1$s</string>
+  <string name="downloader_not_downloaded_yet">Ainda não foi transferido</string>
+  <string name="downloader_download_failed_credentials_error">Não foi possível transferir, tem de iniciar a sessão novamente</string>
   <string name="common_choose_account">Escolha a conta</string>
   <string name="sync_fail_ticker">Falhou a sincronização</string>
   <string name="sync_fail_ticker_unauthorized">Falhou a sincronização, necessita fazer um novo login</string>
   <string name="sync_foreign_files_forgotten_explanation">Tal como na versão 1.3.16, os ficheiros que foram enviados deste dispositivo são copiados para a pasta local %1$s para prevenir perda de dados quando um ficheiro é partilhado com várias contas.\n\nDevido a esta alteração, todos os ficheiros das versões anteriores foram copiados para a pasta %2$s. No entanto, um erro impediu a conclusão deste processo durante a sincronização da conta. Pode deixar o ficheiro(s) como estão e remover o link para %3$s, ou mover o(s) ficheiro(s) para a pasta %1$s e guardar o link para %4$s. \n\nEm baixo estão listados ficheiro(s) locais e remotos em %5$s que foram ligados.</string>
   <string name="sync_current_folder_was_removed">A pasta %1$s já não existe</string>
   <string name="foreign_files_move">Mover Todos</string>
-  <string name="foreign_files_success">Todos os ficheiros foram movidos</string>
+  <string name="foreign_files_success">Foram movidos todos os ficheiros</string>
   <string name="foreign_files_fail">Não foi possível mover alguns ficheiros</string>
   <string name="foreign_files_local_text">Local: %1$s</string>
   <string name="foreign_files_remote_text">Remoto: %1$s</string>
   <string name="upload_query_move_foreign_files">Não existe espaço disponível para copiar o ficheiro seleccionado para a pasta %1$s . Em vez disso deseja mover o ficheiro?</string>
-  <string name="pincode_enter_pin_code">Por favor escreva o PIN da Aplicação</string>
-  <string name="pincode_configure_your_pin">Escreva o PIN da Aplicação</string>
-  <string name="pincode_configure_your_pin_explanation">O PIN vai ser pedido todas as vezes que iniciar a aplicação.</string>
-  <string name="pincode_reenter_your_pincode">Volte a inserir o App PIN, por favor</string>
-  <string name="pincode_remove_your_pincode">Remover o PIN do aplicação.</string>
-  <string name="pincode_mismatch">Os códigos PIN introduzidos não são iguais.</string>
-  <string name="pincode_wrong">Código PIN Incorrecto.</string>
-  <string name="pincode_removed">PIN da aplicação removido</string>
-  <string name="pincode_stored">PIN da aplicação guardado</string>
+  <string name="pincode_enter_pin_code">Por favor, insira o PIN da App</string>
+  <string name="pincode_configure_your_pin">Insira o PIN da App</string>
+  <string name="pincode_configure_your_pin_explanation">O PIN será pedido sempre que a app seja iniciada.</string>
+  <string name="pincode_reenter_your_pincode">Por favor, reinsira o PIN da App</string>
+  <string name="pincode_remove_your_pincode">Remover o seu PIN da App</string>
+  <string name="pincode_mismatch">Os CÓDIGOS da APP não são iguais</string>
+  <string name="pincode_wrong">CÃ\93DIGO da App Incorreto</string>
+  <string name="pincode_removed">CÓDIGOS da App removido</string>
+  <string name="pincode_stored">CÓDIGO da App guardado</string>
   <string name="media_notif_ticker">%1$s leitor de música</string>
-  <string name="media_state_playing">A tocar: %1$s</string>
-  <string name="media_state_loading">%1$s (A carregar)</string>
+  <string name="media_state_playing">%1$s (a reproduzir)</string>
+  <string name="media_state_loading">%1$s (a carregar)</string>
   <string name="media_event_done">%1$s leitura terminada</string>
   <string name="media_err_nothing_to_play">Não foi encontrado nenhum ficheiro de média</string>
   <string name="media_err_no_account">Não foi fornecida conta</string>
   <string name="media_err_not_in_owncloud">O ficheiro não está numa conta válida</string>
-  <string name="media_err_unsupported">Codec de média não suportado</string>
-  <string name="media_err_io">Não foi possível reproduzir o ficheiro</string>
+  <string name="media_err_unsupported">Codec de multimédia não suportado</string>
+  <string name="media_err_io">Não foi possível ler o ficheiro de multimédia</string>
   <string name="media_err_malformed">Ficheiro erradamente codificado (codec)</string>
   <string name="media_err_timeout">O tempo de espera para jogar expirou</string>
   <string name="media_err_invalid_progressive_playback">O ficheiro não pode ser reproduzido (streaming)</string>
   <string name="media_err_security_ex">Erro de segurança a tentar reproduzir o ficheiro %1$s</string>
   <string name="media_err_io_ex">Erro de input a tentar reproduzir %1$s</string>
   <string name="media_err_unexpected">Erro inesperado a tentar reproduzir %1$s</string>
-  <string name="media_rewind_description">Botão de rebobinar</string>
-  <string name="media_play_pause_description">Botão Tocar/Pausa</string>
+  <string name="media_rewind_description">Botão de Retroceder</string>
+  <string name="media_play_pause_description">Botão de Reproduzir/Pausar</string>
   <string name="media_forward_description">Botão de avanço rápido</string>
   <string name="auth_getting_authorization">A obter autorização...</string>
-  <string name="auth_trying_to_login">A tentar entrar...</string>
+  <string name="auth_trying_to_login">A tentar iniciar a sessão...</string>
   <string name="auth_no_net_conn_title">Sem ligação à rede</string>
-  <string name="auth_nossl_plain_ok_title">Ligação segura indisponível</string>
+  <string name="auth_nossl_plain_ok_title">Ligação segura indisponível.</string>
   <string name="auth_connection_established">Ligação estabelecida</string>
   <string name="auth_testing_connection">A testar a ligação...</string>
   <string name="auth_not_configured_title">Configuração do servidor incorrecta.</string>
   <string name="auth_account_not_new">Uma conta para este utilizador e servidor já existe no dispositivo</string>
   <string name="auth_account_not_the_same">O utilizador que escreveu não coincide com o nome de utilizador desta conta</string>
   <string name="auth_unknown_error_title">Ocorreu um erro desconhecido!</string>
-  <string name="auth_unknown_host_title">Não é possível encontrar o servidor</string>
-  <string name="auth_incorrect_path_title">Instância servidor não encontrada</string>
-  <string name="auth_timeout_title">O servidor levou demasiado tempo a responder</string>
+  <string name="auth_unknown_host_title">Não foi possível encontrar o anfitrião</string>
+  <string name="auth_incorrect_path_title">Instância do servidor não encontrada</string>
+  <string name="auth_timeout_title">O servidor demorou muito tempo a responder</string>
   <string name="auth_incorrect_address_title">URL errado</string>
   <string name="auth_ssl_general_error_title">Inicialização de SSL falhou</string>
-  <string name="auth_ssl_unverified_server_title">Não foi possível verificar a identidade SSL do servidor</string>
+  <string name="auth_ssl_unverified_server_title">Não foi possível verificar a identidade do servidor SSL</string>
   <string name="auth_bad_oc_version_title">Versão do servidor não reconhecida</string>
   <string name="auth_wrong_connection_title">Não consegue estabelecer ligação</string>
   <string name="auth_secure_connection">Ligação segura estabelecida</string>
   <string name="auth_oauth_error_access_denied">Acesso negado pelo servidor</string>
   <string name="auth_wtf_reenter_URL">Estado inesperado, por favor, digite a URL do servidor novamente</string>
   <string name="auth_expired_oauth_token_toast">O prazo da sua autorização expirou. Por favor renove-a</string>
-  <string name="auth_expired_basic_auth_toast">Por favor, introduza a password actual</string>
+  <string name="auth_expired_basic_auth_toast">Por favor, insira a palavra-passe atual</string>
   <string name="auth_expired_saml_sso_token_toast">A sua sessão expirou. Por favor autentique-se de novo</string>
   <string name="auth_connecting_auth_server">A verificar a sua autenticação no servidor...</string>
   <string name="auth_unsupported_auth_method">O servidor não suporta este método de autenticação</string>
   <string name="auth_redirect_non_secure_connection_title">Ligação segura é redireccionada para um caminho inseguro.</string>
   <string name="actionbar_logger">Registos de Alterações</string>
   <string name="log_send_history_button">Enviar Histórico</string>
-  <string name="log_mail_subject">Registos das alterações da app ownCloud Android</string>
-  <string name="log_progress_dialog_text">A carregar os dados ...</string>
+  <string name="log_send_no_mail_app">Não foi encontrado o aplicativo para envio de registos . Instale app e-mail!</string>
+  <string name="log_send_mail_subject">%1$s Android Aplicação de Registyos</string>
+  <string name="log_progress_dialog_text">A carregar os dados...</string>
   <string name="saml_authentication_required_text">Autenticação necessária</string>
   <string name="saml_authentication_wrong_pass">Palavra-passe errada</string>
   <string name="actionbar_move">Mover</string>
   <string name="file_list_empty_moving">Não está aqui nada. Pode adicionar uma pasta!</string>
-  <string name="move_choose_button_text">Escolher</string>
+  <string name="folder_picker_choose_button_text">Escolher</string>
   <string name="move_file_not_found">Não é possível mover. Por favor, verifique se o ficheiro existe</string>
   <string name="move_file_invalid_into_descendent">Não é possível mover esta pasta deste modo</string>
   <string name="move_file_invalid_overwrite">O ficheiro já existe na pasta de destino</string>
   <string name="forbidden_permissions_move">para mover este ficheiro</string>
   <string name="prefs_category_instant_uploading">Envios Instantâneos</string>
   <string name="prefs_category_security">Segurança</string>
+  <string name="prefs_instant_video_upload_path_title">Envio do Caminho do Vídeo</string>
+  <string name="shared_subject_header">partilhado</string>
+  <string name="with_you_subject_header">consigo</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 5df31b3..74c1b55 100644 (file)
   <string name="prefs_manage_accounts">Administrare conturi</string>
   <string name="prefs_pincode">PIN-ul aplicaţiei</string>
   <string name="prefs_pincode_summary">Protejaţi-vă clientul</string>
-  <string name="prefs_instant_upload">Încărcare instanta de imagine</string>
-  <string name="prefs_instant_upload_summary">Încărca instantaneu imagini luate de camera</string>
+  <string name="prefs_instant_upload">Încărcare instantă de imagini</string>
+  <string name="prefs_instant_upload_summary">Încarcă instantant imagini luate cu camera</string>
   <string name="prefs_instant_video_upload">Încărcare instantă de videoclipuri.</string>
-  <string name="prefs_instant_video_upload_summary">Încarcă videoclipuri instant, filmate cu camera.</string>
+  <string name="prefs_instant_video_upload_summary">Încarcă instant videoclipuri înregistrate cu camera</string>
   <string name="prefs_log_title">Permite logarea</string>
   <string name="prefs_log_summary">Acesta este folosit pentru a înregistra problemele</string>
   <string name="prefs_log_title_history">Istoria logarilor</string>
   <string name="file_list_empty">Nimic aici. Încarcă ceva!</string>
   <string name="file_list_loading">Se incarca</string>
   <string name="local_file_list_empty">In acest folder nu sunt fisiere.</string>
+  <string name="file_list_folder">director</string>
+  <string name="file_list_folders">directoare</string>
+  <string name="file_list_file">fișier</string>
+  <string name="file_list_files">fișiere</string>
   <string name="filedetails_select_file">Selectati un fisier pentru a afisa informatia aditionala</string>
   <string name="filedetails_size">Mărime:</string>
   <string name="filedetails_type">Tip:</string>
   <string name="placeholder_media_time">12:23:45</string>
   <string name="instant_upload_on_wifi">Incarca poze doar via WiFi</string>
   <string name="instant_video_upload_on_wifi">Încarcă videoclipuri doar via WiFi</string>
-  <string name="instant_upload_path">/Încărcare instanta</string>
+  <string name="instant_upload_path">/Încărcare instantă</string>
   <string name="conflict_title">Actualizați conflictul</string>
   <string name="conflict_message">Fișierul de la distanță %s nu este sincronizat cu fișierul local. Continuand, se va înlocui conținutul fișierului de pe server.</string>
   <string name="conflict_keep_both">Pastreaza amandoua</string>
   <string name="downloader_download_file_not_found">Fișierul nu mai este disponibil pe server</string>
   <string name="prefs_category_accounts">Conturi</string>
   <string name="prefs_add_account">Adaugă cont</string>
-  <string name="log_progress_dialog_text">Se încarcă datele...</string>
   <string name="saml_authentication_required_text">Autentificare necesară</string>
   <string name="saml_authentication_wrong_pass">Parolă greșită</string>
   <string name="actionbar_move">Mutare</string>
   <string name="file_list_empty_moving">Nu este nimic aici. Poți adăuga un director!</string>
-  <string name="move_choose_button_text">Alege</string>
+  <string name="folder_picker_choose_button_text">Alege</string>
   <string name="forbidden_permissions_move">pentru a muta acest fișier</string>
+  <string name="prefs_category_instant_uploading">Încărcări instante</string>
   <string name="prefs_category_security">Securitate</string>
 </resources>
index 1f773f9..0c98373 100644 (file)
@@ -1,13 +1,13 @@
 <?xml version='1.0' encoding='UTF-8'?>
 <resources>
-  <string name="about_android">%1$s Ð\9fÑ\80иложение Ð´Ð»Ñ\8f Ð\90ндÑ\80оида</string>
-  <string name="about_version">Ð\92ерсия %1$s</string>
+  <string name="about_android">%1$s Ð´Ð»Ñ\8f Android</string>
+  <string name="about_version">версия %1$s</string>
   <string name="actionbar_sync">Обновить учетную запись</string>
   <string name="actionbar_upload">Загрузить</string>
   <string name="actionbar_upload_from_apps">Содержимое из других приложений</string>
   <string name="actionbar_upload_files">Файлы</string>
   <string name="actionbar_open_with">Открыть с помощью</string>
-  <string name="actionbar_mkdir">Новая папка</string>
+  <string name="actionbar_mkdir">Новый каталог</string>
   <string name="actionbar_settings">Настройки</string>
   <string name="actionbar_see_details">Подробно</string>
   <string name="actionbar_send_file">Отправить</string>
@@ -15,7 +15,7 @@
   <string name="actionbar_sort_title">Упорядочить по</string>
   <string-array name="actionbar_sortby">
     <item>А-Я</item>
-    <item>Новые - Старые</item>
+    <item>Новое - Старое</item>
   </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_more">Больше</string>
   <string name="prefs_accounts">Учётные записи</string>
   <string name="prefs_manage_accounts">Управление учётными записями</string>
-  <string name="prefs_pincode">App PIN</string>
+  <string name="prefs_pincode">PIN приложения</string>
   <string name="prefs_pincode_summary">Защитить ваш клиент</string>
-  <string name="prefs_instant_upload">Ð\91Ñ\8bÑ\81Ñ\82Ñ\80ая загрузка фотографий</string>
+  <string name="prefs_instant_upload">Ð\9cгновенная загрузка фотографий</string>
   <string name="prefs_instant_upload_summary">Немедленно загружать фотографии сделанные камерой</string>
-  <string name="prefs_instant_video_upload">Ð\91Ñ\8bÑ\81Ñ\82Ñ\80ая загрузка видео</string>
-  <string name="prefs_instant_video_upload_summary">Ð\91Ñ\8bÑ\81Ñ\82Ñ\80аÑ\8f Ð·Ð°Ð³Ñ\80Ñ\83зка Ð²Ð¸Ð´ÐµÐ¾ Ñ\81 ÐºÐ°Ð¼ÐµÑ\80Ñ\8b</string>
+  <string name="prefs_instant_video_upload">Ð\9cгновенная загрузка видео</string>
+  <string name="prefs_instant_video_upload_summary">Ð\9dемедленно Ð·Ð°Ð³Ñ\80Ñ\83жаÑ\82Ñ\8c Ð²Ð¸Ð´ÐµÐ¾ Ñ\81деланнÑ\8bе ÐºÐ°Ð¼ÐµÑ\80ой</string>
   <string name="prefs_log_title">Включить журналирование</string>
   <string name="prefs_log_summary">Используется для регистрации ошибок</string>
   <string name="prefs_log_title_history">Журнал</string>
@@ -38,6 +38,8 @@
   <string name="prefs_recommend">Рекомендовать другу</string>
   <string name="prefs_feedback">Обратная связь</string>
   <string name="prefs_imprint">Штамп</string>
+  <string name="prefs_remember_last_share_location">Запомнить расположение общего ресурса</string>
+  <string name="prefs_remember_last_upload_location_summary">Запомнить расположение загрузки последней публикации</string>
   <string name="recommend_subject">Попробуйте %1$s на вашем смартфоне!</string>
   <string name="recommend_text">Хочу предложить вам использовать %1$s на смартфоне!\nЗагрузить можно здесь: %2$s
        </string>
   <string name="sync_string_files">Файлы</string>
   <string name="setup_btn_connect">Подключиться</string>
   <string name="uploader_btn_upload_text">Загрузить</string>
-  <string name="uploader_top_message">Ð\92Ñ\8bбеÑ\80еÑ\82е Ð¿Ð°Ð¿ÐºÑ\83 для загрузки</string>
+  <string name="uploader_top_message">Ð\92Ñ\8bбеÑ\80иÑ\82е ÐºÐ°Ñ\82алог для загрузки</string>
   <string name="uploader_wrn_no_account_title">Учётная запись не найдена</string>
-  <string name="uploader_wrn_no_account_text">Ð\9dа Ð²Ð°Ñ\88ем Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ве Ð½ÐµÑ\82 Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bÑ\85 Ð·Ð°Ð¿Ð¸Ñ\81ей %1$s. Ð¡Ð½Ð°Ñ\87ала Ð½Ñ\83жно Ð½Ð°Ñ\81Ñ\82Ñ\80оиÑ\82Ñ\8c учётную запись.</string>
-  <string name="uploader_wrn_no_account_setup_btn_text">УÑ\81Ñ\82ановка</string>
+  <string name="uploader_wrn_no_account_text">Ð\9dа Ð²Ð°Ñ\88ем Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ве Ð½ÐµÑ\82 Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bÑ\85 Ð·Ð°Ð¿Ð¸Ñ\81ей %1$s. Ð\9fожалÑ\83йÑ\81Ñ\82а Ð½Ð°Ñ\81Ñ\82Ñ\80ойÑ\82е учётную запись.</string>
+  <string name="uploader_wrn_no_account_setup_btn_text">Ð\9dаÑ\81Ñ\82Ñ\80ойка</string>
   <string name="uploader_wrn_no_account_quit_btn_text">Выход</string>
   <string name="uploader_wrn_no_content_title">Нет содержимого для загрузки</string>
   <string name="uploader_wrn_no_content_text">Содержимое не получено. Нечего загружать.</string>
-  <string name="uploader_error_forbidden_content">%1$s не имеет доступа к опубликованным данным</string>
+  <string name="uploader_error_forbidden_content">Доступ к общему ресурсу для %1$s запрещен</string>
   <string name="uploader_info_uploading">Загрузка</string>
-  <string name="file_list_seconds_ago">только что</string>
+  <string name="file_list_seconds_ago">пару секунд назад</string>
   <string name="file_list_empty">Здесь ничего нет. Загрузите что-нибудь!</string>
   <string name="file_list_loading">Загрузка...</string>
-  <string name="local_file_list_empty">В данной папке нет файлов.</string>
+  <string name="local_file_list_empty">В этом каталоге нет файлов.</string>
+  <string name="file_list_folder">каталог</string>
+  <string name="file_list_folders">каталоги</string>
+  <string name="file_list_file">файл</string>
+  <string name="file_list_files">файлы</string>
   <string name="filedetails_select_file">Нажмите на файл для отображения дополнительной информации.</string>
   <string name="filedetails_size">Размер:</string>
   <string name="filedetails_type">Тип:</string>
@@ -71,7 +77,7 @@
   <string name="filedetails_sync_file">Обновить файл</string>
   <string name="filedetails_renamed_in_upload_msg">Файл был переименован в %1$s во время загрузки</string>
   <string name="action_share_file">Поделиться ссылкой</string>
-  <string name="action_unshare_file">Удалить ссылку</string>
+  <string name="action_unshare_file">УбÑ\80ать ссылку</string>
   <string name="common_yes">Да</string>
   <string name="common_no">Нет</string>
   <string name="common_ok">ОК</string>
   <string name="common_cancel">Отмена</string>
   <string name="common_save_exit">Сохранить и выйти</string>
   <string name="common_error">Ошибка</string>
-  <string name="common_loading">Ð\98дÑ\91Ñ\82 Ð·Ð°Ð³Ñ\80Ñ\83зка...</string>
+  <string name="common_loading">Ð\97агÑ\80Ñ\83зка ...</string>
   <string name="common_error_unknown">Неизвестная ошибка</string>
   <string name="about_title">О программе</string>
   <string name="change_password">Сменить пароль</string>
   <string name="delete_account">Удалить учётную запись</string>
   <string name="create_account">Создать учётную запись</string>
-  <string name="upload_chooser_title">Загрузить из...</string>
-  <string name="uploader_info_dirname">Ð\98мÑ\8f Ð¿Ð°Ð¿ÐºÐ¸</string>
-  <string name="uploader_upload_in_progress_ticker">Загрузка...</string>
-  <string name="uploader_upload_in_progress_content">%1$d%% Ð·Ð°Ð³Ñ\80Ñ\83зки %2$s</string>
+  <string name="upload_chooser_title">Загрузить из ...</string>
+  <string name="uploader_info_dirname">Ð\98мÑ\8f ÐºÐ°Ñ\82алога</string>
+  <string name="uploader_upload_in_progress_ticker">Загрузка ...</string>
+  <string name="uploader_upload_in_progress_content">%1$d%% Ð\97агÑ\80Ñ\83жаеÑ\82Ñ\81Ñ\8f %2$s</string>
   <string name="uploader_upload_succeeded_ticker">Загрузка завершена</string>
   <string name="uploader_upload_succeeded_content_single">%1$s был успешно загружен</string>
   <string name="uploader_upload_failed_ticker">Ошибка загрузки</string>
   <string name="uploader_upload_failed_content_single">Загрузка %1$s не может быть завершена</string>
-  <string name="uploader_upload_failed_credentials_error">Ð\97агÑ\80Ñ\83зка Ð½Ðµ Ñ\83далаÑ\81Ñ\8c, Ð\92ам Ð½ÐµÐ¾Ð±Ñ\85одимо Ð¿ÐµÑ\80еподклÑ\8eÑ\87иÑ\82Ñ\8cÑ\81Ñ\8f</string>
-  <string name="downloader_download_in_progress_ticker">Скачивание...</string>
-  <string name="downloader_download_in_progress_content">%1$d%% скачивания %2$s</string>
+  <string name="uploader_upload_failed_credentials_error">Ð\97агÑ\80Ñ\83зка Ð½Ðµ Ñ\83далаÑ\81Ñ\8c, Ð½Ñ\83жно Ð·Ð°Ð½Ð¾Ð²Ð¾ Ð²Ð¾Ð¹Ñ\82и Ð² Ñ\81воÑ\8e Ñ\83Ñ\87еÑ\82нÑ\83Ñ\8e Ð·Ð°Ð¿Ð¸Ñ\81Ñ\8c</string>
+  <string name="downloader_download_in_progress_ticker">Скачивание ...</string>
+  <string name="downloader_download_in_progress_content">%1$d%% Скачивается %2$s</string>
   <string name="downloader_download_succeeded_ticker">Скачивание завершено</string>
   <string name="downloader_download_succeeded_content">%1$s успешно скачан</string>
   <string name="downloader_download_failed_ticker">Скачивание не удалось</string>
   <string name="downloader_download_failed_content">Скачивание %1$s не может быть завершено</string>
   <string name="downloader_not_downloaded_yet">Ещё не скачано</string>
-  <string name="downloader_download_failed_credentials_error">СкаÑ\87ивание Ð½Ðµ Ñ\83далоÑ\81Ñ\8c, Ð\92ам Ð½ÐµÐ¾Ð±Ñ\85одимо Ð¿ÐµÑ\80еподклÑ\8eÑ\87иÑ\82Ñ\8cÑ\81Ñ\8f</string>
+  <string name="downloader_download_failed_credentials_error">СкаÑ\87ивание Ð½Ðµ Ñ\83далоÑ\81Ñ\8c, Ð½Ñ\83жно Ð·Ð°Ð½Ð¾Ð²Ð¾ Ð²Ð¾Ð¹Ñ\82и Ð² Ñ\81воÑ\8e Ñ\83Ñ\87еÑ\82нÑ\83Ñ\8e Ð·Ð°Ð¿Ð¸Ñ\81Ñ\8c</string>
   <string name="common_choose_account">Выберите учётную запись</string>
   <string name="sync_fail_ticker">Синхронизация прошла неудачно</string>
-  <string name="sync_fail_ticker_unauthorized">СинÑ\85Ñ\80онизаÑ\86иÑ\8f Ð½Ðµ Ñ\83далаÑ\81Ñ\8c, Ð\92ам Ð½ÐµÐ¾Ð±Ñ\85одимо Ð¿ÐµÑ\80еподклÑ\8eÑ\87иÑ\82Ñ\8cÑ\81Ñ\8f</string>
+  <string name="sync_fail_ticker_unauthorized">СинÑ\85Ñ\80онизаÑ\86иÑ\8f Ð½Ðµ Ñ\83далаÑ\81Ñ\8c, Ð½Ñ\83жно Ð·Ð°Ð½Ð¾Ð²Ð¾ Ð²Ð¾Ð¹Ñ\82и Ð² Ñ\81воÑ\8e Ñ\83Ñ\87еÑ\82нÑ\83Ñ\8e Ð·Ð°Ð¿Ð¸Ñ\81Ñ\8c</string>
   <string name="sync_fail_content">Синхронизация %1$s не может быть завершена</string>
   <string name="sync_fail_content_unauthorized">Неверный пароль для %1$s</string>
   <string name="sync_conflicts_in_favourites_ticker">Обнаружены конфликты</string>
-  <string name="sync_conflicts_in_favourites_content">%1$d файлы не могут быть синхронизированы</string>
+  <string name="sync_conflicts_in_favourites_content">%1$d файлов не может быть синхронизировано</string>
   <string name="sync_fail_in_favourites_ticker">Не удалось синхронизировать файлы</string>
   <string name="sync_fail_in_favourites_content">Содержимое %1$d файлов не может быть синхронизировано (конфликтов: %2$d)</string>
-  <string name="sync_foreign_files_forgotten_ticker">Несколько локальных файлов были забыты</string>
-  <string name="sync_foreign_files_forgotten_content"> Не возможно скопировать %1$d файлы из %2$s папки</string>
-  <string name="sync_foreign_files_forgotten_explanation">Ð\9dаÑ\87инаÑ\8f Ñ\81 Ð²ÐµÑ\80Ñ\81ии 1.3.16, Ñ\84айлÑ\8b, Ð·Ð°Ð³Ñ\80Ñ\83жаемÑ\8bе Ñ\81 Ñ\8dÑ\82ого Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ва, ÐºÐ¾Ð¿Ð¸Ñ\80Ñ\83Ñ\8eÑ\82Ñ\81Ñ\8f Ð² Ð»Ð¾ÐºÐ°Ð»Ñ\8cнÑ\83Ñ\8e Ð´Ð¸Ñ\80екÑ\82оÑ\80иÑ\8e %1$s, Ñ\87Ñ\82обÑ\8b Ð¿Ñ\80едоÑ\82вÑ\80аÑ\82иÑ\82Ñ\8c Ð¿Ð¾Ñ\82еÑ\80Ñ\8e Ð´Ð°Ð½Ð½Ñ\8bÑ\85 Ð¿Ñ\80и Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\84айла Ñ\81 Ð½ÐµÑ\81колÑ\8cкими Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bми Ð·Ð°Ð¿Ð¸Ñ\81Ñ\8fми.\n\nÐ\9fоÑ\8dÑ\82омÑ\83 Ð²Ñ\81е Ñ\84айлÑ\8b, Ð·Ð°Ð³Ñ\80Ñ\83женнÑ\8bе Ð¿Ñ\80едÑ\8bдÑ\83Ñ\89ими Ð²ÐµÑ\80Ñ\81иÑ\8fми Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ Ð¿Ñ\80иложениÑ\8f, Ð±Ñ\8bли Ñ\81копиÑ\80ованÑ\8b Ð² Ð´Ð¸Ñ\80екÑ\82оÑ\80иÑ\8e %2$s. Ð\9eднако, Ð²Ð¾ Ð²Ñ\80емÑ\8f Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\87Ñ\82о-Ñ\82о Ð¿Ð¾Ð¼ÐµÑ\88ало Ð·Ð°Ð²ÐµÑ\80Ñ\88иÑ\82Ñ\8c Ñ\8dÑ\82Ñ\83 Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8e. Ð¢ÐµÐ¿ÐµÑ\80Ñ\8c Ð¼Ð¾Ð¶Ð½Ð¾ Ð»Ð¸Ð±Ð¾ оставить файлы как есть и удалить ссылку на %3$s, либо переместить их в %1$s и сохранить ссылку на %4$s.\n\nНиже перечислены локальные файлы, и соответствующие им удалённые файлы в %5$s, к которым они привязаны.</string>
+  <string name="sync_foreign_files_forgotten_ticker">Некоторые загруженные файлы не были перенесены в локальную папку </string>
+  <string name="sync_foreign_files_forgotten_content"> Невозможно скопировать %1$d файлов из папки %2$s</string>
+  <string name="sync_foreign_files_forgotten_explanation">Ð\9dаÑ\87инаÑ\8f Ñ\81 Ð²ÐµÑ\80Ñ\81ии 1.3.16, Ñ\84айлÑ\8b, Ð·Ð°Ð³Ñ\80Ñ\83жаемÑ\8bе Ñ\81 Ñ\8dÑ\82ого Ñ\83Ñ\81Ñ\82Ñ\80ойÑ\81Ñ\82ва, ÐºÐ¾Ð¿Ð¸Ñ\80Ñ\83Ñ\8eÑ\82Ñ\81Ñ\8f Ð² Ð»Ð¾ÐºÐ°Ð»Ñ\8cнÑ\8bй ÐºÐ°Ñ\82алог %1$s, Ñ\87Ñ\82обÑ\8b Ð¿Ñ\80едоÑ\82вÑ\80аÑ\82иÑ\82Ñ\8c Ð¿Ð¾Ñ\82еÑ\80Ñ\8e Ð´Ð°Ð½Ð½Ñ\8bÑ\85 Ð¿Ñ\80и Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\84айла Ñ\81 Ð½ÐµÑ\81колÑ\8cкими Ñ\83Ñ\87Ñ\91Ñ\82нÑ\8bми Ð·Ð°Ð¿Ð¸Ñ\81Ñ\8fми.\n\nÐ\9fоÑ\8dÑ\82омÑ\83 Ð²Ñ\81е Ñ\84айлÑ\8b, Ð·Ð°Ð³Ñ\80Ñ\83женнÑ\8bе Ð¿Ñ\80едÑ\8bдÑ\83Ñ\89ими Ð²ÐµÑ\80Ñ\81иÑ\8fми Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ Ð¿Ñ\80иложениÑ\8f, Ð±Ñ\8bли Ñ\81копиÑ\80ованÑ\8b Ð² ÐºÐ°Ñ\82алог %2$s. Ð\9eднако, Ð²Ð¾ Ð²Ñ\80емÑ\8f Ñ\81инÑ\85Ñ\80онизаÑ\86ии Ñ\87Ñ\82о-Ñ\82о Ð¿Ð¾Ð¼ÐµÑ\88ало Ð·Ð°Ð²ÐµÑ\80Ñ\88иÑ\82Ñ\8c Ñ\8dÑ\82Ñ\83 Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8e. Ð\9cожеÑ\82е оставить файлы как есть и удалить ссылку на %3$s, либо переместить их в %1$s и сохранить ссылку на %4$s.\n\nНиже перечислены локальные файлы, и соответствующие им удалённые файлы в %5$s, к которым они привязаны.</string>
   <string name="sync_current_folder_was_removed">Каталог %1$s больше не существует</string>
   <string name="foreign_files_move">Переместить всё</string>
   <string name="foreign_files_success">Все файлы были перемещены</string>
   <string name="foreign_files_fail">Некоторые файлы не могут быть перемещены</string>
-  <string name="foreign_files_local_text">Локально: %1$s</string>
-  <string name="foreign_files_remote_text">Удаленно: %1$s</string>
-  <string name="upload_query_move_foreign_files">Ð\94лÑ\8f ÐºÐ¾Ð¿Ð¸Ñ\80ованиÑ\8f Ð²Ñ\8bбÑ\80аннÑ\8bÑ\85 Ñ\84айлов Ð² Ð¿Ð°Ð¿ÐºÑ\83 %1$s недостаточно свободного места. Скопировать в другое место?</string>
-  <string name="pincode_enter_pin_code">Ð\92Ñ\81Ñ\82авÑ\8cÑ\82е  App PIN</string>
-  <string name="pincode_configure_your_pin">Введите App PIN</string>
-  <string name="pincode_configure_your_pin_explanation">PIN-код будет запрашиваться при каждом запуске приложения.</string>
-  <string name="pincode_reenter_your_pincode">Повторите ввод App PIN</string>
-  <string name="pincode_remove_your_pincode">Удалить App PIN</string>
-  <string name="pincode_mismatch">Введённые App PIN не совпадают</string>
-  <string name="pincode_wrong">Неверный App PIN</string>
-  <string name="pincode_removed">App PIN удалён</string>
-  <string name="pincode_stored">App PIN сохранён</string>
+  <string name="foreign_files_local_text">Локальные: %1$s</string>
+  <string name="foreign_files_remote_text">Удаленные: %1$s</string>
+  <string name="upload_query_move_foreign_files">Ð\94лÑ\8f ÐºÐ¾Ð¿Ð¸Ñ\80ованиÑ\8f Ð²Ñ\8bбÑ\80аннÑ\8bÑ\85 Ñ\84айлов Ð² ÐºÐ°Ñ\82алог %1$s недостаточно свободного места. Скопировать в другое место?</string>
+  <string name="pincode_enter_pin_code">УкажиÑ\82е PIN Ð¿Ñ\80иложениÑ\8f</string>
+  <string name="pincode_configure_your_pin">Введите PIN приложения</string>
+  <string name="pincode_configure_your_pin_explanation">PIN будет запрашиваться при каждом запуске приложения.</string>
+  <string name="pincode_reenter_your_pincode">Повторите ввод PIN приложения</string>
+  <string name="pincode_remove_your_pincode">Удалить PIN приложения</string>
+  <string name="pincode_mismatch">Введённые PIN не совпадают</string>
+  <string name="pincode_wrong">Неверный PIN приложения</string>
+  <string name="pincode_removed">PIN приложения удалён</string>
+  <string name="pincode_stored">PIN приложения сохранён</string>
   <string name="media_notif_ticker">%1$s аудиоплеер</string>
   <string name="media_state_playing">%1$s (проигрывается)</string>
   <string name="media_state_loading">%1$s (загружается)</string>
   <string name="media_event_done">%1$s воспроизведение завершено</string>
-  <string name="media_err_nothing_to_play">Медиафайлов не найдено</string>
-  <string name="media_err_no_account">Учётная запись не настроена</string>
+  <string name="media_err_nothing_to_play">Медиафайлы не найдены</string>
+  <string name="media_err_no_account">Учётная запись не указана</string>
   <string name="media_err_not_in_owncloud">Файл в неверной учётной записи</string>
   <string name="media_err_unsupported">Неподдерживаемый кодек</string>
   <string name="media_err_io">Медиафайл не может быть прочитан</string>
   <string name="media_err_malformed">Медиафайл некорректно закодирован</string>
-  <string name="media_err_timeout">Ð\92Ñ\80емÑ\8f Ð¿Ð¾Ð¿Ñ\8bÑ\82ок Ð²Ð¾Ñ\81пÑ\80оизведениÑ\8f Ð²Ñ\8bÑ\88ло</string>
+  <string name="media_err_timeout">Ð\98Ñ\81Ñ\82екло Ð²Ñ\80емÑ\8f Ð¿Ð¾Ð¿Ñ\8bÑ\82ки Ð²Ð¾Ñ\81пÑ\80оизведениÑ\8f</string>
   <string name="media_err_invalid_progressive_playback">Невозможно организовать потоковую передачу медиафайла</string>
   <string name="media_err_unknown">Медиафайл не может быть проигран стандартным плеером</string>
   <string name="media_err_security_ex">Ошибка безопасности при воспроизведении %1$s</string>
   <string name="media_rewind_description">Перемотка назад</string>
   <string name="media_play_pause_description">Воспроизведение или пауза</string>
   <string name="media_forward_description">Перемотка вперед</string>
-  <string name="auth_getting_authorization">Ð\9fÑ\80оиÑ\81Ñ\85одиÑ\82 Ð°Ð²Ñ\82оÑ\80изаÑ\86иÑ\8f.....</string>
+  <string name="auth_getting_authorization">Ð\92Ñ\8bполнÑ\8fеÑ\82Ñ\81Ñ\8f Ð°Ð²Ñ\82оÑ\80изаÑ\86иÑ\8f...</string>
   <string name="auth_trying_to_login">Попытка входа...</string>
   <string name="auth_no_net_conn_title">Нет подключения к сети</string>
   <string name="auth_nossl_plain_ok_title">Защищённое соединение недоступно.</string>
   <string name="auth_timeout_title">Сервер слишком долго не отвечает</string>
   <string name="auth_incorrect_address_title">Неверный URL</string>
   <string name="auth_ssl_general_error_title">Ошибка инициализации SSL</string>
-  <string name="auth_ssl_unverified_server_title">Невозможно проверить SSL-сертификат сервера</string>
+  <string name="auth_ssl_unverified_server_title">Невозможно проверить SSL подлинность сервера</string>
   <string name="auth_bad_oc_version_title">Неизвестная версия сервера</string>
-  <string name="auth_wrong_connection_title">Невозможно установить соединение</string>
+  <string name="auth_wrong_connection_title">Не удается установить соединение</string>
   <string name="auth_secure_connection">Защищённое соединение установлено</string>
   <string name="auth_unauthorized">Неверное имя пользователя или пароль</string>
   <string name="auth_oauth_error">Ошибка авторизации</string>
   <string name="auth_oauth_error_access_denied">Сервер авторизации отказал в доступе</string>
   <string name="auth_wtf_reenter_URL">Неожиданный ответ; введите адрес сервера ещё раз</string>
   <string name="auth_expired_oauth_token_toast">Время авторизации истекло. Пожалуйста, авторизуйтесь снова</string>
-  <string name="auth_expired_basic_auth_toast">Пожалуйста, введите пароль</string>
+  <string name="auth_expired_basic_auth_toast">Пожалуйста, укажите текущий пароль</string>
   <string name="auth_expired_saml_sso_token_toast">Время сессии истекло. Пожалуйста, подключитесь снова</string>
   <string name="auth_connecting_auth_server">Подключение к серверу аутентификации...</string>
   <string name="auth_unsupported_auth_method">Сервер не поддерживает выбранный метод аутентификации</string>
-  <string name="auth_unsupported_multiaccount">%1$s не поддерживает сразу несколько учётных записей</string>
-  <string name="auth_fail_get_user_name">Ð\92аÑ\88 Ñ\81еÑ\80веÑ\80 Ð½Ðµ Ð²Ð¾Ð·Ð²Ñ\80аÑ\89аеÑ\82 ÐºÐ¾Ñ\80Ñ\80екÑ\82нÑ\8bй Ð¿Ð¾Ð»Ñ\8cзоваÑ\82елÑ\8cÑ\81кий Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\82оÑ\80, Ð¿Ð¾Ð¶Ð°Ð»Ñ\83йÑ\81Ñ\82а Ñ\81вÑ\8fжиÑ\82еÑ\81Ñ\8c Ñ\81 администратором
+  <string name="auth_unsupported_multiaccount">%1$s не поддерживает несколько учётных записей</string>
+  <string name="auth_fail_get_user_name">СеÑ\80веÑ\80 Ð²ÐµÑ\80нÑ\83л Ð½ÐµÐºÐ¾Ñ\80Ñ\80екÑ\82нÑ\8bй Ð¿Ð¾Ð»Ñ\8cзоваÑ\82елÑ\8cÑ\81кий Ð¸Ð´ÐµÐ½Ñ\82иÑ\84икаÑ\82оÑ\80. Ð\9fожалÑ\83йÑ\81Ñ\82а, Ñ\81вÑ\8fжиÑ\82еÑ\81Ñ\8c Ñ\81 Ð²Ð°Ñ\88им администратором
 ⇥</string>
-  <string name="auth_can_not_auth_against_server">Невозможно аутентифицироваться на этом сервере</string>
+  <string name="auth_can_not_auth_against_server">Невозможно авторизоваться на этом сервере</string>
   <string name="fd_keep_in_sync">Обновлять файл</string>
   <string name="common_rename">Переименовать</string>
   <string name="common_remove">Удалить</string>
   <string name="rename_server_fail_msg">Переименование не может быть завершено</string>
   <string name="sync_file_fail_msg">Удаленный файл не может быть проверен</string>
   <string name="sync_file_nothing_to_do_msg">Содержимое файла уже синхронизировано</string>
-  <string name="create_dir_fail_msg">Не возможно создать папку</string>
+  <string name="create_dir_fail_msg">Невозможно создать каталог</string>
   <string name="filename_forbidden_characters">Недопустимые символы: / \\ &lt; &gt; : \" | ? *</string>
   <string name="filename_empty">Имя файла не может быть пустым</string>
   <string name="wait_a_moment">Подождите немного</string>
   <string name="filedisplay_unexpected_bad_get_content">Неизвестная ошибка; выберите этот файл из другого приложения</string>
   <string name="filedisplay_no_file_selected">Файлы не выбраны</string>
-  <string name="activity_chooser_title">Отправить ссылку...</string>
+  <string name="activity_chooser_title">Отправить ссылку ...</string>
   <string name="oauth_check_onoff">Войти через oAuth2</string>
   <string name="oauth_login_connection">Подключение к серверу oAuth2...</string>
   <string name="ssl_validator_header">Подлинность сайта не может быть проверена</string>
   <string name="ssl_validator_reason_cert_expired">- Срок действия сертификата сервера истёк</string>
   <string name="ssl_validator_reason_cert_not_yet_valid">- Срок действия сертификата сервера ещё не начался</string>
   <string name="ssl_validator_reason_hostname_not_verified">- URL не совпадает с именем сервера в сертификате</string>
-  <string name="ssl_validator_question">Ð\92Ñ\8b Ñ\85оÑ\82иÑ\82е Ð´Ð¾Ð²ÐµÑ\80Ñ\8fÑ\82Ñ\8c Ð´Ð°Ð½Ð½ому сертификату в любом случае?</string>
+  <string name="ssl_validator_question">Ð\94овеÑ\80Ñ\8fÑ\82Ñ\8c Ñ\8dÑ\82ому сертификату в любом случае?</string>
   <string name="ssl_validator_not_saved">Сертификат не может быть сохранён</string>
   <string name="ssl_validator_btn_details_see">Подробно</string>
   <string name="ssl_validator_btn_details_hide">Скрыть</string>
-  <string name="ssl_validator_label_subject">Кому выдано:</string>
-  <string name="ssl_validator_label_issuer">Кем выдано:</string>
+  <string name="ssl_validator_label_subject">Кому выдан:</string>
+  <string name="ssl_validator_label_issuer">Кем выдан:</string>
   <string name="ssl_validator_label_CN">Имя:</string>
   <string name="ssl_validator_label_O">Организация:</string>
-  <string name="ssl_validator_label_OU">Ð\9eÑ\80ганизаÑ\86ионное Ð¿одразделение:</string>
+  <string name="ssl_validator_label_OU">Ð\9fодразделение:</string>
   <string name="ssl_validator_label_C">Страна:</string>
   <string name="ssl_validator_label_ST">Штат:</string>
   <string name="ssl_validator_label_L">Местонахождение:</string>
   <string name="ssl_validator_label_validity">Срок действия:</string>
-  <string name="ssl_validator_label_validity_from">Ð\98з:</string>
-  <string name="ssl_validator_label_validity_to">Ð\92:</string>
+  <string name="ssl_validator_label_validity_from">С:</string>
+  <string name="ssl_validator_label_validity_to">Ð\9fо:</string>
   <string name="ssl_validator_label_signature">Подпись:</string>
   <string name="ssl_validator_label_signature_algorithm">Алгоритм:</string>
   <string name="ssl_validator_null_cert">Сертификат не может быть показан.</string>
-  <string name="ssl_validator_no_info_about_error">- Ð\98нÑ\84оÑ\80маÑ\86ии Ð¾Ð± Ð¾Ñ\88ибке Ð½ÐµÑ\82</string>
+  <string name="ssl_validator_no_info_about_error">- Ð\9dеÑ\82 Ð¸Ð½Ñ\84оÑ\80маÑ\86ии Ð¾Ð± Ð¾Ñ\88ибке</string>
   <string name="placeholder_sentence">Это заполнитель</string>
   <string name="placeholder_filename">placeholder.txt</string>
   <string name="placeholder_filetype">Изображение PNG</string>
   <string name="placeholder_filesize">389 КБ</string>
   <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
   <string name="placeholder_media_time">12:23:45</string>
-  <string name="instant_upload_on_wifi">Ð\97агÑ\80Ñ\83жаÑ\82Ñ\8c Ð¸Ð·Ð¾Ð±Ñ\80ажениÑ\8f только через Wi-Fi</string>
+  <string name="instant_upload_on_wifi">Ð\97агÑ\80Ñ\83зка Ð¸Ð·Ð¾Ð±Ñ\80ажений только через Wi-Fi</string>
   <string name="instant_video_upload_on_wifi">Загрузка видео только через WiFi</string>
   <string name="instant_upload_path">/InstantUpload</string>
   <string name="conflict_title">Конфликт обновления</string>
   <string name="conflict_message">Удаленный файл %s не синхронизирован с локальным. Продолжение приведет к замене содержимого файла на сервере.</string>
   <string name="conflict_keep_both">Сохранить оба</string>
-  <string name="conflict_overwrite">Ð\97аменить</string>
+  <string name="conflict_overwrite">Ð\9fеÑ\80езапиÑ\81ать</string>
   <string name="conflict_dont_upload">Не загружать</string>
   <string name="preview_image_description">Предпросмотр</string>
   <string name="preview_image_error_unknown_format">Это изображение не может быть отображено</string>
-  <string name="error__upload__local_file_not_copied">%1$s не возможно скопировать в локальною папку %2$s </string>
+  <string name="error__upload__local_file_not_copied">%1$s невозможно скопировать в локальный каталог %2$s </string>
   <string name="prefs_instant_upload_path_title">Путь для загрузки</string>
-  <string name="share_link_no_support_share_api">К сожалению, на вашем сервере отключен совместный доступ. Пожалуйста, свяжитесь с вашим администратором.</string>
-  <string name="share_link_file_no_exist">Невозможно добавить в общий доступ. Пожалуйста, проверьте, существует ли файл</string>
-  <string name="share_link_file_error">Ошибка предоставления общего доступа к этому файлу или каталогу</string>
-  <string name="unshare_link_file_no_exist">Невозможно убрать из общего доступа. Пожалуйста, проверьте, существует ли файл</string>
-  <string name="unshare_link_file_error">Ошибка удаления общего доступа к этому файлу или каталогу</string>
+  <string name="share_link_no_support_share_api">Механизм общего доступа не включен на данном сервере. Пожалуйста, свяжитесь с вашим
+⇥⇥администратором.</string>
+  <string name="share_link_file_no_exist">Невозможно поделиться. Убедитесь, что файл существует</string>
+  <string name="share_link_file_error">При попытке поделиться этим файлом или каталогом произошла ошибка</string>
+  <string name="unshare_link_file_no_exist">Невозможно закрыть доступ. Убедитесь что файл существует</string>
+  <string name="unshare_link_file_error">При попытке закрыть доступ к этому файлу или каталогу произошла ошибка</string>
   <string name="activity_chooser_send_file_title">Отправить</string>
   <string name="copy_link">Копировать ссылку</string>
   <string name="clipboard_text_copied">Скопировано в буфер обмена</string>
-  <string name="error_cant_bind_to_operations_service">Ð\9aÑ\80иÑ\82иÑ\87еÑ\81каÑ\8f Ð¾Ñ\88ибка: Ð½ÐµÐ²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ Ð²Ñ\8bполниÑ\82Ñ\8c Ð¾Ð¿ÐµÑ\80аÑ\86ии</string>
+  <string name="error_cant_bind_to_operations_service">Ð\9aÑ\80иÑ\82иÑ\87еÑ\81каÑ\8f Ð¾Ñ\88ибка: Ð½ÐµÐ²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ Ð²Ñ\8bполниÑ\82Ñ\8c Ð´ÐµÐ¹Ñ\81Ñ\82виÑ\8f</string>
   <string name="network_error_socket_exception">При подключении к серверу возникла ошибка</string>
-  <string name="network_error_socket_timeout_exception">Ð\92о Ð²Ñ\80емÑ\8f Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ\8f Ñ\81еÑ\80веÑ\80а Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð° Ð¾Ñ\88ибка, Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8f Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð·Ð°Ð²ÐµÑ\80Ñ\88ена</string>
-  <string name="network_error_connect_timeout_exception">Ð\92о Ð²Ñ\80емÑ\8f Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ\8f Ñ\81еÑ\80веÑ\80а Ð²Ð¾Ð·Ð½Ð¸ÐºÐ»Ð° Ð¾Ñ\88ибка, Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8f Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð·Ð°Ð²ÐµÑ\80Ñ\88ена</string>
-  <string name="network_host_not_available">Ð\9eпеÑ\80аÑ\86иÑ\8f Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð·Ð°Ð²ÐµÑ\80Ñ\88ена, сервер недоступен</string>
+  <string name="network_error_socket_timeout_exception">Ð\92о Ð²Ñ\80емÑ\8f Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ\8f Ñ\81еÑ\80веÑ\80а Ð¿Ñ\80оизоÑ\88ла Ð¾Ñ\88ибка, Ð´ÐµÐ¹Ñ\81Ñ\82вие Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð²Ñ\8bполнено</string>
+  <string name="network_error_connect_timeout_exception">Ð\92о Ð²Ñ\80емÑ\8f Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ\8f Ñ\81еÑ\80веÑ\80а Ð¿Ñ\80оизоÑ\88ла Ð¾Ñ\88ибка, Ð´ÐµÐ¹Ñ\81Ñ\82вие Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð²Ñ\8bполнено</string>
+  <string name="network_host_not_available">Ð\94ейÑ\81Ñ\82вие Ð½Ðµ Ð¼Ð¾Ð¶ÐµÑ\82 Ð±Ñ\8bÑ\82Ñ\8c Ð²Ñ\8bполнено, сервер недоступен</string>
   <string name="empty"></string>
-  <string name="forbidden_permissions">У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð´Ð¾Ñ\81Ñ\82Ñ\83па %s</string>
-  <string name="forbidden_permissions_rename">пеÑ\80еименоваÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 Ñ\84айл</string>
-  <string name="forbidden_permissions_delete">удалить этот файл</string>
-  <string name="share_link_forbidden_permissions">опÑ\83бликоваÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 Ñ\84айл</string>
-  <string name="unshare_link_forbidden_permissions">оÑ\82мениÑ\82Ñ\8c Ð¿Ñ\83бликаÑ\86иÑ\8e Ñ\8dÑ\82ого Ñ\84айла</string>
-  <string name="forbidden_permissions_create">создать файл</string>
-  <string name="uploader_upload_forbidden_permissions">загÑ\80Ñ\83зиÑ\82Ñ\8c Ð² Ñ\8dÑ\82Ñ\83 Ð¿Ð°Ð¿ÐºÑ\83</string>
+  <string name="forbidden_permissions">У Ð²Ð°Ñ\81 Ð½ÐµÑ\82 Ð¿Ñ\80ав %s</string>
+  <string name="forbidden_permissions_rename">длÑ\8f Ð¿ÐµÑ\80еименованиÑ\8f Ñ\8dÑ\82ого Ñ\84айла</string>
+  <string name="forbidden_permissions_delete">для удаления этого файла</string>
+  <string name="share_link_forbidden_permissions">длÑ\8f Ð¾Ñ\82кÑ\80Ñ\8bÑ\82иÑ\8f Ð´Ð¾Ñ\81Ñ\82Ñ\83па Ðº Ñ\8dÑ\82омÑ\83 Ñ\84айлÑ\83</string>
+  <string name="unshare_link_forbidden_permissions">длÑ\8f Ð·Ð°ÐºÑ\80Ñ\8bÑ\82иÑ\8f Ð´Ð¾Ñ\81Ñ\82Ñ\83па Ðº Ñ\8dÑ\82омÑ\83 Ñ\84айлÑ\83</string>
+  <string name="forbidden_permissions_create">для создания файла</string>
+  <string name="uploader_upload_forbidden_permissions">длÑ\8f Ð·Ð°Ð³Ñ\80Ñ\83зки Ð² Ñ\8dÑ\82оÑ\82 ÐºÐ°Ñ\82алог</string>
   <string name="downloader_download_file_not_found">Этот файл больше недоступен на сервере</string>
   <string name="prefs_category_accounts">Учётные записи</string>
   <string name="prefs_add_account">Добавить учетную запись</string>
-  <string name="auth_redirect_non_secure_connection_title">Ð\97аÑ\89иÑ\89Ñ\91нное Ñ\81оединение Ð¿ÐµÑ\80енапÑ\80авлено Ð¿Ð¾ Ð½ÐµÐ·Ð°Ñ\89иÑ\89Ñ\91нному маршруту</string>
+  <string name="auth_redirect_non_secure_connection_title">Ð\97аÑ\89иÑ\89Ñ\91нное Ñ\81оединение Ð¿ÐµÑ\80енапÑ\80авлено Ð¿Ð¾ Ð½ÐµÐ±ÐµÐ·Ð¾Ð¿Ð°Ñ\81ному маршруту</string>
   <string name="actionbar_logger">Журналы</string>
   <string name="log_send_history_button">История Отправлений</string>
-  <string name="log_mail_subject">Журналы Андроид-приложения ownCloud</string>
-  <string name="log_progress_dialog_text">Загружаются данные...</string>
+  <string name="log_send_no_mail_app">Приложение для отправки журнала не найдено. Установите почтовое приложение!</string>
+  <string name="log_send_mail_subject">Журналы %1$s для Android</string>
+  <string name="log_progress_dialog_text">Загрузка данных…</string>
   <string name="saml_authentication_required_text">Требуется аутентификация </string>
   <string name="saml_authentication_wrong_pass">Неправильный пароль</string>
   <string name="actionbar_move">Переместить</string>
-  <string name="file_list_empty_moving">Ð\97деÑ\81Ñ\8c Ð½Ð¸Ñ\87его Ð½ÐµÑ\82. Ð\92Ñ\8b Ð¼Ð¾Ð¶ÐµÑ\82е Ð´Ð¾Ð±Ð°Ð²Ð¸Ñ\82Ñ\8c Ð¿Ð°Ð¿ÐºÑ\83!</string>
-  <string name="move_choose_button_text">Выбрать</string>
-  <string name="move_file_not_found">Ð\9dевозможно Ð¿ÐµÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c. Ð\9fожалÑ\83йÑ\81Ñ\82а, Ð¿Ñ\80овеÑ\80Ñ\8cÑ\82е, Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 Ð»Ð¸ Ñ\84айл</string>
-  <string name="move_file_invalid_into_descendent">Ð\9dевозможно Ð¿ÐµÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c Ð¿Ð°Ð¿ÐºÑ\83 Ð² Ð¿Ð°Ð¿ÐºÑ\83-поÑ\82омок</string>
-  <string name="move_file_invalid_overwrite">Файл Ñ\83же Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 Ð² Ð¿Ð°Ð¿Ðºе назначения</string>
-  <string name="move_file_error">Ð\9fÑ\80оизоÑ\88ла Ð¾Ñ\88ибка Ð¿Ñ\80и Ð¿Ð¾Ð¿Ñ\8bÑ\82ке Ð¿ÐµÑ\80емеÑ\89ениÑ\8f Ñ\8dÑ\82ого Ñ\84айла Ð¸Ð»Ð¸ Ð¿Ð°Ð¿ÐºÐ¸</string>
-  <string name="forbidden_permissions_move">пеÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c Ñ\8dÑ\82оÑ\82 Ñ\84айл</string>
+  <string name="file_list_empty_moving">Ð\97деÑ\81Ñ\8c Ð½Ð¸Ñ\87его Ð½ÐµÑ\82. Ð\92Ñ\8b Ð¼Ð¾Ð¶ÐµÑ\82е Ð´Ð¾Ð±Ð°Ð²Ð¸Ñ\82Ñ\8c ÐºÐ°Ñ\82алог!</string>
+  <string name="folder_picker_choose_button_text">Выбрать</string>
+  <string name="move_file_not_found">Ð\9dевозможно Ð¿ÐµÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c. Ð£Ð±ÐµÐ´Ð¸Ñ\82еÑ\81Ñ\8c, Ñ\87Ñ\82о Ñ\84айл Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82</string>
+  <string name="move_file_invalid_into_descendent">Ð\9dевозможно Ð¿ÐµÑ\80емеÑ\81Ñ\82иÑ\82Ñ\8c ÐºÐ°Ñ\82алог Ð² ÐµÐ³Ð¾ Ð¿Ð¾Ð´ÐºÐ°Ñ\82алог</string>
+  <string name="move_file_invalid_overwrite">Файл Ñ\83же Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 Ð² ÐºÐ°Ñ\82алоге назначения</string>
+  <string name="move_file_error">Ð\9fÑ\80оизоÑ\88ла Ð¾Ñ\88ибка Ð¿Ñ\80и Ð¿Ð¾Ð¿Ñ\8bÑ\82ке Ð¿ÐµÑ\80емеÑ\89ениÑ\8f Ñ\8dÑ\82ого Ñ\84айла Ð¸Ð»Ð¸ ÐºÐ°Ñ\82алога</string>
+  <string name="forbidden_permissions_move">длÑ\8f Ð¿ÐµÑ\80емеÑ\89ениÑ\8f Ñ\8dÑ\82ого Ñ\84айла</string>
   <string name="prefs_category_instant_uploading">Мгновенные загрузки</string>
   <string name="prefs_category_security">Безопасность</string>
+  <string name="prefs_instant_video_upload_path_title">Путь для загрузки Видео</string>
+  <string name="download_folder_failed_content">Загрузка папки %1$s не может быть завершена</string>
+  <string name="shared_subject_header">общие</string>
+  <string name="with_you_subject_header">с вами</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index d413c8f..6eeb4f0 100644 (file)
   <string name="uploader_info_uploading">උඩුගතවේ</string>
   <string name="file_list_seconds_ago">තත්පරයන්ට පෙර</string>
   <string name="file_list_empty">මෙහි කිසිවක් නොමැත. යමක් උඩුගත කරන්න</string>
+  <string name="file_list_folder">ෆෝල්ඩරය</string>
+  <string name="file_list_folders">ෆෝල්ඩර</string>
+  <string name="file_list_file">ගොනුව</string>
+  <string name="file_list_files">ගොනු</string>
   <string name="filedetails_select_file">වැඩි විස්තර සඳහා ගොනුවක් ස්පර්ෂ කරන්න</string>
   <string name="filedetails_size">විශාලත්වය:</string>
   <string name="filedetails_type">ගණය:</string>
@@ -76,5 +80,5 @@
   <string name="ssl_validator_btn_details_hide">සඟවන්න</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">ගිණුම්</string>
-  <string name="move_choose_button_text">තෝරන්න</string>
+  <string name="folder_picker_choose_button_text">තෝරන්න</string>
 </resources>
index 8616f68..04ff620 100644 (file)
   <string name="actionbar_settings">Nastavenia</string>
   <string name="actionbar_see_details">Podrobnosti</string>
   <string name="actionbar_send_file">Odoslať</string>
+  <string name="actionbar_sort">Zoradiť</string>
+  <string name="actionbar_sort_title">Zoradiť podľa</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Z</item>
+    <item>Najnovšie - Najstaršie</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">Všeobecné</string>
   <string name="prefs_recommend">Doporučiť známemu</string>
   <string name="prefs_feedback">Spätná väzba</string>
   <string name="prefs_imprint">Podmienky používania</string>
+  <string name="prefs_remember_last_share_location">Zapamätať umiestnenie zdieľania</string>
+  <string name="prefs_remember_last_upload_location_summary">Zapamätať posledné umiestnenie pre nahranie zdieľaných súborov</string>
   <string name="recommend_subject">Skúste %1$s na vašom telefóne!</string>
+  <string name="recommend_text">Chcel by som vám odporučiť %1$s na vašom smartfóne!\nSťahujte tu: %2$s</string>
   <string name="auth_check_server">Skontrolovať Server</string>
   <string name="auth_host_url">Adresa servera https://...</string>
   <string name="auth_username">Používateľské meno</string>
   <string name="file_list_empty">Žiadny súbor. Nahrajte niečo!</string>
   <string name="file_list_loading">Nahráva sa...</string>
   <string name="local_file_list_empty">V tomto priečinku nie sú žiadne súbory.</string>
+  <string name="file_list_folder">priečinok</string>
+  <string name="file_list_folders">priečinky</string>
+  <string name="file_list_file">súbor</string>
+  <string name="file_list_files">súbory</string>
   <string name="filedetails_select_file">Viac informácií získate kliknutím na súbor.</string>
   <string name="filedetails_size">Veľkosť:</string>
   <string name="filedetails_type">Typ:</string>
   <string name="sync_fail_in_favourites_content">Obsah %1$d súborov nemohol byť synchronizovaný (%2$d konfliktov)</string>
   <string name="sync_foreign_files_forgotten_ticker">Niektoré lokálne súbory boli zabudnuté</string>
   <string name="sync_foreign_files_forgotten_content">%1$d súborov z %2$s priečinkov sa nepodarilo skopírovať do</string>
+  <string name="sync_foreign_files_forgotten_explanation">Od verzie 1.3.16 sú súbory nahrané z tohoto zariadenia kopírované do lokálneho priečinka %1$s, aby sa zabránilo strate dát pri synchronizácii jedného súboru s viacerými účtami.\n\nVšetky súbory nahraté predchádzajúcimi verziami aplikácie boli z tohoto dôvodu prekopírované do priečinka %2$s. Bohužiaľ sa objavila chyba zabraňujúca dokončeniu tejto operácie v priebehu synchronizácie účtu. Buď môžete súbor(y) ponechať ako sú a odobrať odkaz z priečinka %3$s, alebo presunúť súbor(y) do priečinka %1$s a zachovať odkaz na %4$s.\n\nNižšie je uvedený lokálny súbor(y) a vzdialený súbor(y) v %5$s, s ktorým je prepojený.</string>
   <string name="sync_current_folder_was_removed">Priečinok %1$s už existuje</string>
   <string name="foreign_files_move">Premiestniť všetko</string>
   <string name="foreign_files_success">Všetky súbory boli premiestnené</string>
   <string name="preview_image_description">Ukážka obrazu</string>
   <string name="preview_image_error_unknown_format">Obrázok nemožno zobraziť</string>
   <string name="error__upload__local_file_not_copied">%1$s nemožno skopírovať do lokálneho priečinka %2$s</string>
+  <string name="prefs_instant_upload_path_title">Cesta pre nahrávanie</string>
   <string name="share_link_no_support_share_api">Je nám to ľúto, ale zdieľanie nie je na vašom serveri povolené. Prosím kontaktujte vášho
                administrátora.</string>
+  <string name="share_link_file_no_exist">Nemožno zdieľať. Skontrolujte, či súbor existuje</string>
   <string name="share_link_file_error">Pri pokuse o zdieľanie tohto súboru alebo priečinka došlo k chybe</string>
+  <string name="unshare_link_file_no_exist">Nemožno ukončiť zdieľanie. Skontrolujte, či súbor existuje</string>
   <string name="unshare_link_file_error">Pri pokuse zrušiť zdieľanie tohto súboru alebo priečinka došlo k chybe</string>
   <string name="activity_chooser_send_file_title">Odoslať</string>
   <string name="copy_link">Kopíruj odkaz</string>
   <string name="downloader_download_file_not_found">Súbor už na serveri nie je dostupný</string>
   <string name="prefs_category_accounts">Účty</string>
   <string name="prefs_add_account">Pridať účet</string>
+  <string name="auth_redirect_non_secure_connection_title">Zabezpečené pripojenie je presmerované na nezabezpečenú trasu.</string>
+  <string name="actionbar_logger">Logy</string>
+  <string name="log_send_history_button">Odoslať históriu</string>
+  <string name="log_send_no_mail_app">Nebola nájdená aplikácia pre odosielanie log protokolov. Nainštalujte si mailovú aplikáciu!</string>
+  <string name="log_send_mail_subject">%1$s Android app logs</string>
+  <string name="log_progress_dialog_text">Načítavam dáta...</string>
   <string name="saml_authentication_required_text">Vyžaduje sa overenie</string>
   <string name="saml_authentication_wrong_pass">Nesprávne heslo</string>
   <string name="actionbar_move">Presunúť</string>
-  <string name="move_choose_button_text">Vybrať</string>
+  <string name="file_list_empty_moving">Nič tu nie je. Pridajte priečinok!</string>
+  <string name="folder_picker_choose_button_text">Vybrať</string>
+  <string name="move_file_not_found">Nemožno presunúť. Skontrolujte, či súbor existuje</string>
+  <string name="move_file_invalid_into_descendent">Priečinok nemožno presunúť do vlastného podpriečinka</string>
+  <string name="move_file_invalid_overwrite">Súbor už v cieľovom priečinku existuje</string>
+  <string name="move_file_error">Pri pokuse o presun tohoto súboru alebo priečinka nastala chyba</string>
+  <string name="forbidden_permissions_move">pre presun tohoto súboru</string>
+  <string name="prefs_category_instant_uploading">Okamžité nahratie</string>
   <string name="prefs_category_security">Zabezpečenie</string>
+  <string name="prefs_instant_video_upload_path_title">Cesta pre nahrávanie videí</string>
+  <string name="download_folder_failed_content">Sťahovanie %1$s priečinka nebolo dokončené</string>
+  <string name="shared_subject_header">zdieľané</string>
+  <string name="with_you_subject_header">s vami</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index f0c1ee2..7f1a625 100644 (file)
   <string name="file_list_empty">Tukaj še ni ničesar. Najprej je treba datoteke poslati v oblak!</string>
   <string name="file_list_loading">Poteka nalaganje ...</string>
   <string name="local_file_list_empty">V tej mapi ni datotek.</string>
+  <string name="file_list_folder">mapa</string>
+  <string name="file_list_folders">mape</string>
+  <string name="file_list_file">datoteka</string>
+  <string name="file_list_files">datoteke</string>
   <string name="filedetails_select_file">Pritisnite na datoteko za prikaz dodatnih podrobnosti.</string>
   <string name="filedetails_size">Velikost:</string>
   <string name="filedetails_type">Vrsta:</string>
   <string name="auth_redirect_non_secure_connection_title">Varna povezava je preusmerjena preko ne-varne poti.</string>
   <string name="actionbar_logger">Dnevnik</string>
   <string name="log_send_history_button">Pošlji zgodovino</string>
-  <string name="log_mail_subject">Dnevnik programa ownCloud</string>
+  <string name="log_send_no_mail_app">Ni nameščenega poštnega programa za pošiljanje dnevnikov.</string>
+  <string name="log_send_mail_subject">%1$s dnevniki programa</string>
   <string name="log_progress_dialog_text">Poteka nalaganje podatkov ...</string>
   <string name="saml_authentication_required_text">Zahtevana je overitev</string>
   <string name="saml_authentication_wrong_pass">Napačno geslo</string>
   <string name="actionbar_move">Premakni</string>
   <string name="file_list_empty_moving">Ni vsebine in datotek. Lahko ustvarite na primer mapo.</string>
-  <string name="move_choose_button_text">Izbor</string>
+  <string name="folder_picker_choose_button_text">Izbor</string>
   <string name="move_file_not_found">Ni mogoče premakniti datoteke. Preverite, ali obstaja.</string>
   <string name="move_file_invalid_into_descendent">Ni mogoče premakniti mape v podrejeno mapo.</string>
   <string name="move_file_invalid_overwrite">Datoteka v ciljni mapi že obstaja.</string>
   <string name="forbidden_permissions_move">med premikanjem datoteke</string>
   <string name="prefs_category_instant_uploading">Takojšnje pošiljanje v oblak</string>
   <string name="prefs_category_security">Varnost</string>
+  <string name="prefs_instant_video_upload_path_title">Pot videa za pošiljanje</string>
+  <string name="shared_subject_header">v souporabi</string>
+  <string name="with_you_subject_header">z vami</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index a794c6d..7c1b7ff 100644 (file)
@@ -32,6 +32,7 @@
   <string name="filedetails_created">Krijuar:</string>
   <string name="filedetails_modified">Modifikuar:</string>
   <string name="filedetails_download">Shkarko</string>
+  <string name="action_share_file">Ndaje lidhjen</string>
   <string name="common_yes">Po</string>
   <string name="common_no">Jo</string>
   <string name="common_ok">Ok</string>
@@ -74,6 +75,7 @@
   <string name="empty"></string>
   <string name="prefs_category_accounts">Llogarit</string>
   <string name="saml_authentication_wrong_pass">Fjalëkalim i gabuar</string>
-  <string name="move_choose_button_text">Zgjidh</string>
+  <string name="folder_picker_choose_button_text">Zgjidh</string>
   <string name="prefs_category_security">Siguria</string>
+  <string name="shared_subject_header">Ndarë</string>
 </resources>
index 64e2de0..41e1238 100644 (file)
@@ -2,6 +2,7 @@
 <resources>
   <string name="actionbar_upload">Pošalji</string>
   <string name="actionbar_upload_files">Fajlovi</string>
+  <string name="actionbar_mkdir">Novi direktorijum</string>
   <string name="actionbar_settings">Podešavanja</string>
   <string name="actionbar_see_details">Detaljnije</string>
   <string name="actionbar_send_file">Pošalji</string>
   <string name="filedetails_size">Veličina:</string>
   <string name="filedetails_type">Tip:</string>
   <string name="filedetails_download">Preuzmi</string>
+  <string name="action_share_file">Podeli prečicu</string>
   <string name="common_yes">Da</string>
   <string name="common_no">Ne</string>
   <string name="common_ok">Ok</string>
+  <string name="common_cancel_upload">Otkaži otpremanje</string>
   <string name="common_cancel">Otkaži</string>
   <string name="common_error">Greška</string>
+  <string name="common_error_unknown">Nepoznata greška</string>
   <string name="change_password">Izmeni lozinku</string>
   <string name="delete_account">Ukloni nalog</string>
   <string name="create_account">Novi nalog</string>
+  <string name="uploader_info_dirname">Ime fascikle</string>
   <string name="uploader_upload_in_progress_ticker">Otpremanje...</string>
   <string name="uploader_upload_succeeded_ticker">Uspešno otpremljeno</string>
   <string name="uploader_upload_failed_ticker">Otpremanje nije uspelo</string>
@@ -57,5 +62,5 @@
   <string name="activity_chooser_send_file_title">Pošalji</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Nalozi</string>
-  <string name="move_choose_button_text">Izaberi</string>
+  <string name="folder_picker_choose_button_text">Izaberi</string>
 </resources>
index ea8c40e..b9235ad 100644 (file)
   <string name="activity_chooser_send_file_title">Пошаљи</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Налози</string>
-  <string name="move_choose_button_text">Одабери</string>
+  <string name="folder_picker_choose_button_text">Одабери</string>
   <string name="prefs_category_security">Безбедност</string>
 </resources>
index 0a01a65..254ed65 100644 (file)
   <string name="actionbar_settings">Inställningar</string>
   <string name="actionbar_see_details">Detaljer</string>
   <string name="actionbar_send_file">Skicka</string>
+  <string name="actionbar_sort">Sortera</string>
+  <string name="actionbar_sort_title">Sortera efter</string>
+  <string-array name="actionbar_sortby">
+    <item>A-Ö</item>
+    <item>Nyast - Äldst</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">Allmänt</string>
@@ -32,6 +38,8 @@
   <string name="prefs_recommend">Rekommendera till en vän</string>
   <string name="prefs_feedback">Feedback</string>
   <string name="prefs_imprint">Imprint</string>
+  <string name="prefs_remember_last_share_location">Kom ihåg plats för delat</string>
+  <string name="prefs_remember_last_upload_location_summary">Kom ihåg senaste uppladdningsplats vid dela</string>
   <string name="recommend_subject">Prova %1$s på din smartphone!</string>
   <string name="recommend_text">Jag skullje vilja bjuda in dig till att prova %1$s på din smartphone!\nLadda ner appen från Google Play här: %2$s</string>
   <string name="auth_check_server">Kontrollera Server</string>
   <string name="file_list_empty">Ingenting här. Ladda upp något!</string>
   <string name="file_list_loading">Laddar...</string>
   <string name="local_file_list_empty">Det finns inga filer i den här mappen.</string>
+  <string name="file_list_folder">mapp</string>
+  <string name="file_list_folders">mappar</string>
+  <string name="file_list_file">fil</string>
+  <string name="file_list_files">filer</string>
   <string name="filedetails_select_file">Peka på en fil för att visa mer information.</string>
   <string name="filedetails_size">Storlek:</string>
   <string name="filedetails_type">Typ:</string>
   <string name="sync_fail_in_favourites_content">Innehållet i %1$d filer kunde inte synkas (%2$d konflikter)</string>
   <string name="sync_foreign_files_forgotten_ticker">Vissa lokala filer glömdes</string>
   <string name="sync_foreign_files_forgotten_content">%1$d filer från %2$s mappar kunde inte kopieras till</string>
+  <string name="sync_foreign_files_forgotten_explanation">Från och med version 1.3.16 kommer filer uppladdade från denna enhet kopieras in till lokal %1$s mapp för att förhindra dataförlust när en enskild fil synkroniseras med flera konton.\n\nPå grund av denna ändring kommer alla filer uppladdade i tidigare versioner av denna applikation kopieras in till %2$s mapp.  Dock förhindrade ett fel slutförandet av denna operation under konto-synkronisering. Du kan antingen lämna filerna som de är och ta bort länken till %3$s, eller flytta filerna in till %1$s mapp och behålla länken till %4$s.\n\nNedan listas de lokala filerna och de fjrran filerna i %5$s som de länkades till.</string>
   <string name="sync_current_folder_was_removed">Mappen %1$s existerar inte längre</string>
   <string name="foreign_files_move">Flytta allt</string>
   <string name="foreign_files_success">Alla filer flyttades</string>
   <string name="preview_image_description">Förhandsvisa bild</string>
   <string name="preview_image_error_unknown_format">Denna bild kan inte visas</string>
   <string name="error__upload__local_file_not_copied">%1$s kunde inte kopieras till %2$s lokal mapp</string>
+  <string name="prefs_instant_upload_path_title">Uppladdnings-sökväg</string>
   <string name="share_link_no_support_share_api">Ledsen, delning är inte aktiverat på din server. Vänligen kontakta din
                administratör.</string>
+  <string name="share_link_file_no_exist">Lyckades ej dela. Vänligen kontrollera om filen eisterar</string>
   <string name="share_link_file_error">Ett fel uppstod vid försök att dela denna fil eller mapp</string>
+  <string name="unshare_link_file_no_exist">Lyckades ej sluta dela. Vänligen kontrollera om filen existerar</string>
   <string name="unshare_link_file_error">Ett fel uppstod vid försök att sluta dela denna fil eller mapp</string>
   <string name="activity_chooser_send_file_title">Skicka</string>
   <string name="copy_link">Kopiera länk</string>
   <string name="downloader_download_file_not_found">Filen är inte längre tillgänglig på servern</string>
   <string name="prefs_category_accounts">Konton</string>
   <string name="prefs_add_account">Lägg till konto</string>
+  <string name="auth_redirect_non_secure_connection_title">Säker anslutning är omdirigerad till en osäker väg.</string>
+  <string name="actionbar_logger">Loggar</string>
+  <string name="log_send_history_button">Skickat historik</string>
+  <string name="log_send_no_mail_app">Ingen app för att skicka loggar hittades. Installera mail appen!</string>
+  <string name="log_send_mail_subject">%1$s Android app logs</string>
   <string name="log_progress_dialog_text">Laddar data...</string>
   <string name="saml_authentication_required_text">Autentisering krävs</string>
   <string name="saml_authentication_wrong_pass">Fel lösenord</string>
   <string name="actionbar_move">Flytta</string>
   <string name="file_list_empty_moving">Ingenting här. Du kan skapa en mapp!</string>
-  <string name="move_choose_button_text">Välj</string>
+  <string name="folder_picker_choose_button_text">Välj</string>
   <string name="move_file_not_found">Gick inte att flytta. Vänligen kontrollera att filen existerar</string>
+  <string name="move_file_invalid_into_descendent">Det är inte möjligt att flytta mappen in i underliggande struktur</string>
+  <string name="move_file_invalid_overwrite">Filen existerar redan i destinationsmappen</string>
+  <string name="move_file_error">Ett fel uppstod vid försök att flytta denna fil eller mapp</string>
   <string name="forbidden_permissions_move">att flytta den här filen</string>
+  <string name="prefs_category_instant_uploading">Direktuppladning</string>
   <string name="prefs_category_security">Säkerhet</string>
+  <string name="prefs_instant_video_upload_path_title">Uppladdnings-sökväg för video</string>
+  <string name="shared_subject_header">delad</string>
+  <string name="with_you_subject_header">med dig</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index f317edf..42fc1b6 100644 (file)
   <string name="conflict_dont_upload">பதிவேற்ற வேண்டாம்</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">கணக்குகள்</string>
-  <string name="move_choose_button_text">தெரிவுசெய்க </string>
+  <string name="folder_picker_choose_button_text">தெரிவுசெய்க </string>
 </resources>
index f10ec0f..0f2a409 100644 (file)
   <string name="uploader_info_uploading">กำลังอัพโหลด</string>
   <string name="file_list_seconds_ago">วินาที ก่อนหน้านี้</string>
   <string name="file_list_empty">ยังไม่มีไฟล์ใดๆอยู่ที่นี่ กรุณาอัพโหลดไฟล์!</string>
+  <string name="file_list_folder">โฟลเดอร์</string>
+  <string name="file_list_folders">โฟลเดอร์</string>
+  <string name="file_list_file">ไฟล์</string>
+  <string name="file_list_files">ไฟล์</string>
   <string name="filedetails_select_file">แตะที่ไฟล์ เพื่อแสดงข้อมูลเพิ่มเติม</string>
   <string name="filedetails_size">ขนาด:</string>
   <string name="filedetails_type">ชนิด:</string>
   <string name="activity_chooser_send_file_title">ส่ง</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">บัญชี</string>
-  <string name="move_choose_button_text">เลือก</string>
+  <string name="folder_picker_choose_button_text">เลือก</string>
 </resources>
index 2a34fcf..8a0664e 100644 (file)
   <string name="file_list_empty">Burada hiçbir şey yok. Bir şeyler yükleyin!</string>
   <string name="file_list_loading">Yükleniyor...</string>
   <string name="local_file_list_empty">Bu klasörde dosya yok.</string>
+  <string name="file_list_folder">klasör</string>
+  <string name="file_list_folders">klasörler</string>
+  <string name="file_list_file">dosya</string>
+  <string name="file_list_files">dosyalar</string>
   <string name="filedetails_select_file">Ek bilgileri görmek için dosyaya dokunun.</string>
   <string name="filedetails_size">Boyut:</string>
   <string name="filedetails_type">Tür:</string>
   <string name="auth_redirect_non_secure_connection_title">Güvenli bağlantı, güvenli olmayan bir rotaya yönlendirildi.</string>
   <string name="actionbar_logger">Günlükler</string>
   <string name="log_send_history_button">Geçmişi Gönder</string>
-  <string name="log_mail_subject">ownCloud Android uygulama kayıtları</string>
+  <string name="log_send_no_mail_app">Kayıtları göndermek için uygulama bulunamadı. E-posta uygulamasını yükleyin!</string>
+  <string name="log_send_mail_subject">%1$s Android uygulama kayıtları</string>
   <string name="log_progress_dialog_text">Veri yükleniyor...</string>
   <string name="saml_authentication_required_text">Kimlik doğrulama gerekli</string>
   <string name="saml_authentication_wrong_pass">Hatalı parola</string>
   <string name="actionbar_move">Taşı</string>
   <string name="file_list_empty_moving">Burada bir şey yok. Bir klasör ekleyebilirsiniz!</string>
-  <string name="move_choose_button_text">Seç</string>
+  <string name="folder_picker_choose_button_text">Seç</string>
   <string name="move_file_not_found">Taşıma başarısız. Lütfen dosyanın mevcut olup olmadığını denetleyin</string>
   <string name="move_file_invalid_into_descendent">Klasörü, kendi alt klasörüne taşımak mümkün değil</string>
   <string name="move_file_invalid_overwrite">Dosya zaten hedef klasörde mevcut</string>
   <string name="forbidden_permissions_move">bu dosyayı taşımak için</string>
   <string name="prefs_category_instant_uploading">Anında Yüklemeler</string>
   <string name="prefs_category_security">Güvenlik</string>
+  <string name="prefs_instant_video_upload_path_title">Video Yükleme Yolu</string>
+  <string name="download_folder_failed_content">%1$s klasörün indirilmesi tamamlanamadı</string>
+  <string name="shared_subject_header">paylaşılan</string>
+  <string name="with_you_subject_header">sizinle</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 12a5492..abee26e 100644 (file)
@@ -44,4 +44,5 @@
   <string name="empty"></string>
   <string name="prefs_category_accounts">ھېساباتلار</string>
   <string name="prefs_category_security">بىخەتەرلىك</string>
+  <string name="shared_subject_header">ھەمبەھىرلەنگەن</string>
 </resources>
index 597150b..e57daea 100644 (file)
@@ -38,6 +38,8 @@
   <string name="prefs_recommend">Порадити товаришу</string>
   <string name="prefs_feedback">Зворотній зв\'язок</string>
   <string name="prefs_imprint">Відбиток</string>
+  <string name="prefs_remember_last_share_location">Запам\'ятати позицію</string>
+  <string name="prefs_remember_last_upload_location_summary">Запам\'ятати останній опублікований шлях завантаження</string>
   <string name="recommend_subject">Спробуйте %1$s на своєму смартфоні!</string>
   <string name="recommend_text">Пропоную вам користуватися %1$s на вашому смартфоні!\nЗавантажити можна за посиланням: %2$s</string>
   <string name="auth_check_server">Перевірити сервер</string>
   <string name="file_list_empty">Тут нічого немає. Відвантажте що-небудь!</string>
   <string name="file_list_loading">Завантаження...</string>
   <string name="local_file_list_empty">В цій теці немає файлів.</string>
+  <string name="file_list_folder">тека</string>
+  <string name="file_list_folders">теки</string>
+  <string name="file_list_file">файл</string>
+  <string name="file_list_files">файли</string>
   <string name="filedetails_select_file">Натисніть на файлі для відображення додаткової інформації</string>
   <string name="filedetails_size">Розмір:</string>
   <string name="filedetails_type">Тип:</string>
   <string name="auth_redirect_non_secure_connection_title">Безпечне підключення перенаправляється через незабезпечений маршрут.</string>
   <string name="actionbar_logger">Журнали</string>
   <string name="log_send_history_button">Надіслати історію</string>
-  <string name="log_mail_subject">Журнали Android-додатка ownCloud</string>
+  <string name="log_send_no_mail_app">Немає додатку для відправки журналів. Встановіть поштовий додаток!</string>
+  <string name="log_send_mail_subject">%1$s Android лог додатку</string>
   <string name="log_progress_dialog_text">Завантаження даних...</string>
   <string name="saml_authentication_required_text">Потрібна аутентифікація</string>
   <string name="saml_authentication_wrong_pass">Невірний пароль</string>
   <string name="actionbar_move">Перемістити</string>
   <string name="file_list_empty_moving">Тут нічого немає. Ви можете додати теку!</string>
-  <string name="move_choose_button_text">Обрати</string>
+  <string name="folder_picker_choose_button_text">Обрати</string>
   <string name="move_file_not_found">Неможливо перемістити. Будь ласка, перевірте, чи існує файл</string>
   <string name="move_file_invalid_into_descendent">Неможливо перемістити теку до теки-нащадка</string>
   <string name="move_file_invalid_overwrite">Файл вже існує в теці призначення</string>
   <string name="forbidden_permissions_move">перемістити цей файл</string>
   <string name="prefs_category_instant_uploading">Миттєво завантаження</string>
   <string name="prefs_category_security">Безпека</string>
+  <string name="prefs_instant_video_upload_path_title">Шлях завантаження відео</string>
+  <string name="shared_subject_header">спільне</string>
+  <string name="with_you_subject_header">з тобою</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 229bdac..874499e 100644 (file)
@@ -20,5 +20,5 @@
   <string name="common_error_unknown">غیر معروف خرابی</string>
   <string name="activity_chooser_send_file_title">بھجیں</string>
   <string name="empty"></string>
-  <string name="move_choose_button_text">منتخب کریں</string>
+  <string name="folder_picker_choose_button_text">منتخب کریں</string>
 </resources>
index 52e3c56..d2517eb 100644 (file)
   <string name="uploader_info_uploading">Đang tải lên</string>
   <string name="file_list_seconds_ago">vài giây trước</string>
   <string name="file_list_empty">Không có gì ở đây .Hãy tải lên một cái gì đó !</string>
+  <string name="file_list_folder">folder</string>
+  <string name="file_list_folders">folders</string>
+  <string name="file_list_file">file</string>
+  <string name="file_list_files">files</string>
   <string name="filedetails_select_file">Tap vào một tập tin để hiển thị thêm thông tin</string>
   <string name="filedetails_size">Kích thước:</string>
   <string name="filedetails_type">Loại:</string>
   <string name="activity_chooser_send_file_title">Gởi</string>
   <string name="empty"></string>
   <string name="prefs_category_accounts">Tài khoản</string>
-  <string name="move_choose_button_text">Chọn</string>
+  <string name="folder_picker_choose_button_text">Chọn</string>
 </resources>
diff --git a/res/values-yo/strings.xml b/res/values-yo/strings.xml
new file mode 100644 (file)
index 0000000..69623e1
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+  <!--TODO re-enable when server-side folder size calculation is available   
+       <item>Biggest - Smallest</item>-->
+  <string name="empty"></string>
+</resources>
index 8c647c4..8141cc9 100644 (file)
@@ -4,13 +4,19 @@
   <string name="about_version">版本:%1$s</string>
   <string name="actionbar_sync">刷新帐户</string>
   <string name="actionbar_upload">上传</string>
-  <string name="actionbar_upload_from_apps">来自其它app的内容</string>
+  <string name="actionbar_upload_from_apps">来自其它应用的内容</string>
   <string name="actionbar_upload_files">文件</string>
-  <string name="actionbar_open_with">打开</string>
-  <string name="actionbar_mkdir">增加文件夹</string>
+  <string name="actionbar_open_with">打开方式</string>
+  <string name="actionbar_mkdir">新建文件夹</string>
   <string name="actionbar_settings">设置</string>
   <string name="actionbar_see_details">详细信息</string>
   <string name="actionbar_send_file">发送</string>
+  <string name="actionbar_sort">排序</string>
+  <string name="actionbar_sort_title">排序方式</string>
+  <string-array name="actionbar_sortby">
+    <item>A - Z</item>
+    <item>新 - 旧</item>
+  </string-array>
   <!--TODO re-enable when server-side folder size calculation is available   
        <item>Biggest - Smallest</item>-->
   <string name="prefs_category_general">常规</string>
   <string name="prefs_accounts">账号</string>
   <string name="prefs_manage_accounts">管理账号</string>
   <string name="prefs_pincode">App PIN</string>
-  <string name="prefs_pincode_summary">保护您的App客户端</string>
+  <string name="prefs_pincode_summary">保护客户端</string>
   <string name="prefs_instant_upload">即时图片上传</string>
   <string name="prefs_instant_upload_summary">即时上传相机拍摄的图片</string>
-  <string name="prefs_instant_video_upload">立即上传视频</string>
+  <string name="prefs_instant_video_upload">即时上传视频</string>
   <string name="prefs_instant_video_upload_summary">即时上传由相机拍摄的视频</string>
   <string name="prefs_log_title">开启日志</string>
-  <string name="prefs_log_summary">这过去是日志问题</string>
+  <string name="prefs_log_summary">用于记录问题</string>
   <string name="prefs_log_title_history">日志历史</string>
   <string name="prefs_log_summary_history">这显示已经保存的日志</string>
   <string name="prefs_log_delete_history_button">删除历史</string>
   <string name="prefs_recommend">推荐给朋友</string>
   <string name="prefs_feedback">反馈</string>
   <string name="prefs_imprint">版本说明</string>
-  <string name="recommend_subject">在您的智能手机上试用一下 %1$s!</string>
-  <string name="recommend_text">“我邀请你使用在你的智能手机上使用 %1$s,在这下载:%2$s”
-       </string>
+  <string name="prefs_remember_last_share_location">记住共享位置</string>
+  <string name="prefs_remember_last_upload_location_summary">记住上次共享上传的位置</string>
+  <string name="recommend_subject">在您的智能手机上试用 %1$s!</string>
+  <string name="recommend_text">我邀请你在智能手机上使用 %1$s\n下载路径:%2$s</string>
   <string name="auth_check_server">检查服务器</string>
   <string name="auth_host_url">服务器地址 https://...</string>
   <string name="auth_username">用户名</string>
   <string name="auth_password">密码</string>
-  <string name="auth_register">新增到 %1$s?</string>
+  <string name="auth_register">初次使用 %1$s?</string>
   <string name="sync_string_files">文件</string>
   <string name="setup_btn_connect">连接</string>
   <string name="uploader_btn_upload_text">上传</string>
   <string name="uploader_top_message">选择上传文件夹:</string>
   <string name="uploader_wrn_no_account_title">未找到账号</string>
-  <string name="uploader_wrn_no_account_text">设备上未找到账号,请先创建账号。</string>
+  <string name="uploader_wrn_no_account_text">设备上未找到 %1$s 账号,请先设置账号。</string>
   <string name="uploader_wrn_no_account_setup_btn_text">设置</string>
   <string name="uploader_wrn_no_account_quit_btn_text">退出</string>
-  <string name="uploader_wrn_no_content_title">没有上传的内容</string>
-  <string name="uploader_wrn_no_content_text">没æ\9c\89æ\8e¥æ\94¶å\88°å\86\85容ï¼\8cæ\97 å\8f¯ä¸\8aä¼ 。</string>
+  <string name="uploader_wrn_no_content_title">没有需要上传的内容</string>
+  <string name="uploader_wrn_no_content_text">没æ\9c\89æ\8e¥æ\94¶å\88°å\86\85容ï¼\8c没æ\9c\89é\9c\80è¦\81ä¸\8aä¼ ç\9a\84å\86\85容。</string>
   <string name="uploader_error_forbidden_content">%1$s未被允许访问共享内容。</string>
   <string name="uploader_info_uploading">上传</string>
-  <string name="file_list_seconds_ago">秒前</string>
+  <string name="file_list_seconds_ago">秒前</string>
   <string name="file_list_empty">这里还什么都没有。上传些东西吧!</string>
   <string name="file_list_loading">载入中....</string>
   <string name="local_file_list_empty">在该文件夹中不存在文件。</string>
-  <string name="filedetails_select_file">点击一个文件来显示额外的信息。</string>
+  <string name="file_list_folder">文件夹</string>
+  <string name="file_list_folders">文件夹</string>
+  <string name="file_list_file">文件</string>
+  <string name="file_list_files">文件</string>
+  <string name="filedetails_select_file">点击一个文件可以显示额外的信息。</string>
   <string name="filedetails_size">大小:</string>
   <string name="filedetails_type">类型:</string>
   <string name="filedetails_created">创建于:</string>
-  <string name="filedetails_modified">已修改:</string>
+  <string name="filedetails_modified">修改于:</string>
   <string name="filedetails_download">下载</string>
   <string name="filedetails_sync_file">刷新文件</string>
   <string name="filedetails_renamed_in_upload_msg">上传过程中文件被更名为了 %1$s</string>
   <string name="action_share_file">分享链接</string>
-  <string name="action_unshare_file">å\8f\96æ¶\88å\85±享链接</string>
+  <string name="action_unshare_file">å\8f\96æ¶\88å\88\86享链接</string>
   <string name="common_yes">是</string>
   <string name="common_no">否</string>
-  <string name="common_ok">OK</string>
+  <string name="common_ok">确定</string>
   <string name="common_cancel_download">取消下载</string>
   <string name="common_cancel_upload">取消上传</string>
   <string name="common_cancel">取消</string>
   <string name="delete_account">删除账号</string>
   <string name="create_account">创建账号</string>
   <string name="upload_chooser_title">上传自...</string>
-  <string name="uploader_info_dirname">目录名称</string>
+  <string name="uploader_info_dirname">文件夹名称</string>
   <string name="uploader_upload_in_progress_ticker">上传...</string>
   <string name="uploader_upload_in_progress_content">%1$d%% 上传 %2$s</string>
   <string name="uploader_upload_succeeded_ticker">上传成功</string>
   <string name="uploader_upload_succeeded_content_single">%1$s 成功上传</string>
   <string name="uploader_upload_failed_ticker">上传失败</string>
-  <string name="uploader_upload_failed_content_single">1$上传未能完成</string>
+  <string name="uploader_upload_failed_content_single">%1$s 未能成功上传</string>
   <string name="uploader_upload_failed_credentials_error">上传失败,您需要重新登录</string>
-  <string name="downloader_download_in_progress_ticker">下载中……</string>
+  <string name="downloader_download_in_progress_ticker">下载中...</string>
   <string name="downloader_download_in_progress_content">%1$d%% 下载中 %2$s</string>
   <string name="downloader_download_succeeded_ticker">下载成功</string>
-  <string name="downloader_download_succeeded_content">%1$s 成功下载</string>
+  <string name="downloader_download_succeeded_content">成功下载 %1$s </string>
   <string name="downloader_download_failed_ticker">下载失败</string>
-  <string name="downloader_download_failed_content">下载1$s 未能完成</string>
+  <string name="downloader_download_failed_content">%1$s 下载未能完成</string>
   <string name="downloader_not_downloaded_yet">未下载完毕</string>
   <string name="downloader_download_failed_credentials_error">下载失败,您需要重新登录</string>
   <string name="common_choose_account">选择账户</string>
   <string name="sync_fail_ticker">同步失败</string>
   <string name="sync_fail_ticker_unauthorized">同步失败,您需要重新登录</string>
   <string name="sync_fail_content"> %1$s同步未完成。</string>
-  <string name="sync_fail_content_unauthorized">密码错误%1$s</string>
+  <string name="sync_fail_content_unauthorized">%1$s 的密码错误</string>
   <string name="sync_conflicts_in_favourites_ticker">发现冲突</string>
   <string name="sync_conflicts_in_favourites_content">%1$d 文件无法同步</string>
   <string name="sync_fail_in_favourites_ticker">文件同步失败</string>
-  <string name="sync_fail_in_favourites_content">无法同步 %1$d 文件内容(与 %2$d 冲突)</string>
+  <string name="sync_fail_in_favourites_content">无法同步 %1$d 文件内容(%2$d 冲突)</string>
   <string name="sync_foreign_files_forgotten_ticker">某些本地文件已被遗忘</string>
   <string name="sync_foreign_files_forgotten_content">%2$s 目录中的 %1$d 个文件不能被复制到</string>
   <string name="sync_foreign_files_forgotten_explanation">从 1.3.16 版起,从此设备上传的文件将被复制到本地的 %1$s 文件夹,以防止某个单一文件在多个账户间同步而造成的数据损失。\n\n 由于此项变化,此应用之前的版本上传的全部文件都已被复制到了 %2$s 文件夹。然而,账户同步期间有一个错误阻止了此操作的完成。您可能想保持文件不动,并移除指向 %3$s 的链接,或将文件移动到 %1$s 文件夹中并保持其到 %4$s 的链接。下面列出的是本地文件,以及它们被链接到的 %5$s 中的远程文件。</string>
-  <string name="sync_current_folder_was_removed">文件夹%1$s 不存在</string>
+  <string name="sync_current_folder_was_removed">文件夹%1$s 已经不存在</string>
   <string name="foreign_files_move">移动所有</string>
   <string name="foreign_files_success">所有文件已被移动</string>
   <string name="foreign_files_fail">某些文件无法被移动</string>
   <string name="pincode_mismatch">两次 App PIN码不同</string>
   <string name="pincode_wrong">App PIN码不正确</string>
   <string name="pincode_removed">App PIN码已移除</string>
-  <string name="pincode_stored">App PIN码已保存</string>
+  <string name="pincode_stored">App PIN码已保存</string>
   <string name="media_notif_ticker">%1$s 音乐播放器</string>
   <string name="media_state_playing">%1$s (播放中)</string>
   <string name="media_state_loading">%1$s (载入中)</string>
   <string name="media_play_pause_description">播放暂停按钮</string>
   <string name="media_forward_description">快进按钮</string>
   <string name="auth_getting_authorization">正在认证...</string>
-  <string name="auth_trying_to_login">尝试登录</string>
+  <string name="auth_trying_to_login">尝试登录...</string>
   <string name="auth_no_net_conn_title">没有网络连接</string>
-  <string name="auth_nossl_plain_ok_title">安全链接无效。</string>
+  <string name="auth_nossl_plain_ok_title">安全连接不可用。</string>
   <string name="auth_connection_established">连接已建立。</string>
   <string name="auth_testing_connection">测试连接……</string>
-  <string name="auth_not_configured_title">服务器配置不正确</string>
+  <string name="auth_not_configured_title">服务器配置不正确</string>
   <string name="auth_account_not_new">此设备中已经存在同名同服务器的帐号</string>
   <string name="auth_account_not_the_same">输入用户与此帐户的用户不符</string>
   <string name="auth_unknown_error_title">发生未知错误!</string>
-  <string name="auth_unknown_host_title">无法找到服务器</string>
+  <string name="auth_unknown_host_title">无法找到主机</string>
   <string name="auth_incorrect_path_title">未发现服务器实例</string>
   <string name="auth_timeout_title">看起来服务器不太给力</string>
   <string name="auth_incorrect_address_title">网址不正确</string>
   <string name="auth_ssl_unverified_server_title">无法验证 SSL 服务器的身份</string>
   <string name="auth_bad_oc_version_title">不可辨识的服务器服务器版本</string>
   <string name="auth_wrong_connection_title">无法建立连接</string>
-  <string name="auth_secure_connection">å\8a å¯\86连接已建立</string>
-  <string name="auth_unauthorized">用户名或密码错误</string>
+  <string name="auth_secure_connection">å®\89å\85¨连接已建立</string>
+  <string name="auth_unauthorized">用户名或密码错误</string>
   <string name="auth_oauth_error">认证不成功</string>
   <string name="auth_oauth_error_access_denied">访问被认证服务器拒绝</string>
   <string name="auth_wtf_reenter_URL">意外状态;请再次输入服务器的地址</string>
   <string name="auth_expired_oauth_token_toast">你的授权已经过期。请重新授权。</string>
-  <string name="auth_expired_basic_auth_toast">请输入当前密码</string>
+  <string name="auth_expired_basic_auth_toast">请输入当前密码</string>
   <string name="auth_expired_saml_sso_token_toast">您的会话超时了,请重新连接</string>
   <string name="auth_connecting_auth_server">正在连接到认证服务器....</string>
   <string name="auth_unsupported_auth_method">服务器不支持这种验证方式</string>
   <string name="filename_empty">文件名不能为空</string>
   <string name="wait_a_moment">请稍候</string>
   <string name="filedisplay_unexpected_bad_get_content">未知问题;请试试用其他程序选择此文件</string>
-  <string name="filedisplay_no_file_selected">未选择文件</string>
+  <string name="filedisplay_no_file_selected">未选择文件</string>
   <string name="activity_chooser_title">发送链接给 …</string>
   <string name="oauth_check_onoff">使用oAuth2登陆</string>
   <string name="oauth_login_connection">连接oAuth2 服务器...</string>
   <string name="ssl_validator_header">站点身份无法验证</string>
-  <string name="ssl_validator_reason_cert_not_trusted">不受信任的服务器证书</string>
-  <string name="ssl_validator_reason_cert_expired">服务器证书过期</string>
-  <string name="ssl_validator_reason_cert_not_yet_valid">服务器证书过新</string>
-  <string name="ssl_validator_reason_hostname_not_verified">主机名与证书中的记录不匹配</string>
+  <string name="ssl_validator_reason_cert_not_trusted">不受信任的服务器证书</string>
+  <string name="ssl_validator_reason_cert_expired">服务器证书过期</string>
+  <string name="ssl_validator_reason_cert_not_yet_valid">- 服务器证书时间比当前时间还晚</string>
+  <string name="ssl_validator_reason_hostname_not_verified">主机名与证书中的记录不匹配</string>
   <string name="ssl_validator_question">是否信任此证书?</string>
   <string name="ssl_validator_not_saved">证书无法保存</string>
   <string name="ssl_validator_btn_details_see">详细信息</string>
   <string name="placeholder_filesize">389字节</string>
   <string name="placeholder_timestamp">2012/05/18 下午12:23 </string>
   <string name="placeholder_media_time">12:23:45</string>
-  <string name="instant_upload_on_wifi">仅通过WIFI上传图片。</string>
-  <string name="instant_video_upload_on_wifi">仅在 WIFI 下上传视频</string>
+  <string name="instant_upload_on_wifi">仅通过 WIFI 上传图片。</string>
+  <string name="instant_video_upload_on_wifi">仅通过 WIFI 上传视频</string>
   <string name="instant_upload_path">/InstantUpload</string>
   <string name="conflict_title">上传冲突</string>
   <string name="conflict_message">远程文件 %s 未与本地文件同步。继续将替换服务器上的文件内容。</string>
   <string name="conflict_overwrite">覆盖</string>
   <string name="conflict_dont_upload">不上传</string>
   <string name="preview_image_description">图片预览</string>
-  <string name="preview_image_error_unknown_format">不能显示图片</string>
+  <string name="preview_image_error_unknown_format">无法显示图片</string>
   <string name="error__upload__local_file_not_copied">无法复制 %1$s 到本地目录 %2$s</string>
+  <string name="prefs_instant_upload_path_title">上传路径</string>
   <string name="share_link_no_support_share_api">抱歉,共享功能未启用。请联系管理员。</string>
   <string name="share_link_file_no_exist">无法共享。请检查文件是否存在</string>
   <string name="share_link_file_error">共享文件或目录出错</string>
   <string name="network_error_connect_timeout_exception">等待服务器响应时发生了一个错误,此操作无法完成</string>
   <string name="network_host_not_available">服务器不可用,此操作无法完成</string>
   <string name="empty"></string>
-  <string name="forbidden_permissions">你没有许可%s</string>
+  <string name="forbidden_permissions">你没有权限%s</string>
   <string name="forbidden_permissions_rename">重命名该文件</string>
   <string name="forbidden_permissions_delete">删除该文件</string>
-  <string name="share_link_forbidden_permissions">å\88\86享该文件</string>
+  <string name="share_link_forbidden_permissions">å\85±享该文件</string>
   <string name="unshare_link_forbidden_permissions">取消共享该文件</string>
   <string name="forbidden_permissions_create">创建文件</string>
-  <string name="uploader_upload_forbidden_permissions">上传此文件夹</string>
+  <string name="uploader_upload_forbidden_permissions">在此文件夹上传</string>
   <string name="downloader_download_file_not_found">该文件在服务器上不可用</string>
   <string name="prefs_category_accounts">账号</string>
   <string name="prefs_add_account">添加账号</string>
+  <string name="auth_redirect_non_secure_connection_title">安全连接被重定向到非安全路径.</string>
   <string name="actionbar_logger">日志</string>
   <string name="log_send_history_button">发送历史</string>
-  <string name="log_mail_subject">ownCloud安卓客户端日志</string>
-  <string name="log_progress_dialog_text">加载数据中...</string>
+  <string name="log_send_no_mail_app">未找到可以发送日志的程序。请安装 mail!</string>
+  <string name="log_send_mail_subject">%1$s Android 程序日志</string>
+  <string name="log_progress_dialog_text">载入数据...</string>
   <string name="saml_authentication_required_text">需要认证</string>
   <string name="saml_authentication_wrong_pass">错误密码</string>
   <string name="actionbar_move">移动</string>
   <string name="file_list_empty_moving">这里还什么都没有。上传些东西吧!</string>
-  <string name="move_choose_button_text">选择(&amp;C)...</string>
+  <string name="folder_picker_choose_button_text">选择</string>
   <string name="move_file_not_found">无法移动。请检查文件是否存在</string>
-  <string name="move_file_invalid_into_descendent">b不能够把一个目录移动到它的下级</string>
+  <string name="move_file_invalid_into_descendent">无法把一个目录移动到它的下级</string>
   <string name="move_file_invalid_overwrite">该文件已经存在在目标文件夹</string>
   <string name="move_file_error">尝试移动该文件或文件夹时发生错误</string>
   <string name="forbidden_permissions_move">移动该文件</string>
+  <string name="prefs_category_instant_uploading">即时上传</string>
   <string name="prefs_category_security">安全</string>
+  <string name="prefs_instant_video_upload_path_title">视频上传路径</string>
+  <string name="download_folder_failed_content">%1$s 文件夹的下载无法完成</string>
+  <string name="shared_subject_header">分享</string>
 </resources>
index b273179..892944e 100644 (file)
@@ -71,4 +71,5 @@
   <string name="empty"></string>
   <string name="prefs_category_accounts">帳號</string>
   <string name="saml_authentication_wrong_pass">密碼錯誤</string>
+  <string name="prefs_category_security">安全</string>
 </resources>
index 8dc73b0..641adaf 100644 (file)
   <string name="file_list_empty">這裡還沒有東西,上傳一些吧!</string>
   <string name="file_list_loading">載入中…</string>
   <string name="local_file_list_empty">這個目錄中沒有任何檔案.</string>
+  <string name="file_list_folder">資料夾</string>
+  <string name="file_list_folders">資料夾</string>
+  <string name="file_list_file">檔案</string>
+  <string name="file_list_files">檔案</string>
   <string name="filedetails_select_file">在檔案上輕觸來顯示更多資訊。</string>
   <string name="filedetails_size">容量:</string>
   <string name="filedetails_type">類型:</string>
   <string name="placeholder_filesize">389 KB</string>
   <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
   <string name="placeholder_media_time">12:23:45</string>
-  <string name="instant_upload_on_wifi">å\8fªä½¿ç\94¨ WiFi ä¸\8aå\82³</string>
-  <string name="instant_video_upload_on_wifi">只透過無線網路來執行即時影像上傳的功能</string>
+  <string name="instant_upload_on_wifi">å\8fªä½¿ç\94¨ WiFi ä¾\86å\9f·è¡\8cå\8d³æ\99\82å\9c\96ç\89\87ä¸\8aå\82³ç\9a\84å\8a\9fè\83½</string>
+  <string name="instant_video_upload_on_wifi">只使用 WiFi 來執行即時影像上傳的功能</string>
   <string name="instant_upload_path">/InstantUpload</string>
   <string name="conflict_title">更新衝突</string>
   <string name="conflict_message">遠端檔案 %s 未同步到本地. 繼續將取代伺服器上的文件.</string>
   <string name="auth_redirect_non_secure_connection_title">安全連線被轉向到一個非安全的連線</string>
   <string name="actionbar_logger">紀錄</string>
   <string name="log_send_history_button">傳送歷史記錄</string>
-  <string name="log_mail_subject">ownCloud Android 應用程式記錄</string>
+  <string name="log_send_no_mail_app">找不到可以傳送記錄的電子郵件程式. 請安裝電子郵件軟體!</string>
+  <string name="log_send_mail_subject">%1$s Android 應用程式記錄</string>
   <string name="log_progress_dialog_text">資料載入中...</string>
   <string name="saml_authentication_required_text">必須驗證</string>
   <string name="saml_authentication_wrong_pass">密碼錯誤</string>
   <string name="actionbar_move">移動</string>
   <string name="file_list_empty_moving">找不到任何檔案. 你可以新增一個目錄!</string>
-  <string name="move_choose_button_text">選擇</string>
+  <string name="folder_picker_choose_button_text">選擇</string>
   <string name="move_file_not_found">無法搬移. 請檢查該檔案是否存在</string>
   <string name="move_file_invalid_into_descendent">把一個目錄搬移到其底下的子目錄是不可能的</string>
   <string name="move_file_invalid_overwrite">這個檔案已經存在於目的目錄中</string>
   <string name="forbidden_permissions_move">移動這個檔案</string>
   <string name="prefs_category_instant_uploading">即時上傳</string>
   <string name="prefs_category_security">安全性</string>
+  <string name="prefs_instant_video_upload_path_title">影片上傳路徑</string>
+  <string name="shared_subject_header">以分享的</string>
+  <string name="with_you_subject_header">與你</string>
+  <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 </resources>
index 2b1fec9..9577e5e 100644 (file)
@@ -22,5 +22,7 @@
     <color name="filelist_icon_backgorund">#DDDDDD</color>
     <color name="owncloud_blue_bright">#00ddff</color>
     <color name="list_item_lastmod_and_filesize_text">#989898</color>
+    <color name="textColor">#303030</color>
+    <color name="list_divider_background">#fff0f0f0</color>
     
 </resources>
\ No newline at end of file
index d433cba..955f87e 100644 (file)
@@ -18,4 +18,5 @@
 -->
 <resources>
        <dimen name="file_icon_size">32dp</dimen>
+    <dimen name="file_icon_size_grid">128dp</dimen>
 </resources>
index d6b5457..914681b 100644 (file)
     <string name="file_list_empty">Nothing in here. Upload something!</string>
     <string name="file_list_loading">Loading...</string>
     <string name="local_file_list_empty">There are no files in this folder.</string>
+    <string name="file_list_folder">folder</string>
+    <string name="file_list_folders">folders</string>
+    <string name="file_list_file">file</string>
+    <string name="file_list_files">files</string>
     <string name="filedetails_select_file">Tap on a file to display additional information.</string>
     <string name="filedetails_size">Size:</string>
     <string name="filedetails_type">Type:</string>
 
        <string name="actionbar_logger">Logs</string>
        <string name="log_send_history_button">Send History</string>
-       <string name="log_mail_subject">ownCloud Android app logs</string>
-       <string name="log_progress_dialog_text">Loading data...</string>
+       <string name="log_send_no_mail_app">No app for sending logs found. Install mail app!</string>
+       <string name="log_send_mail_subject">%1$s Android app logs</string>
+       <string name="log_progress_dialog_text">Loading data&#8230;</string>
 
        <string name="saml_authentication_required_text">Authentication required</string>
        <string name="saml_authentication_wrong_pass">Wrong password</string>
        <string name="prefs_category_security">Security</string>
 
        <string name="prefs_instant_video_upload_path_title">Upload Video Path</string>
+    <string name="download_folder_failed_content">Download of %1$s folder could not be completed</string>
+
+       <string name="shared_subject_header">shared</string>
+       <string name="with_you_subject_header">with you</string>
+       <string name="subject_token">%1$s %2$s &gt;&gt;%3$s&lt;&lt; %4$s</string>
 
 </resources>
index 1673e21..b674cbc 100644 (file)
                         android:summary="@string/prefs_pincode_summary"/>
        </PreferenceCategory>
 
-    <PreferenceCategory android:title="@string/prefs_category_instant_uploading">
-        <com.owncloud.android.ui.PreferenceWithLongSummary
-                                                       android:title="@string/prefs_instant_upload_path_title"
-                                                       android:key="instant_upload_path" />
-           <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_uploading"
+    <PreferenceCategory android:title="@string/prefs_category_instant_uploading" android:key="instant_uploading_category">
+         <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_uploading"
                                android:title="@string/prefs_instant_upload"
                                android:summary="@string/prefs_instant_upload_summary"/>
-           <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:dependency="instant_uploading"
-                                               android:disableDependentsState="true"
+         <com.owncloud.android.ui.PreferenceWithLongSummary
+                                                       android:title="@string/prefs_instant_upload_path_title"
+                                                       android:key="instant_upload_path" />
+           <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle
                                                android:title="@string/instant_upload_on_wifi"
                                                android:key="instant_upload_on_wifi"/>
+           <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_video_uploading"
+                               android:title="@string/prefs_instant_video_upload"
+                               android:summary="@string/prefs_instant_video_upload_summary" />
            <com.owncloud.android.ui.PreferenceWithLongSummary
                                                        android:title="@string/prefs_instant_video_upload_path_title"
                                                        android:key="instant_video_upload_path" />
-           <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:key="instant_video_uploading"
-                               android:title="@string/prefs_instant_video_upload"
-                               android:summary="@string/prefs_instant_video_upload_summary"/>
-           <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle android:dependency="instant_video_uploading"
-                                               android:disableDependentsState="true"
+           <com.owncloud.android.ui.CheckBoxPreferenceWithLongTitle
                                                android:title="@string/instant_video_upload_on_wifi"
                                                android:key="instant_video_upload_on_wifi"/>
            <!-- DISABLED FOR RELEASE UNTIL FIXED
diff --git a/settings.gradle b/settings.gradle
new file mode 100644 (file)
index 0000000..66022f5
--- /dev/null
@@ -0,0 +1,3 @@
+include ':owncloud-android-library'
+include 'libs/actionbarsherlock_lib'
+include ':'
index dc44a12..0781036 100644 (file)
@@ -1,8 +1,59 @@
+@echo off
+
+:: Use argument to decide which build system should be used
+if "%1" == "gradle" goto initForGradle
+if "%1" == "maven" goto initForMaven
+if "%1" == "ant" goto initForAnt
+goto invalidInput
+
+:initForGradle
+echo "Executing Gradle setup..."
+goto initDefault
+
+:initForMaven
+echo "Executing Maven setup..."
+goto initDefault
+
+:initForAnt
+echo "Executing Ant setup..."
+::If the directory exists the script has already been executed
+if not exist .\actionbarsherlock (
+
+    ::Gets the owncloud-android-library
+    call git submodule init
+    call git submodule update
+    
+    ::Clones the actionbarsherlock and checks-out the right release (4.2.0)
+    git clone "https://github.com/JakeWharton/ActionBarSherlock.git" "actionbarsherlock"
+    cd "actionbarsherlock"
+    git checkout "90939dc3925ffaaa0de269bbbe1b35e274968ea1"
+    cd ../
+
+    call android.bat update project -p actionbarsherlock/library -n ActionBarSherlock --target android-19
+    call android.bat update lib-project -p owncloud-android-library
+    call android.bat update project -p .
+    call android.bat update project -p oc_jb_workaround
+    copy /Y third_party\android-support-library\android-support-v4.jar actionbarsherlock\library\libs\android-support-v4.jar
+    call android.bat update test-project -p tests -m ..
+)
+goto complete
+
+:initDefault
 call git submodule init
 call git submodule update
-call android.bat update project -p actionbarsherlock\library -n ActionBarSherlock
 call android.bat update lib-project -p owncloud-android-library
 call android.bat update project -p .
 call android.bat update project -p oc_jb_workaround
-copy /Y third_party\android-support-library\android-support-v4.jar actionbarsherlock\library\libs\android-support-v4.jar
 call android.bat update test-project -p tests -m ..
+goto complete
+
+:invalidInput
+echo "Input argument invalid."
+echo "Usage: %0 [ant | maven | gradle]."
+goto exit
+
+:complete
+echo "...setup complete."
+goto exit
+
+:exit
\ No newline at end of file
index ae42142..8c9af07 100755 (executable)
@@ -1,10 +1,75 @@
 #!/bin/bash -e
 
-git submodule init
-git submodule update
-android update project -p actionbarsherlock/library -n ActionBarSherlock
-android update lib-project -p owncloud-android-library
-android update project -p .
-android update project -p oc_jb_workaround
-cp third_party/android-support-library/android-support-v4.jar actionbarsherlock/library/libs/android-support-v4.jar 
-android update test-project -p tests -m ..
+
+#Repository
+ActionBarSherlockRepo="https://github.com/JakeWharton/ActionBarSherlock.git"
+
+#Directory for actionbarsherlock
+DIRECTORY="actionbarsherlock"
+
+#Commit for version 4.2 of actionbar sherlock
+COMMIT="90939dc3925ffaaa0de269bbbe1b35e274968ea1"
+
+
+function initDefault {
+    git submodule init
+    git submodule update
+    android update lib-project -p owncloud-android-library
+    android update project -p .
+    android update project -p oc_jb_workaround
+    android update test-project -p tests -m ..
+}
+
+function initForAnt {
+    #If the directory exists the script has already been executed
+    if [ ! -d "$DIRECTORY" ]; then
+
+        #Gets the owncloud-android-library
+        git submodule init
+        git submodule update
+
+        #Clones the actionbarsherlock and checks-out the right release (4.2.0)
+        git clone $ActionBarSherlockRepo $DIRECTORY
+        cd $DIRECTORY
+        git checkout $COMMIT
+        cd ../
+
+        #As default it updates the ant scripts
+        android update project -p "$DIRECTORY"/library -n ActionBarSherlock --target android-19
+        android update lib-project -p owncloud-android-library
+        android update project -p .
+        android update project -p oc_jb_workaround
+        cp third_party/android-support-library/android-support-v4.jar actionbarsherlock/library/libs/android-support-v4.jar
+        android update test-project -p tests -m ..
+    fi
+}
+
+#No args
+if [ $# -lt 1 ]; then
+        echo "No args found"
+        echo "Usage : $0 [gradle | maven | ant]"
+        exit
+fi
+
+#checking args
+case "$1" in
+
+    "ant")  
+        echo "Creating Ant environment"
+        initForAnt
+        ;;
+
+    "gradle")  echo  "Creating gradle environment"
+        initDefault
+        ;;
+
+    "maven")  echo  "Creating maven environment"
+        initDefault
+        ;;
+
+    *)  echo "Argument not recognized"
+        echo "Usage : $0 [gradle | maven | ant]"
+       ;;
+esac
+
+exit
index 7672284..8d7182d 100644 (file)
@@ -683,7 +683,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         \r
         if (mOperationsServiceBinder != null) {\r
             //Log_OC.wtf(TAG, "getting access token..." );\r
-            mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);\r
+            mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);\r
         }\r
     }\r
 \r
@@ -752,7 +752,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
                 normalizeUrlSuffix(uri)\r
             );\r
             if (mOperationsServiceBinder != null) {\r
-                mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);\r
+                mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);\r
             } else {\r
               Log_OC.wtf(TAG, "Server check tried with OperationService unbound!" );\r
             }\r
@@ -888,7 +888,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         \r
         if (mOperationsServiceBinder != null) {\r
             //Log_OC.wtf(TAG, "starting existenceCheckRemoteOperation..." );\r
-            mWaitingForOpId = mOperationsServiceBinder.newOperation(existenceCheckIntent);\r
+            mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(existenceCheckIntent);\r
         }\r
     }\r
 \r
@@ -1703,7 +1703,7 @@ SsoWebViewClientListener, OnSslUntrustedCertListener {
         \r
         if (mOperationsServiceBinder != null) {\r
             //Log_OC.wtf(TAG, "starting getRemoteUserNameOperation..." );\r
-            mWaitingForOpId = mOperationsServiceBinder.newOperation(getUserNameIntent);\r
+            mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getUserNameIntent);\r
         }\r
     }\r
 \r
index 41a5106..816c676 100644 (file)
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Vector;
 
 import com.owncloud.android.MainApp;
@@ -33,7 +34,6 @@ import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.lib.resources.files.FileUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 
-
 import android.accounts.Account;
 import android.content.ContentProviderClient;
 import android.content.ContentProviderOperation;
@@ -41,10 +41,12 @@ import android.content.ContentProviderResult;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
+import android.content.Intent;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
+import android.provider.MediaStore;
 
 public class FileDataStorageManager {
 
@@ -192,6 +194,7 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
         cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
         cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
+        cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
         
         boolean sameRemotePath = fileExists(file.getRemotePath());
         if (sameRemotePath ||
@@ -260,8 +263,8 @@ public class FileDataStorageManager {
      * HERE ONLY DATA CONSISTENCY SHOULD BE GRANTED
      *  
      * @param folder
-     * @param files
-     * @param removeNotUpdated
+     * @param updatedFiles
+     * @param filesToRemove
      */
     public void saveFolder(
             OCFile folder, Collection<OCFile> updatedFiles, Collection<OCFile> filesToRemove
@@ -301,6 +304,7 @@ public class FileDataStorageManager {
             cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
             cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
             cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
+            cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
 
             boolean existsByPath = fileExists(file.getRemotePath());
             if (existsByPath || fileExists(file.getFileId())) {
@@ -346,7 +350,9 @@ public class FileDataStorageManager {
                     ).withSelection(where, whereArgs).build());
                     
                     if (file.isDown()) {
-                        new File(file.getStoragePath()).delete();
+                        String path = file.getStoragePath();
+                        new File(path).delete();
+                        triggerMediaScan(path); // notify MediaScanner about removed file
                     }
                 }
             }
@@ -484,8 +490,12 @@ public class FileDataStorageManager {
                     }
                     success &= (deleted > 0); 
                 }
-                if (removeLocalCopy && file.isDown() && file.getStoragePath() != null && success) {
-                    success = new File(file.getStoragePath()).delete();
+                String localPath = file.getStoragePath();
+                if (removeLocalCopy && file.isDown() && localPath != null && success) {
+                    success = new File(localPath).delete();
+                    if (success) {
+                        deleteFileInMediaScan(localPath);
+                    }
                     if (!removeDBData && success) {
                         // maybe unnecessary, but should be checked TODO remove if unnecessary
                         file.setStoragePath(null);
@@ -532,7 +542,8 @@ public class FileDataStorageManager {
 
     private boolean removeLocalFolder(OCFile folder) {
         boolean success = true;
-        File localFolder = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder));
+        String localFolderPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder);
+        File localFolder = new File(localFolderPath);
         if (localFolder.exists()) {
             // stage 1: remove the local files already registered in the files database
             Vector<OCFile> files = getFolderContent(folder.getFileId());
@@ -545,6 +556,8 @@ public class FileDataStorageManager {
                             File localFile = new File(file.getStoragePath());
                             success &= localFile.delete();
                             if (success) {
+                                // notify MediaScanner about removed file
+                                deleteFileInMediaScan(file.getStoragePath());
                                 file.setStoragePath(null);
                                 saveFile(file);
                             }
@@ -568,6 +581,7 @@ public class FileDataStorageManager {
                 if (localFile.isDirectory()) {
                     success &= removeLocalFolder(localFile);
                 } else {
+                    String path = localFile.getAbsolutePath();
                     success &= localFile.delete();
                 }
             }
@@ -576,109 +590,20 @@ public class FileDataStorageManager {
         return success;
     }
 
+    
     /**
-     * Updates database for a folder that was moved to a different location.
+     * Updates database and file system for a file or folder that was moved to a different location.
      * 
      * TODO explore better (faster) implementations
      * TODO throw exceptions up !
      */
-    public void moveFolder(OCFile folder, String newPath) {
-        // TODO check newPath
-
-        if (    folder != null && folder.isFolder() && 
-                folder.fileExists() && !OCFile.ROOT_PATH.equals(folder.getFileName())
-            ) {
-            /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir')
-            Cursor c = null;
-            if (getContentProviderClient() != null) {
-                try {
-                    c = getContentProviderClient().query (
-                        ProviderTableMeta.CONTENT_URI, 
-                        null,
-                        ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + 
-                                ProviderTableMeta.FILE_PATH + " LIKE ? ",
-                        new String[] { mAccount.name, folder.getRemotePath() + "%"  }, 
-                        ProviderTableMeta.FILE_PATH + " ASC "
-                    );
-                } catch (RemoteException e) {
-                    Log_OC.e(TAG, e.getMessage());
-                }
-            } else {
-                c = getContentResolver().query (
-                    ProviderTableMeta.CONTENT_URI, 
-                    null,
-                    ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + 
-                            ProviderTableMeta.FILE_PATH + " LIKE ? ",
-                    new String[] { mAccount.name, folder.getRemotePath() + "%"  }, 
-                    ProviderTableMeta.FILE_PATH + " ASC "
-                );
-            }
-
-            /// 2. prepare a batch of update operations to change all the descendants
-            ArrayList<ContentProviderOperation> operations = 
-                    new ArrayList<ContentProviderOperation>(c.getCount());
-            int lengthOfOldPath = folder.getRemotePath().length();
-            String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name);
-            int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
-            if (c.moveToFirst()) {
-                do {
-                    ContentValues cv = new ContentValues(); // keep the constructor in the loop
-                    OCFile child = createFileInstance(c);
-                    cv.put(
-                        ProviderTableMeta.FILE_PATH, 
-                        newPath + child.getRemotePath().substring(lengthOfOldPath)
-                    );
-                    if (    child.getStoragePath() != null && 
-                            child.getStoragePath().startsWith(defaultSavePath)  ) {
-                        cv.put(
-                                ProviderTableMeta.FILE_STORAGE_PATH, 
-                                defaultSavePath + newPath + 
-                                    child.getStoragePath().substring(lengthOfOldStoragePath)
-                        );
-                    }
-                    operations.add(
-                            ContentProviderOperation.
-                            newUpdate(ProviderTableMeta.CONTENT_URI).
-                            withValues(cv).
-                            withSelection(  
-                                    ProviderTableMeta._ID + "=?", 
-                                    new String[] { String.valueOf(child.getFileId()) }
-                            ).
-                            build()
-                    );
-                } while (c.moveToNext());
-            }
-            c.close();
-
-            /// 3. apply updates in batch
-            try {
-                if (getContentResolver() != null) {
-                    getContentResolver().applyBatch(MainApp.getAuthority(), operations);
-
-                } else {
-                    getContentProviderClient().applyBatch(operations);
-                }
-
-            } catch (OperationApplicationException e) {
-                Log_OC.e(TAG, "Fail to update descendants of " + 
-                        folder.getFileId() + " in database", e);
-
-            } catch (RemoteException e) {
-                Log_OC.e(TAG, "Fail to update desendants of " + 
-                        folder.getFileId() + " in database", e);
-            }
-
-        }
-    }
-
-    
     public void moveLocalFile(OCFile file, String targetPath, String targetParentPath) {
 
         if (file != null && file.fileExists() && !OCFile.ROOT_PATH.equals(file.getFileName())) {
             
             OCFile targetParent = getFileByPath(targetParentPath);
             if (targetParent == null) {
-                // TODO panic
+                throw new IllegalStateException("Parent folder of the target path does not exist!!");
             }
             
             /// 1. get all the descendants of the moved element in a single QUERY
@@ -718,6 +643,8 @@ public class FileDataStorageManager {
             ArrayList<ContentProviderOperation> operations = 
                     new ArrayList<ContentProviderOperation>(c.getCount());
             String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name);
+            List<String> originalPathsToTriggerMediaScan = new ArrayList<String>();
+            List<String> newPathsToTriggerMediaScan = new ArrayList<String>();
             if (c.moveToFirst()) {
                 int lengthOfOldPath = file.getRemotePath().length();
                 int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath;
@@ -731,11 +658,14 @@ public class FileDataStorageManager {
                     if (child.getStoragePath() != null && 
                             child.getStoragePath().startsWith(defaultSavePath)) {
                         // update link to downloaded content - but local move is not done here!
-                        cv.put(
-                            ProviderTableMeta.FILE_STORAGE_PATH, 
-                            defaultSavePath + targetPath + 
-                                child.getStoragePath().substring(lengthOfOldStoragePath)
-                        );
+                        String targetLocalPath = defaultSavePath + targetPath + 
+                                child.getStoragePath().substring(lengthOfOldStoragePath);
+                        
+                        cv.put(ProviderTableMeta.FILE_STORAGE_PATH, targetLocalPath);
+                        
+                        originalPathsToTriggerMediaScan.add(child.getStoragePath());
+                        newPathsToTriggerMediaScan.add(targetLocalPath);
+                        
                     }
                     if (child.getRemotePath().equals(file.getRemotePath())) {
                         cv.put(
@@ -766,27 +696,35 @@ public class FileDataStorageManager {
                 }
 
             } catch (Exception e) {
-                Log_OC.e(
-                    TAG, 
-                    "Fail to update " + file.getFileId() + " and descendants in database", 
-                    e
-                );
+                Log_OC.e(TAG, "Fail to update " + file.getFileId() + " and descendants in database", e);
             }
 
             /// 4. move in local file system 
-            String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
-            File localFile = new File(localPath);
+            String originalLocalPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
+            String targetLocalPath = defaultSavePath + targetPath;
+            File localFile = new File(originalLocalPath);
             boolean renamed = false;
             if (localFile.exists()) {
-                File targetFile = new File(defaultSavePath + targetPath);
+                File targetFile = new File(targetLocalPath);
                 File targetFolder = targetFile.getParentFile();
                 if (!targetFolder.exists()) {
                     targetFolder.mkdirs();
                 }
                 renamed = localFile.renameTo(targetFile);
             }
-            Log_OC.d(TAG, "Local file RENAMED : " + renamed);
-            
+
+            if (renamed) {
+                Iterator<String> it = originalPathsToTriggerMediaScan.iterator();
+                while (it.hasNext()) {
+                    // Notify MediaScanner about removed file
+                    deleteFileInMediaScan(it.next());
+                }
+                it = newPathsToTriggerMediaScan.iterator();
+                while (it.hasNext()) {
+                    // Notify MediaScanner about new file/folder
+                    triggerMediaScan(it.next());
+                }
+            }
         }
         
     }
@@ -942,6 +880,8 @@ public class FileDataStorageManager {
             file.setRemoteId(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_REMOTE_ID)));
             file.setNeedsUpdateThumbnail(c.getInt(
                     c.getColumnIndex(ProviderTableMeta.FILE_UPDATE_THUMBNAIL)) == 1 ? true : false);
+            file.setDownloading(c.getInt(
+                    c.getColumnIndex(ProviderTableMeta.FILE_IS_DOWNLOADING)) == 1 ? true : false);
                     
         }
         return file;
@@ -1324,6 +1264,10 @@ public class FileDataStorageManager {
                     ProviderTableMeta.FILE_UPDATE_THUMBNAIL, 
                     file.needsUpdateThumbnail() ? 1 : 0
                 );
+                cv.put(
+                        ProviderTableMeta.FILE_IS_DOWNLOADING,
+                        file.isDownloading() ? 1 : 0
+                );
 
                 boolean existsByPath = fileExists(file.getRemotePath());
                 if (existsByPath || fileExists(file.getFileId())) {
@@ -1402,7 +1346,7 @@ public class FileDataStorageManager {
                 path = path + FileUtils.PATH_SEPARATOR;
             }           
 
-            // Update OCFile with data from share: ShareByLink  ¿and publicLink?
+            // Update OCFile with data from share: ShareByLink  and publicLink
             OCFile file = getFileByPath(path);
             if (file != null) {
                 if (share.getShareType().equals(ShareType.PUBLIC_LINK)) {
@@ -1549,4 +1493,52 @@ public class FileDataStorageManager {
         //}
     }
 
+    public void triggerMediaScan(String path) {
+        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+        intent.setData(Uri.fromFile(new File(path)));
+        MainApp.getAppContext().sendBroadcast(intent);
+    }
+
+    public void deleteFileInMediaScan(String path) {
+
+        String mimetypeString = FileStorageUtils.getMimeTypeFromName(path);
+        ContentResolver contentResolver = getContentResolver();
+
+        if (contentResolver != null) {
+            if (mimetypeString.startsWith("image/")) {
+                // Images
+                contentResolver.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                        MediaStore.Images.Media.DATA + "=?", new String[]{path});
+            } else if (mimetypeString.startsWith("audio/")) {
+                // Audio
+                contentResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                        MediaStore.Audio.Media.DATA + "=?", new String[]{path});
+            } else if (mimetypeString.startsWith("video/")) {
+                // Video
+                contentResolver.delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                        MediaStore.Video.Media.DATA + "=?", new String[]{path});
+            }
+        } else {
+            ContentProviderClient contentProviderClient = getContentProviderClient();
+            try {
+                if (mimetypeString.startsWith("image/")) {
+                    // Images
+                    contentProviderClient.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                            MediaStore.Images.Media.DATA + "=?", new String[]{path});
+                } else if (mimetypeString.startsWith("audio/")) {
+                    // Audio
+                    contentProviderClient.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                            MediaStore.Audio.Media.DATA + "=?", new String[]{path});
+                } else if (mimetypeString.startsWith("video/")) {
+                    // Video
+                    contentProviderClient.delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                            MediaStore.Video.Media.DATA + "=?", new String[]{path});
+                }
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, "Exception deleting media file in MediaStore " + e.getMessage());
+            }
+        }
+
+    }
+
 }
index 392d03b..78bdc84 100644 (file)
 
 package com.owncloud.android.datamodel;
 
-import java.io.File;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 import com.owncloud.android.lib.common.utils.Log_OC;
-import third_parties.daveKoeller.AlphanumComparator;
+import com.owncloud.android.utils.FileStorageUtils;
 
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.webkit.MimeTypeMap;
+import java.io.File;
 
+import third_parties.daveKoeller.AlphanumComparator;
 public class OCFile implements Parcelable, Comparable<OCFile> {
 
     public static final Parcelable.Creator<OCFile> CREATOR = new Parcelable.Creator<OCFile>() {
@@ -45,7 +45,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     public static final String ROOT_PATH = PATH_SEPARATOR;
 
     private static final String TAG = OCFile.class.getSimpleName();
-    
+
     private long mId;
     private long mParentId;
     private long mLength;
@@ -61,7 +61,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     private boolean mKeepInSync;
 
     private String mEtag;
-    
+
     private boolean mShareByLink;
     private String mPublicLink;
 
@@ -70,12 +70,14 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     private boolean mNeedsUpdateThumbnail;
 
+    private boolean mIsDownloading;
+
 
     /**
      * Create new {@link OCFile} with given path.
-     * 
+     * <p/>
      * The path received must be URL-decoded. Path separator must be OCFile.PATH_SEPARATOR, and it must be the first character in 'path'.
-     * 
+     *
      * @param path The remote path of the file.
      */
     public OCFile(String path) {
@@ -89,7 +91,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Reconstruct from parcel
-     * 
+     *
      * @param source The source parcel
      */
     private OCFile(Parcel source) {
@@ -112,6 +114,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mPermissions = source.readString();
         mRemoteId = source.readString();
         mNeedsUpdateThumbnail = source.readInt() == 0;
+        mIsDownloading = source.readInt() == 0;
 
     }
 
@@ -136,11 +139,12 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         dest.writeString(mPermissions);
         dest.writeString(mRemoteId);
         dest.writeInt(mNeedsUpdateThumbnail ? 1 : 0);
+        dest.writeInt(mIsDownloading ? 1 : 0);
     }
-    
+
     /**
      * Gets the ID of the file
-     * 
+     *
      * @return the file ID
      */
     public long getFileId() {
@@ -149,7 +153,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Returns the remote path of the file on ownCloud
-     * 
+     *
      * @return The remote path to the file
      */
     public String getRemotePath() {
@@ -159,7 +163,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     /**
      * Can be used to check, whether or not this file exists in the database
      * already
-     * 
+     *
      * @return true, if the file exists in the database
      */
     public boolean fileExists() {
@@ -168,7 +172,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Use this to find out if this file is a folder.
-     * 
+     *
      * @return true if it is a folder
      */
     public boolean isFolder() {
@@ -177,7 +181,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Use this to check if this file is available locally
-     * 
+     *
      * @return true if it is
      */
     public boolean isDown() {
@@ -187,10 +191,10 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         }
         return false;
     }
-    
+
     /**
      * The path, where the file is stored locally
-     * 
+     *
      * @return The local path to the file
      */
     public String getStoragePath() {
@@ -199,7 +203,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Can be used to set the path where the file is stored
-     * 
+     *
      * @param storage_path to set
      */
     public void setStoragePath(String storage_path) {
@@ -208,7 +212,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Get a UNIX timestamp of the file creation time
-     * 
+     *
      * @return A UNIX timestamp of the time that file was created
      */
     public long getCreationTimestamp() {
@@ -217,7 +221,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Set a UNIX timestamp of the time the file was created
-     * 
+     *
      * @param creation_timestamp to set
      */
     public void setCreationTimestamp(long creation_timestamp) {
@@ -227,8 +231,8 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     /**
      * Get a UNIX timestamp of the file modification time.
      *
-     * @return  A UNIX timestamp of the modification time, corresponding to the value returned by the server
-     *          in the last synchronization of the properties of this file. 
+     * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server
+     * in the last synchronization of the properties of this file.
      */
     public long getModificationTimestamp() {
         return mModifiedTimestamp;
@@ -236,22 +240,22 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Set a UNIX timestamp of the time the time the file was modified.
-     * 
-     * To update with the value returned by the server in every synchronization of the properties 
+     * <p/>
+     * To update with the value returned by the server in every synchronization of the properties
      * of this file.
-     * 
+     *
      * @param modification_timestamp to set
      */
     public void setModificationTimestamp(long modification_timestamp) {
         mModifiedTimestamp = modification_timestamp;
     }
 
-    
+
     /**
      * Get a UNIX timestamp of the file modification time.
      *
-     * @return  A UNIX timestamp of the modification time, corresponding to the value returned by the server
-     *          in the last synchronization of THE CONTENTS of this file. 
+     * @return A UNIX timestamp of the modification time, corresponding to the value returned by the server
+     * in the last synchronization of THE CONTENTS of this file.
      */
     public long getModificationTimestampAtLastSyncForData() {
         return mModifiedTimestampAtLastSyncForData;
@@ -259,39 +263,38 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Set a UNIX timestamp of the time the time the file was modified.
-     * 
-     * To update with the value returned by the server in every synchronization of THE CONTENTS 
+     * <p/>
+     * To update with the value returned by the server in every synchronization of THE CONTENTS
      * of this file.
-     * 
-     * @param modification_timestamp to set
+     *
+     * @param modificationTimestamp to set
      */
     public void setModificationTimestampAtLastSyncForData(long modificationTimestamp) {
         mModifiedTimestampAtLastSyncForData = modificationTimestamp;
     }
 
-    
-    
+
     /**
      * Returns the filename and "/" for the root directory
-     * 
+     *
      * @return The name of the file
      */
     public String getFileName() {
         File f = new File(getRemotePath());
         return f.getName().length() == 0 ? ROOT_PATH : f.getName();
     }
-    
+
     /**
      * Sets the name of the file
-     * 
-     * Does nothing if the new name is null, empty or includes "/" ; or if the file is the root directory 
+     * <p/>
+     * Does nothing if the new name is null, empty or includes "/" ; or if the file is the root directory
      */
     public void setFileName(String name) {
         Log_OC.d(TAG, "OCFile name changin from " + mRemotePath);
         if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(ROOT_PATH)) {
             String parent = (new File(getRemotePath())).getParent();
             parent = (parent.endsWith(PATH_SEPARATOR)) ? parent : parent + PATH_SEPARATOR;
-            mRemotePath =  parent + name;
+            mRemotePath = parent + name;
             if (isFolder()) {
                 mRemotePath += PATH_SEPARATOR;
             }
@@ -301,7 +304,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Can be used to get the Mimetype
-     * 
+     *
      * @return the Mimetype as a String
      */
     public String getMimetype() {
@@ -311,10 +314,10 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     /**
      * Adds a file to this directory. If this file is not a directory, an
      * exception gets thrown.
-     * 
+     *
      * @param file to add
      * @throws IllegalStateException if you try to add a something and this is
-     *             not a directory
+     *                               not a directory
      */
     public void addFile(OCFile file) throws IllegalStateException {
         if (isFolder()) {
@@ -349,11 +352,12 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         mPermissions = null;
         mRemoteId = null;
         mNeedsUpdateThumbnail = false;
+        mIsDownloading = false;
     }
 
     /**
      * Sets the ID of the file
-     * 
+     *
      * @param file_id to set
      */
     public void setFileId(long file_id) {
@@ -362,7 +366,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Sets the Mime-Type of the
-     * 
+     *
      * @param mimetype to set
      */
     public void setMimetype(String mimetype) {
@@ -371,7 +375,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Sets the ID of the parent folder
-     * 
+     *
      * @param parent_id to set
      */
     public void setParentId(long parent_id) {
@@ -380,7 +384,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Sets the file size in bytes
-     * 
+     *
      * @param file_len to set
      */
     public void setFileLength(long file_len) {
@@ -389,7 +393,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Returns the size of the file in bytes
-     * 
+     *
      * @return The filesize in bytes
      */
     public long getFileLength() {
@@ -398,7 +402,7 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Returns the ID of the parent Folder
-     * 
+     *
      * @return The ID
      */
     public long getParentId() {
@@ -407,13 +411,13 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     /**
      * Check, if this file needs updating
-     * 
+     *
      * @return
      */
     public boolean needsUpdatingWhileSaving() {
         return mNeedsUpdating;
     }
-    
+
     public boolean needsUpdateThumbnail() {
         return mNeedsUpdateThumbnail;
     }
@@ -425,11 +429,11 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     public long getLastSyncDateForProperties() {
         return mLastSyncDateForProperties;
     }
-    
+
     public void setLastSyncDateForProperties(long lastSyncDate) {
         mLastSyncDateForProperties = lastSyncDate;
     }
-    
+
     public long getLastSyncDateForData() {
         return mLastSyncDateForData;
     }
@@ -441,14 +445,14 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     public void setKeepInSync(boolean keepInSync) {
         mKeepInSync = keepInSync;
     }
-    
+
     public boolean keepInSync() {
         return mKeepInSync;
     }
-    
+
     @Override
     public int describeContents() {
-        return this.hashCode();
+        return ((Object) this).hashCode();
     }
 
     @Override
@@ -465,13 +469,13 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
 
     @Override
     public boolean equals(Object o) {
-        if(o instanceof OCFile){
+        if (o instanceof OCFile) {
             OCFile that = (OCFile) o;
-            if(that != null){
+            if (that != null) {
                 return this.mId == that.mId;
             }
         }
-        
+
         return false;
     }
 
@@ -489,8 +493,8 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
     public void setEtag(String etag) {
         this.mEtag = etag;
     }
-    
-    
+
+
     public boolean isShareByLink() {
         return mShareByLink;
     }
@@ -515,30 +519,26 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         return 0;
     }
 
-    /** @return  'True' if the file contains audio */
+    /**
+     * @return 'True' if the file contains audio
+     */
     public boolean isAudio() {
         return (mMimeType != null && mMimeType.startsWith("audio/"));
     }
 
-    /** @return  'True' if the file contains video */
+    /**
+     * @return 'True' if the file contains video
+     */
     public boolean isVideo() {
         return (mMimeType != null && mMimeType.startsWith("video/"));
     }
 
-    /** @return  'True' if the file contains an image */
+    /**
+     * @return 'True' if the file contains an image
+     */
     public boolean isImage() {
         return ((mMimeType != null && mMimeType.startsWith("image/")) ||
-                 getMimeTypeFromName().startsWith("image/"));
-    }
-    
-    public String getMimeTypeFromName() {
-        String extension = "";
-        int pos = mRemotePath.lastIndexOf('.');
-        if (pos >= 0) {
-            extension = mRemotePath.substring(pos + 1);
-        }
-        String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
-        return (result != null) ? result : "";
+                FileStorageUtils.getMimeTypeFromName(mRemotePath).startsWith("image/"));
     }
 
     public String getPermissions() {
@@ -557,4 +557,16 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
         this.mRemoteId = remoteId;
     }
 
+    public boolean isDownloading() {
+        return mIsDownloading;
+    }
+
+    public void setDownloading(boolean isDownloading) {
+        this.mIsDownloading = isDownloading;
+    }
+
+    public boolean isSynchronizing() {
+        // TODO real implementation
+        return false;
+    }
 }
index 1d2cda8..3cd9b3f 100644 (file)
@@ -74,7 +74,7 @@ public class ThumbnailsCacheManager {
     public static Bitmap mDefaultImg = 
             BitmapFactory.decodeResource(
                     MainApp.getAppContext().getResources(), 
-                    DisplayUtils.getResourceId("image/png", "default.png")
+                    DisplayUtils.getFileTypeIconId("image/png", "default.png")
             );
 
     
@@ -137,44 +137,15 @@ public class ThumbnailsCacheManager {
         return null;
     }
 
-    
-    public static boolean cancelPotentialWork(OCFile file, ImageView imageView) {
-        final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
-        if (bitmapWorkerTask != null) {
-            final OCFile bitmapData = bitmapWorkerTask.mFile;
-            // If bitmapData is not yet set or it differs from the new data
-            if (bitmapData == null || bitmapData != file) {
-                // Cancel previous task
-                bitmapWorkerTask.cancel(true);
-            } else {
-                // The same work is already in progress
-                return false;
-            }
-        }
-        // No task associated with the ImageView, or an existing task was cancelled
-        return true;
-    }
-    
-    public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
-        if (imageView != null) {
-            final Drawable drawable = imageView.getDrawable();
-            if (drawable instanceof AsyncDrawable) {
-                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
-                return asyncDrawable.getBitmapWorkerTask();
-            }
-         }
-         return null;
-     }
-
-    public static class ThumbnailGenerationTask extends AsyncTask<OCFile, Void, Bitmap> {
+    public static class ThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {
         private final WeakReference<ImageView> mImageViewReference;
         private static Account mAccount;
-        private OCFile mFile;
+        private Object mFile;
         private FileDataStorageManager mStorageManager;
-        
+
+
         public ThumbnailGenerationTask(ImageView imageView, FileDataStorageManager storageManager, Account account) {
-         // Use a WeakReference to ensure the ImageView can be garbage collected
+            // Use a WeakReference to ensure the ImageView can be garbage collected
             mImageViewReference = new WeakReference<ImageView>(imageView);
             if (storageManager == null)
                 throw new IllegalArgumentException("storageManager must not be NULL");
@@ -182,92 +153,46 @@ public class ThumbnailsCacheManager {
             mAccount = account;
         }
 
-        // Decode image in background.
+        public ThumbnailGenerationTask(ImageView imageView) {
+            // Use a WeakReference to ensure the ImageView can be garbage collected
+            mImageViewReference = new WeakReference<ImageView>(imageView);
+        }
+
         @Override
-        protected Bitmap doInBackground(OCFile... params) {
+        protected Bitmap doInBackground(Object... params) {
             Bitmap thumbnail = null;
-            
+
             try {
                 if (mAccount != null) {
                     AccountManager accountMgr = AccountManager.get(MainApp.getAppContext());
-                    
+
                     mServerVersion = accountMgr.getUserData(mAccount, Constants.KEY_OC_VERSION);
                     OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, MainApp.getAppContext());
                     mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
                             getClientFor(ocAccount, MainApp.getAppContext());
                 }
-                
+
                 mFile = params[0];
-                final String imageKey = String.valueOf(mFile.getRemoteId());
-    
-                // Check disk cache in background thread
-                thumbnail = getBitmapFromDiskCache(imageKey);
-    
-                // Not found in disk cache
-                if (thumbnail == null || mFile.needsUpdateThumbnail()) { 
-                    // Converts dp to pixel
-                    Resources r = MainApp.getAppContext().getResources();
-                    
-                    int px = (int) Math.round(r.getDimension(R.dimen.file_icon_size));
-                    
-                    if (mFile.isDown()){
-                        Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
-                                mFile.getStoragePath(), px, px);
-                        
-                        if (bitmap != null) {
-                            thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
-    
-                            // Add thumbnail to cache
-                            addBitmapToCache(imageKey, thumbnail);
+                
+                if (mFile instanceof OCFile) {
+                    thumbnail = doOCFileInBackground();
+                }  else if (mFile instanceof File) {
+                    thumbnail = doFileInBackground();
+                } else {
+                    // do nothing
+                }
 
-                            mFile.setNeedsUpdateThumbnail(false);
-                            mStorageManager.saveFile(mFile);
-                        }
-    
-                    } else {
-                        // Download thumbnail from server
-                        if (mClient != null && mServerVersion != null) {
-                            OwnCloudVersion serverOCVersion = new OwnCloudVersion(mServerVersion);
-                            if (serverOCVersion.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS)) >= 0) {
-                                try {
-                                    int status = -1;
-
-                                    String uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" + 
-                                            px + "/" + px + Uri.encode(mFile.getRemotePath(), "/");
-                                    Log_OC.d("Thumbnail", "URI: " + uri);
-                                    GetMethod get = new GetMethod(uri);
-                                    status = mClient.executeMethod(get);
-                                    if (status == HttpStatus.SC_OK) {
-                                        byte[] bytes = get.getResponseBody();
-                                        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
-                                        thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
-
-                                        // Add thumbnail to cache
-                                        if (thumbnail != null) {
-                                            addBitmapToCache(imageKey, thumbnail);
-                                        }
-                                    }
-                                } catch (Exception e) {
-                                    e.printStackTrace();
-                                }
-                            } else {
-                                Log_OC.d(TAG, "Server too old");
-                            }
-                        }
+                }catch(Throwable t){
+                    // the app should never break due to a problem with thumbnails
+                    Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
+                    if (t instanceof OutOfMemoryError) {
+                        System.gc();
                     }
                 }
-                
-            } catch (Throwable t) {
-                // the app should never break due to a problem with thumbnails
-                Log_OC.e(TAG, "Generation of thumbnail for " + mFile + " failed", t);
-                if (t instanceof OutOfMemoryError) {
-                    System.gc();
-                }
-            }
-            
+
             return thumbnail;
         }
-        
+
         protected void onPostExecute(Bitmap bitmap){
             if (isCancelled()) {
                 bitmap = null;
@@ -275,47 +200,183 @@ public class ThumbnailsCacheManager {
 
             if (mImageViewReference != null && bitmap != null) {
                 final ImageView imageView = mImageViewReference.get();
-                final ThumbnailGenerationTask bitmapWorkerTask =
-                        getBitmapWorkerTask(imageView);
+                final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
                 if (this == bitmapWorkerTask && imageView != null) {
-                    if (imageView.getTag().equals(mFile.getFileId())) {
+                    String tagId = "";
+                    if (mFile instanceof OCFile){
+                        tagId = String.valueOf(((OCFile)mFile).getFileId());
+                    } else if (mFile instanceof File){
+                        tagId = String.valueOf(((File)mFile).hashCode());
+                    }
+                    if (String.valueOf(imageView.getTag()).equals(tagId)) {
                         imageView.setImageBitmap(bitmap);
                     }
                 }
             }
         }
+
+        /**
+         * Add thumbnail to cache
+         * @param imageKey: thumb key
+         * @param bitmap:   image for extracting thumbnail
+         * @param path:     image path
+         * @param px:       thumbnail dp
+         * @return Bitmap
+         */
+        private Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int px){
+
+            Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
+
+            // Rotate image, obeying exif tag
+            thumbnail = BitmapUtils.rotateImage(thumbnail,path);
+
+            // Add thumbnail to cache
+            addBitmapToCache(imageKey, thumbnail);
+
+            return thumbnail;
+        }
+
+        /**
+         * Converts size of file icon from dp to pixel
+         * @return int
+         */
+        private int getThumbnailDimension(){
+            // Converts dp to pixel
+            Resources r = MainApp.getAppContext().getResources();
+            return (int) Math.round(r.getDimension(R.dimen.file_icon_size_grid));
+        }
+
+        private Bitmap doOCFileInBackground() {
+            Bitmap thumbnail = null;
+            OCFile file = (OCFile)mFile;
+
+            final String imageKey = String.valueOf(file.getRemoteId());
+
+            // Check disk cache in background thread
+            thumbnail = getBitmapFromDiskCache(imageKey);
+
+            // Not found in disk cache
+            if (thumbnail == null || file.needsUpdateThumbnail()) {
+
+                int px = getThumbnailDimension();
+
+                if (file.isDown()) {
+                    Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
+                            file.getStoragePath(), px, px);
+
+                    if (bitmap != null) {
+                        thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), px);
+
+                        file.setNeedsUpdateThumbnail(false);
+                        mStorageManager.saveFile(file);
+                    }
+
+                } else {
+                    // Download thumbnail from server
+                    if (mClient != null && mServerVersion != null) {
+                        OwnCloudVersion serverOCVersion = new OwnCloudVersion(mServerVersion);
+                        if (serverOCVersion.compareTo(new OwnCloudVersion(MINOR_SERVER_VERSION_FOR_THUMBS)) >= 0) {
+                            try {
+                                int status = -1;
+
+                                String uri = mClient.getBaseUri() + "/index.php/apps/files/api/v1/thumbnail/" +
+                                        px + "/" + px + Uri.encode(file.getRemotePath(), "/");
+                                Log_OC.d("Thumbnail", "URI: " + uri);
+                                GetMethod get = new GetMethod(uri);
+                                status = mClient.executeMethod(get);
+                                if (status == HttpStatus.SC_OK) {
+                                    byte[] bytes = get.getResponseBody();
+                                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+                                    thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
+
+                                    // Add thumbnail to cache
+                                    if (thumbnail != null) {
+                                        addBitmapToCache(imageKey, thumbnail);
+                                    }
+                                }
+                            } catch (Exception e) {
+                                e.printStackTrace();
+                            }
+                        } else {
+                            Log_OC.d(TAG, "Server too old");
+                        }
+                    }
+                }
+            }
+
+            return thumbnail;
+
+        }
+
+        private Bitmap doFileInBackground() {
+            Bitmap thumbnail = null;
+            File file = (File)mFile;
+
+            final String imageKey = String.valueOf(file.hashCode());
+
+            // Check disk cache in background thread
+            thumbnail = getBitmapFromDiskCache(imageKey);
+
+            // Not found in disk cache
+            if (thumbnail == null) {
+
+                int px = getThumbnailDimension();
+
+                Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(
+                        file.getAbsolutePath(), px, px);
+
+                if (bitmap != null) {
+                    thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px);
+                }
+            }
+            return thumbnail;
+        }
+
     }
-  
-    
+
+    public static boolean cancelPotentialWork(Object file, ImageView imageView) {
+        final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+        if (bitmapWorkerTask != null) {
+            final Object bitmapData = bitmapWorkerTask.mFile;
+            // If bitmapData is not yet set or it differs from the new data
+            if (bitmapData == null || bitmapData != file) {
+                // Cancel previous task
+                bitmapWorkerTask.cancel(true);
+            } else {
+                // The same work is already in progress
+                return false;
+            }
+        }
+        // No task associated with the ImageView, or an existing task was cancelled
+        return true;
+    }
+
+    public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
+        if (imageView != null) {
+            final Drawable drawable = imageView.getDrawable();
+            if (drawable instanceof AsyncDrawable) {
+                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+                return asyncDrawable.getBitmapWorkerTask();
+            }
+        }
+        return null;
+    }
+
     public static class AsyncDrawable extends BitmapDrawable {
         private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
 
         public AsyncDrawable(
                 Resources res, Bitmap bitmap, ThumbnailGenerationTask bitmapWorkerTask
-            ) {
-            
+        ) {
+
             super(res, bitmap);
             bitmapWorkerTaskReference =
-                new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
+                    new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
         }
 
         public ThumbnailGenerationTask getBitmapWorkerTask() {
             return bitmapWorkerTaskReference.get();
         }
     }
-
-    
-    /**
-     * Remove from cache the remoteId passed
-     * @param fileRemoteId: remote id of mFile passed
-     */
-    public static void removeFileFromCache(String fileRemoteId){
-        synchronized (mThumbnailsDiskCacheLock) {
-            if (mThumbnailCache != null) {
-                mThumbnailCache.removeKey(fileRemoteId);
-            }
-            mThumbnailsDiskCacheLock.notifyAll(); // Wake any waiting threads
-        }
-    }
-
 }
index 717066b..3682a7b 100644 (file)
@@ -114,7 +114,14 @@ public class DbHandler {
                 db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN attempt INTEGER;");
             }
             db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN message TEXT;");
-
+        }
+        
+        @Override
+        public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            //downgrading is the exception, so deleting and re-creating is acceptable.
+            //otherwise exception will be thrown (cannot downgrade) and oc app will crash.
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_INSTANT_UPLOAD + ";");
+            onCreate(db);
         }
     }
 }
index bc59869..b6bfe4a 100644 (file)
@@ -31,7 +31,7 @@ import com.owncloud.android.MainApp;
 public class ProviderMeta {\r
 \r
     public static final String DB_NAME = "filelist";\r
-    public static final int DB_VERSION = 8;\r
+    public static final int DB_VERSION = 9;\r
 \r
     private ProviderMeta() {\r
     }\r
@@ -71,6 +71,7 @@ public class ProviderMeta {
         public static final String FILE_PERMISSIONS = "permissions";\r
         public static final String FILE_REMOTE_ID = "remote_id";\r
         public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail";\r
+        public static final String FILE_IS_DOWNLOADING= "is_downloading";\r
 \r
         public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME\r
                 + " collate nocase asc";\r
index 6eb746c..89b00dc 100644 (file)
@@ -31,6 +31,8 @@ import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 
 /**
@@ -51,8 +53,8 @@ public class FileMenuFilter {
      * 
      * @param targetFile        {@link OCFile} target of the action to filter in the {@link Menu}.
      * @param account           ownCloud {@link Account} holding targetFile.
-     * @param cg                Accessor to app components, needed to get access the 
-     *                          {@link FileUploader} and {@link FileDownloader} services.
+     * @param cg                Accessor to app components, needed to access the
+     *                          {@link FileUploader} and {@link FileDownloader} services
      * @param context           Android {@link Context}, needed to access build setup resources.
      */
     public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context) {
@@ -140,15 +142,17 @@ public class FileMenuFilter {
         boolean uploading = false;
         if (mComponentsGetter != null && mFile != null && mAccount != null) {
             FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder();
-            downloading = downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile);
+            downloading = (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile));
+            OperationsServiceBinder opsBinder = mComponentsGetter.getOperationsServiceBinder();
+            downloading |= (opsBinder != null && opsBinder.isSynchronizing(mAccount, mFile.getRemotePath()));
             FileUploaderBinder uploaderBinder = mComponentsGetter.getFileUploaderBinder();
-            uploading = uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile);
+            uploading = (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile));
         }
         
         /// decision is taken for each possible action on a file in the menu
         
         // DOWNLOAD 
-        if (mFile == null || mFile.isFolder() || mFile.isDown() || downloading || uploading) {
+        if (mFile == null || mFile.isDown() || downloading || uploading) {
             toHide.add(R.id.action_download_file);
             
         } else {
@@ -189,7 +193,7 @@ public class FileMenuFilter {
         
         
         // CANCEL DOWNLOAD
-        if (mFile == null || !downloading || mFile.isFolder()) {
+        if (mFile == null || !downloading) {
             toHide.add(R.id.action_cancel_download);
         } else {
             toShow.add(R.id.action_cancel_download);
index e1ab195..cf9f474 100644 (file)
@@ -1,5 +1,5 @@
 /* ownCloud Android client application
- *   Copyright (C) 2012-2014 ownCloud Inc.
+ *   Copyright (C) 2012-2015 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -127,7 +127,7 @@ public class FileOperationsHelper {
             service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
             service.putExtra(OperationsService.EXTRA_SEND_INTENT, sendIntent);
-            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
             
         } else {
             Log_OC.wtf(TAG, "Trying to open a NULL OCFile");
@@ -165,7 +165,7 @@ public class FileOperationsHelper {
             service.setAction(OperationsService.ACTION_UNSHARE);
             service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
-            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
             
             mFileActivity.showLoadingDialog();
             
@@ -197,18 +197,25 @@ public class FileOperationsHelper {
     
     
     public void syncFile(OCFile file) {
-        // Sync file
-        Intent service = new Intent(mFileActivity, OperationsService.class);
-        service.setAction(OperationsService.ACTION_SYNC_FILE);
-        service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
-        service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); 
-        service.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true);
-        mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
         
-        mFileActivity.showLoadingDialog();
+        if (!file.isFolder()){
+            Intent intent = new Intent(mFileActivity, OperationsService.class);
+            intent.setAction(OperationsService.ACTION_SYNC_FILE);
+            intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+            intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+            intent.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true);
+            mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(intent);
+            mFileActivity.showLoadingDialog();
+            
+        } else {
+            Intent intent = new Intent(mFileActivity, OperationsService.class);
+            intent.setAction(OperationsService.ACTION_SYNC_FOLDER);
+            intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+            intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+            mFileActivity.startService(intent);
+        }
     }
     
-    
     public void renameFile(OCFile file, String newFilename) {
         // RenameFile
         Intent service = new Intent(mFileActivity, OperationsService.class);
@@ -216,7 +223,7 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
         service.putExtra(OperationsService.EXTRA_NEWNAME, newFilename);
-        mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
         
         mFileActivity.showLoadingDialog();
     }
@@ -229,7 +236,7 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
         service.putExtra(OperationsService.EXTRA_REMOVE_ONLY_LOCAL, onlyLocalCopy);
-        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
         
         mFileActivity.showLoadingDialog();
     }
@@ -242,26 +249,38 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath);
         service.putExtra(OperationsService.EXTRA_CREATE_FULL_PATH, createFullPath);
-        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
         
         mFileActivity.showLoadingDialog();
     }
 
-    
+    /**
+     * Cancel the transference in downloads (files/folders) and file uploads
+     * @param file OCFile
+     */
     public void cancelTransference(OCFile file) {
         Account account = mFileActivity.getAccount();
+        if (file.isFolder()) {
+            OperationsService.OperationsServiceBinder opsBinder = mFileActivity.getOperationsServiceBinder();
+            if (opsBinder != null) {
+                opsBinder.cancel(account, file);
+            }
+        }
+
+        // for both files and folders
         FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
-        FileUploaderBinder uploaderBinder =  mFileActivity.getFileUploaderBinder();
+        FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
         if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
+            downloaderBinder.cancel(account, file);
+
+            // TODO - review why is this here, and solve in a better way
             // Remove etag for parent, if file is a keep_in_sync
             if (file.keepInSync()) {
-               OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
-               parent.setEtag("");
-               mFileActivity.getStorageManager().saveFile(parent);
+                OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
+                parent.setEtag("");
+                mFileActivity.getStorageManager().saveFile(parent);
             }
-            
-            downloaderBinder.cancel(account, file);
-            
+
         } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
             uploaderBinder.cancel(account, file);
         }
@@ -279,7 +298,7 @@ public class FileOperationsHelper {
         service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, newfile.getRemotePath());
         service.putExtra(OperationsService.EXTRA_REMOTE_PATH, currentFile.getRemotePath());
         service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
-        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().newOperation(service);
+        mWaitingForOpId =  mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
 
         mFileActivity.showLoadingDialog();
     }
index fdc35f8..feedc11 100644 (file)
@@ -1,6 +1,6 @@
 /* ownCloud Android client application
  *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2012-2013 ownCloud Inc.
+ *   Copyright (C) 2012-2015 ownCloud Inc.
  *
  *   This program is free software: you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License version 2,
@@ -25,8 +25,6 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Vector;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AuthenticatorActivity;
@@ -64,17 +62,19 @@ import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.support.v4.app.NotificationCompat;
+import android.util.Pair;
 
 public class FileDownloader extends Service implements OnDatatransferProgressListener {
     
     public static final String EXTRA_ACCOUNT = "ACCOUNT";
     public static final String EXTRA_FILE = "FILE";
-    
+
     private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
     private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
     public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";    
     public static final String EXTRA_FILE_PATH = "FILE_PATH";
     public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
+    public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
     public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
     
     private static final String TAG = "FileDownloader";
@@ -83,35 +83,25 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     private ServiceHandler mServiceHandler;
     private IBinder mBinder;
     private OwnCloudClient mDownloadClient = null;
-    private Account mLastAccount = null;
+    private Account mCurrentAccount = null;
     private FileDataStorageManager mStorageManager;
     
-    private ConcurrentMap<String, DownloadFileOperation> mPendingDownloads = new ConcurrentHashMap<String, DownloadFileOperation>();
+    private IndexedForest<DownloadFileOperation> mPendingDownloads = new IndexedForest<DownloadFileOperation>();
+
     private DownloadFileOperation mCurrentDownload = null;
     
     private NotificationManager mNotificationManager;
     private NotificationCompat.Builder mNotificationBuilder;
     private int mLastPercent;
-    
+
     
     public static String getDownloadAddedMessage() {
-        return FileDownloader.class.getName().toString() + DOWNLOAD_ADDED_MESSAGE;
+        return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
     }
     
     public static String getDownloadFinishMessage() {
-        return FileDownloader.class.getName().toString() + DOWNLOAD_FINISH_MESSAGE;
-    }
-    
-    /**
-     * Builds a key for mPendingDownloads from the account and file to download
-     * 
-     * @param account   Account where the file to download is stored
-     * @param file      File to download
-     */
-    private String buildRemoteName(Account account, OCFile file) {
-        return account.name + file.getRemotePath();
+        return FileDownloader.class.getName() + DOWNLOAD_FINISH_MESSAGE;
     }
-
     
     /**
      * Service initialization
@@ -130,52 +120,73 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
 
     /**
      * Entry point to add one or several files to the queue of downloads.
-     * 
-     * New downloads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working 
-     * although the caller activity goes away.
+     *
+     * New downloads are added calling to startService(), resulting in a call to this method.
+     * This ensures the service will keep on working although the caller activity goes away.
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         if (    !intent.hasExtra(EXTRA_ACCOUNT) ||
                 !intent.hasExtra(EXTRA_FILE)
-                /*!intent.hasExtra(EXTRA_FILE_PATH) ||
-                !intent.hasExtra(EXTRA_REMOTE_PATH)*/
            ) {
             Log_OC.e(TAG, "Not enough information provided in intent");
             return START_NOT_STICKY;
-        }
-        Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
-        OCFile file = intent.getParcelableExtra(EXTRA_FILE);
-        
-        AbstractList<String> requestedDownloads = new Vector<String>(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection)
-        String downloadKey = buildRemoteName(account, file);
-        try {
-            DownloadFileOperation newDownload = new DownloadFileOperation(account, file); 
-            mPendingDownloads.putIfAbsent(downloadKey, newDownload);
-            newDownload.addDatatransferProgressListener(this);
-            newDownload.addDatatransferProgressListener((FileDownloaderBinder)mBinder);
-            requestedDownloads.add(downloadKey);
-            sendBroadcastNewDownload(newDownload);
-            
-        } catch (IllegalArgumentException e) {
-            Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
-            return START_NOT_STICKY;
-        }
-        
-        if (requestedDownloads.size() > 0) {
-            Message msg = mServiceHandler.obtainMessage();
-            msg.arg1 = startId;
-            msg.obj = requestedDownloads;
-            mServiceHandler.sendMessage(msg);
+        } else {
+            final Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+            final OCFile file = intent.getParcelableExtra(EXTRA_FILE);
+
+            /*Log_OC.v(
+                    "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Received request to download file"
+            );*/
+
+                AbstractList<String> requestedDownloads = new Vector<String>();
+                try {
+                    DownloadFileOperation newDownload = new DownloadFileOperation(account, file);
+                    newDownload.addDatatransferProgressListener(this);
+                    newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
+                    Pair<String, String> putResult = mPendingDownloads.putIfAbsent(
+                        account, file.getRemotePath(), newDownload
+                    );
+                    String downloadKey = putResult.first;
+                    requestedDownloads.add(downloadKey);
+                    /*Log_OC.v(
+                        "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Download on " + file.getRemotePath() + " added to queue"
+                    );*/
+
+                    // Store file on db with state 'downloading'
+                    /*
+                    TODO - check if helps with UI responsiveness, letting only folders use FileDownloaderBinder to check
+                    FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
+                    file.setDownloading(true);
+                    storageManager.saveFile(file);
+                    */
+
+                    sendBroadcastNewDownload(newDownload, putResult.second);
+
+                } catch (IllegalArgumentException e) {
+                    Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
+                    return START_NOT_STICKY;
+                }
+
+                if (requestedDownloads.size() > 0) {
+                    Message msg = mServiceHandler.obtainMessage();
+                    msg.arg1 = startId;
+                    msg.obj = requestedDownloads;
+                    mServiceHandler.sendMessage(msg);
+                }
+            //}
         }
 
         return START_NOT_STICKY;
     }
-    
-    
+
+
     /**
-     * Provides a binder object that clients can use to perform operations on the queue of downloads, excepting the addition of new files. 
-     * 
+     * Provides a binder object that clients can use to perform operations on the queue of downloads,
+     * excepting the addition of new files.
+     *
      * Implemented to perform cancellation, pause and resume of existing downloads.
      */
     @Override
@@ -193,33 +204,49 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
         return false;   // not accepting rebinding (default behaviour)
     }
 
-    
+
     /**
      *  Binder to let client components to perform operations on the queue of downloads.
-     * 
+     *
      *  It provides by itself the available operations.
      */
     public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener {
         
         /** 
-         * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder} instance 
+         * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder}
+         * instance.
          */
-        private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
-        
-        
+        private Map<Long, OnDatatransferProgressListener> mBoundListeners =
+                new HashMap<Long, OnDatatransferProgressListener>();
+
+
         /**
          * Cancels a pending or current download of a remote file.
-         * 
-         * @param account       Owncloud account where the remote file is stored.
+         *
+         * @param account       ownCloud account where the remote file is stored.
          * @param file          A file in the queue of pending downloads
          */
         public void cancel(Account account, OCFile file) {
-            DownloadFileOperation download = null;
-            synchronized (mPendingDownloads) {
-                download = mPendingDownloads.remove(buildRemoteName(account, file));
-            }
+            /*Log_OC.v(
+                    "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Received request to cancel download of " + file.getRemotePath()
+            );
+            Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Removing download of " + file.getRemotePath());*/
+            Pair<DownloadFileOperation, String> removeResult = mPendingDownloads.remove(account, file.getRemotePath());
+            DownloadFileOperation download = removeResult.first;
             if (download != null) {
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Canceling returned download of " + file.getRemotePath());*/
                 download.cancel();
+            } else {
+                if (mCurrentDownload != null && mCurrentAccount != null &&
+                        mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
+                        account.name.equals(mCurrentAccount.name)) {
+                    /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                            "Canceling current sync as descendant: " + mCurrentDownload.getRemotePath());*/
+                    mCurrentDownload.cancel();
+                }
             }
         }
         
@@ -230,29 +257,18 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
 
 
         /**
-         * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.
+         * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or
+         * waiting to download.
          * 
-         * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download. 
+         * If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
+         * waiting to download.
          * 
-         * @param account       Owncloud account where the remote file is stored.
+         * @param account       ownCloud account where the remote file is stored.
          * @param file          A file that could be in the queue of downloads.
          */
         public boolean isDownloading(Account account, OCFile file) {
             if (account == null || file == null) return false;
-            String targetKey = buildRemoteName(account, file);
-            synchronized (mPendingDownloads) {
-                if (file.isFolder()) {
-                    // this can be slow if there are many downloads :(
-                    Iterator<String> it = mPendingDownloads.keySet().iterator();
-                    boolean found = false;
-                    while (it.hasNext() && !found) {
-                        found = it.next().startsWith(targetKey);
-                    }
-                    return found;
-                } else {
-                    return (mPendingDownloads.containsKey(targetKey));
-                }
-            }
+            return (mPendingDownloads.contains(account, file.getRemotePath()));
         }
 
         
@@ -261,12 +277,14 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
-         * @param file          {@link OCfile} of interest for listener. 
+         * @param file          {@link OCFile} of interest for listener.
          */
-        public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+        public void addDatatransferProgressListener (
+                OnDatatransferProgressListener listener, Account account, OCFile file
+        ) {
             if (account == null || file == null || listener == null) return;
-            String targetKey = buildRemoteName(account, file);
-            mBoundListeners.put(targetKey, listener);
+            //String targetKey = buildKey(account, file.getRemotePath());
+            mBoundListeners.put(file.getFileId(), listener);
         }
         
         
@@ -275,21 +293,24 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
-         * @param file          {@link OCfile} of interest for listener. 
+         * @param file          {@link OCFile} of interest for listener.
          */
-        public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+        public void removeDatatransferProgressListener (
+                OnDatatransferProgressListener listener, Account account, OCFile file
+        ) {
             if (account == null || file == null || listener == null) return;
-            String targetKey = buildRemoteName(account, file);
-            if (mBoundListeners.get(targetKey) == listener) {
-                mBoundListeners.remove(targetKey);
+            //String targetKey = buildKey(account, file.getRemotePath());
+            Long fileId = file.getFileId();
+            if (mBoundListeners.get(fileId) == listener) {
+                mBoundListeners.remove(fileId);
             }
         }
 
         @Override
         public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
                 String fileName) {
-            String key = buildRemoteName(mCurrentDownload.getAccount(), mCurrentDownload.getFile());
-            OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
+            //String key = buildKey(mCurrentDownload.getAccount(), mCurrentDownload.getFile().getRemotePath());
+            OnDatatransferProgressListener boundListener = mBoundListeners.get(mCurrentDownload.getFile().getFileId());
             if (boundListener != null) {
                 boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
             }
@@ -320,7 +341,10 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             if (msg.obj != null) {
                 Iterator<String> it = requestedDownloads.iterator();
                 while (it.hasNext()) {
-                    mService.downloadFile(it.next());
+                    String next = it.next();
+                    /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                            "Handling download file " + next);*/
+                    mService.downloadFile(next);
                 }
             }
             mService.stopSelf(msg.arg1);
@@ -334,11 +358,11 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
      * @param downloadKey   Key to access the download to perform, contained in mPendingDownloads 
      */
     private void downloadFile(String downloadKey) {
-        
-        synchronized(mPendingDownloads) {
-            mCurrentDownload = mPendingDownloads.get(downloadKey);
-        }
-        
+
+        /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                "Getting download of " + downloadKey);*/
+        mCurrentDownload = mPendingDownloads.get(downloadKey);
+
         if (mCurrentDownload != null) {
             
             notifyDownloadStart(mCurrentDownload);
@@ -346,39 +370,48 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
             RemoteOperationResult downloadResult = null;
             try {
                 /// prepare client object to send the request to the ownCloud server
-                if (mDownloadClient == null || !mLastAccount.equals(mCurrentDownload.getAccount())) {
-                    mLastAccount = mCurrentDownload.getAccount();
-                    mStorageManager = 
-                            new FileDataStorageManager(mLastAccount, getContentResolver());
-                    OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this);
-                    mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
-                            getClientFor(ocAccount, this);
-                }
+                if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentDownload.getAccount())) {
+                    mCurrentAccount = mCurrentDownload.getAccount();
+                    mStorageManager = new FileDataStorageManager(
+                            mCurrentAccount,
+                            getContentResolver()
+                    );
+                }   // else, reuse storage manager from previous operation
+
+                // always get client from client manager, to get fresh credentials in case of update
+                OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
+                mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                        getClientFor(ocAccount, this);
+
 
                 /// perform the download
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Executing download of " + mCurrentDownload.getRemotePath());*/
                 downloadResult = mCurrentDownload.execute(mDownloadClient);
                 if (downloadResult.isSuccess()) {
                     saveDownloadedFile();
                 }
             
             } catch (AccountsException e) {
-                Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+                Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
                 downloadResult = new RemoteOperationResult(e);
             } catch (IOException e) {
-                Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+                Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
                 downloadResult = new RemoteOperationResult(e);
                 
             } finally {
-                synchronized(mPendingDownloads) {
-                    mPendingDownloads.remove(downloadKey);
-                }
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Removing payload " + mCurrentDownload.getRemotePath());*/
+
+                Pair<DownloadFileOperation, String> removeResult =
+                        mPendingDownloads.removePayload(mCurrentAccount, mCurrentDownload.getRemotePath());
+
+                /// notify result
+                notifyDownloadResult(mCurrentDownload, downloadResult);
+
+                sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
             }
 
-            
-            /// notify result
-            notifyDownloadResult(mCurrentDownload, downloadResult);
-            
-            sendBroadcastDownloadFinished(mCurrentDownload, downloadResult);
         }
     }
 
@@ -400,6 +433,16 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
         file.setFileLength((new File(mCurrentDownload.getSavePath()).length()));
         file.setRemoteId(mCurrentDownload.getFile().getRemoteId());
         mStorageManager.saveFile(file);
+        mStorageManager.triggerMediaScan(file.getStoragePath());
+    }
+
+    /**
+     * Update the OC File after a unsuccessful download
+     */
+    private void updateUnsuccessfulDownloadedFile() {
+        OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
+        file.setDownloading(false);
+        mStorageManager.saveFile(file);
     }
 
 
@@ -447,7 +490,8 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
      * Callback method to update the progress bar in the status notification.
      */
     @Override
-    public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) {
+    public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath)
+    {
         int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
         if (percent != mLastPercent) {
             mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
@@ -491,7 +535,9 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                 // let the user update credentials with one click
                 Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
                 updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, download.getAccount());
-                updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN);
+                updateAccountCredentials.putExtra(
+                        AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
+                );
                 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                 updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
@@ -499,8 +545,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                     .setContentIntent(PendingIntent.getActivity(
                         this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT));
                 
-                mDownloadClient = null;   // grant that future retries on the same account will get the fresh credentials
-                
             } else {
                 // TODO put something smart in showDetailsIntent
                 Intent   showDetailsIntent = new Intent();
@@ -509,7 +553,9 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                         this, (int) System.currentTimeMillis(), showDetailsIntent, 0));
             }
             
-            mNotificationBuilder.setContentText(ErrorMessageAdapter.getErrorCauseMessage(downloadResult, download, getResources()));
+            mNotificationBuilder.setContentText(
+                    ErrorMessageAdapter.getErrorCauseMessage(downloadResult, download, getResources())
+            );
             mNotificationManager.notify(tickerId, mNotificationBuilder.build());
             
             // Remove success notification
@@ -528,15 +574,22 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     /**
      * Sends a broadcast when a download finishes in order to the interested activities can update their view
      * 
-     * @param download          Finished download operation
-     * @param downloadResult    Result of the download operation
+     * @param download                  Finished download operation
+     * @param downloadResult            Result of the download operation
+     * @param unlinkedFromRemotePath    Path in the downloads tree where the download was unlinked from
      */
-    private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) {
+    private void sendBroadcastDownloadFinished(
+            DownloadFileOperation download,
+            RemoteOperationResult downloadResult,
+            String unlinkedFromRemotePath) {
         Intent end = new Intent(getDownloadFinishMessage());
         end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
         end.putExtra(ACCOUNT_NAME, download.getAccount().name);
         end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        if (unlinkedFromRemotePath != null) {
+            end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
+        }
         sendStickyBroadcast(end);
     }
     
@@ -544,13 +597,15 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
     /**
      * Sends a broadcast when a new download is added to the queue.
      * 
-     * @param download          Added download operation
+     * @param download              Added download operation
+     * @param linkedToRemotePath    Path in the downloads tree where the download was linked to
      */
-    private void sendBroadcastNewDownload(DownloadFileOperation download) {
+    private void sendBroadcastNewDownload(DownloadFileOperation download, String linkedToRemotePath) {
         Intent added = new Intent(getDownloadAddedMessage());
         added.putExtra(ACCOUNT_NAME, download.getAccount().name);
         added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
         added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
+        added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
         sendStickyBroadcast(added);
     }
 
index 0480440..386f185 100644 (file)
@@ -199,7 +199,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
         if (uploadType == UPLOAD_SINGLE_FILE) {
 
             if (intent.hasExtra(KEY_FILE)) {
-                files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
+                files = new OCFile[] { (OCFile) intent.getParcelableExtra(KEY_FILE) };
 
             } else {
                 localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
@@ -372,8 +372,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * 
          * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload. 
          * 
-         * @param account Owncloud account where the remote file will be stored.
-         * @param file A file that could be in the queue of pending uploads
+         * @param account   ownCloud account where the remote file will be stored.
+         * @param file      A file that could be in the queue of pending uploads
          */
         public boolean isUploading(Account account, OCFile file) {
             if (account == null || file == null)
@@ -400,7 +400,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
-         * @param file          {@link OCfile} of interest for listener. 
+         * @param file          {@link OCFile} of interest for listener.
          */
         public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
             if (account == null || file == null || listener == null) return;
@@ -415,7 +415,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
          * 
          * @param listener      Object to notify about progress of transfer.    
          * @param account       ownCloud account holding the file of interest.
-         * @param file          {@link OCfile} of interest for listener. 
+         * @param file          {@link OCFile} of interest for listener.
          */
         public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
             if (account == null || file == null || listener == null) return;
diff --git a/src/com/owncloud/android/files/services/IndexedForest.java b/src/com/owncloud/android/files/services/IndexedForest.java
new file mode 100644 (file)
index 0000000..e2e9cb8
--- /dev/null
@@ -0,0 +1,225 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.files.services;
+
+import android.accounts.Account;
+import android.util.Pair;
+
+import com.owncloud.android.datamodel.OCFile;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ *  Helper structure to keep the trees of folders containing any file downloading or synchronizing.
+ *
+ *  A map provides the indexation based in hashing.
+ *
+ *  A tree is created per account.
+ *
+ * @author David A. Velasco
+ */
+public class IndexedForest<V> {
+
+    private ConcurrentMap<String, Node<V>> mMap = new ConcurrentHashMap<String, Node<V>>();
+
+    private class Node<V> {
+        String mKey = null;
+        Node<V> mParent = null;
+        Set<Node<V>> mChildren = new HashSet<Node<V>>();    // TODO be careful with hash()
+        V mPayload = null;
+
+        // payload is optional
+        public Node(String key, V payload) {
+            if (key == null) {
+                throw new IllegalArgumentException("Argument key MUST NOT be null");
+            }
+            mKey = key;
+            mPayload = payload;
+        }
+
+        public Node<V> getParent() {
+            return mParent;
+        };
+
+        public Set<Node<V>> getChildren() {
+            return mChildren;
+        }
+
+        public String getKey() {
+            return mKey;
+        }
+
+        public V getPayload() {
+            return mPayload;
+        }
+
+        public void addChild(Node<V> child) {
+            mChildren.add(child);
+            child.setParent(this);
+        }
+
+        private void setParent(Node<V> parent) {
+            mParent = parent;
+        }
+
+        public boolean hasChildren() {
+            return mChildren.size() > 0;
+        }
+
+        public void removeChild(Node<V> removed) {
+            mChildren.remove(removed);
+        }
+
+        public void clearPayload() {
+            mPayload = null;
+        }
+    }
+
+
+    public /* synchronized */ Pair<String, String> putIfAbsent(Account account, String remotePath, V value) {
+        String targetKey = buildKey(account, remotePath);
+        Node<V> valuedNode = new Node(targetKey, value);
+        mMap.putIfAbsent(
+                targetKey,
+                valuedNode
+        );
+
+        String currentPath = remotePath, parentPath = null, parentKey = null;
+        Node<V> currentNode = valuedNode, parentNode = null;
+        boolean linked = false;
+        while (!OCFile.ROOT_PATH.equals(currentPath) && !linked) {
+            parentPath = new File(currentPath).getParent();
+            if (!parentPath.endsWith(OCFile.PATH_SEPARATOR)) {
+                parentPath += OCFile.PATH_SEPARATOR;
+            }
+            parentKey = buildKey(account, parentPath);
+            parentNode = mMap.get(parentKey);
+            if (parentNode == null) {
+                parentNode = new Node(parentKey, null);
+                parentNode.addChild(currentNode);
+                mMap.put(parentKey, parentNode);
+            } else {
+                parentNode.addChild(currentNode);
+                linked = true;
+            }
+            currentPath = parentPath;
+            currentNode = parentNode;
+        }
+
+        String linkedTo = OCFile.ROOT_PATH;
+        if (linked) {
+            linkedTo = parentNode.getKey().substring(account.name.length());
+        }
+        return new Pair<String, String>(targetKey, linkedTo);
+    };
+
+
+    public Pair<V, String> removePayload(Account account, String remotePath) {
+        String targetKey = buildKey(account, remotePath);
+        Node<V> target = mMap.get(targetKey);
+        if (target != null) {
+            target.clearPayload();
+            if (!target.hasChildren()) {
+                return remove(account, remotePath);
+            }
+        }
+        return new Pair<V, String>(null, null);
+    }
+
+
+    public /* synchronized */ Pair<V, String> remove(Account account, String remotePath) {
+        String targetKey = buildKey(account, remotePath);
+        Node<V> firstRemoved = mMap.remove(targetKey);
+        String unlinkedFrom = null;
+
+        if (firstRemoved != null) {
+            /// remove children
+            removeDescendants(firstRemoved);
+
+            /// remove ancestors if only here due to firstRemoved
+            Node<V> removed = firstRemoved;
+            Node<V> parent = removed.getParent();
+            boolean unlinked = false;
+            while (parent != null) {
+                parent.removeChild(removed);
+                if (!parent.hasChildren()) {
+                    removed = mMap.remove(parent.getKey());
+                    parent = removed.getParent();
+                } else {
+                    break;
+                }
+            }
+
+            if (parent != null) {
+                unlinkedFrom = parent.getKey().substring(account.name.length());
+            }
+
+            return new Pair<V, String>(firstRemoved.getPayload(), unlinkedFrom);
+        }
+
+        return new Pair<V, String>(null, null);
+    }
+
+    private void removeDescendants(Node<V> removed) {
+        Iterator<Node<V>> childrenIt = removed.getChildren().iterator();
+        Node<V> child = null;
+        while (childrenIt.hasNext()) {
+            child = childrenIt.next();
+            mMap.remove(child.getKey());
+            removeDescendants(child);
+        }
+    }
+
+    public boolean contains(Account account, String remotePath) {
+        String targetKey = buildKey(account, remotePath);
+        return mMap.containsKey(targetKey);
+    }
+
+    public /* synchronized */ V get(String key) {
+        Node<V> node = mMap.get(key);
+        if (node != null) {
+            return node.getPayload();
+        } else {
+            return null;
+        }
+    }
+
+    public V get(Account account, String remotePath) {
+        String key = buildKey(account, remotePath);
+        return get(key);
+    }
+
+
+    /**
+     * Builds a key to index files
+     *
+     * @param account       Account where the file to download is stored
+     * @param remotePath    Path of the file in the server
+     */
+    private String buildKey(Account account, String remotePath) {
+        return account.name + remotePath;
+    }
+
+
+
+}
index c32c477..fc44f53 100644 (file)
@@ -24,8 +24,10 @@ package com.owncloud.android.operations;
  *
  */
 
+import android.content.Context;
 import android.content.Intent;
 
+import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudClient;
@@ -46,6 +48,7 @@ public class CreateShareOperation extends SyncOperation {
 
     protected FileDataStorageManager mStorageManager;
 
+    private Context mContext;
     private String mPath;
     private ShareType mShareType;
     private String mShareWith;
@@ -56,25 +59,27 @@ public class CreateShareOperation extends SyncOperation {
 
     /**
      * Constructor
+     * @param context       The context that the share is coming from.
      * @param path          Full path of the file/folder being shared. Mandatory argument
-     * @param shareType     \910\92 = user, \911\92 = group, \913\92 = Public link. Mandatory argument
+     * @param shareType     0 = user, 1 = group, 3 = Public link. Mandatory argument
      * @param shareWith     User/group ID with who the file should be shared.  This is mandatory for shareType of 0 or 1
-     * @param publicUpload  If \91false\92 (default) public cannot upload to a public shared folder. 
-     *                      If \91true\92 public can upload to a shared folder. Only available for public link shares
+     * @param publicUpload  If false (default) public cannot upload to a public shared folder. 
+     *                      If true public can upload to a shared folder. Only available for public link shares
      * @param password      Password to protect a public link share. Only available for public link shares
-     * @param permissions   1 - Read only \96 Default for \93public\94 shares
+     * @param permissions   1 - Read only - Default for public shares
      *                      2 - Update
      *                      4 - Create
      *                      8 - Delete
      *                      16- Re-share
-     *                      31- All above \96 Default for \93private\94 shares
+     *                      31- All above - Default for private shares
      *                      For user or group shares.
      *                      To obtain combinations, add the desired values together.  
-     *                      For instance, for \93Re-Share\94\93delete\94\93read\94\93update\94, add 16+8+2+1 = 27.
+     *                      For instance, for Re-Share, delete, read, update, add 16+8+2+1 = 27.
      */
-    public CreateShareOperation(String path, ShareType shareType, String shareWith, boolean publicUpload, 
+    public CreateShareOperation(Context context, String path, ShareType shareType, String shareWith, boolean publicUpload,
             String password, int permissions, Intent sendIntent) {
 
+        mContext = context;
         mPath = path;
         mShareType = shareType;
         mShareWith = shareWith;
@@ -128,6 +133,9 @@ public class CreateShareOperation extends SyncOperation {
         OCFile file = getStorageManager().getFileByPath(mPath);
         if (file!=null) {
             mSendIntent.putExtra(Intent.EXTRA_TEXT, share.getShareLink());
+            mSendIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(mContext.getString(R.string.subject_token),
+                    getClient().getCredentials().getUsername(), mContext.getString(R.string.shared_subject_header),
+                    file.getFileName(), mContext.getString(R.string.with_you_subject_header)));
             file.setPublicLink(share.getShareLink());
             file.setShareByLink(true);
             getStorageManager().saveFile(file);
index c86c8b5..5afc421 100644 (file)
@@ -36,7 +36,6 @@ import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation;
 
 import android.content.Context;
 import android.net.Uri;
-import android.util.Log;
 
 /**
  * Operation to find out what authentication method requires
index 9f2bed7..0a5ff94 100644 (file)
@@ -150,13 +150,11 @@ public class DownloadFileOperation extends RemoteOperation {
             newFile = new File(getSavePath());
             newFile.getParentFile().mkdirs();
             moved = tmpFile.renameTo(newFile);
-        
             if (!moved)
                 result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED);
         }
         Log_OC.i(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage());
         
-        
         return result;
     }
 
index c319d62..649437d 100644 (file)
@@ -45,10 +45,10 @@ public class GetSharesForFileOperation extends SyncOperation {
      * Constructor
      * 
      * @param path      Path to file or folder
-     * @param reshares  If set to \91false\92 (default), only shares from the current user are returned
-     *                  If set to \91true\92, all shares from the given file are returned
-     * @param subfiles  If set to \91false\92 (default), lists only the folder being shared
-     *                  If set to \91true\92, all shared files within the folder are returned.
+     * @param reshares  If set to false (default), only shares from the current user are returned
+     *                  If set to true, all shares from the given file are returned
+     * @param subfiles  If set to false (default), lists only the folder being shared
+     *                  If set to true, all shared files within the folder are returned.
      */
     public GetSharesForFileOperation(String path, boolean reshares, boolean subfiles) {
         mPath = path;
diff --git a/src/com/owncloud/android/operations/RefreshFolderOperation.java b/src/com/owncloud/android/operations/RefreshFolderOperation.java
new file mode 100644 (file)
index 0000000..5371789
--- /dev/null
@@ -0,0 +1,610 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012-2014 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.operations;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.http.HttpStatus;
+import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+//import android.support.v4.content.LocalBroadcastManager;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
+
+import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.utils.FileStorageUtils;
+
+
+
+/**
+ *  Remote operation performing the synchronization of the list of files contained 
+ *  in a folder identified with its remote path.
+ *  
+ *  Fetches the list and properties of the files contained in the given folder, including their 
+ *  properties, and updates the local database with them.
+ *  
+ *  Does NOT enter in the child folders to synchronize their contents also.
+ * 
+ *  @author David A. Velasco
+ */
+public class RefreshFolderOperation extends RemoteOperation {
+
+    private static final String TAG = RefreshFolderOperation.class.getSimpleName();
+
+    public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED  = 
+            RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
+    public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED    = 
+            RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
+    
+    /** Time stamp for the synchronization process in progress */
+    private long mCurrentSyncTime;
+    
+    /** Remote folder to synchronize */
+    private OCFile mLocalFolder;
+    
+    /** Access to the local database */
+    private FileDataStorageManager mStorageManager;
+    
+    /** Account where the file to synchronize belongs */
+    private Account mAccount;
+    
+    /** Android context; necessary to send requests to the download service */
+    private Context mContext;
+    
+    /** Files and folders contained in the synchronized folder after a successful operation */
+    private List<OCFile> mChildren;
+
+    /** Counter of conflicts found between local and remote files */
+    private int mConflictsFound;
+
+    /** Counter of failed operations in synchronization of kept-in-sync files */
+    private int mFailsInFavouritesFound;
+
+    /**
+     * Map of remote and local paths to files that where locally stored in a location 
+     * out of the ownCloud folder and couldn't be copied automatically into it 
+     **/
+    private Map<String, String> mForgottenLocalFiles;
+
+    /** 'True' means that this operation is part of a full account synchronization */ 
+    private boolean mSyncFullAccount;
+
+    /** 'True' means that Share resources bound to the files into should be refreshed also */
+    private boolean mIsShareSupported;
+    
+    /** 'True' means that the remote folder changed and should be fetched */
+    private boolean mRemoteFolderChanged;
+
+    /** 'True' means that Etag will be ignored */
+    private boolean mIgnoreETag;
+
+    
+    /**
+     * Creates a new instance of {@link RefreshFolderOperation}.
+     * 
+     * @param   folder                  Folder to synchronize.
+     * @param   currentSyncTime         Time stamp for the synchronization process in progress.
+     * @param   syncFullAccount         'True' means that this operation is part of a full account 
+     *                                  synchronization.
+     * @param   isShareSupported        'True' means that the server supports the sharing API.           
+     * @param   ignoreEtag              'True' means that the content of the remote folder should
+     *                                  be fetched and updated even though the 'eTag' did not 
+     *                                  change.  
+     * @param   dataStorageManager      Interface with the local database.
+     * @param   account                 ownCloud account where the folder is located. 
+     * @param   context                 Application context.
+     */
+    public RefreshFolderOperation(OCFile folder,
+                                  long currentSyncTime,
+                                  boolean syncFullAccount,
+                                  boolean isShareSupported,
+                                  boolean ignoreETag,
+                                  FileDataStorageManager dataStorageManager,
+                                  Account account,
+                                  Context context) {
+        mLocalFolder = folder;
+        mCurrentSyncTime = currentSyncTime;
+        mSyncFullAccount = syncFullAccount;
+        mIsShareSupported = isShareSupported;
+        mStorageManager = dataStorageManager;
+        mAccount = account;
+        mContext = context;
+        mForgottenLocalFiles = new HashMap<String, String>();
+        mRemoteFolderChanged = false;
+        mIgnoreETag = ignoreETag;
+    }
+    
+    
+    public int getConflictsFound() {
+        return mConflictsFound;
+    }
+    
+    public int getFailsInFavouritesFound() {
+        return mFailsInFavouritesFound;
+    }
+    
+    public Map<String, String> getForgottenLocalFiles() {
+        return mForgottenLocalFiles;
+    }
+    
+    /**
+     * Returns the list of files and folders contained in the synchronized folder, 
+     * if called after synchronization is complete.
+     * 
+     * @return  List of files and folders contained in the synchronized folder.
+     */
+    public List<OCFile> getChildren() {
+        return mChildren;
+    }
+    
+    /**
+     * Performs the synchronization.
+     * 
+     * {@inheritDoc}
+     */
+    @Override
+    protected RemoteOperationResult run(OwnCloudClient client) {
+        RemoteOperationResult result = null;
+        mFailsInFavouritesFound = 0;
+        mConflictsFound = 0;
+        mForgottenLocalFiles.clear();
+        
+        if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) {
+            updateOCVersion(client);
+        }
+        
+        result = checkForChanges(client);
+        
+        if (result.isSuccess()) {
+            if (mRemoteFolderChanged) {
+                result = fetchAndSyncRemoteFolder(client);
+            } else {
+                mChildren = mStorageManager.getFolderContent(mLocalFolder);
+            }
+        }
+        
+        if (!mSyncFullAccount) {            
+            sendLocalBroadcast(
+                    EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
+            );
+        }
+        
+        if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) {
+            refreshSharesForFolder(client); // share result is ignored 
+        }
+        
+        if (!mSyncFullAccount) {            
+            sendLocalBroadcast(
+                    EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
+            );
+        }
+        
+        return result;
+        
+    }
+
+
+    private void updateOCVersion(OwnCloudClient client) {
+        UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext);
+        RemoteOperationResult result = update.execute(client);
+        if (result.isSuccess()) {
+            mIsShareSupported = update.getOCVersion().isSharedSupported();
+        }
+    }
+
+    
+    private RemoteOperationResult checkForChanges(OwnCloudClient client) {
+        mRemoteFolderChanged = true;
+        RemoteOperationResult result = null;
+        String remotePath = null;
+
+        remotePath = mLocalFolder.getRemotePath();
+        Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath);
+        
+        // remote request 
+        ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+        result = operation.execute(client);
+        if (result.isSuccess()){
+            OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+
+            if (!mIgnoreETag) {
+                // check if remote and local folder are different
+                mRemoteFolderChanged = 
+                        !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
+            }
+
+            result = new RemoteOperationResult(ResultCode.OK);
+        
+            Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + 
+                    (mRemoteFolderChanged ? "changed" : "not changed"));
+            
+        } else {
+            // check failed
+            if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
+                removeLocalFolder();
+            }
+            if (result.isException()) {
+                Log_OC.e(TAG, "Checked " + mAccount.name + remotePath  + " : " + 
+                        result.getLogMessage(), result.getException());
+            } else {
+                Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + 
+                        result.getLogMessage());
+            }
+        }
+        
+        return result;
+    }
+
+
+    private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
+        String remotePath = mLocalFolder.getRemotePath();
+        ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath);
+        RemoteOperationResult result = operation.execute(client);
+        Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath);
+        
+        if (result.isSuccess()) {
+            synchronizeData(result.getData(), client);
+            if (mConflictsFound > 0  || mFailsInFavouritesFound > 0) { 
+                result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);   
+                    // should be a different result code, but will do the job
+            }
+        } else {
+            if (result.getCode() == ResultCode.FILE_NOT_FOUND)
+                removeLocalFolder();
+        }
+        
+        return result;
+    }
+
+    
+    private void removeLocalFolder() {
+        if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+            String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
+            mStorageManager.removeFolder(
+                    mLocalFolder, 
+                    true, 
+                    (   mLocalFolder.isDown() && 
+                            mLocalFolder.getStoragePath().startsWith(currentSavePath)
+                    )
+            );
+        }
+    }
+
+
+    /**
+     *  Synchronizes the data retrieved from the server about the contents of the target folder 
+     *  with the current data in the local database.
+     *  
+     *  Grants that mChildren is updated with fresh data after execution.
+     *  
+     *  @param folderAndFiles   Remote folder and children files in Folder 
+     *  
+     *  @param client           Client instance to the remote server where the data were 
+     *                          retrieved.  
+     *  @return                 'True' when any change was made in the local data, 'false' otherwise
+     */
+    private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client) {
+        // get 'fresh data' from the database
+        mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
+
+        // parse data from remote folder 
+        OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
+        remoteFolder.setParentId(mLocalFolder.getParentId());
+        remoteFolder.setFileId(mLocalFolder.getFileId());
+        
+        Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() 
+                + " changed - starting update of local data ");
+        
+        List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
+        List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+
+        // get current data about local contents of the folder to synchronize
+        List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
+        Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
+        for (OCFile file : localFiles) {
+            localFilesMap.put(file.getRemotePath(), file);
+        }
+        
+        // loop to update every child
+        OCFile remoteFile = null, localFile = null;
+        for (int i=1; i<folderAndFiles.size(); i++) {
+            /// new OCFile instance with the data from the server
+            remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
+            remoteFile.setParentId(mLocalFolder.getFileId());
+
+            /// retrieve local data for the read file 
+            //  localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
+            localFile = localFilesMap.remove(remoteFile.getRemotePath());
+            
+            /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
+            remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
+            if (localFile != null) {
+                // some properties of local state are kept unmodified
+                remoteFile.setFileId(localFile.getFileId());
+                remoteFile.setKeepInSync(localFile.keepInSync());
+                remoteFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
+                remoteFile.setModificationTimestampAtLastSyncForData(
+                        localFile.getModificationTimestampAtLastSyncForData()
+                );
+                remoteFile.setStoragePath(localFile.getStoragePath());
+                // eTag will not be updated unless contents are synchronized 
+                //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
+                remoteFile.setEtag(localFile.getEtag());    
+                if (remoteFile.isFolder()) {
+                    remoteFile.setFileLength(localFile.getFileLength()); 
+                        // TODO move operations about size of folders to FileContentProvider
+                } else if (mRemoteFolderChanged && remoteFile.isImage() &&
+                        remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
+                    remoteFile.setNeedsUpdateThumbnail(true);
+                    Log.d(TAG, "Image " + remoteFile.getFileName() + " updated on the server");
+                }
+                remoteFile.setPublicLink(localFile.getPublicLink());
+                remoteFile.setShareByLink(localFile.isShareByLink());
+            } else {
+                // remote eTag will not be updated unless contents are synchronized 
+                //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
+                remoteFile.setEtag(""); 
+            }
+
+            /// check and fix, if needed, local storage path
+            checkAndFixForeignStoragePath(remoteFile);      // policy - local files are COPIED 
+                                                            // into the ownCloud local folder;
+            searchForLocalFileInDefaultPath(remoteFile);    // legacy   
+
+            /// prepare content synchronization for kept-in-sync files
+            if (remoteFile.keepInSync()) {
+                SynchronizeFileOperation operation = new SynchronizeFileOperation(  localFile,        
+                                                                                    remoteFile, 
+                                                                                    mAccount, 
+                                                                                    true, 
+                                                                                    mContext
+                                                                                    );
+                
+                filesToSyncContents.add(operation);
+            }
+            
+            updatedFiles.add(remoteFile);
+        }
+
+        // save updated contents in local database
+        mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+
+        // request for the synchronization of file contents AFTER saving current remote properties
+        startContentSynchronizations(filesToSyncContents, client);
+
+        mChildren = updatedFiles;
+    }
+
+    /**
+     * Performs a list of synchronization operations, determining if a download or upload is needed
+     * or if exists conflict due to changes both in local and remote contents of the each file.
+     * 
+     * If download or upload is needed, request the operation to the corresponding service and goes 
+     * on.
+     * 
+     * @param filesToSyncContents       Synchronization operations to execute.
+     * @param client                    Interface to the remote ownCloud server.
+     */
+    private void startContentSynchronizations(
+            List<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
+        ) {
+        RemoteOperationResult contentsResult = null;
+        for (SynchronizeFileOperation op: filesToSyncContents) {
+            contentsResult = op.execute(mStorageManager, mContext);   // async
+            if (!contentsResult.isSuccess()) {
+                if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
+                    mConflictsFound++;
+                } else {
+                    mFailsInFavouritesFound++;
+                    if (contentsResult.getException() != null) {
+                        Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                                +  contentsResult.getLogMessage(), contentsResult.getException());
+                    } else {
+                        Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                                + contentsResult.getLogMessage());
+                    }
+                }
+            }   // won't let these fails break the synchronization process
+        }
+    }
+
+
+    public boolean isMultiStatus(int status) {
+        return (status == HttpStatus.SC_MULTI_STATUS); 
+    }
+
+    /**
+     * Creates and populates a new {@link OCFile} object with the data read from the server.
+     * 
+     * @param remote    remote file read from the server (remote file or folder).
+     * @return          New OCFile instance representing the remote resource described by we.
+     */
+    private OCFile fillOCFile(RemoteFile remote) {
+        OCFile file = new OCFile(remote.getRemotePath());
+        file.setCreationTimestamp(remote.getCreationTimestamp());
+        file.setFileLength(remote.getLength());
+        file.setMimetype(remote.getMimeType());
+        file.setModificationTimestamp(remote.getModifiedTimestamp());
+        file.setEtag(remote.getEtag());
+        file.setPermissions(remote.getPermissions());
+        file.setRemoteId(remote.getRemoteId());
+        return file;
+    }
+    
+
+    /**
+     * Checks the storage path of the OCFile received as parameter. 
+     * If it's out of the local ownCloud folder, tries to copy the file inside it. 
+     * 
+     * If the copy fails, the link to the local file is nullified. The account of forgotten 
+     * files is kept in {@link #mForgottenLocalFiles}
+     *) 
+     * @param file      File to check and fix.
+     */
+    private void checkAndFixForeignStoragePath(OCFile file) {
+        String storagePath = file.getStoragePath();
+        String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
+        if (storagePath != null && !storagePath.equals(expectedPath)) {
+            /// fix storagePaths out of the local ownCloud folder
+            File originalFile = new File(storagePath);
+            if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
+                mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+                file.setStoragePath(null);
+                    
+            } else {
+                InputStream in = null;
+                OutputStream out = null;
+                try {
+                    File expectedFile = new File(expectedPath);
+                    File expectedParent = expectedFile.getParentFile();
+                    expectedParent.mkdirs();
+                    if (!expectedParent.isDirectory()) {
+                        throw new IOException(
+                                "Unexpected error: parent directory could not be created"
+                        );
+                    }
+                    expectedFile.createNewFile();
+                    if (!expectedFile.isFile()) {
+                        throw new IOException("Unexpected error: target file could not be created");
+                    }                    
+                    in = new FileInputStream(originalFile);
+                    out = new FileOutputStream(expectedFile);
+                    byte[] buf = new byte[1024];
+                    int len;
+                    while ((len = in.read(buf)) > 0){
+                        out.write(buf, 0, len);
+                    }
+                    file.setStoragePath(expectedPath);
+                    
+                } catch (Exception e) {
+                    Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e);
+                    mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+                    file.setStoragePath(null);
+                    
+                } finally {
+                    try {
+                        if (in != null) in.close();
+                    } catch (Exception e) {
+                        Log_OC.d(TAG, "Weird exception while closing input stream for " 
+                                + storagePath + " (ignoring)", e);
+                    }
+                    try {
+                        if (out != null) out.close();
+                    } catch (Exception e) {
+                        Log_OC.d(TAG, "Weird exception while closing output stream for " 
+                                + expectedPath + " (ignoring)", e);
+                    }
+                }
+            }
+        }
+    }
+    
+    
+    private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
+        RemoteOperationResult result = null;
+        
+        // remote request 
+        GetRemoteSharesForFileOperation operation = 
+                new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true);
+        result = operation.execute(client);
+        
+        if (result.isSuccess()) {
+            // update local database
+            ArrayList<OCShare> shares = new ArrayList<OCShare>();
+            for(Object obj: result.getData()) {
+                shares.add((OCShare) obj);
+            }
+            mStorageManager.saveSharesInFolder(shares, mLocalFolder);
+        }
+
+        return result;
+    }
+    
+
+    /**
+     * Scans the default location for saving local copies of files searching for
+     * a 'lost' file with the same full name as the {@link OCFile} received as 
+     * parameter.
+     *  
+     * @param file      File to associate a possible 'lost' local file.
+     */
+    private void searchForLocalFileInDefaultPath(OCFile file) {
+        if (file.getStoragePath() == null && !file.isFolder()) {
+            File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+            if (f.exists()) {
+                file.setStoragePath(f.getAbsolutePath());
+                file.setLastSyncDateForData(f.lastModified());
+            }
+        }
+    }
+
+    
+    /**
+     * Sends a message to any application component interested in the progress 
+     * of the synchronization.
+     * 
+     * @param event
+     * @param dirRemotePath     Remote path of a folder that was just synchronized 
+     *                          (with or without success)
+     * @param result
+     */
+    private void sendLocalBroadcast(
+            String event, String dirRemotePath, RemoteOperationResult result
+        ) {
+        Log_OC.d(TAG, "Send broadcast " + event);
+        Intent intent = new Intent(event);
+        intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, mAccount.name);
+        if (dirRemotePath != null) {
+            intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
+        }
+        intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result);
+        mContext.sendStickyBroadcast(intent);
+        //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+    }
+
+
+    public boolean getRemoteFolderChanged() {
+        return mRemoteFolderChanged;
+    }
+
+}
index 95a5a9b..aa98607 100644 (file)
@@ -29,8 +29,6 @@ import com.owncloud.android.lib.resources.files.RenameRemoteFileOperation;
 import com.owncloud.android.operations.common.SyncOperation;
 import com.owncloud.android.utils.FileStorageUtils;
 
-import android.accounts.Account;
-
 
 /**
  * Remote operation performing the rename of a remote file (or folder?) in the ownCloud server.
@@ -44,7 +42,6 @@ public class RenameFileOperation extends SyncOperation {
     
     private OCFile mFile;
     private String mRemotePath;
-    private Account mAccount;
     private String mNewName;
     private String mNewRemotePath;
 
@@ -54,12 +51,10 @@ public class RenameFileOperation extends SyncOperation {
      * Constructor
      * 
      * @param remotePath            RemotePath of the OCFile instance describing the remote file or folder to rename
-     * @param account               OwnCloud account containing the remote file 
      * @param newName               New name to set as the name of file.
      */
-    public RenameFileOperation(String remotePath, Account account, String newName) {
+    public RenameFileOperation(String remotePath, String newName) {
         mRemotePath = remotePath;
-        mAccount = account;
         mNewName = newName;
         mNewRemotePath = null;
     }
@@ -103,7 +98,8 @@ public class RenameFileOperation extends SyncOperation {
 
             if (result.isSuccess()) {
                 if (mFile.isFolder()) {
-                    saveLocalDirectory();
+                    getStorageManager().moveLocalFile(mFile, mNewRemotePath, parent);
+                    //saveLocalDirectory();
 
                 } else {
                     saveLocalFile();
@@ -118,28 +114,24 @@ public class RenameFileOperation extends SyncOperation {
         return result;
     }
 
-    
-    private void saveLocalDirectory() {
-        getStorageManager().moveFolder(mFile, mNewRemotePath);
-        String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile);
-        File localDir = new File(localPath);
-        if (localDir.exists()) {
-            localDir.renameTo(new File(FileStorageUtils.getSavePath(mAccount.name) + mNewRemotePath));
-            // TODO - if renameTo fails, children files that are already down will result unlinked
-        }
-    }
-
     private void saveLocalFile() {
         mFile.setFileName(mNewName);
-        
+
         // try to rename the local copy of the file
         if (mFile.isDown()) {
-            File f = new File(mFile.getStoragePath());
+            String oldPath = mFile.getStoragePath();
+            File f = new File(oldPath);
             String parentStoragePath = f.getParent();
             if (!parentStoragePath.endsWith(File.separator))
                 parentStoragePath += File.separator;
             if (f.renameTo(new File(parentStoragePath + mNewName))) {
-                mFile.setStoragePath(parentStoragePath + mNewName);
+                String newPath = parentStoragePath + mNewName;
+                mFile.setStoragePath(newPath);
+
+                // notify MediaScanner about removed file
+                getStorageManager().deleteFileInMediaScan(oldPath);
+                // notify to scan about new file
+                getStorageManager().triggerMediaScan(newPath);
             }
             // else - NOTHING: the link to the local file is kept although the local name can't be updated
             // TODO - study conditions when this could be a problem
@@ -165,7 +157,7 @@ public class RenameFileOperation extends SyncOperation {
      */
     private boolean isValidNewName() throws IOException {
         // check tricky names
-        if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator) || mNewName.contains("%")) { 
+        if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator)) {
             return false;
         }
         // create a test file
index 45a7305..72cb22b 100644 (file)
@@ -54,13 +54,22 @@ public class SynchronizeFileOperation extends SyncOperation {
     
     private boolean mTransferWasRequested = false;
 
+    /** 
+     * When 'false', uploads to the server are not done; only downloads or conflict detection.  
+     * This is a temporal field. 
+     * TODO Remove when 'folder synchronization' replaces 'folder download'.
+     */    
+    private boolean mAllowUploads;
+
     
     /**
-     * Constructor.
+     * Constructor for "full synchronization mode".
      * 
-     * Uses remotePath to retrieve all the data in local cache and remote server when the operation
+     * Uses remotePath to retrieve all the data both in local cache and in the remote OC server when the operation
      * is executed, instead of reusing {@link OCFile} instances.
      * 
+     * Useful for direct synchronization of a single file.
+     * 
      * @param 
      * @param account               ownCloud account holding the file.
      * @param syncFileContents      When 'true', transference of data will be started by the 
@@ -79,16 +88,21 @@ public class SynchronizeFileOperation extends SyncOperation {
         mAccount = account;
         mSyncFileContents = syncFileContents;
         mContext = context;
+        mAllowUploads = true;
     }
 
     
     /**
-     * Constructor allowing to reuse {@link OCFile} instances just queried from cache or network.
+     * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or from remote OC server.
      * 
-     * Useful for folder / account synchronizations.
+     * Useful to include this operation as part of the synchronization of a folder (or a full account), avoiding the
+     * repetition of fetch operations (both in local database or remote server).
      * 
-     * @param localFile             Data of file currently hold in device cache. MUSTN't be null.
-     * @param serverFile            Data of file just retrieved from network. If null, will be
+     * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them, use the other 
+     * constructor.
+     * 
+     * @param localFile             Data of file (just) retrieved from local cache/database.
+     * @param serverFile            Data of file (just) retrieved from a remote server. If null, will be
      *                              retrieved from network by the operation when executed.
      * @param account               ownCloud account holding the file.
      * @param syncFileContents      When 'true', transference of data will be started by the 
@@ -104,10 +118,53 @@ public class SynchronizeFileOperation extends SyncOperation {
         
         mLocalFile = localFile;
         mServerFile = serverFile;
-        mRemotePath = localFile.getRemotePath();
+        if (mLocalFile != null) {
+            mRemotePath = mLocalFile.getRemotePath();
+            if (mServerFile != null && !mServerFile.getRemotePath().equals(mRemotePath)) {
+                throw new IllegalArgumentException("serverFile and localFile do not correspond to the same OC file");
+            }
+        } else if (mServerFile != null) {
+            mRemotePath = mServerFile.getRemotePath();
+        } else {
+            throw new IllegalArgumentException("Both serverFile and localFile are NULL");
+        }
         mAccount = account;
         mSyncFileContents = syncFileContents;
         mContext = context;
+        mAllowUploads = true;
+    }
+    
+
+    /**
+     * Temporal constructor.
+     * 
+     * Extends the previous one to allow constrained synchronizations where uploads are never performed - only
+     * downloads or conflict detection.
+     * 
+     * Do not use unless you are involved in 'folder synchronization' or 'folder download' work in progress.
+     * 
+     * TODO Remove when 'folder synchronization' replaces 'folder download'.
+     * 
+     * @param localFile             Data of file (just) retrieved from local cache/database. MUSTN't be null.
+     * @param serverFile            Data of file (just) retrieved from a remote server. If null, will be
+     *                              retrieved from network by the operation when executed.
+     * @param account               ownCloud account holding the file.
+     * @param syncFileContents      When 'true', transference of data will be started by the 
+     *                              operation if needed and no conflict is detected.
+     * @param allowUploads          When 'false', uploads to the server are not done; only downloads or conflict
+     *                              detection. 
+     * @param context               Android context; needed to start transfers.
+     */
+    public SynchronizeFileOperation(
+            OCFile localFile,
+            OCFile serverFile, 
+            Account account, 
+            boolean syncFileContents,
+            boolean allowUploads, 
+            Context context) {
+        
+        this(localFile, serverFile, account, syncFileContents, context);
+        mAllowUploads = allowUploads;
     }
     
 
@@ -145,13 +202,15 @@ public class SynchronizeFileOperation extends SyncOperation {
                 boolean serverChanged = false;
                 /* time for eTag is coming, but not yet
                     if (mServerFile.getEtag() != null) {
-                        serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag()));   // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
+                        serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag()));
                     } else { */
-                // server without etags
-                serverChanged = (mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData());
+                serverChanged = (
+                    mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData()
+                );
                 //}
-                boolean localChanged = (mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
-                // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads
+                boolean localChanged = (
+                    mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()
+                );
 
                 /// decide action to perform depending upon changes
                 //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
@@ -159,7 +218,7 @@ public class SynchronizeFileOperation extends SyncOperation {
                     result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
 
                 } else if (localChanged) {
-                    if (mSyncFileContents) {
+                    if (mSyncFileContents && mAllowUploads) {
                         requestForUpload(mLocalFile);
                         // the local update of file properties will be done by the FileUploader service when the upload finishes
                     } else {
@@ -195,7 +254,8 @@ public class SynchronizeFileOperation extends SyncOperation {
 
         }
 
-        Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage());
+        Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " 
+                + result.getLogMessage());
 
         return result;
     }
index d61e678..4b5343b 100644 (file)
 
 package com.owncloud.android.operations;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Vector;
-
-import org.apache.http.HttpStatus;
 import android.accounts.Account;
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
-//import android.support.v4.content.LocalBroadcastManager;
 
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-
+import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.resources.shares.OCShare;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.OperationCancelledException;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
-import com.owncloud.android.lib.resources.files.FileUtils;
 import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
 import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
 import com.owncloud.android.lib.resources.files.RemoteFile;
-
-import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.operations.common.SyncOperation;
+import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.utils.FileStorageUtils;
 
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+//import android.support.v4.content.LocalBroadcastManager;
 
 
 /**
@@ -67,225 +59,177 @@ import com.owncloud.android.utils.FileStorageUtils;
  * 
  *  @author David A. Velasco
  */
-public class SynchronizeFolderOperation extends RemoteOperation {
+public class SynchronizeFolderOperation extends SyncOperation {
 
     private static final String TAG = SynchronizeFolderOperation.class.getSimpleName();
 
-    public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED  = 
-            SynchronizeFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
-    public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED    = 
-            SynchronizeFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
-    
     /** Time stamp for the synchronization process in progress */
     private long mCurrentSyncTime;
-    
-    /** Remote folder to synchronize */
-    private OCFile mLocalFolder;
-    
-    /** Access to the local database */
-    private FileDataStorageManager mStorageManager;
+
+    /** Remote path of the folder to synchronize */
+    private String mRemotePath;
     
     /** Account where the file to synchronize belongs */
     private Account mAccount;
-    
+
     /** Android context; necessary to send requests to the download service */
     private Context mContext;
-    
+
+    /** Locally cached information about folder to synchronize */
+    private OCFile mLocalFolder;
+
     /** Files and folders contained in the synchronized folder after a successful operation */
-    private List<OCFile> mChildren;
+    //private List<OCFile> mChildren;
 
     /** Counter of conflicts found between local and remote files */
     private int mConflictsFound;
 
     /** Counter of failed operations in synchronization of kept-in-sync files */
-    private int mFailsInFavouritesFound;
+    private int mFailsInFileSyncsFound;
 
-    /**
-     * Map of remote and local paths to files that where locally stored in a location 
-     * out of the ownCloud folder and couldn't be copied automatically into it 
-     **/
-    private Map<String, String> mForgottenLocalFiles;
-
-    /** 'True' means that this operation is part of a full account synchronization */ 
-    private boolean mSyncFullAccount;
-
-    /** 'True' means that Share resources bound to the files into should be refreshed also */
-    private boolean mIsShareSupported;
-    
     /** 'True' means that the remote folder changed and should be fetched */
     private boolean mRemoteFolderChanged;
 
-    /** 'True' means that Etag will be ignored */
-    private boolean mIgnoreETag;
-
+    private List<OCFile> mFilesForDirectDownload;
+        // to avoid extra PROPFINDs when there was no change in the folder
     
+    private List<SyncOperation> mFilesToSyncContentsWithoutUpload;
+        // this will go out when 'folder synchronization' replaces 'folder download'; step by step  
+
+    private List<SyncOperation> mFavouriteFilesToSyncContents;
+        // this will be used for every file when 'folder synchronization' replaces 'folder download' 
+
+    private final AtomicBoolean mCancellationRequested;
+
     /**
      * Creates a new instance of {@link SynchronizeFolderOperation}.
-     * 
-     * @param   folder                  Folder to synchronize.
-     * @param   currentSyncTime         Time stamp for the synchronization process in progress.
-     * @param   syncFullAccount         'True' means that this operation is part of a full account 
-     *                                  synchronization.
-     * @param   isShareSupported        'True' means that the server supports the sharing API.           
-     * @param   ignoreEtag              'True' means that the content of the remote folder should
-     *                                  be fetched and updated even though the 'eTag' did not 
-     *                                  change.  
-     * @param   dataStorageManager      Interface with the local database.
-     * @param   account                 ownCloud account where the folder is located. 
+     *
      * @param   context                 Application context.
+     * @param   remotePath              Path to synchronize.
+     * @param   account                 ownCloud account where the folder is located.
+     * @param   currentSyncTime         Time stamp for the synchronization process in progress.
      */
-    public SynchronizeFolderOperation(  OCFile folder, 
-                                        long currentSyncTime, 
-                                        boolean syncFullAccount,
-                                        boolean isShareSupported,
-                                        boolean ignoreETag,
-                                        FileDataStorageManager dataStorageManager, 
-                                        Account account, 
-                                        Context context ) {
-        mLocalFolder = folder;
+    public SynchronizeFolderOperation(Context context, String remotePath, Account account, long currentSyncTime){
+        mRemotePath = remotePath;
         mCurrentSyncTime = currentSyncTime;
-        mSyncFullAccount = syncFullAccount;
-        mIsShareSupported = isShareSupported;
-        mStorageManager = dataStorageManager;
         mAccount = account;
         mContext = context;
-        mForgottenLocalFiles = new HashMap<String, String>();
         mRemoteFolderChanged = false;
-        mIgnoreETag = ignoreETag;
+        mFilesForDirectDownload = new Vector<OCFile>();
+        mFilesToSyncContentsWithoutUpload = new Vector<SyncOperation>();
+        mFavouriteFilesToSyncContents = new Vector<SyncOperation>();
+        mCancellationRequested = new AtomicBoolean(false);
     }
-    
-    
+
+
     public int getConflictsFound() {
         return mConflictsFound;
     }
-    
-    public int getFailsInFavouritesFound() {
-        return mFailsInFavouritesFound;
-    }
-    
-    public Map<String, String> getForgottenLocalFiles() {
-        return mForgottenLocalFiles;
-    }
-    
-    /**
-     * Returns the list of files and folders contained in the synchronized folder, 
-     * if called after synchronization is complete.
-     * 
-     * @return  List of files and folders contained in the synchronized folder.
-     */
-    public List<OCFile> getChildren() {
-        return mChildren;
+
+    public int getFailsInFileSyncsFound() {
+        return mFailsInFileSyncsFound;
     }
-    
+
     /**
      * Performs the synchronization.
-     * 
+     *
      * {@inheritDoc}
      */
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
         RemoteOperationResult result = null;
-        mFailsInFavouritesFound = 0;
+        mFailsInFileSyncsFound = 0;
         mConflictsFound = 0;
-        mForgottenLocalFiles.clear();
-        
-        if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) {
-            updateOCVersion(client);
-        }
         
-        result = checkForChanges(client);
-        
-        if (result.isSuccess()) {
-            if (mRemoteFolderChanged) {
-                result = fetchAndSyncRemoteFolder(client);
-            } else {
-                mChildren = mStorageManager.getFolderContent(mLocalFolder);
+        try {
+            // get locally cached information about folder 
+            mLocalFolder = getStorageManager().getFileByPath(mRemotePath);   
+            
+            result = checkForChanges(client);
+    
+            if (result.isSuccess()) {
+                if (mRemoteFolderChanged) {
+                    result = fetchAndSyncRemoteFolder(client);
+                    
+                } else {
+                    prepareOpsFromLocalKnowledge();
+                }
+                
+                if (result.isSuccess()) {
+                    syncContents(client);
+                }
+
             }
+            
+            if (mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
+            
+        } catch (OperationCancelledException e) {
+            result = new RemoteOperationResult(e);
         }
-        
-        if (!mSyncFullAccount) {            
-            sendLocalBroadcast(
-                    EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
-            );
-        }
-        
-        if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) {
-            refreshSharesForFolder(client); // share result is ignored 
-        }
-        
-        if (!mSyncFullAccount) {            
-            sendLocalBroadcast(
-                    EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
-            );
-        }
-        
-        return result;
-        
-    }
 
+        return result;
 
-    private void updateOCVersion(OwnCloudClient client) {
-        UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext);
-        RemoteOperationResult result = update.execute(client);
-        if (result.isSuccess()) {
-            mIsShareSupported = update.getOCVersion().isSharedSupported();
-        }
     }
 
-    
-    private RemoteOperationResult checkForChanges(OwnCloudClient client) {
+    private RemoteOperationResult checkForChanges(OwnCloudClient client) throws OperationCancelledException {
+        Log_OC.d(TAG, "Checking changes in " + mAccount.name + mRemotePath);
+
         mRemoteFolderChanged = true;
         RemoteOperationResult result = null;
-        String remotePath = null;
-
-        remotePath = mLocalFolder.getRemotePath();
-        Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath);
         
-        // remote request 
-        ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+        if (mCancellationRequested.get()) {
+            throw new OperationCancelledException();
+        }
+        
+        // remote request
+        ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mRemotePath);
         result = operation.execute(client);
         if (result.isSuccess()){
             OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
 
-            if (!mIgnoreETag) {
-                // check if remote and local folder are different
-                mRemoteFolderChanged = 
+            // check if remote and local folder are different
+            mRemoteFolderChanged =
                         !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
-            }
 
             result = new RemoteOperationResult(ResultCode.OK);
-        
-            Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + 
+
+            Log_OC.i(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
                     (mRemoteFolderChanged ? "changed" : "not changed"));
-            
+
         } else {
             // check failed
             if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
                 removeLocalFolder();
             }
             if (result.isException()) {
-                Log_OC.e(TAG, "Checked " + mAccount.name + remotePath  + " : " + 
+                Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath  + " : " +
                         result.getLogMessage(), result.getException());
             } else {
-                Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + 
+                Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
                         result.getLogMessage());
             }
+
         }
-        
+
         return result;
     }
 
 
-    private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
-        String remotePath = mLocalFolder.getRemotePath();
-        ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath);
-        RemoteOperationResult result = operation.execute(client);
-        Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath);
+    private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) throws OperationCancelledException {
+        if (mCancellationRequested.get()) {
+            throw new OperationCancelledException();
+        }
         
+        ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(mRemotePath);
+        RemoteOperationResult result = operation.execute(client);
+        Log_OC.d(TAG, "Synchronizing " + mAccount.name + mRemotePath);
+
         if (result.isSuccess()) {
             synchronizeData(result.getData(), client);
-            if (mConflictsFound > 0  || mFailsInFavouritesFound > 0) { 
-                result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);   
+            if (mConflictsFound > 0  || mFailsInFileSyncsFound > 0) {
+                result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
                     // should be a different result code, but will do the job
             }
         } else {
@@ -293,17 +237,19 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 removeLocalFolder();
         }
         
+
         return result;
     }
 
-    
+
     private void removeLocalFolder() {
-        if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+        FileDataStorageManager storageManager = getStorageManager();
+        if (storageManager.fileExists(mLocalFolder.getFileId())) {
             String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
-            mStorageManager.removeFolder(
-                    mLocalFolder, 
-                    true, 
-                    (   mLocalFolder.isDown() && 
+            storageManager.removeFolder(
+                    mLocalFolder,
+                    true,
+                    (   mLocalFolder.isDown() &&        // TODO: debug, I think this is always false for folders
                             mLocalFolder.getStoragePath().startsWith(currentSavePath)
                     )
             );
@@ -312,50 +258,56 @@ public class SynchronizeFolderOperation extends RemoteOperation {
 
 
     /**
-     *  Synchronizes the data retrieved from the server about the contents of the target folder 
+     *  Synchronizes the data retrieved from the server about the contents of the target folder
      *  with the current data in the local database.
-     *  
+     *
      *  Grants that mChildren is updated with fresh data after execution.
-     *  
-     *  @param folderAndFiles   Remote folder and children files in Folder 
-     *  
-     *  @param client           Client instance to the remote server where the data were 
-     *                          retrieved.  
+     *
+     *  @param folderAndFiles   Remote folder and children files in Folder
+     *
+     *  @param client           Client instance to the remote server where the data were
+     *                          retrieved.
      *  @return                 'True' when any change was made in the local data, 'false' otherwise
      */
-    private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client) {
-        // get 'fresh data' from the database
-        mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
-
-        // parse data from remote folder 
+    private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client)
+            throws OperationCancelledException {
+        FileDataStorageManager storageManager = getStorageManager();
+        
+        // parse data from remote folder
         OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
         remoteFolder.setParentId(mLocalFolder.getParentId());
         remoteFolder.setFileId(mLocalFolder.getFileId());
-        
-        Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() 
+
+        Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
                 + " changed - starting update of local data ");
-        
+
         List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
-        List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+        mFilesForDirectDownload.clear();
+        mFilesToSyncContentsWithoutUpload.clear();
+        mFavouriteFilesToSyncContents.clear();
+
+        if (mCancellationRequested.get()) {
+            throw new OperationCancelledException();
+        }
 
         // get current data about local contents of the folder to synchronize
-        List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
+        List<OCFile> localFiles = storageManager.getFolderContent(mLocalFolder);
         Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
         for (OCFile file : localFiles) {
             localFilesMap.put(file.getRemotePath(), file);
         }
-        
-        // loop to update every child
+
+        // loop to synchronize every child
         OCFile remoteFile = null, localFile = null;
         for (int i=1; i<folderAndFiles.size(); i++) {
             /// new OCFile instance with the data from the server
             remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
             remoteFile.setParentId(mLocalFolder.getFileId());
 
-            /// retrieve local data for the read file 
+            /// retrieve local data for the read file
             //  localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
             localFile = localFilesMap.remove(remoteFile.getRemotePath());
-            
+
             /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
             remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
             if (localFile != null) {
@@ -367,11 +319,11 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                         localFile.getModificationTimestampAtLastSyncForData()
                 );
                 remoteFile.setStoragePath(localFile.getStoragePath());
-                // eTag will not be updated unless contents are synchronized 
+                // eTag will not be updated unless contents are synchronized
                 //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
-                remoteFile.setEtag(localFile.getEtag());    
+                remoteFile.setEtag(localFile.getEtag());
                 if (remoteFile.isFolder()) {
-                    remoteFile.setFileLength(localFile.getFileLength()); 
+                    remoteFile.setFileLength(localFile.getFileLength());
                         // TODO move operations about size of folders to FileContentProvider
                 } else if (mRemoteFolderChanged && remoteFile.isImage() &&
                         remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
@@ -381,81 +333,142 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 remoteFile.setPublicLink(localFile.getPublicLink());
                 remoteFile.setShareByLink(localFile.isShareByLink());
             } else {
-                // remote eTag will not be updated unless contents are synchronized 
+                // remote eTag will not be updated unless contents are synchronized
                 //  (Synchronize[File|Folder]Operation with remoteFile as parameter)
-                remoteFile.setEtag(""); 
+                remoteFile.setEtag("");
             }
 
             /// check and fix, if needed, local storage path
-            checkAndFixForeignStoragePath(remoteFile);      // policy - local files are COPIED 
-                                                            // into the ownCloud local folder;
-            searchForLocalFileInDefaultPath(remoteFile);    // legacy   
-
-            /// prepare content synchronization for kept-in-sync files
-            if (remoteFile.keepInSync()) {
-                SynchronizeFileOperation operation = new SynchronizeFileOperation(  localFile,        
-                                                                                    remoteFile, 
-                                                                                    mAccount, 
-                                                                                    true, 
-                                                                                    mContext
-                                                                                    );
+            searchForLocalFileInDefaultPath(remoteFile);
+            
+            /// classify file to sync/download contents later
+            if (remoteFile.isFolder()) {
+                /// to download children files recursively
+                synchronized(mCancellationRequested) {
+                    if (mCancellationRequested.get()) {
+                        throw new OperationCancelledException();
+                    }
+                    startSyncFolderOperation(remoteFile.getRemotePath());
+                }
+
+            } else if (remoteFile.keepInSync()) {
+                /// prepare content synchronization for kept-in-sync files
+                SynchronizeFileOperation operation = new SynchronizeFileOperation(
+                        localFile,
+                        remoteFile,
+                        mAccount,
+                        true,
+                        mContext
+                    );
+                mFavouriteFilesToSyncContents.add(operation);
                 
-                filesToSyncContents.add(operation);
+            } else {
+                /// prepare limited synchronization for regular files
+                SynchronizeFileOperation operation = new SynchronizeFileOperation(
+                        localFile,
+                        remoteFile,
+                        mAccount,
+                        true,
+                        false,
+                        mContext
+                    );
+                mFilesToSyncContentsWithoutUpload.add(operation);
             }
-            
+
             updatedFiles.add(remoteFile);
         }
 
         // save updated contents in local database
-        mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+        storageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
 
-        // request for the synchronization of file contents AFTER saving current remote properties
-        startContentSynchronizations(filesToSyncContents, client);
+    }
+    
+    
+    private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
+        List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder);
+        for (OCFile child : children) {
+            /// classify file to sync/download contents later
+            if (child.isFolder()) {
+                /// to download children files recursively
+                synchronized(mCancellationRequested) {
+                    if (mCancellationRequested.get()) {
+                        throw new OperationCancelledException();
+                    }
+                    startSyncFolderOperation(child.getRemotePath());
+                }
 
-        mChildren = updatedFiles;
+            } else {
+                /// prepare limited synchronization for regular files
+                if (!child.isDown()) {
+                    mFilesForDirectDownload.add(child);
+                }
+            }
+        }
+    }
+
+
+    private void syncContents(OwnCloudClient client) throws OperationCancelledException {
+        startDirectDownloads();
+        startContentSynchronizations(mFilesToSyncContentsWithoutUpload, client);
+        startContentSynchronizations(mFavouriteFilesToSyncContents, client);
+    }
+
+    
+    private void startDirectDownloads() throws OperationCancelledException {
+        for (OCFile file : mFilesForDirectDownload) {
+            synchronized(mCancellationRequested) {
+                if (mCancellationRequested.get()) {
+                    throw new OperationCancelledException();
+                }
+                Intent i = new Intent(mContext, FileDownloader.class);
+                i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+                i.putExtra(FileDownloader.EXTRA_FILE, file);
+                mContext.startService(i);
+            }
+        }
     }
 
     /**
      * Performs a list of synchronization operations, determining if a download or upload is needed
      * or if exists conflict due to changes both in local and remote contents of the each file.
-     * 
-     * If download or upload is needed, request the operation to the corresponding service and goes 
+     *
+     * If download or upload is needed, request the operation to the corresponding service and goes
      * on.
-     * 
+     *
      * @param filesToSyncContents       Synchronization operations to execute.
      * @param client                    Interface to the remote ownCloud server.
      */
-    private void startContentSynchronizations(
-            List<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
-        ) {
+    private void startContentSynchronizations(List<SyncOperation> filesToSyncContents, OwnCloudClient client) 
+            throws OperationCancelledException {
+        
         RemoteOperationResult contentsResult = null;
-        for (SynchronizeFileOperation op: filesToSyncContents) {
-            contentsResult = op.execute(mStorageManager, mContext);   // async
+        for (SyncOperation op: filesToSyncContents) {
+            if (mCancellationRequested.get()) {
+                throw new OperationCancelledException();
+            }
+            contentsResult = op.execute(getStorageManager(), mContext);
             if (!contentsResult.isSuccess()) {
                 if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
                     mConflictsFound++;
                 } else {
-                    mFailsInFavouritesFound++;
+                    mFailsInFileSyncsFound++;
                     if (contentsResult.getException() != null) {
-                        Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                        Log_OC.e(TAG, "Error while synchronizing file : "
                                 +  contentsResult.getLogMessage(), contentsResult.getException());
                     } else {
-                        Log_OC.e(TAG, "Error while synchronizing favourites : " 
+                        Log_OC.e(TAG, "Error while synchronizing file : "
                                 + contentsResult.getLogMessage());
                     }
                 }
+                // TODO - use the errors count in notifications
             }   // won't let these fails break the synchronization process
         }
     }
 
-
-    public boolean isMultiStatus(int status) {
-        return (status == HttpStatus.SC_MULTI_STATUS); 
-    }
-
+    
     /**
-     * Creates and populates a new {@link OCFile} object with the data read from the server.
-     * 
+     * Creates and populates a new {@link com.owncloud.android.datamodel.OCFile} object with the data read from the server.
+     *
      * @param remote    remote file read from the server (remote file or folder).
      * @return          New OCFile instance representing the remote resource described by we.
      */
@@ -470,100 +483,11 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         file.setRemoteId(remote.getRemoteId());
         return file;
     }
-    
 
-    /**
-     * Checks the storage path of the OCFile received as parameter. 
-     * If it's out of the local ownCloud folder, tries to copy the file inside it. 
-     * 
-     * If the copy fails, the link to the local file is nullified. The account of forgotten 
-     * files is kept in {@link #mForgottenLocalFiles}
-     *) 
-     * @param file      File to check and fix.
-     */
-    private void checkAndFixForeignStoragePath(OCFile file) {
-        String storagePath = file.getStoragePath();
-        String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
-        if (storagePath != null && !storagePath.equals(expectedPath)) {
-            /// fix storagePaths out of the local ownCloud folder
-            File originalFile = new File(storagePath);
-            if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
-                mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
-                file.setStoragePath(null);
-                    
-            } else {
-                InputStream in = null;
-                OutputStream out = null;
-                try {
-                    File expectedFile = new File(expectedPath);
-                    File expectedParent = expectedFile.getParentFile();
-                    expectedParent.mkdirs();
-                    if (!expectedParent.isDirectory()) {
-                        throw new IOException(
-                                "Unexpected error: parent directory could not be created"
-                        );
-                    }
-                    expectedFile.createNewFile();
-                    if (!expectedFile.isFile()) {
-                        throw new IOException("Unexpected error: target file could not be created");
-                    }                    
-                    in = new FileInputStream(originalFile);
-                    out = new FileOutputStream(expectedFile);
-                    byte[] buf = new byte[1024];
-                    int len;
-                    while ((len = in.read(buf)) > 0){
-                        out.write(buf, 0, len);
-                    }
-                    file.setStoragePath(expectedPath);
-                    
-                } catch (Exception e) {
-                    Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e);
-                    mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
-                    file.setStoragePath(null);
-                    
-                } finally {
-                    try {
-                        if (in != null) in.close();
-                    } catch (Exception e) {
-                        Log_OC.d(TAG, "Weird exception while closing input stream for " 
-                                + storagePath + " (ignoring)", e);
-                    }
-                    try {
-                        if (out != null) out.close();
-                    } catch (Exception e) {
-                        Log_OC.d(TAG, "Weird exception while closing output stream for " 
-                                + expectedPath + " (ignoring)", e);
-                    }
-                }
-            }
-        }
-    }
-    
-    
-    private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
-        RemoteOperationResult result = null;
-        
-        // remote request 
-        GetRemoteSharesForFileOperation operation = 
-                new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true);
-        result = operation.execute(client);
-        
-        if (result.isSuccess()) {
-            // update local database
-            ArrayList<OCShare> shares = new ArrayList<OCShare>();
-            for(Object obj: result.getData()) {
-                shares.add((OCShare) obj);
-            }
-            mStorageManager.saveSharesInFolder(shares, mLocalFolder);
-        }
-
-        return result;
-    }
-    
 
     /**
      * Scans the default location for saving local copies of files searching for
-     * a 'lost' file with the same full name as the {@link OCFile} received as 
+     * a 'lost' file with the same full name as the {@link com.owncloud.android.datamodel.OCFile} received as
      * parameter.
      *  
      * @param file      File to associate a possible 'lost' local file.
@@ -580,31 +504,29 @@ public class SynchronizeFolderOperation extends RemoteOperation {
 
     
     /**
-     * Sends a message to any application component interested in the progress 
-     * of the synchronization.
-     * 
-     * @param event
-     * @param dirRemotePath     Remote path of a folder that was just synchronized 
-     *                          (with or without success)
-     * @param result
+     * Cancel operation
      */
-    private void sendLocalBroadcast(
-            String event, String dirRemotePath, RemoteOperationResult result
-        ) {
-        Log_OC.d(TAG, "Send broadcast " + event);
-        Intent intent = new Intent(event);
-        intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, mAccount.name);
-        if (dirRemotePath != null) {
-            intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
-        }
-        intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result);
-        mContext.sendStickyBroadcast(intent);
-        //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+    public void cancel() {
+        mCancellationRequested.set(true);
     }
 
+    public String getFolderPath() {
+        String path = mLocalFolder.getStoragePath();
+        if (path != null && path.length() > 0) {
+            return path;
+        }
+        return FileStorageUtils.getDefaultSavePathFor(mAccount.name, mLocalFolder);
+    }
 
-    public boolean getRemoteFolderChanged() {
-        return mRemoteFolderChanged;
+    private void startSyncFolderOperation(String path){
+        Intent intent = new Intent(mContext, OperationsService.class);
+        intent.setAction(OperationsService.ACTION_SYNC_FOLDER);
+        intent.putExtra(OperationsService.EXTRA_ACCOUNT, mAccount);
+        intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, path);
+        mContext.startService(intent);
     }
 
+    public String getRemotePath() {
+        return mRemotePath;
+    }
 }
index a94454c..4ea4812 100644 (file)
@@ -22,7 +22,6 @@ import java.util.ArrayList;
 import java.util.HashMap;
 
 import com.owncloud.android.R;
-import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.db.ProviderMeta;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -98,6 +97,8 @@ public class FileContentProvider extends ContentProvider {
                 ProviderTableMeta.FILE_REMOTE_ID);
         mFileProjectionMap.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL,
                 ProviderTableMeta.FILE_UPDATE_THUMBNAIL);
+        mFileProjectionMap.put(ProviderTableMeta.FILE_IS_DOWNLOADING,
+                ProviderTableMeta.FILE_IS_DOWNLOADING);
     }
 
     private static final int SINGLE_FILE = 1;
@@ -625,7 +626,8 @@ public class FileContentProvider extends ContentProvider {
                     + ProviderTableMeta.FILE_PUBLIC_LINK  + " TEXT, "
                     + ProviderTableMeta.FILE_PERMISSIONS  + " TEXT null,"
                     + ProviderTableMeta.FILE_REMOTE_ID  + " TEXT null,"
-                    + ProviderTableMeta.FILE_UPDATE_THUMBNAIL  + " INTEGER);" //boolean
+                    + ProviderTableMeta.FILE_UPDATE_THUMBNAIL  + " INTEGER," //boolean
+                    + ProviderTableMeta.FILE_IS_DOWNLOADING  + " INTEGER);" //boolean
                     );
             
             // Create table ocshares
@@ -796,7 +798,25 @@ public class FileContentProvider extends ContentProvider {
                 }
             }
             if (!upgraded)
-                Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + 
+                Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
+                        ", newVersion == " + newVersion);
+
+            if (oldVersion < 9 && newVersion >= 9) {
+                Log_OC.i("SQL", "Entering in the #9 ADD in onUpgrade");
+                db.beginTransaction();
+                try {
+                    db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME +
+                            " ADD COLUMN " + ProviderTableMeta.FILE_IS_DOWNLOADING + " INTEGER " +
+                            " DEFAULT 0");
+
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+            if (!upgraded)
+                Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion +
                         ", newVersion == " + newVersion);
         }
     }
index a5bb22d..2e57ada 100644 (file)
@@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentMap;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -48,6 +49,7 @@ import com.owncloud.android.operations.OAuth2GetAccessToken;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 
 import android.accounts.Account;
@@ -81,7 +83,8 @@ public class OperationsService extends Service {
     public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
     public static final String EXTRA_RESULT = "RESULT";
     public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
-    
+    public static final String EXTRA_FILE = "FILE";
+
     // TODO review if ALL OF THEM are necessary
     public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT";
     public static final String EXTRA_USERNAME = "USERNAME";
@@ -99,13 +102,13 @@ public class OperationsService extends Service {
     public static final String ACTION_REMOVE = "REMOVE";
     public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER";
     public static final String ACTION_SYNC_FILE = "SYNC_FILE";
+    public static final String ACTION_SYNC_FOLDER = "SYNC_FOLDER";  // for the moment, just to download
+    //public static final String ACTION_CANCEL_SYNC_FOLDER = "CANCEL_SYNC_FOLDER";  // for the moment, just to download
     public static final String ACTION_MOVE_FILE = "MOVE_FILE";
     
     public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
     public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
 
-    private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations = 
-            new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
 
     private ConcurrentMap<Integer, Pair<RemoteOperation, RemoteOperationResult>> 
         mUndispatchedFinishedOperations =
@@ -130,14 +133,10 @@ public class OperationsService extends Service {
         }
     }
 
-    private Looper mServiceLooper;
-    private ServiceHandler mServiceHandler;
-    private OperationsServiceBinder mBinder;
-    private OwnCloudClient mOwnCloudClient = null;
-    private Target mLastTarget = null;
-    private FileDataStorageManager mStorageManager;
-    private RemoteOperation mCurrentOperation = null;
+    private ServiceHandler mOperationsHandler;
+    private OperationsServiceBinder mOperationsBinder;
     
+    private SyncFolderHandler mSyncFolderHandler;
     
     /**
      * Service initialization
@@ -145,11 +144,16 @@ public class OperationsService extends Service {
     @Override
     public void onCreate() {
         super.onCreate();
-        HandlerThread thread = new HandlerThread("Operations service thread", Process.THREAD_PRIORITY_BACKGROUND);
+        /// First worker thread for most of operations 
+        HandlerThread thread = new HandlerThread("Operations thread", Process.THREAD_PRIORITY_BACKGROUND);
         thread.start();
-        mServiceLooper = thread.getLooper();
-        mServiceHandler = new ServiceHandler(mServiceLooper, this);
-        mBinder = new OperationsServiceBinder();
+        mOperationsHandler = new ServiceHandler(thread.getLooper(), this);
+        mOperationsBinder = new OperationsServiceBinder(mOperationsHandler);
+        
+        /// Separated worker thread for download of folders (WIP)
+        thread = new HandlerThread("Syncfolder thread", Process.THREAD_PRIORITY_BACKGROUND);
+        thread.start();
+        mSyncFolderHandler = new SyncFolderHandler(thread.getLooper(), this);
     }
 
     
@@ -158,17 +162,43 @@ public class OperationsService extends Service {
      * 
      * New operations are added calling to startService(), resulting in a call to this method. 
      * This ensures the service will keep on working although the caller activity goes away.
-     * 
-     * IMPORTANT: the only operations performed here right now is {@link GetSharedFilesOperation}. The class
-     * is taking advantage of it due to time constraints.
      */
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        //Log_OC.wtf(TAG, "onStartCommand init" );
-        Message msg = mServiceHandler.obtainMessage();
-        msg.arg1 = startId;
-        mServiceHandler.sendMessage(msg);
-        //Log_OC.wtf(TAG, "onStartCommand end" );
+        // WIP: for the moment, only SYNC_FOLDER and CANCEL_SYNC_FOLDER is expected here;
+        // the rest of the operations are requested through the Binder
+        if (ACTION_SYNC_FOLDER.equals(intent.getAction())) {
+
+            /*Log_OC.v("NOW " + TAG + ", thread " + Thread.currentThread().getName(), "Received request to sync folder");*/
+
+            if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_REMOTE_PATH)) {
+                Log_OC.e(TAG, "Not enough information provided in intent");
+                return START_NOT_STICKY;
+            }
+            Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+            String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
+
+            Pair<Account, String> itemSyncKey =  new Pair<Account , String>(account, remotePath);
+
+            Pair<Target, RemoteOperation> itemToQueue = newOperation(intent);
+            if (itemToQueue != null) {
+                mSyncFolderHandler.add(account, remotePath, (SynchronizeFolderOperation)itemToQueue.second);
+                Message msg = mSyncFolderHandler.obtainMessage();
+                msg.arg1 = startId;
+                msg.obj = itemSyncKey;
+                /*Log_OC.v(
+                        "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Sync folder " + remotePath + " added to queue"
+                );*/
+                mSyncFolderHandler.sendMessage(msg);
+            }
+
+        } else {
+            Message msg = mOperationsHandler.obtainMessage();
+            msg.arg1 = startId;
+            mOperationsHandler.sendMessage(msg);
+        }
+        
         return START_NOT_STICKY;
     }
 
@@ -191,14 +221,13 @@ public class OperationsService extends Service {
             e.printStackTrace();
         }
         
-        //Log_OC.wtf(TAG, "Clear mUndispatchedFinisiedOperations" );
+        //Log_OC.wtf(TAG, "Clear mUndispatchedFinishedOperations" );
         mUndispatchedFinishedOperations.clear();
         
         //Log_OC.wtf(TAG, "onDestroy end" );
         super.onDestroy();
     }
 
-
     /**
      * Provides a binder object that clients can use to perform actions on the queue of operations, 
      * except the addition of new operations. 
@@ -206,7 +235,7 @@ public class OperationsService extends Service {
     @Override
     public IBinder onBind(Intent intent) {
         //Log_OC.wtf(TAG, "onBind" );
-        return mBinder;
+        return mOperationsBinder;
     }
 
     
@@ -215,11 +244,11 @@ public class OperationsService extends Service {
      */
     @Override
     public boolean onUnbind(Intent intent) {
-        ((OperationsServiceBinder)mBinder).clearListeners();
+        ((OperationsServiceBinder)mOperationsBinder).clearListeners();
         return false;   // not accepting rebinding (default behaviour)
     }
 
-    
+
     /**
      *  Binder to let client components to perform actions on the queue of operations.
      * 
@@ -233,16 +262,28 @@ public class OperationsService extends Service {
         private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners = 
                 new ConcurrentHashMap<OnRemoteOperationListener, Handler>();
         
+        private ServiceHandler mServiceHandler = null;   
+
+        public OperationsServiceBinder(ServiceHandler serviceHandler) {
+            mServiceHandler = serviceHandler;
+        }
+
+
         /**
-         * Cancels an operation
+         * Cancels a pending or current synchronization.
          *
-         * TODO
+         * @param account       ownCloud account where the remote folder is stored.
+         * @param file          A folder in the queue of pending synchronizations
          */
-        public void cancel() {
-            // TODO
+        public void cancel(Account account, OCFile file) {
+            /*Log_OC.v(
+                    "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Received request to cancel folder " + file.getRemotePath()
+            );*/
+            mSyncFolderHandler.cancel(account, file);
         }
-        
-        
+
+
         public void clearListeners() {
             
             mBoundListeners.clear();
@@ -280,131 +321,31 @@ public class OperationsService extends Service {
          * @return  'True' when an operation that enforces the user to wait for completion is in process.
          */
         public boolean isPerformingBlockingOperation() {
-            return (!mPendingOperations.isEmpty());
+            return (!mServiceHandler.mPendingOperations.isEmpty());
         }
 
 
         /**
-         * Creates and adds to the queue a new operation, as described by operationIntent
+         * Creates and adds to the queue a new operation, as described by operationIntent.
+         * 
+         * Calls startService to make the operation is processed by the ServiceHandler.
          * 
          * @param operationIntent       Intent describing a new operation to queue and execute.
          * @return                      Identifier of the operation created, or null if failed.
          */
-        public long newOperation(Intent operationIntent) {
-            RemoteOperation operation = null;
-            Target target = null;
-            try {
-                if (!operationIntent.hasExtra(EXTRA_ACCOUNT) && 
-                        !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
-                    Log_OC.e(TAG, "Not enough information provided in intent");
-                    
-                } else {
-                    Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
-                    String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
-                    String username = operationIntent.getStringExtra(EXTRA_USERNAME);
-                    String password = operationIntent.getStringExtra(EXTRA_PASSWORD);
-                    String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN);
-                    String cookie = operationIntent.getStringExtra(EXTRA_COOKIE);
-                    target = new Target(
-                            account, 
-                            (serverUrl == null) ? null : Uri.parse(serverUrl),
-                            username,
-                            password,
-                            authToken,
-                            cookie
-                    );
-                    
-                    String action = operationIntent.getAction();
-                    if (action.equals(ACTION_CREATE_SHARE)) {  // Create Share
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
-                        if (remotePath.length() > 0) {
-                            operation = new CreateShareOperation(remotePath, ShareType.PUBLIC_LINK, 
-                                    "", false, "", 1, sendIntent);
-                        }
-                        
-                    } else if (action.equals(ACTION_UNSHARE)) {  // Unshare file
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        if (remotePath.length() > 0) {
-                            operation = new UnshareLinkOperation(
-                                    remotePath, 
-                                    OperationsService.this);
-                        }
-                        
-                    } else if (action.equals(ACTION_GET_SERVER_INFO)) { 
-                        // check OC server and get basic information from it
-                        operation = new GetServerInfoOperation(serverUrl, OperationsService.this);
-                        
-                    } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
-                        /// GET ACCESS TOKEN to the OAuth server
-                        String oauth2QueryParameters =
-                                operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
-                        operation = new OAuth2GetAccessToken(
-                                getString(R.string.oauth2_client_id), 
-                                getString(R.string.oauth2_redirect_uri),       
-                                getString(R.string.oauth2_grant_type),
-                                oauth2QueryParameters);
-                        
-                    } else if (action.equals(ACTION_EXISTENCE_CHECK)) {
-                        // Existence Check 
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false);
-                        operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent);
-                        
-                    } else if (action.equals(ACTION_GET_USER_NAME)) {
-                        // Get User Name
-                        operation = new GetRemoteUserNameOperation();
-                        
-                    } else if (action.equals(ACTION_RENAME)) {
-                        // Rename file or folder
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
-                        operation = new RenameFileOperation(remotePath, account, newName);
-                        
-                    } else if (action.equals(ACTION_REMOVE)) {
-                        // Remove file or folder
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
-                        operation = new RemoveFileOperation(remotePath, onlyLocalCopy);
-                        
-                    } else if (action.equals(ACTION_CREATE_FOLDER)) {
-                        // Create Folder
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
-                        operation = new CreateFolderOperation(remotePath, createFullPath);
-                        
-                    } else if (action.equals(ACTION_SYNC_FILE)) {
-                        // Sync file
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true);
-                        operation = new SynchronizeFileOperation(remotePath, account, syncFileContents, getApplicationContext());
-                    } else if (action.equals(ACTION_MOVE_FILE)) {
-                        // Move file/folder
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
-                        operation = new MoveFileOperation(remotePath,newParentPath,account);
-                    }
-                    
-                }
-                    
-            } catch (IllegalArgumentException e) {
-                Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
-                operation = null;
-            }
-
-            if (operation != null) {
-                mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
+        public long queueNewOperation(Intent operationIntent) {
+            Pair<Target, RemoteOperation> itemToQueue = newOperation(operationIntent);
+            if (itemToQueue != null) {
+                mServiceHandler.mPendingOperations.add(itemToQueue);
                 startService(new Intent(OperationsService.this, OperationsService.class));
-                //Log_OC.wtf(TAG, "New operation added, opId: " + operation.hashCode());
-                // better id than hash? ; should be good enough by the time being
-                return operation.hashCode();
+                return itemToQueue.second.hashCode();
                 
             } else {
-                //Log_OC.wtf(TAG, "New operation failed, returned Long.MAX_VALUE");
                 return Long.MAX_VALUE;
             }
         }
-
+        
+        
         public boolean dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) {
             Pair<RemoteOperation, RemoteOperationResult> undispatched = 
                     mUndispatchedFinishedOperations.remove(operationId);
@@ -413,7 +354,7 @@ public class OperationsService extends Service {
                 return true;
                 //Log_OC.wtf(TAG, "Sending callback later");
             } else {
-                if (!mPendingOperations.isEmpty()) {
+                if (!mServiceHandler.mPendingOperations.isEmpty()) {
                     return true;
                 } else {
                     return false;
@@ -421,18 +362,46 @@ public class OperationsService extends Service {
                 //Log_OC.wtf(TAG, "Not finished yet");
             }
         }
+        
+        
+        /**
+         * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting
+         * to download.
+         * 
+         * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting
+         * to download.
+         * 
+         * @param account       ownCloud account where the remote file is stored.
+         * @param remotePath    Path of the folder to check if something is synchronizing / downloading / uploading
+         *                      inside.
+         */
+        public boolean isSynchronizing(Account account, String remotePath) {
+            return mSyncFolderHandler.isSynchronizing(account, remotePath);
+        }
 
     }
-    
-    
-    /** 
+
+
+    /**
      * Operations worker. Performs the pending operations in the order they were requested. 
      * 
      * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}. 
      */
     private static class ServiceHandler extends Handler {
         // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
+        
+        
         OperationsService mService;
+        
+        
+        private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
+                new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+        private RemoteOperation mCurrentOperation = null;
+        private Target mLastTarget = null;
+        private OwnCloudClient mOwnCloudClient = null;
+        private FileDataStorageManager mStorageManager;
+        
+        
         public ServiceHandler(Looper looper, OperationsService service) {
             super(looper);
             if (service == null) {
@@ -443,107 +412,241 @@ public class OperationsService extends Service {
 
         @Override
         public void handleMessage(Message msg) {
-            mService.nextOperation();
+            nextOperation();
             mService.stopSelf(msg.arg1);
         }
-    }
-    
-
-    /**
-     * Performs the next operation in the queue
-     */
-    private void nextOperation() {
         
-        //Log_OC.wtf(TAG, "nextOperation init" );
         
-        Pair<Target, RemoteOperation> next = null;
-        synchronized(mPendingOperations) {
-            next = mPendingOperations.peek();
-        }
-
-        if (next != null) {
+        /**
+         * Performs the next operation in the queue
+         */
+        private void nextOperation() {
             
-            mCurrentOperation = next.second;
-            RemoteOperationResult result = null;
-            try {
-                /// prepare client object to send the request to the ownCloud server
-                if (mLastTarget == null || !mLastTarget.equals(next.first)) {
-                    mLastTarget = next.first;
-                    if (mLastTarget.mAccount != null) {
-                        OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, this);
-                        mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
-                                getClientFor(ocAccount, this);
-                        mStorageManager = 
-                                new FileDataStorageManager(
-                                        mLastTarget.mAccount, 
-                                        getContentResolver());
-                    } else {
-                        OwnCloudCredentials credentials = null;
-                        if (mLastTarget.mUsername != null && 
-                                mLastTarget.mUsername.length() > 0) {
-                            credentials = OwnCloudCredentialsFactory.newBasicCredentials(
-                                    mLastTarget.mUsername, 
-                                    mLastTarget.mPassword);  // basic
-                            
-                        } else if (mLastTarget.mAuthToken != null && 
-                                mLastTarget.mAuthToken.length() > 0) {
-                            credentials = OwnCloudCredentialsFactory.newBearerCredentials(
-                                    mLastTarget.mAuthToken);  // bearer token
-                            
-                        } else if (mLastTarget.mCookie != null &&
-                                mLastTarget.mCookie.length() > 0) {
-                            credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials(
-                                    mLastTarget.mCookie); // SAML SSO
+            //Log_OC.wtf(TAG, "nextOperation init" );
+            
+            Pair<Target, RemoteOperation> next = null;
+            synchronized(mPendingOperations) {
+                next = mPendingOperations.peek();
+            }
+
+            if (next != null) {
+                
+                mCurrentOperation = next.second;
+                RemoteOperationResult result = null;
+                try {
+                    /// prepare client object to send the request to the ownCloud server
+                    if (mLastTarget == null || !mLastTarget.equals(next.first)) {
+                        mLastTarget = next.first;
+                        if (mLastTarget.mAccount != null) {
+                            OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, mService);
+                            mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                                    getClientFor(ocAccount, mService);
+                            mStorageManager = new FileDataStorageManager(
+                                    mLastTarget.mAccount, 
+                                    mService.getContentResolver()
+                            );
+                        } else {
+                            OwnCloudCredentials credentials = null;
+                            if (mLastTarget.mUsername != null && 
+                                    mLastTarget.mUsername.length() > 0) {
+                                credentials = OwnCloudCredentialsFactory.newBasicCredentials(
+                                        mLastTarget.mUsername, 
+                                        mLastTarget.mPassword);  // basic
+                                
+                            } else if (mLastTarget.mAuthToken != null && 
+                                    mLastTarget.mAuthToken.length() > 0) {
+                                credentials = OwnCloudCredentialsFactory.newBearerCredentials(
+                                        mLastTarget.mAuthToken);  // bearer token
+                                
+                            } else if (mLastTarget.mCookie != null &&
+                                    mLastTarget.mCookie.length() > 0) {
+                                credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials(
+                                        mLastTarget.mCookie); // SAML SSO
+                            }
+                            OwnCloudAccount ocAccount = new OwnCloudAccount(
+                                    mLastTarget.mServerUrl, credentials);
+                            mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                                    getClientFor(ocAccount, mService);
+                            mStorageManager = null;
                         }
-                        OwnCloudAccount ocAccount = new OwnCloudAccount(
-                                mLastTarget.mServerUrl, credentials);
-                        mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
-                                getClientFor(ocAccount, this);
-                        mStorageManager = null;
                     }
-                }
 
-                /// perform the operation
-                if (mCurrentOperation instanceof SyncOperation) {
-                    result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
-                } else {
-                    result = mCurrentOperation.execute(mOwnCloudClient);
-                }
+                    /// perform the operation
+                    if (mCurrentOperation instanceof SyncOperation) {
+                        result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
+                    } else {
+                        result = mCurrentOperation.execute(mOwnCloudClient);
+                    }
+                    
+                } catch (AccountsException e) {
+                    if (mLastTarget.mAccount == null) {
+                        Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
+                    } else {
+                        Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+                    }
+                    result = new RemoteOperationResult(e);
+                    
+                } catch (IOException e) {
+                    if (mLastTarget.mAccount == null) {
+                        Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
+                    } else {
+                        Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+                    }
+                    result = new RemoteOperationResult(e);
+                } catch (Exception e) {
+                    if (mLastTarget.mAccount == null) {
+                        Log_OC.e(TAG, "Unexpected error for a NULL account", e);
+                    } else {
+                        Log_OC.e(TAG, "Unexpected error for " + mLastTarget.mAccount.name, e);
+                    }
+                    result = new RemoteOperationResult(e);
                 
-            } catch (AccountsException e) {
-                if (mLastTarget.mAccount == null) {
-                    Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
-                } else {
-                    Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+                } finally {
+                    synchronized(mPendingOperations) {
+                        mPendingOperations.poll();
+                    }
                 }
-                result = new RemoteOperationResult(e);
                 
-            } catch (IOException e) {
-                if (mLastTarget.mAccount == null) {
-                    Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
-                } else {
-                    Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
-                }
-                result = new RemoteOperationResult(e);
-            } catch (Exception e) {
-                if (mLastTarget.mAccount == null) {
-                    Log_OC.e(TAG, "Unexpected error for a NULL account", e);
-                } else {
-                    Log_OC.e(TAG, "Unexpected error for " + mLastTarget.mAccount.name, e);
-                }
-                result = new RemoteOperationResult(e);
-            
-            } finally {
-                synchronized(mPendingOperations) {
-                    mPendingOperations.poll();
-                }
+                //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
+                mService.dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result);
             }
-            
-            //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
-            dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result);
         }
+
+
+        
     }
+    
 
+    /**
+     * Creates a new operation, as described by operationIntent.
+     * 
+     * TODO - move to ServiceHandler (probably)
+     * 
+     * @param operationIntent       Intent describing a new operation to queue and execute.
+     * @return                      Pair with the new operation object and the information about its target server.
+     */
+    private Pair<Target , RemoteOperation> newOperation(Intent operationIntent) {
+        RemoteOperation operation = null;
+        Target target = null;
+        try {
+            if (!operationIntent.hasExtra(EXTRA_ACCOUNT) && 
+                    !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
+                Log_OC.e(TAG, "Not enough information provided in intent");
+                
+            } else {
+                Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
+                String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
+                String username = operationIntent.getStringExtra(EXTRA_USERNAME);
+                String password = operationIntent.getStringExtra(EXTRA_PASSWORD);
+                String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN);
+                String cookie = operationIntent.getStringExtra(EXTRA_COOKIE);
+                target = new Target(
+                        account, 
+                        (serverUrl == null) ? null : Uri.parse(serverUrl),
+                        username,
+                        password,
+                        authToken,
+                        cookie
+                );
+                
+                String action = operationIntent.getAction();
+                if (action.equals(ACTION_CREATE_SHARE)) {  // Create Share
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
+                    if (remotePath.length() > 0) {
+                        operation = new CreateShareOperation(OperationsService.this, remotePath, ShareType.PUBLIC_LINK,
+                                "", false, "", 1, sendIntent);
+                    }
+                    
+                } else if (action.equals(ACTION_UNSHARE)) {  // Unshare file
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    if (remotePath.length() > 0) {
+                        operation = new UnshareLinkOperation(
+                                remotePath, 
+                                OperationsService.this);
+                    }
+                    
+                } else if (action.equals(ACTION_GET_SERVER_INFO)) { 
+                    // check OC server and get basic information from it
+                    operation = new GetServerInfoOperation(serverUrl, OperationsService.this);
+                    
+                } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
+                    /// GET ACCESS TOKEN to the OAuth server
+                    String oauth2QueryParameters =
+                            operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
+                    operation = new OAuth2GetAccessToken(
+                            getString(R.string.oauth2_client_id), 
+                            getString(R.string.oauth2_redirect_uri),       
+                            getString(R.string.oauth2_grant_type),
+                            oauth2QueryParameters);
+                    
+                } else if (action.equals(ACTION_EXISTENCE_CHECK)) {
+                    // Existence Check 
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false);
+                    operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent);
+                    
+                } else if (action.equals(ACTION_GET_USER_NAME)) {
+                    // Get User Name
+                    operation = new GetRemoteUserNameOperation();
+                    
+                } else if (action.equals(ACTION_RENAME)) {
+                    // Rename file or folder
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
+                    operation = new RenameFileOperation(remotePath, newName);
+                    
+                } else if (action.equals(ACTION_REMOVE)) {
+                    // Remove file or folder
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
+                    operation = new RemoveFileOperation(remotePath, onlyLocalCopy);
+                    
+                } else if (action.equals(ACTION_CREATE_FOLDER)) {
+                    // Create Folder
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
+                    operation = new CreateFolderOperation(remotePath, createFullPath);
+                    
+                } else if (action.equals(ACTION_SYNC_FILE)) {
+                    // Sync file
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true);
+                    operation = new SynchronizeFileOperation(
+                            remotePath, account, syncFileContents, getApplicationContext()
+                    );
+                    
+                } else if (action.equals(ACTION_SYNC_FOLDER)) {
+                    // Sync file
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    operation = new SynchronizeFolderOperation(
+                            this,                       // TODO remove this dependency from construction time 
+                            remotePath,
+                            account, 
+                            System.currentTimeMillis()  // TODO remove this dependency from construction time
+                    );
+                    
+                } else if (action.equals(ACTION_MOVE_FILE)) {
+                    // Move file/folder
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
+                    operation = new MoveFileOperation(remotePath,newParentPath,account);
+                }
+                
+            }
+                
+        } catch (IllegalArgumentException e) {
+            Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
+            operation = null;
+        }
+
+        if (operation != null) {
+            return new Pair<Target , RemoteOperation>(target, operation);  
+        } else {
+            return null;
+        }
+    }
+    
 
     /**
      * Sends a broadcast when a new operation is added to the queue.
@@ -593,18 +696,18 @@ public class OperationsService extends Service {
     
     /**
      * Notifies the currently subscribed listeners about the end of an operation.
-     * 
+     *
      * @param target            Account or URL pointing to an OC server.
      * @param operation         Finished operation.
      * @param result            Result of the operation.
      */
-    private void dispatchResultToOperationListeners(
+    protected void dispatchResultToOperationListeners(
             Target target, final RemoteOperation operation, final RemoteOperationResult result) {
         int count = 0;
-        Iterator<OnRemoteOperationListener> listeners = mBinder.mBoundListeners.keySet().iterator();
+        Iterator<OnRemoteOperationListener> listeners = mOperationsBinder.mBoundListeners.keySet().iterator();
         while (listeners.hasNext()) {
             final OnRemoteOperationListener listener = listeners.next();
-            final Handler handler = mBinder.mBoundListeners.get(listener);
+            final Handler handler = mOperationsBinder.mBoundListeners.get(listener);
             if (handler != null) { 
                 handler.post(new Runnable() {
                     @Override
@@ -623,6 +726,4 @@ public class OperationsService extends Service {
         }
         Log_OC.d(TAG, "Called " + count + " listeners");
     }
-    
-
 }
diff --git a/src/com/owncloud/android/services/SyncFolderHandler.java b/src/com/owncloud/android/services/SyncFolderHandler.java
new file mode 100644 (file)
index 0000000..b68d930
--- /dev/null
@@ -0,0 +1,213 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.services;
+
+import android.accounts.Account;
+import android.accounts.AccountsException;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Pair;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
+import com.owncloud.android.files.services.IndexedForest;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.utils.FileStorageUtils;
+
+import java.io.IOException;
+
+/**
+ * SyncFolder worker. Performs the pending operations in the order they were requested.
+ *
+ * Created with the Looper of a new thread, started in
+ * {@link com.owncloud.android.services.OperationsService#onCreate()}.
+ */
+class SyncFolderHandler extends Handler {
+
+    private static final String TAG = SyncFolderHandler.class.getSimpleName();
+
+
+    OperationsService mService;
+
+    private IndexedForest<SynchronizeFolderOperation> mPendingOperations =
+            new IndexedForest<SynchronizeFolderOperation>();
+
+    private OwnCloudClient mOwnCloudClient = null;
+    private Account mCurrentAccount = null;
+    private FileDataStorageManager mStorageManager;
+    private SynchronizeFolderOperation mCurrentSyncOperation;
+
+
+    public SyncFolderHandler(Looper looper, OperationsService service) {
+        super(looper);
+        if (service == null) {
+            throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
+        }
+        mService = service;
+    }
+
+
+    /**
+     * Returns True when the folder located in 'remotePath' in the ownCloud account 'account', or any of its
+     * descendants, is being synchronized (or waiting for it).
+     *
+     * @param account       ownCloud account where the remote folder is stored.
+     * @param remotePath    The path to a folder that could be in the queue of synchronizations.
+     */
+    public boolean isSynchronizing(Account account, String remotePath) {
+        if (account == null || remotePath == null) return false;
+        return (mPendingOperations.contains(account, remotePath));
+    }
+
+
+    @Override
+    public void handleMessage(Message msg) {
+        Pair<Account, String> itemSyncKey = (Pair<Account, String>) msg.obj;
+        /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Handling sync folder " + itemSyncKey.second);*/
+        doOperation(itemSyncKey.first, itemSyncKey.second);
+        mService.stopSelf(msg.arg1);
+    }
+
+
+    /**
+     * Performs the next operation in the queue
+     */
+    private void doOperation(Account account, String remotePath) {
+
+        /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                "Getting sync folder " + remotePath);*/
+        mCurrentSyncOperation = mPendingOperations.get(account, remotePath);
+
+        if (mCurrentSyncOperation != null) {
+            RemoteOperationResult result = null;
+
+            try {
+
+                if (mCurrentAccount == null || !mCurrentAccount.equals(account)) {
+                    mCurrentAccount = account;
+                    mStorageManager = new FileDataStorageManager(
+                            account,
+                            mService.getContentResolver()
+                    );
+                }   // else, reuse storage manager from previous operation
+
+                // always get client from client manager, to get fresh credentials in case of update
+                OwnCloudAccount ocAccount = new OwnCloudAccount(account, mService);
+                mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                        getClientFor(ocAccount, mService);
+
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Executing sync folder " + remotePath);*/
+                result = mCurrentSyncOperation.execute(mOwnCloudClient, mStorageManager);
+
+            } catch (AccountsException e) {
+                Log_OC.e(TAG, "Error while trying to get authorization", e);
+            } catch (IOException e) {
+                Log_OC.e(TAG, "Error while trying to get authorization", e);
+            } finally {
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Removing payload " + remotePath);*/
+
+                mPendingOperations.removePayload(account, remotePath);
+
+                mService.dispatchResultToOperationListeners(null, mCurrentSyncOperation, result);
+
+                sendBroadcastFinishedSyncFolder(account, remotePath, result.isSuccess());
+            }
+        }
+    }
+
+    public void add(Account account, String remotePath, SynchronizeFolderOperation syncFolderOperation){
+        mPendingOperations.putIfAbsent(account, remotePath, syncFolderOperation);
+        sendBroadcastNewSyncFolder(account, remotePath);    // TODO upgrade!
+    }
+
+
+    /**
+     * Cancels a pending or current sync' operation.
+     *
+     * @param account       ownCloud account where the remote file is stored.
+     * @param file          A file in the queue of pending synchronizations
+     */
+    public void cancel(Account account, OCFile file){
+        if (account == null || file == null) {
+            Log_OC.e(TAG, "Cannot cancel with NULL parameters");
+            return;
+        }
+        /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                "Removing sync folder " + file.getRemotePath());*/
+        Pair<SynchronizeFolderOperation, String> removeResult =
+                mPendingOperations.remove(account, file.getRemotePath());
+        SynchronizeFolderOperation synchronization = removeResult.first;
+        if (synchronization != null) {
+            /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                    "Canceling returned sync of " + file.getRemotePath());*/
+            synchronization.cancel();
+        } else {
+            // TODO synchronize?
+            if (mCurrentSyncOperation != null && mCurrentAccount != null &&
+                    mCurrentSyncOperation.getRemotePath().startsWith(file.getRemotePath()) &&
+                    account.name.equals(mCurrentAccount.name)) {
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Canceling current sync as descendant: " + mCurrentSyncOperation.getRemotePath());*/
+                mCurrentSyncOperation.cancel();
+            } else {
+                /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
+                        "Nothing else in cancelation of " + file.getRemotePath());*/
+            }
+        }
+
+        //sendBroadcastFinishedSyncFolder(account, file.getRemotePath());
+    }
+
+    /**
+     * TODO review this method when "folder synchronization" replaces "folder download"; this is a fast and ugly
+     * patch.
+     */
+    private void sendBroadcastNewSyncFolder(Account account, String remotePath) {
+        Intent added = new Intent(FileDownloader.getDownloadAddedMessage());
+        added.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
+        added.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
+        added.putExtra(FileDownloader.EXTRA_FILE_PATH, FileStorageUtils.getSavePath(account.name) + remotePath);
+        mService.sendStickyBroadcast(added);
+    }
+
+    /**
+     * TODO review this method when "folder synchronization" replaces "folder download"; this is a fast and ugly
+     * patch.
+     */
+    private void sendBroadcastFinishedSyncFolder(Account account, String remotePath, boolean success) {
+        Intent finished = new Intent(FileDownloader.getDownloadFinishMessage());
+        finished.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
+        finished.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
+        finished.putExtra(FileDownloader.EXTRA_FILE_PATH, FileStorageUtils.getSavePath(account.name) + remotePath);
+        finished.putExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, success);
+        mService.sendStickyBroadcast(finished);
+    }
+
+
+}
index 33e2400..4683896 100644 (file)
@@ -31,7 +31,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.UpdateOCVersionOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -260,7 +260,7 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
         }
         */
         // folder synchronization
-        SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation(  folder, 
+        RefreshFolderOperation synchFolderOp = new RefreshFolderOperation(  folder,
                                                                                     mCurrentSyncTime, 
                                                                                     true,
                                                                                     mIsShareSupported,
diff --git a/src/com/owncloud/android/ui/ExtendedListView.java b/src/com/owncloud/android/ui/ExtendedListView.java
deleted file mode 100644 (file)
index 9fe885b..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/* ownCloud Android client application
- *   Copyright (C) 2012 Bartek Przybylski
- *   Copyright (C) 2012-2013 ownCloud Inc.
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package com.owncloud.android.ui;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.widget.ListView;
-
-/**
- * ListView allowing to specify the position of an item that should be centered in the visible area, if possible.
- * 
- * The cleanest way I found to overcome the problem due to getHeight() returns 0 until the view is really drawn. 
- *  
- * @author David A. Velasco
- */
-public class ExtendedListView extends ListView {
-
-    private int mPositionToSetAndCenter;
-
-    public ExtendedListView(Context context) {
-        super(context);
-    }
-
-    public ExtendedListView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public ExtendedListView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    /**
-     * {@inheritDoc}
-     * 
-     * 
-     */
-    @Override
-    protected void onDraw (Canvas canvas) {
-        super.onDraw(canvas);
-        if (mPositionToSetAndCenter > 0) {
-            this.setSelectionFromTop(mPositionToSetAndCenter, getHeight() / 2);
-            mPositionToSetAndCenter = 0;
-        }
-    }
-    
-    /**
-     * Public method to set the position of the item that should be centered in the visible area of the view.
-     * 
-     * The position is saved here and checked in onDraw().
-     *  
-     * @param position         Position (in the list of items) of the item to center in the visible area.     
-     */
-    public void setAndCenterSelection(int position) {
-        mPositionToSetAndCenter = position;
-    }
-}
diff --git a/src/com/owncloud/android/ui/LongClickableCheckBoxPreference.java b/src/com/owncloud/android/ui/LongClickableCheckBoxPreference.java
deleted file mode 100644 (file)
index 5befe64..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.owncloud.android.ui;
-
-import android.content.Context;
-import android.preference.CheckBoxPreference;
-import android.view.View;
-
-public class LongClickableCheckBoxPreference extends CheckBoxPreference implements View.OnLongClickListener {
-
-    public LongClickableCheckBoxPreference(Context context) {
-        super(context);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        return true;
-    }
-}
diff --git a/src/com/owncloud/android/ui/RadioButtonPreference.java b/src/com/owncloud/android/ui/RadioButtonPreference.java
new file mode 100644 (file)
index 0000000..8f562b3
--- /dev/null
@@ -0,0 +1,20 @@
+package com.owncloud.android.ui;
+
+import android.content.Context;
+import android.preference.CheckBoxPreference;
+import android.view.View;
+
+import com.owncloud.android.R;
+
+public class RadioButtonPreference extends CheckBoxPreference implements View.OnLongClickListener {
+    
+    public RadioButtonPreference(Context context) {
+        super(context, null, android.R.attr.checkBoxPreferenceStyle);
+        setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
+    }
+  
+    @Override
+    public boolean onLongClick(View v) {
+        return true;
+    }
+}
diff --git a/src/com/owncloud/android/ui/SquareImageView.java b/src/com/owncloud/android/ui/SquareImageView.java
new file mode 100644 (file)
index 0000000..455bdc9
--- /dev/null
@@ -0,0 +1,42 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+public class SquareImageView extends ImageView {
+
+    public SquareImageView(Context context) {
+        super(context);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+    }
+}
diff --git a/src/com/owncloud/android/ui/SquareLinearLayout.java b/src/com/owncloud/android/ui/SquareLinearLayout.java
new file mode 100644 (file)
index 0000000..4a8148f
--- /dev/null
@@ -0,0 +1,42 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+public class SquareLinearLayout extends LinearLayout {
+
+    public SquareLinearLayout(Context context) {
+        super(context);
+    }
+
+    public SquareLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SquareLinearLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+    }
+}
index 076a6cb..2916ac3 100644 (file)
@@ -22,28 +22,31 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.files.FileOperationsHelper;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 
 public interface ComponentsGetter {
 
     /**
-     * Callback method invoked when the parent activity is fully created to get a reference to the FileDownloader service API.
-     * 
-     * @return  Directory to list firstly. Can be NULL.
+     * To be invoked when the parent activity is fully created to get a reference  to the FileDownloader service API.
      */
     public FileDownloaderBinder getFileDownloaderBinder();
 
     
     /**
-     * Callback method invoked when the parent activity is fully created to get a reference to the FileUploader service API.
-     * 
-     * @return  Directory to list firstly. Can be NULL.
+     * To be invoked when the parent activity is fully created to get a reference to the FileUploader service API.
      */
     public FileUploaderBinder getFileUploaderBinder();
 
     
+    /**
+     * To be invoked when the parent activity is fully created to get a reference to the OperationsSerivce service API.
+     */
+    public OperationsServiceBinder getOperationsServiceBinder();
+
     
     public FileDataStorageManager getStorageManager();
     
     public FileOperationsHelper getFileOperationsHelper();
 
+
 }
index 136bdb5..536800b 100644 (file)
@@ -54,6 +54,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CreateShareOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 
 import com.owncloud.android.services.OperationsService;
@@ -464,7 +465,10 @@ implements OnRemoteOperationListener, ComponentsGetter {
         } else if (operation instanceof UnshareLinkOperation) {
             onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result);
         
-        } 
+        } else if (operation instanceof SynchronizeFolderOperation) {
+            onSynchronizeFolderOperationFinish((SynchronizeFolderOperation)operation, result);
+
+        }
     }
 
     protected void requestCredentialsUpdate() {
@@ -506,7 +510,14 @@ implements OnRemoteOperationListener, ComponentsGetter {
             t.show();
         } 
     }
-    
+
+    private void onSynchronizeFolderOperationFinish(SynchronizeFolderOperation operation, RemoteOperationResult result) {
+        if (!result.isSuccess() && result.getCode() != ResultCode.CANCELLED){
+            Toast t = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+                    Toast.LENGTH_LONG);
+            t.show();
+        }
+    }
     
     protected void updateFileFromDB(){
         OCFile file = getFile();
index 762cb99..7e60776 100644 (file)
@@ -25,6 +25,8 @@ import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
@@ -90,7 +92,7 @@ import com.owncloud.android.operations.MoveFileOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 import com.owncloud.android.services.observer.FileObserverService;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
@@ -107,6 +109,7 @@ import com.owncloud.android.ui.preview.PreviewMediaFragment;
 import com.owncloud.android.ui.preview.PreviewVideoActivity;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
+import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.UriUtils;
 
 
@@ -517,7 +520,7 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
             
             // Read sorting order, default to sort by name ascending
             Integer sortOrder = appPreferences
-                    .getInt("sortOrder", FileListListAdapter.SORT_NAME);
+                    .getInt("sortOrder", FileStorageUtils.SORT_NAME);
             
             AlertDialog.Builder builder = new AlertDialog.Builder(this);
             builder.setTitle(R.string.actionbar_sort_title)
@@ -605,13 +608,23 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
     /**
      * Called, when the user selected something for uploading
+     *
      */
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
 
         if (requestCode == ACTION_SELECT_CONTENT_FROM_APPS && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
-            requestSimpleUpload(data, resultCode);
-
+            //getClipData is only supported on api level 16+, Jelly Bean
+            if (data.getData() == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN){
+                for( int i = 0; i < data.getClipData().getItemCount(); i++){
+                    Intent intent = new Intent();
+                    intent.setData(data.getClipData().getItemAt(i).getUri());
+                    requestSimpleUpload(intent, resultCode);
+                }
+            }else {
+                requestSimpleUpload(data, resultCode);
+            }
         } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) {
             requestMultipleUpload(data, resultCode);
 
@@ -797,8 +810,8 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
         IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
         mSyncBroadcastReceiver = new SyncBroadcastReceiver();
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
         //LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
@@ -877,6 +890,10 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
                     } else if (item == 1) {
                         Intent action = new Intent(Intent.ACTION_GET_CONTENT);
                         action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE);
+                        //Intent.EXTRA_ALLOW_MULTIPLE is only supported on api level 18+, Jelly Bean
+                        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+                            action.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+                        }
                         startActivityForResult(Intent.createChooser(action, getString(R.string.upload_chooser_title)),
                                 ACTION_SELECT_CONTENT_FROM_APPS);
                     }
@@ -1082,9 +1099,9 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
                             setFile(currentFile);
                         }
                         
-                        mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+                        mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
                                 
-                        if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+                        if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
                                     equals(event) &&
                                 /// TODO refactor and make common
                                 synchResult != null && !synchResult.isSuccess() &&  
@@ -1238,26 +1255,36 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
 
     /**
-     * Class waiting for broadcast events from the {@link FielDownloader} service.
+     * Class waiting for broadcast events from the {@link FileDownloader} service.
      * 
      * Updates the UI when a download is started or finished, provided that it is relevant for the
      * current folder.
      */
     private class DownloadFinishReceiver extends BroadcastReceiver {
+
+        //int refreshCounter = 0;
         @Override
         public void onReceive(Context context, Intent intent) {
             try {
                 boolean sameAccount = isSameAccount(context, intent);
                 String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
                 boolean isDescendant = isDescendant(downloadedRemotePath);
-    
+
                 if (sameAccount && isDescendant) {
-                    refreshListOfFilesFragment();
-                    refreshSecondFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false));
+                    String linkedToRemotePath = intent.getStringExtra(FileDownloader.EXTRA_LINKED_TO_PATH);
+                    if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) {
+                        //Log_OC.v(TAG, "refresh #" + ++refreshCounter);
+                        refreshListOfFilesFragment();
+                    }
+                    refreshSecondFragment(
+                            intent.getAction(),
+                            downloadedRemotePath,
+                            intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)
+                    );
                 }
     
                 if (mWaitingToSend != null) {
-                    mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath()); // Update the file to send
+                    mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath());
                     if (mWaitingToSend.isDown()) { 
                         sendDownloadedFile();
                     }
@@ -1272,7 +1299,19 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
         private boolean isDescendant(String downloadedRemotePath) {
             OCFile currentDir = getCurrentDir();
-            return (currentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(currentDir.getRemotePath()));
+            return (
+                currentDir != null &&
+                downloadedRemotePath != null &&
+                downloadedRemotePath.startsWith(currentDir.getRemotePath())
+            );
+        }
+
+        private boolean isAscendant(String linkedToRemotePath) {
+            OCFile currentDir = getCurrentDir();
+            return (
+                currentDir != null &&
+                currentDir.getRemotePath().startsWith(linkedToRemotePath)
+            );
         }
 
         private boolean isSameAccount(Context context, Intent intent) {
@@ -1708,6 +1747,7 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
 
     private void requestForDownload() {
         Account account = getAccount();
+        //if (!mWaitingToPreview.isDownloading()) {
         if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
             Intent i = new Intent(this, FileDownloader.class);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
@@ -1736,7 +1776,7 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
         mSyncInProgress = true;
                 
         // perform folder synchronization
-        RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,  
+        RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
                                                                         currentSyncTime, 
                                                                         false,
                                                                         getFileOperationsHelper().isSharedSupported(),
@@ -1765,7 +1805,7 @@ OnSslUntrustedCertListener, OnEnforceableRefreshListener {
     
     private void requestForDownload(OCFile file) {
         Account account = getAccount();
-        if (!mDownloaderBinder.isDownloading(account, file)) {
+        if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) {
             Intent i = new Intent(this, FileDownloader.class);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
             i.putExtra(FileDownloader.EXTRA_FILE, file);
index 07c9213..e4b1886 100644 (file)
@@ -55,7 +55,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CreateFolderOperation;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
@@ -208,7 +208,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         mSyncInProgress = true;
                 
         // perform folder synchronization
-        RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,  
+        RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
                                                                         currentSyncTime, 
                                                                         false,
                                                                         getFileOperationsHelper().isSharedSupported(),
@@ -236,8 +236,8 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
         IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
         syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
         mSyncBroadcastReceiver = new SyncBroadcastReceiver();
         registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
         
@@ -478,9 +478,9 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
                         }
                         
                         mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && 
-                                !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+                                !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
                                 
-                        if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+                        if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
                                     equals(event) &&
                                 /// TODO refactor and make common
                                 synchResult != null && !synchResult.isSuccess() &&  
index 676388d..793b3d9 100644 (file)
@@ -25,6 +25,7 @@ import java.lang.ref.WeakReference;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 
+import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -36,6 +37,7 @@ import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.actionbarsherlock.app.ActionBar;
 import com.actionbarsherlock.app.SherlockFragmentActivity;
@@ -51,12 +53,15 @@ public class LogHistoryActivity extends SherlockFragmentActivity  {
 
     private static final String MAIL_ATTACHMENT_TYPE = "text/plain";
 
+    private static final String KEY_LOG_TEXT = "LOG_TEXT";
+
     private static final String TAG = LogHistoryActivity.class.getSimpleName();
 
     private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT";
 
     private String mLogPath = FileStorageUtils.getLogPath();
     private File logDIR = null;
+    private String mLogText;
 
 
     @Override
@@ -70,6 +75,7 @@ public class LogHistoryActivity extends SherlockFragmentActivity  {
         actionBar.setDisplayHomeAsUpEnabled(true);
         Button deleteHistoryButton = (Button) findViewById(R.id.deleteLogHistoryButton);
         Button sendHistoryButton = (Button) findViewById(R.id.sendLogHistoryButton);
+        TextView logTV = (TextView) findViewById(R.id.logTV);
 
         deleteHistoryButton.setOnClickListener(new OnClickListener() {
             
@@ -89,20 +95,22 @@ public class LogHistoryActivity extends SherlockFragmentActivity  {
             }
         });
 
-        if (mLogPath != null) {
-            logDIR = new File(mLogPath);
-        }
-
-        if (logDIR != null && logDIR.isDirectory()) {
-            // Show a dialog while log data is being loaded
-            showLoadingDialog();
-
-            TextView logTV = (TextView) findViewById(R.id.logTV);
+        if (savedInstanceState == null) {
+            if (mLogPath != null) {
+                logDIR = new File(mLogPath);
+            }
 
-            // Start a new thread that will load all the log data
-            LoadingLogTask task = new LoadingLogTask(logTV);
-            task.execute();
+            if (logDIR != null && logDIR.isDirectory()) {
+                // Show a dialog while log data is being loaded
+                showLoadingDialog();
 
+                // Start a new thread that will load all the log data
+                LoadingLogTask task = new LoadingLogTask(logTV);
+                task.execute();
+            }
+        } else {
+            mLogText = savedInstanceState.getString(KEY_LOG_TEXT);
+            logTV.setText(mLogText);
         }
     }
 
@@ -125,41 +133,45 @@ public class LogHistoryActivity extends SherlockFragmentActivity  {
      */
     private void sendMail() {
 
+        // For the moment we need to consider the possibility that setup.xml
+        // does not include the "mail_logger" entry. This block prevents that
+        // compilation fails in this case.
         String emailAddress;
         try {
             Class<?> stringClass = R.string.class;
             Field mailLoggerField = stringClass.getField("mail_logger");
-            int emailAddressId = (Integer)mailLoggerField.get(null);
+            int emailAddressId = (Integer) mailLoggerField.get(null);
             emailAddress = getString(emailAddressId);
-            
         } catch (Exception e) {
             emailAddress = "";
         }
-
+        
         ArrayList<Uri> uris = new ArrayList<Uri>();
 
         // Convert from paths to Android friendly Parcelable Uri's
         for (String file : Log_OC.getLogFileNames())
         {
-            if (new File(mLogPath + File.separator, file).exists()) {
-                Uri u = Uri.parse("file://" + mLogPath + File.separator + file);
-                uris.add(u);
+            File logFile = new File(mLogPath, file);
+            if (logFile.exists()) {
+                uris.add(Uri.fromFile(logFile));
             }
         }
 
         Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
 
-        // Explicitly only use Gmail to send
-        intent.setClassName("com.google.android.gm","com.google.android.gm.ComposeActivityGmail");
-        intent.putExtra(Intent.EXTRA_EMAIL, new String[]{ emailAddress });
-        intent.putExtra(Intent.EXTRA_SUBJECT, getText(R.string.log_mail_subject));
+        intent.putExtra(Intent.EXTRA_EMAIL, emailAddress);
+        String subject = String.format(getString(R.string.log_send_mail_subject), getString(R.string.app_name));
+        intent.putExtra(Intent.EXTRA_SUBJECT, subject);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.setType(MAIL_ATTACHMENT_TYPE);
         intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
-
-        if (intent.resolveActivity(getPackageManager()) != null) {
+        try {
             startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            Toast.makeText(this, getString(R.string.log_send_no_mail_app), Toast.LENGTH_LONG).show();
+            Log_OC.i(TAG, "Could not find app for sending log history.");
         }
+
     }
 
     /**
@@ -183,7 +195,8 @@ public class LogHistoryActivity extends SherlockFragmentActivity  {
             if (textViewReference != null && result != null) {
                 final TextView logTV = textViewReference.get();
                 if (logTV != null) {
-                    logTV.setText(result);
+                    mLogText = result;
+                    logTV.setText(mLogText);
                     dismissLoadingDialog();
                 }
             }
@@ -258,4 +271,12 @@ public class LogHistoryActivity extends SherlockFragmentActivity  {
             loading.dismiss();
         }
     }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        /// global state
+        outState.putString(KEY_LOG_TEXT, mLogText);
+    }
 }
\ No newline at end of file
index f794cf1..7a29073 100644 (file)
@@ -53,7 +53,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.db.DbHandler;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.ui.LongClickableCheckBoxPreference;
+import com.owncloud.android.ui.RadioButtonPreference;
 import com.owncloud.android.utils.DisplayUtils;
 
 
@@ -79,8 +79,13 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
     private String mAccountName;
     private boolean mShowContextMenu = false;
     private String mUploadPath;
+    private PreferenceCategory mPrefInstantUploadCategory;
+    private Preference mPrefInstantUpload;
     private Preference mPrefInstantUploadPath;
+    private Preference mPrefInstantUploadPathWiFi;
+    private Preference mPrefInstantVideoUpload;
     private Preference mPrefInstantVideoUploadPath;
+    private Preference mPrefInstantVideoUploadPathWiFi;
     private String mUploadVideoPath;
 
 
@@ -107,9 +112,9 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                 ListAdapter listAdapter = listView.getAdapter();
                 Object obj = listAdapter.getItem(position);
 
-                if (obj != null && obj instanceof LongClickableCheckBoxPreference) {
+                if (obj != null && obj instanceof RadioButtonPreference) {
                     mShowContextMenu = true;
-                    mAccountName = ((LongClickableCheckBoxPreference) obj).getKey();
+                    mAccountName = ((RadioButtonPreference) obj).getKey();
 
                     Preferences.this.openContextMenu(listView);
 
@@ -119,7 +124,18 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                 return false;
             }
         });
-
+        
+        // Load package info
+        String temp;
+        try {
+            PackageInfo pkg = getPackageManager().getPackageInfo(getPackageName(), 0);
+            temp = pkg.versionName;
+        } catch (NameNotFoundException e) {
+            temp = "";
+            Log_OC.e(TAG, "Error while showing about dialog", e);
+        } 
+        final String appVersion = temp;
+       
         // Register context menu for list of preferences.
         registerForContextMenu(getListView());
 
@@ -208,7 +224,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                     @Override
                     public boolean onPreferenceClick(Preference preference) {
                         String feedbackMail   =(String) getText(R.string.mail_feedback);
-                        String feedback   =(String) getText(R.string.prefs_feedback);
+                        String feedback   =(String) getText(R.string.prefs_feedback) + " - android v" + appVersion;
                         Intent intent = new Intent(Intent.ACTION_SENDTO); 
                         intent.setType("text/plain");
                         intent.putExtra(Intent.EXTRA_SUBJECT, feedback);
@@ -264,7 +280,23 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                     }
                 });
         }
-
+        
+        mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category");
+        
+        mPrefInstantUploadPathWiFi =  findPreference("instant_upload_on_wifi");
+        mPrefInstantUpload = findPreference("instant_uploading");
+        
+        toggleInstantPictureOptions(((CheckBoxPreference) mPrefInstantUpload).isChecked());
+        
+        mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+            
+            @Override
+            public boolean onPreferenceChange(Preference preference, Object newValue) {
+                toggleInstantPictureOptions((Boolean) newValue);
+                return true;
+            }
+        });
+       
         mPrefInstantVideoUploadPath =  findPreference("instant_video_upload_path");
         if (mPrefInstantVideoUploadPath != null){
 
@@ -281,24 +313,51 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                     }
                 });
         }
+        
+        mPrefInstantVideoUploadPathWiFi =  findPreference("instant_video_upload_on_wifi");
+        mPrefInstantVideoUpload = findPreference("instant_video_uploading");
+        toggleInstantVideoOptions(((CheckBoxPreference) mPrefInstantVideoUpload).isChecked());
+        
+        mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+            
+            @Override
+            public boolean onPreferenceChange(Preference preference, Object newValue) {
+                toggleInstantVideoOptions((Boolean) newValue);
+                return true;
+            }
+        });
             
         /* About App */
        pAboutApp = (Preference) findPreference("about_app");
        if (pAboutApp != null) { 
                pAboutApp.setTitle(String.format(getString(R.string.about_android), getString(R.string.app_name)));
-               PackageInfo pkg;
-               try {
-                   pkg = getPackageManager().getPackageInfo(getPackageName(), 0);
-                   pAboutApp.setSummary(String.format(getString(R.string.about_version), pkg.versionName));
-               } catch (NameNotFoundException e) {
-                   Log_OC.e(TAG, "Error while showing about dialog", e);
-               }
+               pAboutApp.setSummary(String.format(getString(R.string.about_version), appVersion));
        }
 
        loadInstantUploadPath();
        loadInstantUploadVideoPath();
 
     }
+    
+    private void toggleInstantPictureOptions(Boolean value){
+        if (value){
+            mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPathWiFi);
+            mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPath);
+        } else {
+            mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPathWiFi);
+            mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPath);
+        }
+    }
+    
+    private void toggleInstantVideoOptions(Boolean value){
+        if (value){
+            mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPathWiFi);
+            mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPath);
+        } else {
+            mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPathWiFi);
+            mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPath);
+        }
+    }
 
     @Override
     protected void onPause() {
@@ -463,7 +522,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
         else {
 
             for (Account a : accounts) {
-                LongClickableCheckBoxPreference accountPreference = new LongClickableCheckBoxPreference(this);
+                RadioButtonPreference accountPreference = new RadioButtonPreference(this);
                 accountPreference.setKey(a.name);
                 // Handle internationalized domain names
                 accountPreference.setTitle(DisplayUtils.convertIdn(a.name, false));
@@ -483,7 +542,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa
                         AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE);
                         Account accounts[] = am.getAccountsByType(MainApp.getAccountType());
                         for (Account a : accounts) {
-                            CheckBoxPreference p = (CheckBoxPreference) findPreference(a.name);
+                            RadioButtonPreference p = (RadioButtonPreference) findPreference(a.name);
                             if (key.equals(a.name)) {
                                 boolean accountChanged = !p.isChecked(); 
                                 p.setChecked(true);
index 1df1211..d809f14 100644 (file)
@@ -1,6 +1,6 @@
 /* ownCloud Android client application\r
  *   Copyright (C) 2011  Bartek Przybylski\r
- *   Copyright (C) 2012-2014 ownCloud Inc.\r
+ *   Copyright (C) 2012-2015 ownCloud Inc.\r
  *\r
  *   This program is free software: you can redistribute it and/or modify\r
  *   it under the terms of the GNU General Public License version 2,\r
  */\r
 package com.owncloud.android.ui.adapter;\r
 \r
-
+\r
 import java.io.File;\r
-import java.util.Collections;\r
-import java.util.Comparator;\r
 import java.util.Vector;\r
 \r
-import third_parties.daveKoeller.AlphanumComparator;\r
 import android.accounts.Account;\r
 import android.content.Context;\r
 import android.content.SharedPreferences;\r
@@ -34,9 +31,9 @@ import android.view.LayoutInflater;
 import android.view.View;\r
 import android.view.ViewGroup;\r
 import android.widget.BaseAdapter;\r
+import android.widget.GridView;\r
 import android.widget.ImageView;\r
 import android.widget.ListAdapter;\r
-import android.widget.ListView;\r
 import android.widget.TextView;\r
 \r
 import com.owncloud.android.R;\r
@@ -44,13 +41,13 @@ import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.FileDataStorageManager;\r
 import com.owncloud.android.datamodel.OCFile;\r
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;\r
-import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncDrawable;\r
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;\r
 import com.owncloud.android.ui.activity.ComponentsGetter;\r
 import com.owncloud.android.utils.DisplayUtils;\r
 import com.owncloud.android.utils.FileStorageUtils;\r
-
+\r
 \r
 /**\r
  * This Adapter populates a ListView with all files and folders in an ownCloud\r
@@ -60,49 +57,46 @@ import com.owncloud.android.utils.FileStorageUtils;
  * @author Tobias Kaminsky\r
  * @author David A. Velasco\r
  */\r
-public class FileListListAdapter extends BaseAdapter implements ListAdapter {
+public class FileListListAdapter extends BaseAdapter implements ListAdapter {\r
     private final static String PERMISSION_SHARED_WITH_ME = "S";\r
-    \r
+\r
     private Context mContext;\r
     private OCFile mFile = null;\r
     private Vector<OCFile> mFiles = null;\r
+    private Vector<OCFile> mFilesOrig = new Vector<OCFile>();\r
     private boolean mJustFolders;\r
 \r
-    private FileDataStorageManager mStorageManager;
-    private Account mAccount;
+    private FileDataStorageManager mStorageManager;\r
+    private Account mAccount;\r
     private ComponentsGetter mTransferServiceGetter;\r
-    private Integer mSortOrder;\r
-    public static final Integer SORT_NAME = 0;\r
-    public static final Integer SORT_DATE = 1;\r
-    public static final Integer SORT_SIZE = 2;\r
-    private Boolean mSortAscending;\r
+\r
+    private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM };\r
+\r
     private SharedPreferences mAppPreferences;\r
     \r
     public FileListListAdapter(\r
             boolean justFolders, \r
-            Context context, \r
+            Context context,\r
             ComponentsGetter transferServiceGetter\r
             ) {\r
-\r
+        \r
         mJustFolders = justFolders;\r
         mContext = context;\r
         mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);\r
-
-        mTransferServiceGetter = transferServiceGetter;
-        \r
+        mTransferServiceGetter = transferServiceGetter;\r
+\r
         mAppPreferences = PreferenceManager\r
                 .getDefaultSharedPreferences(mContext);\r
         \r
         // Read sorting order, default to sort by name ascending\r
-        mSortOrder = mAppPreferences\r
-                .getInt("sortOrder", 0);\r
-        mSortAscending = mAppPreferences.getBoolean("sortAscending", true);
+        FileStorageUtils.mSortOrder = mAppPreferences.getInt("sortOrder", 0);\r
+        FileStorageUtils.mSortAscending = mAppPreferences.getBoolean("sortAscending", true);\r
+\r
         \r
         // initialise thumbnails cache on background thread\r
         new ThumbnailsCacheManager.InitDiskCacheTask().execute();\r
-\r
     }\r
-    
+    \r
     @Override\r
     public boolean areAllItemsEnabled() {\r
         return true;\r
@@ -139,149 +133,193 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
 \r
     @Override\r
     public View getView(int position, View convertView, ViewGroup parent) {\r
+\r
+        boolean fileView = DisplayUtils.decideViewLayout(mFiles);\r
+\r
         View view = convertView;\r
-        if (view == null) {\r
-            LayoutInflater inflator = (LayoutInflater) mContext\r
-                    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);\r
-            view = inflator.inflate(R.layout.list_item, null);\r
-        }\r
-         \r
+        OCFile file = null;\r
+        LayoutInflater inflator = (LayoutInflater) mContext\r
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);\r
+\r
         if (mFiles != null && mFiles.size() > position) {\r
-            OCFile file = mFiles.get(position);\r
-            TextView fileName = (TextView) view.findViewById(R.id.Filename);           \r
-            String name = file.getFileName();\r
+            file = mFiles.get(position);\r
+        }\r
+\r
+        // Find out which layout should be displayed\r
+        ViewType viewType;\r
+        if (!fileView){\r
+            viewType = ViewType.LIST_ITEM;\r
+        } else if (file.isImage()){\r
+            viewType = ViewType.GRID_IMAGE;\r
+        } else {\r
+            viewType = ViewType.GRID_ITEM;\r
+        }\r
+\r
+        // Create View\r
+        switch (viewType){\r
+            case GRID_IMAGE:\r
+                view = inflator.inflate(R.layout.grid_image, null);\r
+                break;\r
+            case GRID_ITEM:\r
+                view = inflator.inflate(R.layout.grid_item, null);\r
+                break;\r
+            case LIST_ITEM:\r
+                view = inflator.inflate(R.layout.list_item, null);\r
+                break;\r
+        }\r
 \r
-            fileName.setText(name);\r
-            ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1);\r
+        view.invalidate();\r
+\r
+        if (file != null){\r
+\r
+            ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);\r
             fileIcon.setTag(file.getFileId());\r
-            ImageView sharedIconV = (ImageView) view.findViewById(R.id.sharedIcon);\r
-            ImageView sharedWithMeIconV = (ImageView) view.findViewById(R.id.sharedWithMeIcon);\r
-            sharedWithMeIconV.setVisibility(View.GONE);\r
-\r
-            ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);\r
-            localStateView.bringToFront();\r
-            FileDownloaderBinder downloaderBinder = \r
-                    mTransferServiceGetter.getFileDownloaderBinder();\r
-            FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();\r
-            if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {\r
-                localStateView.setImageResource(R.drawable.downloading_file_indicator);\r
-                localStateView.setVisibility(View.VISIBLE);\r
-            } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {\r
-                localStateView.setImageResource(R.drawable.uploading_file_indicator);\r
-                localStateView.setVisibility(View.VISIBLE);\r
-            } else if (file.isDown()) {\r
-                localStateView.setImageResource(R.drawable.local_file_indicator);\r
-                localStateView.setVisibility(View.VISIBLE);\r
-            } else {\r
-                localStateView.setVisibility(View.INVISIBLE);\r
+            TextView fileName;\r
+            String name;\r
+\r
+            switch (viewType){\r
+                case LIST_ITEM:\r
+                    TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);\r
+                    TextView lastModV = (TextView) view.findViewById(R.id.last_mod);\r
+                    ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);\r
+\r
+                    lastModV.setVisibility(View.VISIBLE);\r
+                    lastModV.setText(showRelativeTimestamp(file));\r
+\r
+                    checkBoxV.setVisibility(View.GONE);\r
+\r
+                    fileSizeV.setVisibility(View.VISIBLE);\r
+                    fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));\r
+\r
+                    if (!file.isFolder()) {\r
+                        GridView parentList = (GridView)parent;\r
+                        if (parentList.getChoiceMode() == GridView.CHOICE_MODE_NONE) {\r
+                            checkBoxV.setVisibility(View.GONE);\r
+                        } else {\r
+                            if (parentList.isItemChecked(position)) {\r
+                                checkBoxV.setImageResource(android.R.drawable.checkbox_on_background);\r
+                            } else {\r
+                                checkBoxV.setImageResource(android.R.drawable.checkbox_off_background);\r
+                            }\r
+                            checkBoxV.setVisibility(View.VISIBLE);\r
+                        }\r
+\r
+                    } else { //Folder\r
+                        fileSizeV.setVisibility(View.INVISIBLE);\r
+                    }\r
+\r
+                case GRID_ITEM:\r
+                    // filename\r
+                    fileName = (TextView) view.findViewById(R.id.Filename);\r
+                    name = file.getFileName();\r
+                    fileName.setText(name);\r
+\r
+                case GRID_IMAGE:\r
+                    // sharedIcon\r
+                    ImageView sharedIconV = (ImageView) view.findViewById(R.id.sharedIcon);\r
+                    if (file.isShareByLink()) {\r
+                        sharedIconV.setVisibility(View.VISIBLE);\r
+                        sharedIconV.bringToFront();\r
+                    } else {\r
+                        sharedIconV.setVisibility(View.GONE);\r
+                    }\r
+\r
+                    // local state\r
+                    ImageView localStateView = (ImageView) view.findViewById(R.id.localFileIndicator);\r
+                    localStateView.bringToFront();\r
+                    FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();\r
+                    FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();\r
+                    boolean downloading = (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file));\r
+                    OperationsServiceBinder opsBinder = mTransferServiceGetter.getOperationsServiceBinder();\r
+                    downloading |= (opsBinder != null && opsBinder.isSynchronizing(mAccount, file.getRemotePath()));\r
+                    if (downloading) {\r
+                        localStateView.setImageResource(R.drawable.downloading_file_indicator);\r
+                        localStateView.setVisibility(View.VISIBLE);\r
+                    } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {\r
+                        localStateView.setImageResource(R.drawable.uploading_file_indicator);\r
+                        localStateView.setVisibility(View.VISIBLE);\r
+                    } else if (file.isDown()) {\r
+                        localStateView.setImageResource(R.drawable.local_file_indicator);\r
+                        localStateView.setVisibility(View.VISIBLE);\r
+                    } else {\r
+                        localStateView.setVisibility(View.INVISIBLE);\r
+                    }\r
+\r
+                    // share with me icon\r
+                    if (!file.isFolder()) {\r
+                        ImageView sharedWithMeIconV = (ImageView) view.findViewById(R.id.sharedWithMeIcon);\r
+                        sharedWithMeIconV.bringToFront();\r
+                        if (checkIfFileIsSharedWithMe(file)) {\r
+                            sharedWithMeIconV.setVisibility(View.VISIBLE);\r
+                        } else {\r
+                            sharedWithMeIconV.setVisibility(View.GONE);\r
+                        }\r
+                    }\r
+\r
+                    break;\r
             }\r
             \r
-            TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);\r
-            TextView lastModV = (TextView) view.findViewById(R.id.last_mod);\r
-            ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);\r
+            // For all Views\r
             \r
+            // this if-else is needed even though favorite icon is visible by default\r
+            // because android reuses views in listview\r
+            if (!file.keepInSync()) {\r
+                view.findViewById(R.id.favoriteIcon).setVisibility(View.GONE);\r
+            } else {\r
+                view.findViewById(R.id.favoriteIcon).setVisibility(View.VISIBLE);\r
+            }\r
+            \r
+            // No Folder\r
             if (!file.isFolder()) {\r
-                fileSizeV.setVisibility(View.VISIBLE);\r
-                fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));\r
-                lastModV.setVisibility(View.VISIBLE);\r
-                lastModV.setText(showRelativeTimestamp(file));\r
-                // this if-else is needed even thoe fav icon is visible by default\r
-                // because android reuses views in listview\r
-                if (!file.keepInSync()) {\r
-                    view.findViewById(R.id.imageView3).setVisibility(View.GONE);\r
-                } else {\r
-                    view.findViewById(R.id.imageView3).setVisibility(View.VISIBLE);\r
-                }\r
-                \r
-                ListView parentList = (ListView)parent;\r
-                if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { \r
-                    checkBoxV.setVisibility(View.GONE);\r
-                } else {\r
-                    if (parentList.isItemChecked(position)) {\r
-                        checkBoxV.setImageResource(android.R.drawable.checkbox_on_background);\r
-                    } else {\r
-                        checkBoxV.setImageResource(android.R.drawable.checkbox_off_background);\r
-                    }\r
-                    checkBoxV.setVisibility(View.VISIBLE);\r
-                }               \r
-                \r
-                // get Thumbnail if file is image\r
                 if (file.isImage() && file.getRemoteId() != null){\r
-                     // Thumbnail in Cache?\r
+                    // Thumbnail in Cache?\r
                     Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(\r
                             String.valueOf(file.getRemoteId())\r
-                    );\r
+                            );\r
                     if (thumbnail != null && !file.needsUpdateThumbnail()){\r
                         fileIcon.setImageBitmap(thumbnail);\r
                     } else {\r
                         // generate new Thumbnail\r
                         if (ThumbnailsCacheManager.cancelPotentialWork(file, fileIcon)) {\r
-                            final ThumbnailsCacheManager.ThumbnailGenerationTask task = \r
+                            final ThumbnailsCacheManager.ThumbnailGenerationTask task =\r
                                     new ThumbnailsCacheManager.ThumbnailGenerationTask(\r
                                             fileIcon, mStorageManager, mAccount\r
-                                    );\r
+                                            );\r
                             if (thumbnail == null) {\r
                                 thumbnail = ThumbnailsCacheManager.mDefaultImg;\r
                             }\r
-                            final AsyncDrawable asyncDrawable = new AsyncDrawable(\r
+                            final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =\r
+                                    new ThumbnailsCacheManager.AsyncDrawable(\r
                                     mContext.getResources(), \r
                                     thumbnail, \r
                                     task\r
-                            );\r
+                                    );\r
                             fileIcon.setImageDrawable(asyncDrawable);\r
                             task.execute(file);\r
                         }\r
                     }\r
                 } else {\r
-                    fileIcon.setImageResource(\r
-                            DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())\r
-                    );\r
-                }\r
-
-                if (checkIfFileIsSharedWithMe(file)) {\r
-                    sharedWithMeIconV.setVisibility(View.VISIBLE);\r
+                    fileIcon.setImageResource(DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName()));\r
                 }\r
-            } \r
-            else {\r
-                  // TODO Re-enable when server supports folder-size calculation\r
-//                if (FileStorageUtils.getDefaultSavePathFor(mAccount.name, file) != null){\r
-//                    fileSizeV.setVisibility(View.VISIBLE);\r
-//                    fileSizeV.setText(getFolderSizeHuman(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)));\r
-//                } else {\r
-                    fileSizeV.setVisibility(View.INVISIBLE);\r
-//                }\r
-
-                lastModV.setVisibility(View.VISIBLE);\r
-                lastModV.setText(showRelativeTimestamp(file));\r
-                checkBoxV.setVisibility(View.GONE);\r
-                view.findViewById(R.id.imageView3).setVisibility(View.GONE);\r
-\r
+            } else {\r
+                // Folder\r
                 if (checkIfFileIsSharedWithMe(file)) {\r
                     fileIcon.setImageResource(R.drawable.shared_with_me_folder);\r
-                    sharedWithMeIconV.setVisibility(View.VISIBLE);\r
+                } else if (file.isShareByLink()) {\r
+                    // If folder is sharedByLink, icon folder must be changed to\r
+                    // folder-public one\r
+                    fileIcon.setImageResource(R.drawable.folder_public);\r
                 } else {\r
                     fileIcon.setImageResource(\r
-                            DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())\r
+                            DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName())\r
                     );\r
                 }\r
-\r
-                // If folder is sharedByLink, icon folder must be changed to\r
-                // folder-public one\r
-                if (file.isShareByLink()) {\r
-                    fileIcon.setImageResource(R.drawable.folder_public);\r
-                }\r
-            }\r
-\r
-            if (file.isShareByLink()) {\r
-                sharedIconV.setVisibility(View.VISIBLE);\r
-            } else {\r
-                sharedIconV.setVisibility(View.GONE);\r
             }\r
         }\r
 \r
         return view;\r
     }\r
-
+\r
     /**\r
      * Local Folder size in human readable format\r
      * \r
@@ -294,7 +332,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         File dir = new File(path);\r
 \r
         if (dir.exists()) {\r
-            long bytes = getFolderSize(dir);\r
+            long bytes = FileStorageUtils.getFolderSize(dir);\r
             return DisplayUtils.bytesToHumanReadable(bytes);\r
         }\r
 \r
@@ -320,8 +358,8 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
             return result;\r
         }\r
         return 0;\r
-    } 
-
+    } \r
+\r
     @Override\r
     public int getViewTypeCount() {\r
         return 1;\r
@@ -352,6 +390,9 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
         }\r
         if (mStorageManager != null) {\r
             mFiles = mStorageManager.getFolderContent(mFile);\r
+            mFilesOrig.clear();\r
+            mFilesOrig.addAll(mFiles);\r
+            \r
             if (mJustFolders) {\r
                 mFiles = getFolders(mFiles);\r
             }\r
@@ -359,29 +400,11 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
             mFiles = null;\r
         }\r
 \r
-        sortDirectory();\r
-    }\r
-    \r
-    /**\r
-     * Sorts all filenames, regarding last user decision \r
-     */\r
-    private void sortDirectory(){\r
-        switch (mSortOrder){\r
-        case 0:\r
-            sortByName(mSortAscending);\r
-            break;\r
-        case 1:\r
-            sortByDate(mSortAscending);\r
-            break;\r
-        case 2: \r
-            sortBySize(mSortAscending);\r
-            break;\r
-        }\r
-        \r
+        mFiles = FileStorageUtils.sortFolder(mFiles);\r
         notifyDataSetChanged();\r
     }\r
     \r
-    \r
+\r
     /**\r
      * Filter for getting only the folders\r
      * @param files\r
@@ -414,110 +437,23 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter {
                 && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));\r
     }\r
 \r
-    /**\r
-     * Sorts list by Date\r
-     * @param sortAscending true: ascending, false: descending\r
-     */\r
-    private void sortByDate(boolean sortAscending){\r
-        final Integer val;\r
-        if (sortAscending){\r
-            val = 1;\r
-        } else {\r
-            val = -1;\r
-        }\r
-        \r
-        Collections.sort(mFiles, new Comparator<OCFile>() {\r
-            public int compare(OCFile o1, OCFile o2) {\r
-                if (o1.isFolder() && o2.isFolder()) {\r
-                    Long obj1 = o1.getModificationTimestamp();\r
-                    return val * obj1.compareTo(o2.getModificationTimestamp());\r
-                }\r
-                else if (o1.isFolder()) {\r
-                    return -1;\r
-                } else if (o2.isFolder()) {\r
-                    return 1;\r
-                } else if (o1.getModificationTimestamp() == 0 || o2.getModificationTimestamp() == 0){\r
-                    return 0;\r
-                } else {\r
-                    Long obj1 = o1.getModificationTimestamp();\r
-                    return val * obj1.compareTo(o2.getModificationTimestamp());\r
-                }\r
-            }\r
-        });\r
-    }\r
-\r
-    /**\r
-     * Sorts list by Size\r
-     * @param sortAscending true: ascending, false: descending\r
-     */\r
-    private void sortBySize(boolean sortAscending){\r
-        final Integer val;\r
-        if (sortAscending){\r
-            val = 1;\r
-        } else {\r
-            val = -1;\r
-        }\r
-        \r
-        Collections.sort(mFiles, new Comparator<OCFile>() {\r
-            public int compare(OCFile o1, OCFile o2) {\r
-                if (o1.isFolder() && o2.isFolder()) {\r
-                    Long obj1 = getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o1)));\r
-                    return val * obj1.compareTo(getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o2))));\r
-                }\r
-                else if (o1.isFolder()) {\r
-                    return -1;\r
-                } else if (o2.isFolder()) {\r
-                    return 1;\r
-                } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){\r
-                    return 0;\r
-                } else {\r
-                    Long obj1 = o1.getFileLength();\r
-                    return val * obj1.compareTo(o2.getFileLength());\r
-                }\r
-            }\r
-        });\r
-    }\r
-\r
-    /**\r
-     * Sorts list by Name\r
-     * @param sortAscending true: ascending, false: descending\r
-     */\r
-    private void sortByName(boolean sortAscending){\r
-        final Integer val;\r
-        if (sortAscending){\r
-            val = 1;\r
-        } else {\r
-            val = -1;\r
-        }\r
-\r
-        Collections.sort(mFiles, new Comparator<OCFile>() {\r
-            public int compare(OCFile o1, OCFile o2) {\r
-                if (o1.isFolder() && o2.isFolder()) {\r
-                    return val * o1.getRemotePath().toLowerCase().compareTo(o2.getRemotePath().toLowerCase());\r
-                } else if (o1.isFolder()) {\r
-                    return -1;\r
-                } else if (o2.isFolder()) {\r
-                    return 1;\r
-                }\r
-                return val * new AlphanumComparator().compare(o1, o2);\r
-            }\r
-        });\r
-    }\r
-\r
     public void setSortOrder(Integer order, boolean ascending) {\r
         SharedPreferences.Editor editor = mAppPreferences.edit();\r
         editor.putInt("sortOrder", order);\r
         editor.putBoolean("sortAscending", ascending);\r
         editor.commit();\r
         \r
-        mSortOrder = order;\r
-        mSortAscending = ascending;\r
+        FileStorageUtils.mSortOrder = order;\r
+        FileStorageUtils.mSortAscending = ascending;\r
         \r
-        sortDirectory();\r
-    }    \r
+\r
+        mFiles = FileStorageUtils.sortFolder(mFiles);\r
+        notifyDataSetChanged();\r
+\r
+    }\r
     \r
     private CharSequence showRelativeTimestamp(OCFile file){\r
         return DisplayUtils.getRelativeDateTimeString(mContext, file.getModificationTimestamp(),\r
                 DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0);\r
-    }
+    }\r
 }\r
index 6190ebe..9982a96 100644 (file)
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.Comparator;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -32,8 +33,12 @@ import android.widget.ListView;
 import android.widget.TextView;
 
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+import com.owncloud.android.utils.BitmapUtils;
 import com.owncloud.android.utils.DisplayUtils;
 
+import third_parties.in.srain.cube.GridViewWithHeaderAndFooter;
+
 /**
  * This Adapter populates a ListView with all files and directories contained
  * in a local directory
@@ -46,7 +51,7 @@ public class LocalFileListAdapter extends BaseAdapter implements ListAdapter {
     private Context mContext;
     private File mDirectory;
     private File[] mFiles = null;
-
+    
     public LocalFileListAdapter(File directory, Context context) {
         mContext = context;
         swapDirectory(directory);
@@ -99,12 +104,13 @@ public class LocalFileListAdapter extends BaseAdapter implements ListAdapter {
             String name = file.getName();
             fileName.setText(name);
             
-            ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1);
+            ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
             if (!file.isDirectory()) {
                 fileIcon.setImageResource(R.drawable.file);
             } else {
                 fileIcon.setImageResource(R.drawable.ic_menu_archive);
             }
+            fileIcon.setTag(file.hashCode());
 
             TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);
             TextView lastModV = (TextView) view.findViewById(R.id.last_mod);
@@ -112,9 +118,10 @@ public class LocalFileListAdapter extends BaseAdapter implements ListAdapter {
             if (!file.isDirectory()) {
                 fileSizeV.setVisibility(View.VISIBLE);
                 fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.length()));
+
                 lastModV.setVisibility(View.VISIBLE);
                 lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.lastModified()));
-                ListView parentList = (ListView)parent;
+                GridViewWithHeaderAndFooter parentList = (GridViewWithHeaderAndFooter)parent;
                 if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { 
                     checkBoxV.setVisibility(View.GONE);
                 } else {
@@ -125,15 +132,47 @@ public class LocalFileListAdapter extends BaseAdapter implements ListAdapter {
                     }
                     checkBoxV.setVisibility(View.VISIBLE);
                 }
+                
+             // get Thumbnail if file is image
+                if (BitmapUtils.isImage(file)){
+                // Thumbnail in Cache?
+                    Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
+                            String.valueOf(file.hashCode())
+                    );
+                    if (thumbnail != null){
+                        fileIcon.setImageBitmap(thumbnail);
+                    } else {
+
+                        // generate new Thumbnail
+                        if (ThumbnailsCacheManager.cancelPotentialWork(file, fileIcon)) {
+                            final ThumbnailsCacheManager.ThumbnailGenerationTask task =
+                                    new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon);
+                            if (thumbnail == null) {
+                                thumbnail = ThumbnailsCacheManager.mDefaultImg;
+                            }
+                            final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
+                                       new ThumbnailsCacheManager.AsyncDrawable(
+                                    mContext.getResources(), 
+                                    thumbnail, 
+                                    task
+                                       );
+                            fileIcon.setImageDrawable(asyncDrawable);
+                            task.execute(file);
+                        }
+                    }
+                } else {
+                    fileIcon.setImageResource(DisplayUtils.getFileTypeIconId(null, file.getName()));
+                }  
 
             } else {
                 fileSizeV.setVisibility(View.GONE);
                 lastModV.setVisibility(View.GONE);
                 checkBoxV.setVisibility(View.GONE);
             }
-            
-            view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE);   // not GONE; the alignment changes; ugly way to keep it
-            view.findViewById(R.id.imageView3).setVisibility(View.GONE);
+
+            // not GONE; the alignment changes; ugly way to keep it
+            view.findViewById(R.id.localFileIndicator).setVisibility(View.INVISIBLE);   
+            view.findViewById(R.id.favoriteIcon).setVisibility(View.GONE);
             
             view.findViewById(R.id.sharedIcon).setVisibility(View.GONE);
             view.findViewById(R.id.sharedWithMeIcon).setVisibility(View.GONE);
index a78584a..2453404 100644 (file)
@@ -26,15 +26,15 @@ package com.owncloud.android.ui.dialog;
  */
 import java.util.Vector;
 
+import android.app.Dialog;
+import android.os.Bundle;
+
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
 
-import android.app.Dialog;
-import android.os.Bundle;
-
 public class RemoveFileDialogFragment extends ConfirmationDialogFragment 
 implements ConfirmationDialogFragmentListener {
 
@@ -105,8 +105,7 @@ implements ConfirmationDialogFragmentListener {
     @Override
     public void onNeutral(String callerTag) {
         ComponentsGetter cg = (ComponentsGetter)getSherlockActivity();
-        cg.getFileOperationsHelper()
-            .removeFile(mTargetFile, true);
+        cg.getFileOperationsHelper().removeFile(mTargetFile, true);
         
         FileDataStorageManager storageManager = cg.getStorageManager();
         
@@ -141,4 +140,4 @@ implements ConfirmationDialogFragmentListener {
         // nothing to do here
     }
     
-}
+}
\ No newline at end of file
index 530e920..d285f1e 100644 (file)
@@ -22,12 +22,6 @@ package com.owncloud.android.ui.dialog;
  * 
  *  Triggers the rename operation. 
  */
-import com.actionbarsherlock.app.SherlockDialogFragment;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.resources.files.FileUtils;
-import com.owncloud.android.ui.activity.ComponentsGetter;
-
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
@@ -39,6 +33,12 @@ import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.actionbarsherlock.app.SherlockDialogFragment;
+import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.ui.activity.ComponentsGetter;
+
 
 /**
  *  Dialog to input a new name for a file or folder to rename.  
@@ -125,12 +125,8 @@ extends SherlockDialogFragment implements DialogInterface.OnClickListener {
                         Toast.LENGTH_LONG).show();
                 return;
             }
-            
-            ((ComponentsGetter)getSherlockActivity()).
-                getFileOperationsHelper().renameFile(mTargetFile, newFileName);
-            
-            
+
+            ((ComponentsGetter)getSherlockActivity()).getFileOperationsHelper().renameFile(mTargetFile, newFileName);
         }
     }
-    
 }
index 5d3f7ac..8aefec0 100644 (file)
@@ -27,33 +27,31 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
+import android.widget.GridView;
 import android.widget.ListAdapter;
-import android.widget.ListView;
 import android.widget.TextView;
 
 import com.actionbarsherlock.app.SherlockFragment;
 import com.owncloud.android.R;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.ui.ExtendedListView;
+import third_parties.in.srain.cube.GridViewWithHeaderAndFooter;
 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
 
 /**
- *  TODO extending SherlockListFragment instead of SherlockFragment 
+ * TODO extending SherlockListFragment instead of SherlockFragment
  */
 public class ExtendedListFragment extends SherlockFragment 
 implements OnItemClickListener, OnEnforceableRefreshListener {
-    
+
     private static final String TAG = ExtendedListFragment.class.getSimpleName();
 
-    private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION"; 
+    private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION";
     private static final String KEY_INDEXES = "INDEXES";
     private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
     private static final String KEY_TOPS = "TOPS";
     private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
     private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE";
 
-    protected ExtendedListView mList;
-    
     private SwipeRefreshLayout mRefreshLayout;
     private SwipeRefreshLayout mRefreshEmptyLayout;
     private TextView mEmptyListMessage;
@@ -66,47 +64,70 @@ implements OnItemClickListener, OnEnforceableRefreshListener {
 
     private OnEnforceableRefreshListener mOnRefreshListener = null;
     
-    
+    protected GridViewWithHeaderAndFooter imageView;
+       
     public void setListAdapter(ListAdapter listAdapter) {
-        mList.setAdapter(listAdapter);
-        mList.invalidate();
+        imageView.setAdapter(listAdapter);
+        imageView.invalidate();
+    }
+
+    public GridView getGridView() {
+        return imageView;
+    }
+
+    public void setFooterView(View footer) {
+        imageView.addFooterView(footer, null, false);
+        imageView.invalidate();
     }
 
-    public ListView getListView() {
-        return mList;
+    public void removeFooterView(View footer) {
+        imageView.removeFooterView(footer);
+        imageView.invalidate();
+    }
+
+    public int getFooterViewCount() {
+        return imageView.getFooterViewCount();
+    }
+    
+    protected void switchImageView(){
+       imageView.setNumColumns(GridView.AUTO_FIT);
+       imageView.invalidateRowHeight();  // Force to recalculate mRowHeight of imageView
+       imageView.invalidate();
+    }
+    
+    protected void switchFileView(){
+       imageView.setNumColumns(1);
+       imageView.invalidate();
     }
     
     
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         Log_OC.e(TAG, "onCreateView");
-        
-        View v = inflater.inflate(R.layout.list_fragment, null);
-        mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view);
-        mList = (ExtendedListView)(v.findViewById(R.id.list_root));
-        mList.setOnItemClickListener(this);
 
-        mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator));
-        mList.setDividerHeight(1);
+        View v = inflater.inflate(R.layout.list_fragment, null);
+        
+        imageView = (GridViewWithHeaderAndFooter)(v.findViewById(R.id.list_root));
+        imageView.setOnItemClickListener(this);
 
         if (savedInstanceState != null) {
             int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION);
             setReferencePosition(referencePosition);
         }
-        
+
         // Pull down refresh
         mRefreshLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files);
         mRefreshEmptyLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files_emptyView);
+        mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view);
         
         onCreateSwipeToRefresh(mRefreshLayout);
         onCreateSwipeToRefresh(mRefreshEmptyLayout);
-        
-        mList.setEmptyView(mRefreshEmptyLayout);
 
+        imageView.setEmptyView(mRefreshEmptyLayout);
+        
         return v;
     }
 
-    
     /**
      * {@inheritDoc}
      */
@@ -142,32 +163,34 @@ implements OnItemClickListener, OnEnforceableRefreshListener {
         savedInstanceState.putString(KEY_EMPTY_LIST_MESSAGE, getEmptyViewText());
     }
 
-    
     /**
-     * Calculates the position of the item that will be used as a reference to reposition the visible items in the list when
-     * the device is turned to other position. 
+     * Calculates the position of the item that will be used as a reference to
+     * reposition the visible items in the list when the device is turned to
+     * other position.
      * 
-     * THe current policy is take as a reference the visible item in the center of the screen.  
+     * THe current policy is take as a reference the visible item in the center
+     * of the screen.
      * 
-     * @return      The position in the list of the visible item in the center of the screen.
+     * @return The position in the list of the visible item in the center of the
+     *         screen.
      */
     protected int getReferencePosition() {
-        if (mList != null) {
-            return (mList.getFirstVisiblePosition() + mList.getLastVisiblePosition()) / 2;
+        if (imageView != null) {
+            return (imageView.getFirstVisiblePosition() + imageView.getLastVisiblePosition()) / 2;
         } else {
             return 0;
         }
     }
 
-    
     /**
      * Sets the visible part of the list from the reference position.
      * 
-     * @param   position    Reference position previously returned by {@link LocalFileListFragment#getReferencePosition()}
+     * @param position Reference position previously returned by
+     *            {@link LocalFileListFragment#getReferencePosition()}
      */
     protected void setReferencePosition(int position) {
-        if (mList != null) {
-            mList.setAndCenterSelection(position);
+        if (imageView != null) {
+            imageView.setSelection(position);
         }
     }
 
@@ -185,22 +208,14 @@ implements OnItemClickListener, OnEnforceableRefreshListener {
             
             int top = mTops.remove(mTops.size() - 1);
             
-            mList.setSelectionFromTop(firstPosition, top);
+            imageView.smoothScrollToPosition(firstPosition);
             
             // Move the scroll if the selection is not visible
             int indexPosition = mHeightCell*index;
-            int height = mList.getHeight();
+            int height = imageView.getHeight();
             
             if (indexPosition > height) {
-                if (android.os.Build.VERSION.SDK_INT >= 11)
-                {
-                    mList.smoothScrollToPosition(index); 
-                }
-                else if (android.os.Build.VERSION.SDK_INT >= 8)
-                {
-                    mList.setSelectionFromTop(index, 0);
-                }
-                
+                imageView.smoothScrollToPosition(index);
             }
         }
     }
@@ -212,10 +227,10 @@ implements OnItemClickListener, OnEnforceableRefreshListener {
         
         mIndexes.add(index);
         
-        int firstPosition = mList.getFirstVisiblePosition();
+        int firstPosition = imageView.getFirstVisiblePosition();
         mFirstPositions.add(firstPosition);
         
-        View view = mList.getChildAt(0);
+        View view = imageView.getChildAt(0);
         int top = (view == null) ? 0 : view.getTop() ;
 
         mTops.add(top);
@@ -227,7 +242,7 @@ implements OnItemClickListener, OnEnforceableRefreshListener {
     
     @Override
     public void onItemClick (AdapterView<?> parent, View view, int position, long id) {
-        // to be @overriden  
+        // to be @overriden
     }
 
     @Override
@@ -240,7 +255,6 @@ implements OnItemClickListener, OnEnforceableRefreshListener {
             mOnRefreshListener.onRefresh();
         }
     }
-    
     public void setOnRefreshListener(OnEnforceableRefreshListener listener) {
         mOnRefreshListener = listener;
     }
index 2ff2925..30ac7e1 100644 (file)
@@ -348,7 +348,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
             // configure UI for depending upon local state of the file
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
-            if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))) {
+            if (transferring ||
+                    (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) ||
+                    (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))
+                    ) {
                 setButtonsForTransferring();
                 
             } else if (file.isDown()) {
@@ -396,7 +399,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
         }
         ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon);
         if (iv != null) {
-            iv.setImageResource(DisplayUtils.getResourceId(mimetype, filename));
+            iv.setImageResource(DisplayUtils.getFileTypeIconId(mimetype, filename));
         }
     }
 
@@ -449,6 +452,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
             progressText.setVisibility(View.VISIBLE);
             FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
+            //if (getFile().isDownloading()) {
             if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) {
                 progressText.setText(R.string.downloader_download_in_progress_ticker);
             } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) {
index c9408b1..a20cc3c 100644 (file)
@@ -76,7 +76,7 @@ public class LocalFileListFragment extends ExtendedListFragment {
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         Log_OC.i(TAG, "onCreateView() start");
         View v = super.onCreateView(inflater, container, savedInstanceState);
-        getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+        getGridView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
         disableSwipe(); // Disable pull refresh
         setMessageForEmptyList(getString(R.string.local_file_list_empty));
         Log_OC.i(TAG, "onCreateView() end");
@@ -98,7 +98,6 @@ public class LocalFileListFragment extends ExtendedListFragment {
         Log_OC.i(TAG, "onActivityCreated() stop");
     }
     
-    
     /**
      * Checks the file clicked over. Browses inside if it is a directory. Notifies the container activity in any case.
      */
@@ -118,7 +117,7 @@ public class LocalFileListFragment extends ExtendedListFragment {
             } else {    /// Click on a file
                 ImageView checkBoxV = (ImageView) v.findViewById(R.id.custom_checkbox);
                 if (checkBoxV != null) {
-                    if (getListView().isItemChecked(position)) {
+                    if (getGridView().isItemChecked(position)) {
                         checkBoxV.setImageResource(android.R.drawable.checkbox_on_background);
                     } else {
                         checkBoxV.setImageResource(android.R.drawable.checkbox_off_background);
@@ -195,10 +194,10 @@ public class LocalFileListFragment extends ExtendedListFragment {
             directory = directory.getParentFile();
         }
 
-        mList.clearChoices();   // by now, only files in the same directory will be kept as selected
+        imageView.clearChoices();   // by now, only files in the same directory will be kept as selected
         mAdapter.swapDirectory(directory);
         if (mDirectory == null || !mDirectory.equals(directory)) {
-            mList.setSelectionFromTop(0, 0);
+            imageView.setSelection(0);
         }
         mDirectory = directory;
     }
@@ -211,11 +210,11 @@ public class LocalFileListFragment extends ExtendedListFragment {
      */
     public String[] getCheckedFilePaths() {
         ArrayList<String> result = new ArrayList<String>();
-        SparseBooleanArray positions = mList.getCheckedItemPositions();
+        SparseBooleanArray positions = imageView.getCheckedItemPositions();
         if (positions.size() > 0) {
             for (int i = 0; i < positions.size(); i++) {
                 if (positions.get(positions.keyAt(i)) == true) {
-                    result.add(((File) mList.getItemAtPosition(positions.keyAt(i))).getAbsolutePath());
+                    result.add(((File) imageView.getItemAtPosition(positions.keyAt(i))).getAbsolutePath());
                 }
             }
 
index db9f28f..5386ac8 100644 (file)
 package com.owncloud.android.ui.fragment;
 
 import java.io.File;
+import java.util.Vector;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.v4.widget.SwipeRefreshLayout;
@@ -27,8 +29,11 @@ import android.view.ContextMenu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.TextView;
+import android.view.LayoutInflater;
 
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
@@ -44,6 +49,8 @@ import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
 import com.owncloud.android.ui.preview.PreviewImageFragment;
 import com.owncloud.android.ui.preview.PreviewMediaFragment;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.FileStorageUtils;
 
 /**
  * A Fragment that lists all files and folders in a given path.
@@ -70,6 +77,7 @@ public class OCFileListFragment extends ExtendedListFragment {
    
     private OCFile mFile = null;
     private FileListListAdapter mAdapter;
+    private View mFooterView;
     
     private OCFile mTargetFile;
 
@@ -112,22 +120,26 @@ public class OCFileListFragment extends ExtendedListFragment {
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         Log_OC.e(TAG, "onActivityCreated() start");
-        
+
         if (savedInstanceState != null) {
             mFile = savedInstanceState.getParcelable(KEY_FILE);
         }
-        
+
+        mFooterView = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
+                        R.layout.list_footer, null, false);
+        setFooterView(mFooterView);
+
         Bundle args = getArguments();
         boolean justFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false); 
         mAdapter = new FileListListAdapter(
                 justFolders,
-                getSherlockActivity(), 
+                getSherlockActivity(),
                 mContainerActivity
-        );
+                );
         setListAdapter(mAdapter);
         
-        registerForContextMenu(getListView());
-        getListView().setOnCreateContextMenuListener(this);
+        registerForContextMenu(getGridView());
+        getGridView().setOnCreateContextMenuListener(this);
   }
     
     /**
@@ -248,15 +260,9 @@ public class OCFileListFragment extends ExtendedListFragment {
                 );
                 mf.filter(menu);
             }
-            
-            /// additional restrictions for this fragment 
-            // TODO allow in the future 'open with' for previewable files
-            MenuItem item = menu.findItem(R.id.action_open_file_with);
-            if (item != null) {
-                item.setVisible(false);
-                item.setEnabled(false);
-            }
+                 
             /// TODO break this direct dependency on FileDisplayActivity... if possible
+            MenuItem item = menu.findItem(R.id.action_open_file_with);
             FileFragment frag = ((FileDisplayActivity)getSherlockActivity()).getSecondFragment();
             if (frag != null && frag instanceof FileDetailFragment && 
                     frag.getFile().getFileId() == targetFile.getFileId()) {
@@ -282,6 +288,10 @@ public class OCFileListFragment extends ExtendedListFragment {
                 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
                 return true;
             }
+            case R.id.action_open_file_with: {
+                mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
+                return true;
+            }
             case R.id.action_unshare_file: {
                 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
                 return true;
@@ -381,22 +391,79 @@ public class OCFileListFragment extends ExtendedListFragment {
 
             mAdapter.swapDirectory(directory, storageManager);
             if (mFile == null || !mFile.equals(directory)) {
-                mList.setSelectionFromTop(0, 0);
+                imageView.setSelection(0);
             }
             mFile = directory;
+
+            Vector<OCFile> files = storageManager.getFolderContent(directory);
+            // Update Footer
+            TextView footerText = (TextView) mFooterView.findViewById(R.id.footerText);
+            footerText.setText(generateFooterText(directory));
+            if (DisplayUtils.decideViewLayout(files)){
+                switchImageView();
+            } else {
+                switchFileView();
+            }
         }
     }
     
+    private String generateFooterText(OCFile directory) {
+        Integer files = 0;
+        Integer folders = 0;
+
+        FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
+        Vector<OCFile> mFiles = storageManager.getFolderContent(mFile);
+
+        for (OCFile ocFile : mFiles) {
+            if (ocFile.isFolder()) {
+                folders++;
+            } else {
+                files++;
+            }
+        }
+
+        String output = "";
+       
+        if (files > 0){
+            if (files == 1) {
+                output = output + files.toString() + " " + getResources().getString(R.string.file_list_file);
+            } else {
+                output = output + files.toString() + " " + getResources().getString(R.string.file_list_files);
+            }
+        }
+        if (folders > 0 && files > 0){
+            output = output + ", ";
+        }
+        if (folders == 1) {
+            output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folder);
+        } else if (folders > 1) {
+            output = output + folders.toString() + " " + getResources().getString(R.string.file_list_folders);
+        }
+
+        // Fix for showing or not to show the footerView
+        if (folders == 0 && files == 0) {   // If no files or folders, remove footerView for allowing
+                                            // to show the emptyList message
+            removeFooterView(mFooterView);
+        } else { // set a new footerView if there is not one for showing the number or files/folders
+            if (getFooterViewCount()== 0) {
+                ((ViewGroup)mFooterView.getParent()).removeView(mFooterView);
+                setFooterView(mFooterView);
+            }
+        }
+        
+        return output;
+    }
+    
     public void sortByName(boolean descending) {
-        mAdapter.setSortOrder(FileListListAdapter.SORT_NAME, descending);
+        mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
     }
 
     public void sortByDate(boolean descending) {
-        mAdapter.setSortOrder(FileListListAdapter.SORT_DATE, descending);
+        mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
     }
 
     public void sortBySize(boolean descending) {
-        mAdapter.setSortOrder(FileListListAdapter.SORT_SIZE, descending);
+        mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
     }  
 
 }
index 98bbda3..7af29c6 100644 (file)
@@ -211,10 +211,11 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
      * @param   transferring    When true, the view must be updated assuming that the holded file is 
      *                          downloading, no matter what the downloaderBinder says.
      */
+    /*
     public void updateView(boolean transferring) {
         // configure UI for depending upon local state of the file
-        FileDownloaderBinder downloaderBinder = (mContainerActivity == null) ? null : mContainerActivity.getFileDownloaderBinder();
-        if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile()))) {
+        // TODO remove
+        if (transferring || getFile().isDownloading()) {
             setButtonsForTransferring();
             
         } else if (getFile().isDown()) {
@@ -227,7 +228,7 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
         getView().invalidate();
         
     }
-
+    */
 
     /**
      * Enables or disables buttons for a file being downloaded
index c06f341..213aee0 100644 (file)
@@ -79,7 +79,9 @@ ViewPager.OnPageChangeListener, OnRemoteOperationListener {
     private static final int INITIAL_HIDE_DELAY = 0; // immediate hide
 
     private ExtendedViewPager mViewPager;
-    private PreviewImagePagerAdapter mPreviewImagePagerAdapter;    
+    private PreviewImagePagerAdapter mPreviewImagePagerAdapter;
+    private int mSavedPosition = 0;
+    private boolean mHasSavedPosition = false;
     
     private boolean mRequestWaitingForBinder;
     
@@ -146,7 +148,7 @@ ViewPager.OnPageChangeListener, OnRemoteOperationListener {
         }
         mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), parentFolder, getAccount(), getStorageManager());
         mViewPager = (ExtendedViewPager) findViewById(R.id.fragmentPager);
-        int position = mPreviewImagePagerAdapter.getFilePosition(getFile());
+        int position = mHasSavedPosition ? mSavedPosition : mPreviewImagePagerAdapter.getFilePosition(getFile());
         position = (position >= 0) ? position : 0;
         mViewPager.setAdapter(mPreviewImagePagerAdapter); 
         mViewPager.setOnPageChangeListener(this);
@@ -378,6 +380,8 @@ ViewPager.OnPageChangeListener, OnRemoteOperationListener {
      */
     @Override
     public void onPageSelected(int position) {
+        mSavedPosition = position;
+        mHasSavedPosition = true;
         if (mDownloaderBinder == null) {
             mRequestWaitingForBinder = true;
             
@@ -422,7 +426,7 @@ ViewPager.OnPageChangeListener, OnRemoteOperationListener {
     
 
     /**
-     * Class waiting for broadcast events from the {@link FielDownloader} service.
+     * Class waiting for broadcast events from the {@link FileDownloader} service.
      * 
      * Updates the UI when a download is started or finished, provided that it is relevant for the
      * folder displayed in the gallery.
index 4dd5c43..0995793 100644 (file)
@@ -53,6 +53,7 @@ import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
+import com.owncloud.android.utils.BitmapUtils;
 import com.owncloud.android.utils.TouchImageViewCustom;
 
 
@@ -82,6 +83,8 @@ public class PreviewImageFragment extends FileFragment {
     private static final String TAG = PreviewImageFragment.class.getSimpleName();
 
     private boolean mIgnoreFirstSavedState;
+    
+    private LoadBitmapTask mLoadBitmapTask = null;
 
     
     /**
@@ -190,12 +193,22 @@ public class PreviewImageFragment extends FileFragment {
     public void onStart() {
         super.onStart();
         if (getFile() != null) {
-           BitmapLoader bl = new BitmapLoader(mImageView, mMessageView, mProgressWheel);
-           bl.execute(new String[]{getFile().getStoragePath()});
+           mLoadBitmapTask = new LoadBitmapTask(mImageView, mMessageView, mProgressWheel);
+           mLoadBitmapTask.execute(new String[]{getFile().getStoragePath()});
         }
     }
     
     
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mLoadBitmapTask != null) {
+            mLoadBitmapTask.cancel(true);
+            mLoadBitmapTask = null;
+        }
+        
+    }
+    
     /**
      * {@inheritDoc}
      */
@@ -329,7 +342,7 @@ public class PreviewImageFragment extends FileFragment {
     }
     
     
-    private class BitmapLoader extends AsyncTask<String, Void, Bitmap> {
+    private class LoadBitmapTask extends AsyncTask<String, Void, Bitmap> {
 
         /**
          * Weak reference to the target {@link ImageView} where the bitmap will be loaded into.
@@ -365,7 +378,7 @@ public class PreviewImageFragment extends FileFragment {
          * 
          * @param imageView     Target {@link ImageView} where the bitmap will be loaded into.
          */
-        public BitmapLoader(ImageViewCustom imageView, TextView messageView, ProgressBar progressWheel) {
+        public LoadBitmapTask(ImageViewCustom imageView, TextView messageView, ProgressBar progressWheel) {
             mImageViewRef = new WeakReference<ImageViewCustom>(imageView);
             mMessageViewRef = new WeakReference<TextView>(messageView);
             mProgressWheelRef = new WeakReference<ProgressBar>(progressWheel);
@@ -379,43 +392,65 @@ public class PreviewImageFragment extends FileFragment {
             String storagePath = params[0];
             try {
 
+                if (isCancelled()) return result;
+                
                 File picture = new File(storagePath);
 
                 if (picture != null) {
-                    //Decode file into a bitmap in real size for being able to make zoom on the image
+                    // Decode file into a bitmap in real size for being able to make zoom on 
+                    // the image
                     result = BitmapFactory.decodeStream(new FlushedInputStream
                             (new BufferedInputStream(new FileInputStream(picture))));
                 }
 
+                if (isCancelled()) return result;
+                
                 if (result == null) {
                     mErrorMessageId = R.string.preview_image_error_unknown_format;
                     Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath);
+                } else {
+                    // Rotate image, obeying exif tag.
+                    result = BitmapUtils.rotateImage(result, storagePath);
                 }
                 
             } catch (OutOfMemoryError e) {
                 Log_OC.e(TAG, "Out of memory occured for file " + storagePath, e);
 
-                // If out of memory error when loading image, try to load it scaled
+                if (isCancelled()) return result;
+                
+                // If out of memory error when loading or rotating image, try to load it scaled
                 result = loadScaledImage(storagePath);
 
                 if (result == null) {
                     mErrorMessageId = R.string.preview_image_error_unknown_format;
                     Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath);
+                } else {
+                    // Rotate scaled image, obeying exif tag
+                    result = BitmapUtils.rotateImage(result, storagePath);
                 }
                     
             } catch (NoSuchFieldError e) {
                 mErrorMessageId = R.string.common_error_unknown;
-                Log_OC.e(TAG, "Error from access to unexisting field despite protection; file " + storagePath, e);
+                Log_OC.e(TAG, "Error from access to unexisting field despite protection; file " 
+                                + storagePath, e);
                     
             } catch (Throwable t) {
                 mErrorMessageId = R.string.common_error_unknown;
                 Log_OC.e(TAG, "Unexpected error loading " + getFile().getStoragePath(), t);
                 
             }
+            
             return result;
         }
         
         @Override
+        protected void onCancelled(Bitmap result) {
+            if (result != null) {
+                result.recycle();
+            }
+        }
+
+        @Override
         protected void onPostExecute(Bitmap result) {
             hideProgressWheel();
             if (result != null) {
@@ -424,7 +459,7 @@ public class PreviewImageFragment extends FileFragment {
                 showErrorMessage();
             }
         }
-
+        
         @SuppressLint("InlinedApi")
         private void showLoadedImage(Bitmap result) {
             if (mImageViewRef != null) {
index f2a9a9b..3c15de7 100644 (file)
@@ -16,6 +16,8 @@
  */
 package com.owncloud.android.ui.preview;
 
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -31,7 +33,9 @@ import android.view.ViewGroup;
 
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.adapter.FileListListAdapter;
 import com.owncloud.android.ui.fragment.FileFragment;
+import com.owncloud.android.utils.FileStorageUtils;
 
 /**
  * Adapter class that provides Fragment instances  
@@ -73,13 +77,15 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter {
         mAccount = account;
         mStorageManager = storageManager;
         mImageFiles = mStorageManager.getFolderImages(parentFolder); 
+        
+        mImageFiles = FileStorageUtils.sortFolder(mImageFiles);
+        
         mObsoleteFragments = new HashSet<Object>();
         mObsoletePositions = new HashSet<Integer>();
         mDownloadErrors = new HashSet<Integer>();
         //mFragmentManager = fragmentManager;
         mCachedFragments = new HashMap<Integer, FileFragment>();
     }
-
     
     /**
      * Returns the image files handled by the adapter.
index 687b5a4..92824f4 100644 (file)
  */
 package com.owncloud.android.utils;
 
+import com.owncloud.android.lib.common.utils.Log_OC;
+
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
 import android.graphics.BitmapFactory.Options;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import java.io.File;
 
 /**
  * Utility class with methods for decoding Bitmaps.
@@ -96,4 +104,87 @@ public class BitmapUtils {
         return inSampleSize;
     }
     
+    /**
+     * Rotate bitmap according to EXIF orientation. 
+     * Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ 
+     * @param bitmap Bitmap to be rotated
+     * @param storagePath Path to source file of bitmap. Needed for EXIF information. 
+     * @return correctly EXIF-rotated bitmap
+     */
+    public static Bitmap rotateImage(Bitmap bitmap, String storagePath){
+        Bitmap resultBitmap = bitmap;
+
+        try
+        {
+            ExifInterface exifInterface = new ExifInterface(storagePath);
+            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
+
+            Matrix matrix = new Matrix();
+
+            // 1: nothing to do
+            
+            // 2
+            if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL)
+            {
+                matrix.postScale(-1.0f, 1.0f);
+            }
+            // 3
+            else if (orientation == ExifInterface.ORIENTATION_ROTATE_180)
+            {
+                matrix.postRotate(180);
+            }
+            // 4
+            else if (orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL)
+            {
+                matrix.postScale(1.0f, -1.0f);
+            }
+            // 5
+            else if (orientation == ExifInterface.ORIENTATION_TRANSPOSE)
+            {
+                matrix.postRotate(-90);
+                matrix.postScale(1.0f, -1.0f);
+            }
+            // 6
+            else if (orientation == ExifInterface.ORIENTATION_ROTATE_90)
+            {
+                matrix.postRotate(90);
+            }
+            // 7
+            else if (orientation == ExifInterface.ORIENTATION_TRANSVERSE)
+            {
+                matrix.postRotate(90);
+                matrix.postScale(1.0f, -1.0f);
+            }
+            // 8
+            else if (orientation == ExifInterface.ORIENTATION_ROTATE_270)
+            {
+                matrix.postRotate(270);
+            } 
+            
+            // Rotate the bitmap
+            resultBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+            if (resultBitmap != bitmap) {
+                bitmap.recycle();
+            }
+        }
+        catch (Exception exception)
+        {
+            Log_OC.e("BitmapUtil", "Could not rotate the image: " + storagePath);
+        }
+        return resultBitmap;
+    }
+
+    /**
+     * Checks if file passed is an image
+     * @param file
+     * @return true/false
+     */
+    public static boolean isImage(File file) {
+        Uri selectedUri = Uri.fromFile(file);
+        String fileExtension = MimeTypeMap.getFileExtensionFromUrl(selectedUri.toString().toLowerCase());
+        String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);
+
+        return (mimeType != null && mimeType.startsWith("image/"));
+    }
+    
 }
index a1afb89..e22b6de 100644 (file)
 package com.owncloud.android.utils;\r
 \r
 import java.net.IDN;\r
+import java.text.DateFormat;\r
 import java.util.Arrays;\r
 import java.util.Calendar;\r
 import java.util.Date;\r
 import java.util.HashMap;\r
 import java.util.HashSet;\r
 import java.util.Set;\r
+import java.util.Vector;\r
 \r
 import android.annotation.TargetApi;\r
 import android.content.Context;\r
 import android.os.Build;\r
 import android.text.format.DateUtils;\r
+import android.webkit.MimeTypeMap;\r
 \r
 import com.owncloud.android.MainApp;\r
 import com.owncloud.android.R;\r
@@ -49,6 +52,8 @@ public class DisplayUtils {
     \r
     private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };\r
 \r
+    private final static Double THUMBNAIL_THRESHOLD = 0.5;\r
+\r
     private static HashMap<String, String> mimeType2HUmanReadable;\r
     static {\r
         mimeType2HUmanReadable = new HashMap<String, String>();\r
@@ -73,21 +78,28 @@ public class DisplayUtils {
     private static final String TYPE_VIDEO = "video";\r
     \r
     private static final String SUBTYPE_PDF = "pdf";\r
-    private static final String[] SUBTYPES_DOCUMENT = { "msword",\r
-                                                        "vnd.openxmlformats-officedocument.wordprocessingml.document",\r
-                                                        "vnd.oasis.opendocument.text",\r
-                                                        "rtf"\r
-                                                        };\r
+    private static final String SUBTYPE_XML = "xml";\r
+    private static final String[] SUBTYPES_DOCUMENT = { \r
+        "msword",\r
+        "vnd.openxmlformats-officedocument.wordprocessingml.document",\r
+        "vnd.oasis.opendocument.text",\r
+        "rtf",\r
+        "javascript"\r
+    };\r
     private static Set<String> SUBTYPES_DOCUMENT_SET = new HashSet<String>(Arrays.asList(SUBTYPES_DOCUMENT));\r
-    private static final String[] SUBTYPES_SPREADSHEET = { "msexcel",\r
-                                                           "vnd.openxmlformats-officedocument.spreadsheetml.sheet",\r
-                                                           "vnd.oasis.opendocument.spreadsheet"\r
-                                                           };\r
+    private static final String[] SUBTYPES_SPREADSHEET = {\r
+        "msexcel",\r
+        "vnd.ms-excel",\r
+        "vnd.openxmlformats-officedocument.spreadsheetml.sheet",\r
+        "vnd.oasis.opendocument.spreadsheet"\r
+    };\r
     private static Set<String> SUBTYPES_SPREADSHEET_SET = new HashSet<String>(Arrays.asList(SUBTYPES_SPREADSHEET));\r
-    private static final String[] SUBTYPES_PRESENTATION = { "mspowerpoint",\r
-                                                            "vnd.openxmlformats-officedocument.presentationml.presentation",\r
-                                                            "vnd.oasis.opendocument.presentation"\r
-                                                            };\r
+    private static final String[] SUBTYPES_PRESENTATION = { \r
+        "mspowerpoint",\r
+        "vnd.ms-powerpoint",\r
+        "vnd.openxmlformats-officedocument.presentationml.presentation",\r
+        "vnd.oasis.opendocument.presentation"\r
+    };\r
     private static Set<String> SUBTYPES_PRESENTATION_SET = new HashSet<String>(Arrays.asList(SUBTYPES_PRESENTATION));\r
     private static final String[] SUBTYPES_COMPRESSED = {"x-tar", "x-gzip", "zip"};\r
     private static final Set<String> SUBTYPES_COMPRESSED_SET = new HashSet<String>(Arrays.asList(SUBTYPES_COMPRESSED));\r
@@ -95,6 +107,8 @@ public class DisplayUtils {
     private static final String EXTENSION_RAR = "rar";\r
     private static final String EXTENSION_RTF = "rtf";\r
     private static final String EXTENSION_3GP = "3gp";\r
+    private static final String EXTENSION_PY = "py";\r
+    private static final String EXTENSION_JS = "js";\r
     \r
     /**\r
      * Converts the file size in bytes to human readable output.\r
@@ -114,30 +128,6 @@ public class DisplayUtils {
     }\r
 \r
     /**\r
-     * Removes special HTML entities from a string\r
-     * \r
-     * @param s Input string\r
-     * @return A cleaned version of the string\r
-     */\r
-    public static String HtmlDecode(String s) {\r
-        /*\r
-         * TODO: Perhaps we should use something more proven like:\r
-         * http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#unescapeHtml%28java.lang.String%29\r
-         */\r
-\r
-        String ret = "";\r
-        for (int i = 0; i < s.length(); ++i) {\r
-            if (s.charAt(i) == '%') {\r
-                ret += (char) Integer.parseInt(s.substring(i + 1, i + 3), 16);\r
-                i += 2;\r
-            } else {\r
-                ret += s.charAt(i);\r
-            }\r
-        }\r
-        return ret;\r
-    }\r
-\r
-    /**\r
      * Converts MIME types like "image/jpg" to more end user friendly output\r
      * like "JPG image".\r
      * \r
@@ -155,18 +145,25 @@ public class DisplayUtils {
     \r
     \r
     /**\r
-     * Returns the resource identifier of an image resource to use as icon associated to a \r
-     * known MIME type.\r
+     * Returns the resource identifier of an image to use as icon associated to a known MIME type.\r
      * \r
-     * @param mimetype      MIME type string.\r
-     * @param filename      name, with extension\r
-     * @return              Resource identifier of an image resource.\r
+     * @param mimetype      MIME type string; if NULL, the method tries to guess it from the extension in filename\r
+     * @param filename      Name, with extension.\r
+     * @return              Identifier of an image resource.\r
      */\r
-    public static int getResourceId(String mimetype, String filename) {\r
+    public static int getFileTypeIconId(String mimetype, String filename) {\r
 \r
-        if (mimetype == null || "DIR".equals(mimetype)) {\r
-            return R.drawable.ic_menu_archive;\r
+        if (mimetype == null) {\r
+            String fileExtension = getExtension(filename);\r
+            mimetype = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension);\r
+            if (mimetype == null) {\r
+                mimetype = TYPE_APPLICATION + "/" + SUBTYPE_OCTET_STREAM;\r
+            }\r
+        } \r
             \r
+        if ("DIR".equals(mimetype)) {\r
+            return R.drawable.ic_menu_archive;\r
+\r
         } else {\r
             String [] parts = mimetype.split("/");\r
             String type = parts[0];\r
@@ -189,6 +186,9 @@ public class DisplayUtils {
                 if (SUBTYPE_PDF.equals(subtype)) {\r
                     return R.drawable.file_pdf;\r
                     \r
+                } else if (SUBTYPE_XML.equals(subtype)) {\r
+                    return R.drawable.file_doc;\r
+\r
                 } else if (SUBTYPES_DOCUMENT_SET.contains(subtype)) {\r
                     return R.drawable.file_doc;\r
 \r
@@ -200,7 +200,7 @@ public class DisplayUtils {
 \r
                 } else if (SUBTYPES_COMPRESSED_SET.contains(subtype)) {\r
                     return R.drawable.file_zip;\r
-                    \r
+\r
                 } else if (SUBTYPE_OCTET_STREAM.equals(subtype) ) {\r
                     if (getExtension(filename).equalsIgnoreCase(EXTENSION_RAR)) {\r
                         return R.drawable.file_zip;\r
@@ -210,7 +210,10 @@ public class DisplayUtils {
                         \r
                     } else if (getExtension(filename).equalsIgnoreCase(EXTENSION_3GP)) {\r
                         return R.drawable.file_movie;\r
-                        \r
+                     \r
+                    } else if ( getExtension(filename).equalsIgnoreCase(EXTENSION_PY) ||\r
+                                getExtension(filename).equalsIgnoreCase(EXTENSION_JS)) {\r
+                        return R.drawable.file_doc;\r
                     } \r
                 } \r
             }\r
@@ -222,19 +225,19 @@ public class DisplayUtils {
 \r
     \r
     private static String getExtension(String filename) {\r
-        String extension = filename.substring(filename.lastIndexOf(".") + 1);\r
-        \r
+        String extension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();\r
         return extension;\r
     }\r
     \r
     /**\r
      * Converts Unix time to human readable format\r
-     * @param miliseconds that have passed since 01/01/1970\r
+     * @param milliseconds that have passed since 01/01/1970\r
      * @return The human readable time for the users locale\r
      */\r
     public static String unixTimeToHumanReadable(long milliseconds) {\r
         Date date = new Date(milliseconds);\r
-        return date.toLocaleString();\r
+        DateFormat df = DateFormat.getDateTimeInstance();\r
+        return df.format(date);\r
     }\r
     \r
     \r
@@ -295,7 +298,11 @@ public class DisplayUtils {
         return fileExtension;\r
     }\r
 \r
-    public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution, long transitionResolution, int flags){\r
+    @SuppressWarnings("deprecation")\r
+    public static CharSequence getRelativeDateTimeString (\r
+            Context c, long time, long minResolution, long transitionResolution, int flags\r
+            ){\r
+        \r
         CharSequence dateString = "";\r
         \r
         // in Future\r
@@ -307,18 +314,21 @@ public class DisplayUtils {
             return c.getString(R.string.file_list_seconds_ago);\r
         } else {\r
             // Workaround 2.x bug (see https://github.com/owncloud/android/issues/716)\r
-            if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB && (System.currentTimeMillis() - time) > 24 * 60 * 60 * 1000){\r
+            if (    Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB && \r
+                    (System.currentTimeMillis() - time) > 24 * 60 * 60 * 1000   ) {\r
                 Date date = new Date(time);\r
                 date.setHours(0);\r
                 date.setMinutes(0);\r
                 date.setSeconds(0);\r
-                dateString = DateUtils.getRelativeDateTimeString(c, date.getTime(), minResolution, transitionResolution, flags);\r
+                dateString = DateUtils.getRelativeDateTimeString(\r
+                        c, date.getTime(), minResolution, transitionResolution, flags\r
+                );\r
             } else {\r
                 dateString = DateUtils.getRelativeDateTimeString(c, time, minResolution, transitionResolution, flags);\r
             }\r
         }\r
         \r
-        return dateString.toString().split(",")[0];
+        return dateString.toString().split(",")[0];\r
     }\r
 \r
     /**\r
@@ -333,4 +343,31 @@ public class DisplayUtils {
         }\r
         return path;\r
     }\r
+\r
+    /**\r
+     *\r
+     * @param mFiles\r
+     * @return true: imageView, false: listView\r
+     */\r
+    public static boolean decideViewLayout(Vector<OCFile> mFiles){\r
+        // decide image vs. file view\r
+        double countImages = 0;\r
+        double countFiles = 0;\r
+\r
+        for (OCFile file : mFiles){\r
+            if (!file.isFolder()){\r
+                countFiles++;\r
+\r
+                if (file.isImage()){\r
+                    countImages++;\r
+                }\r
+            }\r
+        }\r
+\r
+        if ((countImages / countFiles) >= THUMBNAIL_THRESHOLD){\r
+            return true;\r
+        } else {\r
+            return false;\r
+        }\r
+    }\r
 }\r
index e56e876..9f13804 100644 (file)
@@ -36,6 +36,7 @@ import com.owncloud.android.operations.MoveFileOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareLinkOperation;
 import com.owncloud.android.operations.UploadFileOperation;
 
@@ -206,6 +207,21 @@ public class ErrorMessageAdapter {
                 // Show a Message, operation finished without success
                 message = res.getString(R.string.move_file_error);
             }
+        } else if (operation instanceof SynchronizeFolderOperation) {
+
+            if (!result.isSuccess()) {
+                String folderPathName = new File(
+                        ((SynchronizeFolderOperation) operation).getFolderPath()).getName();
+                if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
+                    message = String.format(res.getString(R.string.sync_current_folder_was_removed),
+                            folderPathName);
+
+                } else {    // Generic error
+                    // Show a Message, operation finished without success
+                    message = String.format(res.getString(R.string.download_folder_failed_content),
+                            folderPathName);
+                }
+            }
         }
         
         return message;
index 892a1ca..b0c337d 100644 (file)
 package com.owncloud.android.utils;
 
 import java.io.File;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Vector;
+
+import third_parties.daveKoeller.AlphanumComparator;
 
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
@@ -31,6 +36,7 @@ import android.preference.PreferenceManager;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.StatFs;
+import android.webkit.MimeTypeMap;
 
 
 /**
@@ -39,6 +45,13 @@ import android.os.StatFs;
  * @author David A. Velasco
  */
 public class FileStorageUtils {
+    public static Integer mSortOrder;
+    public static Boolean mSortAscending;
+    public static final Integer SORT_NAME = 0;
+    public static final Integer SORT_DATE = 1;
+    public static final Integer SORT_SIZE = 2;
+  
+    
     //private static final String LOG_TAG = "FileStorageUtils";
 
     public static final String getSavePath(String accountName) {
@@ -123,7 +136,7 @@ public class FileStorageUtils {
     /**
      * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}.
      * 
-     * @param oCFile    OCFile 
+     * @param ocFile    OCFile
      * @return          New RemoteFile instance representing the resource described by ocFile.
      */
     public static RemoteFile fillRemoteFile(OCFile ocFile){
@@ -137,5 +150,156 @@ public class FileStorageUtils {
         file.setRemoteId(ocFile.getRemoteId());
         return file;
     }
+    
+    /**
+     * Sorts all filenames, regarding last user decision 
+     */
+    public static Vector<OCFile> sortFolder(Vector<OCFile> files){
+        switch (mSortOrder){
+        case 0:
+            files = FileStorageUtils.sortByName(files);
+            break;
+        case 1:
+            files = FileStorageUtils.sortByDate(files);
+            break;
+        case 2: 
+           // mFiles = FileStorageUtils.sortBySize(mSortAscending);
+            break;
+        }
+       
+        return files;
+    }
+    
+    /**
+     * Sorts list by Date
+     * @param files
+     */
+    public static Vector<OCFile> sortByDate(Vector<OCFile> files){
+        final Integer val;
+        if (mSortAscending){
+            val = 1;
+        } else {
+            val = -1;
+        }
+        
+        Collections.sort(files, new Comparator<OCFile>() {
+            public int compare(OCFile o1, OCFile o2) {
+                if (o1.isFolder() && o2.isFolder()) {
+                    Long obj1 = o1.getModificationTimestamp();
+                    return val * obj1.compareTo(o2.getModificationTimestamp());
+                }
+                else if (o1.isFolder()) {
+                    return -1;
+                } else if (o2.isFolder()) {
+                    return 1;
+                } else if (o1.getModificationTimestamp() == 0 || o2.getModificationTimestamp() == 0){
+                    return 0;
+                } else {
+                    Long obj1 = o1.getModificationTimestamp();
+                    return val * obj1.compareTo(o2.getModificationTimestamp());
+                }
+            }
+        });
+        
+        return files;
+    }
+
+//    /**
+//     * Sorts list by Size
+//     * @param sortAscending true: ascending, false: descending
+//     */
+//    public static Vector<OCFile> sortBySize(Vector<OCFile> files){
+//        final Integer val;
+//        if (mSortAscending){
+//            val = 1;
+//        } else {
+//            val = -1;
+//        }
+//        
+//        Collections.sort(files, new Comparator<OCFile>() {
+//            public int compare(OCFile o1, OCFile o2) {
+//                if (o1.isFolder() && o2.isFolder()) {
+//                    Long obj1 = getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o1)));
+//                    return val * obj1.compareTo(getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o2))));
+//                }
+//                else if (o1.isFolder()) {
+//                    return -1;
+//                } else if (o2.isFolder()) {
+//                    return 1;
+//                } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){
+//                    return 0;
+//                } else {
+//                    Long obj1 = o1.getFileLength();
+//                    return val * obj1.compareTo(o2.getFileLength());
+//                }
+//            }
+//        });
+//        
+//        return files;
+//    }
+
+    /**
+     * Sorts list by Name
+     * @param files     files to sort
+     */
+    public static Vector<OCFile> sortByName(Vector<OCFile> files){
+        final Integer val;
+        if (mSortAscending){
+            val = 1;
+        } else {
+            val = -1;
+        }
+
+        Collections.sort(files, new Comparator<OCFile>() {
+            public int compare(OCFile o1, OCFile o2) {
+                if (o1.isFolder() && o2.isFolder()) {
+                    return val * o1.getRemotePath().toLowerCase().compareTo(o2.getRemotePath().toLowerCase());
+                } else if (o1.isFolder()) {
+                    return -1;
+                } else if (o2.isFolder()) {
+                    return 1;
+                }
+                return val * new AlphanumComparator().compare(o1, o2);
+            }
+        });
+        
+        return files;
+    }
+    
+    /**
+     * Local Folder size
+     * @param dir File
+     * @return Size in bytes
+     */
+    public static long getFolderSize(File dir) {
+        if (dir.exists()) {
+            long result = 0;
+            File[] fileList = dir.listFiles();
+            for(int i = 0; i < fileList.length; i++) {
+                if(fileList[i].isDirectory()) {
+                    result += getFolderSize(fileList[i]);
+                } else {
+                    result += fileList[i].length();
+                }
+            }
+            return result;
+        }
+        return 0;
+    }
+
+    /**
+     * Mimetype String of a file
+     * @param path
+     * @return
+     */
+    public static String getMimeTypeFromName(String path) {
+        String extension = "";
+        int pos = path.lastIndexOf('.');
+        if (pos >= 0) {
+            extension = path.substring(pos + 1);
+        }
+        String result = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.toLowerCase());
+        return (result != null) ? result : "";
+    }
   
 }
diff --git a/src/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java b/src/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java
new file mode 100644 (file)
index 0000000..013181b
--- /dev/null
@@ -0,0 +1,787 @@
+
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package third_parties.in.srain.cube;
+
+
+        import android.annotation.TargetApi;
+        import android.content.Context;
+        import android.database.DataSetObservable;
+        import android.database.DataSetObserver;
+        import android.os.Build;
+        import android.util.AttributeSet;
+        import android.util.Log;
+        import android.view.View;
+        import android.view.ViewGroup;
+        import android.widget.*;
+
+        import java.lang.reflect.Field;
+        import java.util.ArrayList;
+
+/**
+ * A {@link GridView} that supports adding header rows in a
+ * very similar way to {@link android.widget.ListView}.
+ * See {@link GridViewWithHeaderAndFooter#addHeaderView(View, Object, boolean)}
+ * See {@link GridViewWithHeaderAndFooter#addFooterView(View, Object, boolean)}
+ */
+public class GridViewWithHeaderAndFooter extends GridView {
+
+    public static boolean DEBUG = false;
+
+    /**
+     * A class that represents a fixed view in a list, for example a header at the top
+     * or a footer at the bottom.
+     */
+    private static class FixedViewInfo {
+        /**
+         * The view to add to the grid
+         */
+        public View view;
+        public ViewGroup viewContainer;
+        /**
+         * The data backing the view. This is returned from {@link ListAdapter#getItem(int)}.
+         */
+        public Object data;
+        /**
+         * <code>true</code> if the fixed view should be selectable in the grid
+         */
+        public boolean isSelectable;
+    }
+
+    private int mNumColumns = AUTO_FIT;
+    private View mViewForMeasureRowHeight = null;
+    private int mRowHeight = -1;
+    private static final String LOG_TAG = "grid-view-with-header-and-footer";
+
+    private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>();
+    private ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<FixedViewInfo>();
+
+    private void initHeaderGridView() {
+    }
+
+    public GridViewWithHeaderAndFooter(Context context) {
+        super(context);
+        initHeaderGridView();
+    }
+
+    public GridViewWithHeaderAndFooter(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initHeaderGridView();
+    }
+
+    public GridViewWithHeaderAndFooter(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initHeaderGridView();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        ListAdapter adapter = getAdapter();
+        if (adapter != null && adapter instanceof HeaderViewGridAdapter) {
+            ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumnsCompatible());
+            ((HeaderViewGridAdapter) adapter).setRowHeight(getRowHeight());
+        }
+    }
+
+    @Override
+    public void setClipChildren(boolean clipChildren) {
+        // Ignore, since the header rows depend on not being clipped
+    }
+
+    /**
+     * Do not call this method unless you know how it works.
+     *
+     * @param clipChildren
+     */
+    public void setClipChildrenSupper(boolean clipChildren) {
+        super.setClipChildren(false);
+    }
+
+    /**
+     * Add a fixed view to appear at the top of the grid. If addHeaderView is
+     * called more than once, the views will appear in the order they were
+     * added. Views added using this call can take focus if they want.
+     * <p/>
+     * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
+     * the supplied cursor with one that will also account for header views.
+     *
+     * @param v The view to add.
+     */
+    public void addHeaderView(View v) {
+        addHeaderView(v, null, true);
+    }
+
+    /**
+     * Add a fixed view to appear at the top of the grid. If addHeaderView is
+     * called more than once, the views will appear in the order they were
+     * added. Views added using this call can take focus if they want.
+     * <p/>
+     * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
+     * the supplied cursor with one that will also account for header views.
+     *
+     * @param v            The view to add.
+     * @param data         Data to associate with this view
+     * @param isSelectable whether the item is selectable
+     */
+    public void addHeaderView(View v, Object data, boolean isSelectable) {
+        ListAdapter adapter = getAdapter();
+        if (adapter != null && !(adapter instanceof HeaderViewGridAdapter)) {
+            throw new IllegalStateException(
+                    "Cannot add header view to grid -- setAdapter has already been called.");
+        }
+
+        ViewGroup.LayoutParams lyp = v.getLayoutParams();
+
+        FixedViewInfo info = new FixedViewInfo();
+        FrameLayout fl = new FullWidthFixedViewLayout(getContext());
+
+        if (lyp != null) {
+            v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height));
+            fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height));
+        }
+        fl.addView(v);
+        info.view = v;
+        info.viewContainer = fl;
+        info.data = data;
+        info.isSelectable = isSelectable;
+        mHeaderViewInfos.add(info);
+        // in the case of re-adding a header view, or adding one later on,
+        // we need to notify the observer
+        if (adapter != null) {
+            ((HeaderViewGridAdapter) adapter).notifyDataSetChanged();
+        }
+    }
+
+    public void addFooterView(View v) {
+        addFooterView(v, null, true);
+    }
+
+    public void addFooterView(View v, Object data, boolean isSelectable) {
+        ListAdapter mAdapter = getAdapter();
+        if (mAdapter != null && !(mAdapter instanceof HeaderViewGridAdapter)) {
+            throw new IllegalStateException(
+                    "Cannot add header view to grid -- setAdapter has already been called.");
+        }
+
+        ViewGroup.LayoutParams lyp = v.getLayoutParams();
+
+        FixedViewInfo info = new FixedViewInfo();
+        FrameLayout fl = new FullWidthFixedViewLayout(getContext());
+
+        if (lyp != null) {
+            v.setLayoutParams(new FrameLayout.LayoutParams(lyp.width, lyp.height));
+            fl.setLayoutParams(new AbsListView.LayoutParams(lyp.width, lyp.height));
+        }
+        fl.addView(v);
+        info.view = v;
+        info.viewContainer = fl;
+        info.data = data;
+        info.isSelectable = isSelectable;
+        mFooterViewInfos.add(info);
+
+        if (mAdapter != null) {
+            ((HeaderViewGridAdapter) mAdapter).notifyDataSetChanged();
+        }
+    }
+
+    public int getHeaderViewCount() {
+        return mHeaderViewInfos.size();
+    }
+
+    public int getFooterViewCount() {
+        return mFooterViewInfos.size();
+    }
+
+    /**
+     * Removes a previously-added header view.
+     *
+     * @param v The view to remove
+     * @return true if the view was removed, false if the view was not a header
+     * view
+     */
+    public boolean removeHeaderView(View v) {
+        if (mHeaderViewInfos.size() > 0) {
+            boolean result = false;
+            ListAdapter adapter = getAdapter();
+            if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) {
+                result = true;
+            }
+            removeFixedViewInfo(v, mHeaderViewInfos);
+            return result;
+        }
+        return false;
+    }
+
+    /**
+     * Removes a previously-added footer view.
+     *
+     * @param v The view to remove
+     * @return true if the view was removed, false if the view was not a header
+     * view
+     */
+    public boolean removeFooterView(View v) {
+        if (mFooterViewInfos.size() > 0) {
+            boolean result = false;
+            ListAdapter adapter = getAdapter();
+            if (adapter != null && ((HeaderViewGridAdapter) adapter).removeFooter(v)) {
+                result = true;
+            }
+            removeFixedViewInfo(v, mFooterViewInfos);
+            return result;
+        }
+        return false;
+    }
+
+    private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
+        int len = where.size();
+        for (int i = 0; i < len; ++i) {
+            FixedViewInfo info = where.get(i);
+            if (info.view == v) {
+                where.remove(i);
+                break;
+            }
+        }
+    }
+
+    @TargetApi(11)
+    private int getNumColumnsCompatible() {
+        if (Build.VERSION.SDK_INT >= 11) {
+            return super.getNumColumns();
+        } else {
+            try {
+                Field numColumns = getClass().getSuperclass().getDeclaredField("mNumColumns");
+                numColumns.setAccessible(true);
+                return numColumns.getInt(this);
+            } catch (Exception e) {
+                if (mNumColumns != -1) {
+                    return mNumColumns;
+                }
+                throw new RuntimeException("Can not determine the mNumColumns for this API platform, please call setNumColumns to set it.");
+            }
+        }
+    }
+
+    @TargetApi(16)
+    private int getColumnWidthCompatible() {
+        if (Build.VERSION.SDK_INT >= 16) {
+            return super.getColumnWidth();
+        } else {
+            try {
+                Field numColumns = getClass().getSuperclass().getDeclaredField("mColumnWidth");
+                numColumns.setAccessible(true);
+                return numColumns.getInt(this);
+            } catch (NoSuchFieldException e) {
+                throw new RuntimeException(e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mViewForMeasureRowHeight = null;
+    }
+
+    public void invalidateRowHeight() {
+        mRowHeight = -1;
+    }
+
+    public int getRowHeight() {
+        if (mRowHeight > 0) {
+            return mRowHeight;
+        }
+        ListAdapter adapter = getAdapter();
+        int numColumns = getNumColumnsCompatible();
+
+        // adapter has not been set or has no views in it;
+        if (adapter == null || adapter.getCount() <= numColumns * (mHeaderViewInfos.size() + mFooterViewInfos.size())) {
+            return -1;
+        }
+        int mColumnWidth = getColumnWidthCompatible();
+        View view = getAdapter().getView(numColumns * mHeaderViewInfos.size(), mViewForMeasureRowHeight, this);
+        AbsListView.LayoutParams p = (AbsListView.LayoutParams) view.getLayoutParams();
+        if (p == null) {
+            p = new AbsListView.LayoutParams(-1, -2, 0);
+            view.setLayoutParams(p);
+        }
+        int childHeightSpec = getChildMeasureSpec(
+                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
+        int childWidthSpec = getChildMeasureSpec(
+                MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
+        view.measure(childWidthSpec, childHeightSpec);
+        mViewForMeasureRowHeight = view;
+        mRowHeight = view.getMeasuredHeight();
+        return mRowHeight;
+    }
+
+    @TargetApi(11)
+    public void tryToScrollToBottomSmoothly() {
+        int lastPos = getAdapter().getCount() - 1;
+        if (Build.VERSION.SDK_INT >= 11) {
+            smoothScrollToPositionFromTop(lastPos, 0);
+        } else {
+            setSelection(lastPos);
+        }
+    }
+
+    @TargetApi(11)
+    public void tryToScrollToBottomSmoothly(int duration) {
+        int lastPos = getAdapter().getCount() - 1;
+        if (Build.VERSION.SDK_INT >= 11) {
+            smoothScrollToPositionFromTop(lastPos, 0, duration);
+        } else {
+            setSelection(lastPos);
+        }
+    }
+
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
+            HeaderViewGridAdapter headerViewGridAdapter = new HeaderViewGridAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
+            int numColumns = getNumColumnsCompatible();
+            if (numColumns > 1) {
+                headerViewGridAdapter.setNumColumns(numColumns);
+            }
+            headerViewGridAdapter.setRowHeight(getRowHeight());
+            super.setAdapter(headerViewGridAdapter);
+        } else {
+            super.setAdapter(adapter);
+        }
+    }
+
+    /**
+     * full width
+     */
+    private class FullWidthFixedViewLayout extends FrameLayout {
+
+        public FullWidthFixedViewLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            int realLeft = GridViewWithHeaderAndFooter.this.getPaddingLeft() + getPaddingLeft();
+            // Try to make where it should be, from left, full width
+            if (realLeft != left) {
+                offsetLeftAndRight(realLeft - left);
+            }
+            super.onLayout(changed, left, top, right, bottom);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            int targetWidth = GridViewWithHeaderAndFooter.this.getMeasuredWidth()
+                    - GridViewWithHeaderAndFooter.this.getPaddingLeft()
+                    - GridViewWithHeaderAndFooter.this.getPaddingRight();
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth,
+                    MeasureSpec.getMode(widthMeasureSpec));
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    @Override
+    public void setNumColumns(int numColumns) {
+        super.setNumColumns(numColumns);
+        mNumColumns = numColumns;
+        ListAdapter adapter = getAdapter();
+        if (adapter != null && adapter instanceof HeaderViewGridAdapter) {
+            ((HeaderViewGridAdapter) adapter).setNumColumns(numColumns);
+        }
+    }
+
+    /**
+     * ListAdapter used when a HeaderGridView has header views. This ListAdapter
+     * wraps another one and also keeps track of the header views and their
+     * associated data objects.
+     * <p>This is intended as a base class; you will probably not need to
+     * use this class directly in your own code.
+     */
+    private static class HeaderViewGridAdapter implements WrapperListAdapter, Filterable {
+        // This is used to notify the container of updates relating to number of columns
+        // or headers changing, which changes the number of placeholders needed
+        private final DataSetObservable mDataSetObservable = new DataSetObservable();
+        private final ListAdapter mAdapter;
+        static final ArrayList<FixedViewInfo> EMPTY_INFO_LIST =
+                new ArrayList<FixedViewInfo>();
+
+        // This ArrayList is assumed to NOT be null.
+        ArrayList<FixedViewInfo> mHeaderViewInfos;
+        ArrayList<FixedViewInfo> mFooterViewInfos;
+        private int mNumColumns = 1;
+        private int mRowHeight = -1;
+        boolean mAreAllFixedViewsSelectable;
+        private final boolean mIsFilterable;
+        private boolean mCachePlaceHoldView = true;
+        // From Recycle Bin or calling getView, this a question...
+        private boolean mCacheFirstHeaderView = false;
+
+        public HeaderViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ArrayList<FixedViewInfo> footViewInfos, ListAdapter adapter) {
+            mAdapter = adapter;
+            mIsFilterable = adapter instanceof Filterable;
+            if (headerViewInfos == null) {
+                mHeaderViewInfos = EMPTY_INFO_LIST;
+            } else {
+                mHeaderViewInfos = headerViewInfos;
+            }
+
+            if (footViewInfos == null) {
+                mFooterViewInfos = EMPTY_INFO_LIST;
+            } else {
+                mFooterViewInfos = footViewInfos;
+            }
+            mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos)
+                    && areAllListInfosSelectable(mFooterViewInfos);
+        }
+
+        public void setNumColumns(int numColumns) {
+            if (numColumns < 1) {
+                return;
+            }
+            if (mNumColumns != numColumns) {
+                mNumColumns = numColumns;
+                notifyDataSetChanged();
+            }
+        }
+
+        public void setRowHeight(int height) {
+            mRowHeight = height;
+        }
+
+        public int getHeadersCount() {
+            return mHeaderViewInfos.size();
+        }
+
+        public int getFootersCount() {
+            return mFooterViewInfos.size();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0 && getFootersCount() == 0;
+        }
+
+        private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) {
+            if (infos != null) {
+                for (FixedViewInfo info : infos) {
+                    if (!info.isSelectable) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        public boolean removeHeader(View v) {
+            for (int i = 0; i < mHeaderViewInfos.size(); i++) {
+                FixedViewInfo info = mHeaderViewInfos.get(i);
+                if (info.view == v) {
+                    mHeaderViewInfos.remove(i);
+                    mAreAllFixedViewsSelectable =
+                            areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos);
+                    mDataSetObservable.notifyChanged();
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public boolean removeFooter(View v) {
+            for (int i = 0; i < mFooterViewInfos.size(); i++) {
+                FixedViewInfo info = mFooterViewInfos.get(i);
+                if (info.view == v) {
+                    mFooterViewInfos.remove(i);
+                    mAreAllFixedViewsSelectable =
+                            areAllListInfosSelectable(mHeaderViewInfos) && areAllListInfosSelectable(mFooterViewInfos);
+                    mDataSetObservable.notifyChanged();
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public int getCount() {
+            if (mAdapter != null) {
+                return (getFootersCount() + getHeadersCount()) * mNumColumns + getAdapterAndPlaceHolderCount();
+            } else {
+                return (getFootersCount() + getHeadersCount()) * mNumColumns;
+            }
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            if (mAdapter != null) {
+                return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
+            } else {
+                return true;
+            }
+        }
+
+        private int getAdapterAndPlaceHolderCount() {
+            final int adapterCount = (int) (Math.ceil(1f * mAdapter.getCount() / mNumColumns) * mNumColumns);
+            return adapterCount;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            // Header (negative positions will throw an IndexOutOfBoundsException)
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (position < numHeadersAndPlaceholders) {
+                return position % mNumColumns == 0
+                        && mHeaderViewInfos.get(position / mNumColumns).isSelectable;
+            }
+
+            // Adapter
+            final int adjPosition = position - numHeadersAndPlaceholders;
+            int adapterCount = 0;
+            if (mAdapter != null) {
+                adapterCount = getAdapterAndPlaceHolderCount();
+                if (adjPosition < adapterCount) {
+                    return adjPosition < mAdapter.getCount() && mAdapter.isEnabled(adjPosition);
+                }
+            }
+
+            // Footer (off-limits positions will throw an IndexOutOfBoundsException)
+            final int footerPosition = adjPosition - adapterCount;
+            return footerPosition % mNumColumns == 0
+                    && mFooterViewInfos.get(footerPosition / mNumColumns).isSelectable;
+        }
+
+        @Override
+        public Object getItem(int position) {
+            // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (position < numHeadersAndPlaceholders) {
+                if (position % mNumColumns == 0) {
+                    return mHeaderViewInfos.get(position / mNumColumns).data;
+                }
+                return null;
+            }
+
+            // Adapter
+            final int adjPosition = position - numHeadersAndPlaceholders;
+            int adapterCount = 0;
+            if (mAdapter != null) {
+                adapterCount = getAdapterAndPlaceHolderCount();
+                if (adjPosition < adapterCount) {
+                    if (adjPosition < mAdapter.getCount()) {
+                        return mAdapter.getItem(adjPosition);
+                    } else {
+                        return null;
+                    }
+                }
+            }
+
+            // Footer (off-limits positions will throw an IndexOutOfBoundsException)
+            final int footerPosition = adjPosition - adapterCount;
+            if (footerPosition % mNumColumns == 0) {
+                return mFooterViewInfos.get(footerPosition).data;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public long getItemId(int position) {
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (mAdapter != null && position >= numHeadersAndPlaceholders) {
+                int adjPosition = position - numHeadersAndPlaceholders;
+                int adapterCount = mAdapter.getCount();
+                if (adjPosition < adapterCount) {
+                    return mAdapter.getItemId(adjPosition);
+                }
+            }
+            return -1;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            if (mAdapter != null) {
+                return mAdapter.hasStableIds();
+            }
+            return false;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (DEBUG) {
+                Log.d(LOG_TAG, String.format("getView: %s, reused: %s", position, convertView == null));
+            }
+            // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (position < numHeadersAndPlaceholders) {
+                View headerViewContainer = mHeaderViewInfos
+                        .get(position / mNumColumns).viewContainer;
+                if (position % mNumColumns == 0) {
+                    return headerViewContainer;
+                } else {
+                    if (convertView == null) {
+                        convertView = new View(parent.getContext());
+                    }
+                    // We need to do this because GridView uses the height of the last item
+                    // in a row to determine the height for the entire row.
+                    convertView.setVisibility(View.INVISIBLE);
+                    convertView.setMinimumHeight(headerViewContainer.getHeight());
+                    return convertView;
+                }
+            }
+            // Adapter
+            final int adjPosition = position - numHeadersAndPlaceholders;
+            int adapterCount = 0;
+            if (mAdapter != null) {
+                adapterCount = getAdapterAndPlaceHolderCount();
+                if (adjPosition < adapterCount) {
+                    if (adjPosition < mAdapter.getCount()) {
+                        View view = mAdapter.getView(adjPosition, convertView, parent);
+                        return view;
+                    } else {
+                        if (convertView == null) {
+                            convertView = new View(parent.getContext());
+                        }
+                        convertView.setVisibility(View.INVISIBLE);
+                        convertView.setMinimumHeight(mRowHeight);
+                        return convertView;
+                    }
+                }
+            }
+            // Footer
+            final int footerPosition = adjPosition - adapterCount;
+            if (footerPosition < getCount()) {
+                View footViewContainer = mFooterViewInfos
+                        .get(footerPosition / mNumColumns).viewContainer;
+                if (position % mNumColumns == 0) {
+                    return footViewContainer;
+                } else {
+                    if (convertView == null) {
+                        convertView = new View(parent.getContext());
+                    }
+                    // We need to do this because GridView uses the height of the last item
+                    // in a row to determine the height for the entire row.
+                    convertView.setVisibility(View.INVISIBLE);
+                    convertView.setMinimumHeight(footViewContainer.getHeight());
+                    return convertView;
+                }
+            }
+            throw new ArrayIndexOutOfBoundsException(position);
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+
+            final int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            final int adapterViewTypeStart = mAdapter == null ? 0 : mAdapter.getViewTypeCount() - 1;
+            int type = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
+            if (mCachePlaceHoldView) {
+                // Header
+                if (position < numHeadersAndPlaceholders) {
+                    if (position == 0) {
+                        if (mCacheFirstHeaderView) {
+                            type = adapterViewTypeStart + mHeaderViewInfos.size() + mFooterViewInfos.size() + 1 + 1;
+                        }
+                    }
+                    if (position % mNumColumns != 0) {
+                        type = adapterViewTypeStart + (position / mNumColumns + 1);
+                    }
+                }
+            }
+
+            // Adapter
+            final int adjPosition = position - numHeadersAndPlaceholders;
+            int adapterCount = 0;
+            if (mAdapter != null) {
+                adapterCount = getAdapterAndPlaceHolderCount();
+                if (adjPosition >= 0 && adjPosition < adapterCount) {
+                    if (adjPosition < mAdapter.getCount()) {
+                        type = mAdapter.getItemViewType(adjPosition);
+                    } else {
+                        if (mCachePlaceHoldView) {
+                            type = adapterViewTypeStart + mHeaderViewInfos.size() + 1;
+                        }
+                    }
+                }
+            }
+
+            if (mCachePlaceHoldView) {
+                // Footer
+                final int footerPosition = adjPosition - adapterCount;
+                if (footerPosition >= 0 && footerPosition < getCount() && (footerPosition % mNumColumns) != 0) {
+                    type = adapterViewTypeStart + mHeaderViewInfos.size() + 1 + (footerPosition / mNumColumns + 1);
+                }
+            }
+            if (DEBUG) {
+                Log.d(LOG_TAG, String.format("getItemViewType: pos: %s, result: %s", position, type, mCachePlaceHoldView, mCacheFirstHeaderView));
+            }
+            return type;
+        }
+
+        /**
+         * content view, content view holder, header[0], header and footer placeholder(s)
+         *
+         * @return
+         */
+        @Override
+        public int getViewTypeCount() {
+            int count = mAdapter == null ? 1 : mAdapter.getViewTypeCount();
+            if (mCachePlaceHoldView) {
+                int offset = mHeaderViewInfos.size() + 1 + mFooterViewInfos.size();
+                if (mCacheFirstHeaderView) {
+                    offset += 1;
+                }
+                count += offset;
+            }
+            if (DEBUG) {
+                Log.d(LOG_TAG, String.format("getViewTypeCount: %s", count));
+            }
+            return count;
+        }
+
+        @Override
+        public void registerDataSetObserver(DataSetObserver observer) {
+            mDataSetObservable.registerObserver(observer);
+            if (mAdapter != null) {
+                mAdapter.registerDataSetObserver(observer);
+            }
+        }
+
+        @Override
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+            mDataSetObservable.unregisterObserver(observer);
+            if (mAdapter != null) {
+                mAdapter.unregisterDataSetObserver(observer);
+            }
+        }
+
+        @Override
+        public Filter getFilter() {
+            if (mIsFilterable) {
+                return ((Filterable) mAdapter).getFilter();
+            }
+            return null;
+        }
+
+        @Override
+        public ListAdapter getWrappedAdapter() {
+            return mAdapter;
+        }
+
+        public void notifyDataSetChanged() {
+            mDataSetObservable.notifyChanged();
+        }
+    }
+}
diff --git a/src/third_parties/in/srain/cube/lapache-2.0.txt b/src/third_parties/in/srain/cube/lapache-2.0.txt
new file mode 100644 (file)
index 0000000..72f817f
--- /dev/null
@@ -0,0 +1,201 @@
+  Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
\ No newline at end of file
index 187bdf4..d0b8535 100644 (file)
Binary files a/third_party/android-support-library/android-support-v4.jar and b/third_party/android-support-library/android-support-v4.jar differ
diff --git a/user_manual/Makefile b/user_manual/Makefile
new file mode 100644 (file)
index 0000000..74c47b1
--- /dev/null
@@ -0,0 +1,173 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html       to make standalone HTML files"
+       @echo "  dirhtml    to make HTML files named index.html in directories"
+       @echo "  singlehtml to make a single large HTML file"
+       @echo "  pickle     to make pickle files"
+       @echo "  json       to make JSON files"
+       @echo "  htmlhelp   to make HTML files and a HTML help project"
+       @echo "  qthelp     to make HTML files and a qthelp project"
+       @echo "  devhelp    to make HTML files and a Devhelp project"
+       @echo "  epub       to make an epub"
+       @echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+       @echo "  pdf        to make PDF files"
+       @echo "  text       to make text files"
+       @echo "  man        to make manual pages"
+       @echo "  texinfo    to make Texinfo files"
+       @echo "  info       to make Texinfo files and run them through makeinfo"
+       @echo "  gettext    to make PO message catalogs"
+       @echo "  changes    to make an overview of all changed/added/deprecated items"
+       @echo "  linkcheck  to check all external links for integrity"
+       @echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+       -rm -rf $(BUILDDIR)/*
+
+html:   html-org
+
+html-all: html-release html-org html-com
+
+html-release:
+       $(SPHINXBUILD) -b html -D html_theme='owncloud_release' $(ALLSPHINXOPTS) $(BUILDDIR)/html/release
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/release."
+
+html-org:
+       $(SPHINXBUILD) -b html -D html_theme='owncloud_org' $(ALLSPHINXOPTS) $(BUILDDIR)/html/org
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/org."
+
+html-com:
+       $(SPHINXBUILD) -b html -D html_theme='owncloud_com' $(ALLSPHINXOPTS) $(BUILDDIR)/html/com
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/com."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+       $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+       @echo
+       @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+       @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/OwncloudDocumentation.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/OwncloudDocumentation.qhc"
+
+devhelp:
+       $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+       @echo
+       @echo "Build finished."
+       @echo "To view the help file:"
+       @echo "# mkdir -p $$HOME/.local/share/devhelp/OwncloudDocumentation"
+       @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/OwncloudDocumentation"
+       @echo "# devhelp"
+
+epub:
+       $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+       @echo
+       @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+       @echo "Run \`make' in that directory to run these through (pdf)latex" \
+             "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo "Running LaTeX files through pdflatex..."
+       $(MAKE) -C $(BUILDDIR)/latex all-pdf
+       @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+pdf:
+       $(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) $(BUILDDIR)/pdf
+       @echo
+       @echo "build finished. the text files are in $(BUILDDIR)/pdf."
+
+text:
+       $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+       @echo
+       @echo "build finished. the text files are in $(BUILDDIR)/text."
+
+man:
+       $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+       @echo
+       @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+       $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+       @echo
+       @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+       @echo "Run \`make' in that directory to run these through makeinfo" \
+             "(use \`make info' here to do that automatically)."
+
+info:
+       $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+       @echo "Running Texinfo files through makeinfo..."
+       make -C $(BUILDDIR)/texinfo info
+       @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+       $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+       @echo
+       @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+       @echo
+       @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in $(BUILDDIR)/doctest/output.txt."
index f81d4ba..20e5bda 100644 (file)
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys, os
+import sys, os, inspect
 
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #sys.path.insert(0, os.path.abspath('.'))
 
+#path to this script
+scriptpath = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
+
 # -- General configuration -----------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
@@ -28,7 +31,7 @@ import sys, os
 extensions = ['sphinx.ext.todo']
 
 # Add any paths that contain templates here, relative to this directory.
-# templates_path = ['@CMAKE_CURRENT_SOURCE_DIR@/ocdoc/_shared_assets/templates']
+templates_path = [scriptpath+'/ocdoc/_shared_assets/templates']
 
 # The suffix of source filenames.
 source_suffix = '.rst'
@@ -48,9 +51,9 @@ copyright = u'2013-2015, The ownCloud developers'
 # built documents.
 #
 # The short X.Y version.
-version = '@VERSION_MAJOR@.@VERSION_MINOR@'
+version = '1.6.2'
 # The full version, including alpha/beta/rc tags.
-release = '@VERSION@'
+release = version
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -95,7 +98,7 @@ pygments_style = 'sphinx'
 #html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-# html_theme_path = ['@CMAKE_CURRENT_SOURCE_DIR@/ocdoc/_shared_assets/themes']
+html_theme_path = [scriptpath+'/ocdoc/_shared_assets/themes']
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
@@ -120,7 +123,7 @@ html_short_title = "Android App Manual"
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['@CMAKE_CURRENT_SOURCE_DIR@/ocdoc/_shared_assets/static']
+html_static_path = [scriptpath+'/ocdoc/_shared_assets/static']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
@@ -213,8 +216,7 @@ latex_documents = [
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('owncloud.1', 'owncloud', u'Android synchronisation and file management 
-utility.',
+    ('owncloud.1', 'owncloud', u'Android synchronisation and file management utility.',
      [u'The ownCloud developers'], 1),
     ('owncloudcmd.1', 'owncloudcmd', u'ownCloud Android app.',
      [u'The ownCloud developers'], 1),
diff --git a/user_manual/make.bat b/user_manual/make.bat
new file mode 100644 (file)
index 0000000..3fbb57f
--- /dev/null
@@ -0,0 +1,199 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+       set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+set I18NSPHINXOPTS=%SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+       set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+       set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+       :help
+       echo.Please use `make ^<target^>` where ^<target^> is one of
+       echo.  html       to make standalone HTML files
+       echo.  dirhtml    to make HTML files named index.html in directories
+       echo.  singlehtml to make a single large HTML file
+       echo.  pdf        to make a PDF file with rst2pdf
+       echo.  pickle     to make pickle files
+       echo.  json       to make JSON files
+       echo.  htmlhelp   to make HTML files and a HTML help project
+       echo.  qthelp     to make HTML files and a qthelp project
+       echo.  devhelp    to make HTML files and a Devhelp project
+       echo.  epub       to make an epub
+       echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+       echo.  text       to make text files
+       echo.  man        to make manual pages
+       echo.  texinfo    to make Texinfo files
+       echo.  gettext    to make PO message catalogs
+       echo.  changes    to make an overview over all changed/added/deprecated items
+       echo.  linkcheck  to check all external links for integrity
+       echo.  doctest    to run all doctests embedded in the documentation if enabled
+       goto end
+)
+
+if "%1" == "clean" (
+       for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+       del /q /s %BUILDDIR%\*
+       goto end
+)
+
+if "%1" == "html" (
+       %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+       goto end
+)
+
+if "%1" == "dirhtml" (
+       %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+       goto end
+)
+
+if "%1" == "singlehtml" (
+       %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+       goto end
+)
+
+if "%1" == "pdf" (
+       %SPHINXBUILD% -b pdf %ALLSPHINXOPTS% %BUILDDIR%/pdf
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The PDF file is in %BUILDDIR%/pdf.
+       goto end
+)
+
+if "%1" == "pickle" (
+       %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can process the pickle files.
+       goto end
+)
+
+if "%1" == "json" (
+       %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can process the JSON files.
+       goto end
+)
+
+if "%1" == "htmlhelp" (
+       %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+       goto end
+)
+
+if "%1" == "qthelp" (
+       %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+       echo.^> qcollectiongenerator %BUILDDIR%\qthelp\OwncloudDocumentation.qhcp
+       echo.To view the help file:
+       echo.^> assistant -collectionFile %BUILDDIR%\qthelp\OwncloudDocumentation.ghc
+       goto end
+)
+
+if "%1" == "devhelp" (
+       %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished.
+       goto end
+)
+
+if "%1" == "epub" (
+       %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The epub file is in %BUILDDIR%/epub.
+       goto end
+)
+
+if "%1" == "latex" (
+       %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+       goto end
+)
+
+if "%1" == "text" (
+       %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The text files are in %BUILDDIR%/text.
+       goto end
+)
+
+if "%1" == "man" (
+       %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The manual pages are in %BUILDDIR%/man.
+       goto end
+)
+
+if "%1" == "texinfo" (
+       %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+       goto end
+)
+
+if "%1" == "gettext" (
+       %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+       goto end
+)
+
+if "%1" == "changes" (
+       %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.The overview file is in %BUILDDIR%/changes.
+       goto end
+)
+
+if "%1" == "linkcheck" (
+       %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+       goto end
+)
+
+if "%1" == "doctest" (
+       %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+       if errorlevel 1 exit /b 1
+       echo.
+       echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+       goto end
+)
+
+:end
diff --git a/user_manual/ocdoc b/user_manual/ocdoc
new file mode 160000 (submodule)
index 0000000..343496c
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit 343496c792616459e8204b6614fd42a1b16a6d68