From: masensio Date: Tue, 8 Apr 2014 10:50:50 +0000 (+0200) Subject: Merge branch 'develop' into videoInstandUploads X-Git-Tag: oc-android-1.7.0_signed~332^2~10 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/3351db65273a8d8ff1d012e00025e728cc428419?hp=536cc166b60733aa54466850ee167163fd2f3cbd Merge branch 'develop' into videoInstandUploads --- diff --git a/.classpath b/.classpath index 3f9691c5..7bc01d9a 100644 --- a/.classpath +++ b/.classpath @@ -1,8 +1,9 @@ - - + + + diff --git a/.gitignore b/.gitignore index 0105cb34..9b9bd8e3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,9 +11,24 @@ # generated files bin/ gen/ +target/ -# Local configuration file (sdk path, etc) +# Local configuration files (sdk path, etc) local.properties +oc_workaround/local.properties +oc_framework/local.properties +oc_framework-test-project/local.properties +tests/local.properties # Mac .DS_Store files .DS_Store + +# Proguard README +proguard-project.txt +oc_workaround/proguard-project.txt +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 diff --git a/.gitmodules b/.gitmodules index b41da321..f0ca8721 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [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 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 0ff9ee10..81acefee 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,7 @@ eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7ec196c7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +language: java +jdk: oraclejdk7 +before_install: + # Install base Android SDK + - sudo apt-get update -qq + - sudo apt-get install -qq libstdc++6:i386 lib32z1 + - export COMPONENTS=build-tools-18.1.0,android-14,android-17,android-19,sysimg-19,extra-android-support + - curl -L https://raw.github.com/embarkmobile/android-sdk-installer/master/android-sdk-installer | bash /dev/stdin --install=$COMPONENTS + - source ~/.android-sdk-installer/env + - rm pom.xml + - ./setup_env.sh + +script: + - ant clean + - ant debug + diff --git a/.tx/config b/.tx/config index f30f5ef0..7be0c299 100644 --- a/.tx/config +++ b/.tx/config @@ -5,4 +5,4 @@ host = https://www.transifex.com file_filter = res/values-/strings.xml source_file = res/values/strings.xml source_lang = en -lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, he_IL: he-rIL, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ku_IQ: ku-rIQ, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, my_MM: my, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sr@latin: sr-rSP, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, ta_LK: ta-rLK, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_CN.GB2312:zh-rBG, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA +lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, en@pirate: en-rpirate, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, he_IL: he-rIL, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ku_IQ: ku-rIQ, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, my_MM: my, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sr@latin: sr-rSP, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, ta_LK: ta-rLK, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_CN.GB2312:zh-rBG, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d88f6562..48d5b0c1 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3,7 +3,7 @@ ownCloud Android client application Copyright (C) 2012 Bartek Przybylski - Copyright (C) 2012-2013 ownCloud Inc. + 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, @@ -18,8 +18,8 @@ along with this program. If not, see . --> + android:versionCode="105005" + android:versionName="1.5.5" xmlns:android="http://schemas.android.com/apk/res/android"> @@ -38,21 +38,22 @@ + android:targetSdkVersion="19" /> + android:label="@string/app_name" + > - @@ -61,7 +62,7 @@ - + @@ -81,7 +82,8 @@ - + + - + - + @@ -110,7 +116,8 @@ + android:exported="true" + > @@ -122,7 +129,7 @@ + android:theme="@style/Theme.ownCloud.noActionBar" + android:launchMode="singleTask"> + + + + + + + - - - @@ -156,6 +168,9 @@ + + + @@ -173,6 +188,12 @@ + + + - + diff --git a/README.md b/README.md index 3ba9f73a..45cb4be9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -This is the android client for [owncloud][0]. +This is the Android client for [ownCloud][0]. + +The app performs file synchronization with an ownCloud server. Other ownCloud features may be added in the future, but they are not a priority right now. Make sure you read [SETUP.md][1] when you start working on this project. diff --git a/SETUP.md b/SETUP.md index d527d4fe..a9650644 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,23 +1,87 @@ - If you want to start development of ownCloud android client you have two way to do so: +If you want to start help developing ownCloud please follow the [contribution guidelines][0] and observe these instructions: + +### 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" +* Move to the project folder with "cd android" +* Checkout remote develop branch: "git checkout -b develop remotes/origin/develop" +* 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" . - 1. Building with console: +At this point you can continue using different tools to build the project. Section 2, 3 and 4 describe some of the existing alternatives. - - Use setup_env.sh or setup_env.bat - - NOTE: You must have git, ant/bin, android/tools in your enviroment path +### 2. Building with Ant: - 2. Building with eclipse: +NOTE: You must have the Android SDK 'tools/', and 'platforms-tools/' folders in your environment path variable. + +* Run "ant clean" . +* Run "ant debug" to generate a debuggable version of the ownCloud app. + +### 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. - - Run ant clean debug - - Open Eclipse and import *actionbarsherlock/library* project to your workspace - - NOTE: You must have 'tools' and 'platforms-tools' in your path in order to run setup_env.sh +Download/install Android plugin for Maven, install owncloud-android-library, then build ownCloud with mvn: - After those actions you should be good to go. +* cd .. +* git clone https://github.com/mosabua/maven-android-sdk-deployer.git +* cd maven-android-sdk-deployer +* mvn -pl com.simpligility.android.sdk-deployer:android-19 -am install +* cd ../android/owncloud-android-library +* mvn install +* cd .. - HAVE FUN! +Now you can create ownCloud APK using "mvn package" + +### 4. 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. +* 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. +* 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. +* 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/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. +* 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. + +### 5. 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" +* 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. +### 6. 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. +* git fetch upstream +* 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 +[0]: https://github.com/owncloud/android/blob/master/CONTRIBUTING.md +[1]: http://owncloud.org/about/contributor-agreement/ diff --git a/THIRD_PARTY.txt b/THIRD_PARTY.txt index 1cfdddc8..d85eb899 100644 --- a/THIRD_PARTY.txt +++ b/THIRD_PARTY.txt @@ -44,7 +44,7 @@ The third party software included and used by this project is: Original license document included at libs/LICENSE.txt See http://jackrabbit.apache.org/ - * Transifex client.JavaMail API, version 1.4.3 + * Transifex client. Copyright (C) Transifex. Licensed under GNU General Public License. Placed at third_party/transifex-client. diff --git a/doc/CodeStyleFormatter.xml b/doc/CodeStyleFormatter.xml index af0006c6..ce1e4347 100644 --- a/doc/CodeStyleFormatter.xml +++ b/doc/CodeStyleFormatter.xml @@ -1,16 +1,17 @@ - + + - + - + @@ -23,11 +24,11 @@ - + - + @@ -50,7 +51,7 @@ - + @@ -72,8 +73,8 @@ - - + + @@ -81,14 +82,14 @@ - + - + - + @@ -101,7 +102,7 @@ - + @@ -119,11 +120,11 @@ - + - + @@ -149,21 +150,21 @@ - + - + - + - + @@ -181,15 +182,15 @@ - + - + - + @@ -204,7 +205,7 @@ - + @@ -216,16 +217,16 @@ - + - - + + - + - + @@ -233,10 +234,10 @@ - + - + @@ -246,16 +247,16 @@ - + - - + + - - + + @@ -277,7 +278,7 @@ - + diff --git a/libs/jackrabbit-webdav-2.2.5-jar-with-dependencies.jar b/libs/jackrabbit-webdav-2.2.5-jar-with-dependencies.jar deleted file mode 100755 index 2dd374f9..00000000 Binary files a/libs/jackrabbit-webdav-2.2.5-jar-with-dependencies.jar and /dev/null differ diff --git a/lint.xml b/lint.xml new file mode 100644 index 00000000..ee0eead5 --- /dev/null +++ b/lint.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/oc_jb_workaround/.classpath b/oc_jb_workaround/.classpath index 3f9691c5..0461652e 100644 --- a/oc_jb_workaround/.classpath +++ b/oc_jb_workaround/.classpath @@ -1,7 +1,8 @@ - - + + + diff --git a/oc_jb_workaround/AndroidManifest.xml b/oc_jb_workaround/AndroidManifest.xml index acaf6ebc..640b6aba 100644 --- a/oc_jb_workaround/AndroidManifest.xml +++ b/oc_jb_workaround/AndroidManifest.xml @@ -1,18 +1,18 @@ + android:versionCode="0100014" + android:versionName="1.0.14" > + android:targetSdkVersion="19" /> + android:icon="@drawable/workaround_app_icon" + android:label="@string/workaround_app_name" + > diff --git a/oc_jb_workaround/build.xml b/oc_jb_workaround/build.xml new file mode 100644 index 00000000..9e71b187 --- /dev/null +++ b/oc_jb_workaround/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/oc_jb_workaround/project.properties b/oc_jb_workaround/project.properties index 9b84a6b4..a3ee5ab6 100644 --- a/oc_jb_workaround/project.properties +++ b/oc_jb_workaround/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-16 +target=android-17 diff --git a/oc_jb_workaround/res/drawable-hdpi/main_app_icon.png b/oc_jb_workaround/res/drawable-hdpi/main_app_icon.png new file mode 100644 index 00000000..6fe153bb Binary files /dev/null and b/oc_jb_workaround/res/drawable-hdpi/main_app_icon.png differ diff --git a/oc_jb_workaround/res/drawable-hdpi/oc_icon.png b/oc_jb_workaround/res/drawable-hdpi/oc_icon.png deleted file mode 100644 index e388c7be..00000000 Binary files a/oc_jb_workaround/res/drawable-hdpi/oc_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/drawable-hdpi/oc_workaround_icon.png b/oc_jb_workaround/res/drawable-hdpi/oc_workaround_icon.png deleted file mode 100644 index 7daeea4a..00000000 Binary files a/oc_jb_workaround/res/drawable-hdpi/oc_workaround_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/drawable-hdpi/workaround_app_icon.png b/oc_jb_workaround/res/drawable-hdpi/workaround_app_icon.png new file mode 100644 index 00000000..f28286a7 Binary files /dev/null and b/oc_jb_workaround/res/drawable-hdpi/workaround_app_icon.png differ diff --git a/oc_jb_workaround/res/drawable-ldpi/main_app_icon.png b/oc_jb_workaround/res/drawable-ldpi/main_app_icon.png new file mode 100644 index 00000000..1bc470be Binary files /dev/null and b/oc_jb_workaround/res/drawable-ldpi/main_app_icon.png differ diff --git a/oc_jb_workaround/res/drawable-ldpi/oc_icon.png b/oc_jb_workaround/res/drawable-ldpi/oc_icon.png deleted file mode 100644 index 11cf0ab1..00000000 Binary files a/oc_jb_workaround/res/drawable-ldpi/oc_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/drawable-ldpi/oc_workaround_icon.png b/oc_jb_workaround/res/drawable-ldpi/oc_workaround_icon.png deleted file mode 100644 index 8cd92ff9..00000000 Binary files a/oc_jb_workaround/res/drawable-ldpi/oc_workaround_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/drawable-ldpi/workaround_app_icon.png b/oc_jb_workaround/res/drawable-ldpi/workaround_app_icon.png new file mode 100644 index 00000000..cd8eb078 Binary files /dev/null and b/oc_jb_workaround/res/drawable-ldpi/workaround_app_icon.png differ diff --git a/oc_jb_workaround/res/drawable-mdpi/main_app_icon.png b/oc_jb_workaround/res/drawable-mdpi/main_app_icon.png new file mode 100644 index 00000000..9008b9d3 Binary files /dev/null and b/oc_jb_workaround/res/drawable-mdpi/main_app_icon.png differ diff --git a/oc_jb_workaround/res/drawable-mdpi/oc_icon.png b/oc_jb_workaround/res/drawable-mdpi/oc_icon.png deleted file mode 100644 index 6997c7e4..00000000 Binary files a/oc_jb_workaround/res/drawable-mdpi/oc_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/drawable-mdpi/oc_workaround_icon.png b/oc_jb_workaround/res/drawable-mdpi/oc_workaround_icon.png deleted file mode 100644 index 5bc795a3..00000000 Binary files a/oc_jb_workaround/res/drawable-mdpi/oc_workaround_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/drawable-mdpi/workaround_app_icon.png b/oc_jb_workaround/res/drawable-mdpi/workaround_app_icon.png new file mode 100644 index 00000000..f7ca7b00 Binary files /dev/null and b/oc_jb_workaround/res/drawable-mdpi/workaround_app_icon.png differ diff --git a/oc_jb_workaround/res/drawable-xhdpi/oc_icon.png b/oc_jb_workaround/res/drawable-xhdpi/oc_icon.png deleted file mode 100644 index e388c7be..00000000 Binary files a/oc_jb_workaround/res/drawable-xhdpi/oc_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/drawable-xhdpi/oc_workaround_icon.png b/oc_jb_workaround/res/drawable-xhdpi/oc_workaround_icon.png deleted file mode 100644 index 7daeea4a..00000000 Binary files a/oc_jb_workaround/res/drawable-xhdpi/oc_workaround_icon.png and /dev/null differ diff --git a/oc_jb_workaround/res/values-v11/styles.xml b/oc_jb_workaround/res/values-v11/styles.xml deleted file mode 100644 index 541752f6..00000000 --- a/oc_jb_workaround/res/values-v11/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/oc_jb_workaround/res/values-v14/styles.xml b/oc_jb_workaround/res/values-v14/styles.xml deleted file mode 100644 index f20e0150..00000000 --- a/oc_jb_workaround/res/values-v14/styles.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/oc_jb_workaround/res/values/setup.xml b/oc_jb_workaround/res/values/setup.xml new file mode 100644 index 00000000..0bf5e1dd --- /dev/null +++ b/oc_jb_workaround/res/values/setup.xml @@ -0,0 +1,25 @@ + + + + + ownCloud Jelly Bean Workaround for lost credentials + ownCloud + owncloud + + \ No newline at end of file diff --git a/oc_jb_workaround/res/values/strings.xml b/oc_jb_workaround/res/values/strings.xml deleted file mode 100644 index 1bfdbe02..00000000 --- a/oc_jb_workaround/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - ownCloud Jelly Bean Workaround for lost credentials - ownCloud - - \ No newline at end of file diff --git a/oc_jb_workaround/res/values/styles.xml b/oc_jb_workaround/res/values/styles.xml deleted file mode 100644 index 4a10ca49..00000000 --- a/oc_jb_workaround/res/values/styles.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/oc_jb_workaround/res/xml/authenticator.xml b/oc_jb_workaround/res/xml/authenticator.xml index 0bb57600..f1ec8462 100644 --- a/oc_jb_workaround/res/xml/authenticator.xml +++ b/oc_jb_workaround/res/xml/authenticator.xml @@ -1,7 +1,7 @@ + android:accountType="@string/account_type" + android:icon="@drawable/main_app_icon" + android:label="@string/main_app_name" + android:smallIcon="@drawable/main_app_icon"> \ No newline at end of file diff --git a/oc_jb_workaround/src/com/owncloud/android/workaround/accounts/AccountAuthenticatorService.java b/oc_jb_workaround/src/com/owncloud/android/workaround/accounts/AccountAuthenticatorService.java index 9b2922e3..5a7c57e6 100644 --- a/oc_jb_workaround/src/com/owncloud/android/workaround/accounts/AccountAuthenticatorService.java +++ b/oc_jb_workaround/src/com/owncloud/android/workaround/accounts/AccountAuthenticatorService.java @@ -33,7 +33,7 @@ import android.os.IBinder; public class AccountAuthenticatorService extends Service { private AccountAuthenticator mAuthenticator; - static final public String ACCOUNT_TYPE = "owncloud"; + //static final public String ACCOUNT_TYPE = "owncloud"; @Override public void onCreate() { diff --git a/owncloud-android-library b/owncloud-android-library new file mode 160000 index 00000000..d066e9da --- /dev/null +++ b/owncloud-android-library @@ -0,0 +1 @@ +Subproject commit d066e9da51a04837504f9be3e266bdc82caabc64 diff --git a/pom.xml b/pom.xml index 27263337..15bd0b68 100644 --- a/pom.xml +++ b/pom.xml @@ -5,14 +5,17 @@ 4.0.0 com.owncloud.android owncloud - 1.3.21-SNAPSHOT + ${owncloud.version} apk Owncloud Android + 1.5.1-SNAPSHOT 1.6 - 4.1.1.4 - r7 + + 4.4.2_r2 + + 19 4.2.0 @@ -23,23 +26,17 @@ scm:git:git@github.com:owncloud/android.git https://github.com/owncloud/android - + - com.google.android + android android ${google.android-version} provided - com.google.android - support-v4 - ${google.android.support-version} - - - com.actionbarsherlock actionbarsherlock ${actionbarsherlock-version} @@ -53,11 +50,12 @@ apklib + - org.apache.jackrabbit - jackrabbit-webdav - 2.5.2 - + com.owncloud.android + owncloud-android-library + ${owncloud.version} + @@ -81,12 +79,11 @@ com.jayway.maven.plugins.android.generation2 android-maven-plugin - 3.5.0 + 3.8.0 - ${env.ANDROID_HOME} - 17 + ${google.android-api} true diff --git a/project.properties b/project.properties index 716944a4..28edd993 100644 --- a/project.properties +++ b/project.properties @@ -8,5 +8,6 @@ # project structure. # Project target. -target=android-17 +target=android-19 android.library.reference.1=actionbarsherlock/library +android.library.reference.2=owncloud-android-library diff --git a/res/drawable-hdpi-v11/notification_icon.png b/res/drawable-hdpi-v11/notification_icon.png new file mode 100644 index 00000000..9e634a8c Binary files /dev/null and b/res/drawable-hdpi-v11/notification_icon.png differ diff --git a/res/drawable-hdpi-v9/ic_action_download.png b/res/drawable-hdpi-v9/ic_action_download.png new file mode 100644 index 00000000..25476cd7 Binary files /dev/null and b/res/drawable-hdpi-v9/ic_action_download.png differ diff --git a/res/drawable-hdpi-v9/ic_action_search.png b/res/drawable-hdpi-v9/ic_action_search.png deleted file mode 100644 index 3549f84d..00000000 Binary files a/res/drawable-hdpi-v9/ic_action_search.png and /dev/null differ diff --git a/res/drawable-hdpi-v9/ic_action_settings.png b/res/drawable-hdpi-v9/ic_action_settings.png new file mode 100644 index 00000000..86b54f4c Binary files /dev/null and b/res/drawable-hdpi-v9/ic_action_settings.png differ diff --git a/res/drawable-hdpi/calendar.png b/res/drawable-hdpi/calendar.png deleted file mode 100644 index 7652fa58..00000000 Binary files a/res/drawable-hdpi/calendar.png and /dev/null differ diff --git a/res/drawable-hdpi/common_error.png b/res/drawable-hdpi/common_error.png new file mode 100644 index 00000000..213976c7 Binary files /dev/null and b/res/drawable-hdpi/common_error.png differ diff --git a/res/drawable-hdpi/contacts.png b/res/drawable-hdpi/contacts.png deleted file mode 100644 index 6436cfa9..00000000 Binary files a/res/drawable-hdpi/contacts.png and /dev/null differ diff --git a/res/drawable-hdpi/copy_link.png b/res/drawable-hdpi/copy_link.png new file mode 100644 index 00000000..35df55f7 Binary files /dev/null and b/res/drawable-hdpi/copy_link.png differ diff --git a/res/drawable-hdpi/download.png b/res/drawable-hdpi/download.png deleted file mode 100644 index 19593cf3..00000000 Binary files a/res/drawable-hdpi/download.png and /dev/null differ diff --git a/res/drawable-hdpi/home.png b/res/drawable-hdpi/home.png deleted file mode 100644 index 06362e55..00000000 Binary files a/res/drawable-hdpi/home.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_action_create_dir_old.png b/res/drawable-hdpi/ic_action_create_dir_old.png deleted file mode 100644 index 49a226e2..00000000 Binary files a/res/drawable-hdpi/ic_action_create_dir_old.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_action_download.png b/res/drawable-hdpi/ic_action_download.png new file mode 100644 index 00000000..05e75b4b Binary files /dev/null and b/res/drawable-hdpi/ic_action_download.png differ diff --git a/res/drawable-hdpi/ic_action_search.png b/res/drawable-hdpi/ic_action_search.png deleted file mode 100644 index f89c4e96..00000000 Binary files a/res/drawable-hdpi/ic_action_search.png and /dev/null differ diff --git a/res/drawable-hdpi/ic_action_settings.png b/res/drawable-hdpi/ic_action_settings.png new file mode 100644 index 00000000..999d0f0d Binary files /dev/null and b/res/drawable-hdpi/ic_action_settings.png differ diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png index e388c7be..6fe153bb 100644 Binary files a/res/drawable-hdpi/icon.png and b/res/drawable-hdpi/icon.png differ diff --git a/res/drawable-hdpi/logo.png b/res/drawable-hdpi/logo.png new file mode 100644 index 00000000..188d693a Binary files /dev/null and b/res/drawable-hdpi/logo.png differ diff --git a/res/drawable-hdpi/music.png b/res/drawable-hdpi/music.png deleted file mode 100644 index 1c7a1f0a..00000000 Binary files a/res/drawable-hdpi/music.png and /dev/null differ diff --git a/res/drawable-hdpi/notification_icon.png b/res/drawable-hdpi/notification_icon.png new file mode 100644 index 00000000..fd2bc5b4 Binary files /dev/null and b/res/drawable-hdpi/notification_icon.png differ diff --git a/res/drawable-hdpi/owncloud_logo.png b/res/drawable-hdpi/owncloud_logo.png deleted file mode 100644 index ecea3b0e..00000000 Binary files a/res/drawable-hdpi/owncloud_logo.png and /dev/null differ diff --git a/res/drawable-hdpi/settings.png b/res/drawable-hdpi/settings.png deleted file mode 100644 index 94944016..00000000 Binary files a/res/drawable-hdpi/settings.png and /dev/null differ diff --git a/res/drawable-hdpi/sharedlink.png b/res/drawable-hdpi/sharedlink.png new file mode 100644 index 00000000..222172a7 Binary files /dev/null and b/res/drawable-hdpi/sharedlink.png differ diff --git a/res/drawable-hdpi/winter_holidays_icon.png b/res/drawable-hdpi/winter_holidays_icon.png new file mode 100644 index 00000000..c1764b6d Binary files /dev/null and b/res/drawable-hdpi/winter_holidays_icon.png differ diff --git a/res/drawable-ldpi-v9/ic_action_download.png b/res/drawable-ldpi-v9/ic_action_download.png new file mode 100644 index 00000000..41fe95a4 Binary files /dev/null and b/res/drawable-ldpi-v9/ic_action_download.png differ diff --git a/res/drawable-ldpi-v9/ic_action_search.png b/res/drawable-ldpi-v9/ic_action_search.png deleted file mode 100644 index 587d9e0b..00000000 Binary files a/res/drawable-ldpi-v9/ic_action_search.png and /dev/null differ diff --git a/res/drawable-ldpi-v9/ic_action_settings.png b/res/drawable-ldpi-v9/ic_action_settings.png new file mode 100644 index 00000000..e2c2d517 Binary files /dev/null and b/res/drawable-ldpi-v9/ic_action_settings.png differ diff --git a/res/drawable-ldpi/calendar.png b/res/drawable-ldpi/calendar.png deleted file mode 100644 index a667b5b7..00000000 Binary files a/res/drawable-ldpi/calendar.png and /dev/null differ diff --git a/res/drawable-ldpi/common_error.png b/res/drawable-ldpi/common_error.png new file mode 100644 index 00000000..f1a804a6 Binary files /dev/null and b/res/drawable-ldpi/common_error.png differ diff --git a/res/drawable-ldpi/contacts.png b/res/drawable-ldpi/contacts.png deleted file mode 100644 index fe4bced5..00000000 Binary files a/res/drawable-ldpi/contacts.png and /dev/null differ diff --git a/res/drawable-ldpi/copy_link.png b/res/drawable-ldpi/copy_link.png new file mode 100644 index 00000000..b3caf522 Binary files /dev/null and b/res/drawable-ldpi/copy_link.png differ diff --git a/res/drawable-ldpi/download.png b/res/drawable-ldpi/download.png deleted file mode 100644 index f8b790f4..00000000 Binary files a/res/drawable-ldpi/download.png and /dev/null differ diff --git a/res/drawable-ldpi/home.png b/res/drawable-ldpi/home.png deleted file mode 100644 index f2227be4..00000000 Binary files a/res/drawable-ldpi/home.png and /dev/null differ diff --git a/res/drawable-ldpi/ic_action_create_dir_old.png b/res/drawable-ldpi/ic_action_create_dir_old.png deleted file mode 100644 index ce8cdeff..00000000 Binary files a/res/drawable-ldpi/ic_action_create_dir_old.png and /dev/null differ diff --git a/res/drawable-ldpi/ic_action_download.png b/res/drawable-ldpi/ic_action_download.png new file mode 100644 index 00000000..7293d6da Binary files /dev/null and b/res/drawable-ldpi/ic_action_download.png differ diff --git a/res/drawable-ldpi/ic_action_search.png b/res/drawable-ldpi/ic_action_search.png deleted file mode 100644 index 45d2398a..00000000 Binary files a/res/drawable-ldpi/ic_action_search.png and /dev/null differ diff --git a/res/drawable-ldpi/ic_action_settings.png b/res/drawable-ldpi/ic_action_settings.png new file mode 100644 index 00000000..c290e590 Binary files /dev/null and b/res/drawable-ldpi/ic_action_settings.png differ diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png index 11cf0ab1..1bc470be 100644 Binary files a/res/drawable-ldpi/icon.png and b/res/drawable-ldpi/icon.png differ diff --git a/res/drawable-ldpi/logo.png b/res/drawable-ldpi/logo.png new file mode 100644 index 00000000..23998ff5 Binary files /dev/null and b/res/drawable-ldpi/logo.png differ diff --git a/res/drawable-ldpi/music.png b/res/drawable-ldpi/music.png deleted file mode 100644 index aa4aac16..00000000 Binary files a/res/drawable-ldpi/music.png and /dev/null differ diff --git a/res/drawable-ldpi/owncloud_logo.png b/res/drawable-ldpi/owncloud_logo.png deleted file mode 100644 index 62dff255..00000000 Binary files a/res/drawable-ldpi/owncloud_logo.png and /dev/null differ diff --git a/res/drawable-ldpi/settings.png b/res/drawable-ldpi/settings.png deleted file mode 100644 index 59f4a8b5..00000000 Binary files a/res/drawable-ldpi/settings.png and /dev/null differ diff --git a/res/drawable-ldpi/winter_holidays_icon.png b/res/drawable-ldpi/winter_holidays_icon.png new file mode 100644 index 00000000..9261d327 Binary files /dev/null and b/res/drawable-ldpi/winter_holidays_icon.png differ diff --git a/res/drawable-mdpi-v11/notification_icon.png b/res/drawable-mdpi-v11/notification_icon.png new file mode 100644 index 00000000..e33e6535 Binary files /dev/null and b/res/drawable-mdpi-v11/notification_icon.png differ diff --git a/res/drawable-mdpi-v9/ic_action_download.png b/res/drawable-mdpi-v9/ic_action_download.png new file mode 100644 index 00000000..2684c83f Binary files /dev/null and b/res/drawable-mdpi-v9/ic_action_download.png differ diff --git a/res/drawable-mdpi-v9/ic_action_search.png b/res/drawable-mdpi-v9/ic_action_search.png deleted file mode 100644 index f12e005e..00000000 Binary files a/res/drawable-mdpi-v9/ic_action_search.png and /dev/null differ diff --git a/res/drawable-mdpi-v9/ic_action_settings.png b/res/drawable-mdpi-v9/ic_action_settings.png new file mode 100644 index 00000000..47ef3f44 Binary files /dev/null and b/res/drawable-mdpi-v9/ic_action_settings.png differ diff --git a/res/drawable-mdpi/calendar.png b/res/drawable-mdpi/calendar.png deleted file mode 100644 index efac984d..00000000 Binary files a/res/drawable-mdpi/calendar.png and /dev/null differ diff --git a/res/drawable-mdpi/common_error.png b/res/drawable-mdpi/common_error.png new file mode 100644 index 00000000..ee60165e Binary files /dev/null and b/res/drawable-mdpi/common_error.png differ diff --git a/res/drawable-mdpi/contacts.png b/res/drawable-mdpi/contacts.png deleted file mode 100644 index 1704265b..00000000 Binary files a/res/drawable-mdpi/contacts.png and /dev/null differ diff --git a/res/drawable-mdpi/copy_link.png b/res/drawable-mdpi/copy_link.png new file mode 100644 index 00000000..4e2af289 Binary files /dev/null and b/res/drawable-mdpi/copy_link.png differ diff --git a/res/drawable-mdpi/download.png b/res/drawable-mdpi/download.png deleted file mode 100644 index e055568c..00000000 Binary files a/res/drawable-mdpi/download.png and /dev/null differ diff --git a/res/drawable-mdpi/home.png b/res/drawable-mdpi/home.png deleted file mode 100644 index b6d6ccd6..00000000 Binary files a/res/drawable-mdpi/home.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_action_create_dir_old.png b/res/drawable-mdpi/ic_action_create_dir_old.png deleted file mode 100644 index 1d1a03e6..00000000 Binary files a/res/drawable-mdpi/ic_action_create_dir_old.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_action_download.png b/res/drawable-mdpi/ic_action_download.png new file mode 100644 index 00000000..afb2cb2e Binary files /dev/null and b/res/drawable-mdpi/ic_action_download.png differ diff --git a/res/drawable-mdpi/ic_action_search.png b/res/drawable-mdpi/ic_action_search.png deleted file mode 100644 index f6719d22..00000000 Binary files a/res/drawable-mdpi/ic_action_search.png and /dev/null differ diff --git a/res/drawable-mdpi/ic_action_settings.png b/res/drawable-mdpi/ic_action_settings.png new file mode 100644 index 00000000..0eb78f7c Binary files /dev/null and b/res/drawable-mdpi/ic_action_settings.png differ diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png index 6997c7e4..9008b9d3 100644 Binary files a/res/drawable-mdpi/icon.png and b/res/drawable-mdpi/icon.png differ diff --git a/res/drawable-mdpi/logo.png b/res/drawable-mdpi/logo.png new file mode 100644 index 00000000..23998ff5 Binary files /dev/null and b/res/drawable-mdpi/logo.png differ diff --git a/res/drawable-mdpi/music.png b/res/drawable-mdpi/music.png deleted file mode 100644 index c76c778b..00000000 Binary files a/res/drawable-mdpi/music.png and /dev/null differ diff --git a/res/drawable-mdpi/notification_icon.png b/res/drawable-mdpi/notification_icon.png new file mode 100644 index 00000000..2e8beda6 Binary files /dev/null and b/res/drawable-mdpi/notification_icon.png differ diff --git a/res/drawable-mdpi/owncloud_logo.png b/res/drawable-mdpi/owncloud_logo.png deleted file mode 100644 index ecea3b0e..00000000 Binary files a/res/drawable-mdpi/owncloud_logo.png and /dev/null differ diff --git a/res/drawable-mdpi/settings.png b/res/drawable-mdpi/settings.png deleted file mode 100644 index 80b89e17..00000000 Binary files a/res/drawable-mdpi/settings.png and /dev/null differ diff --git a/res/drawable-mdpi/sharedlink.png b/res/drawable-mdpi/sharedlink.png new file mode 100644 index 00000000..8300eacf Binary files /dev/null and b/res/drawable-mdpi/sharedlink.png differ diff --git a/res/drawable-mdpi/winter_holidays_icon.png b/res/drawable-mdpi/winter_holidays_icon.png new file mode 100644 index 00000000..b0226dce Binary files /dev/null and b/res/drawable-mdpi/winter_holidays_icon.png differ diff --git a/res/drawable-xhdpi-v11/notification_icon.png b/res/drawable-xhdpi-v11/notification_icon.png new file mode 100644 index 00000000..1ad49714 Binary files /dev/null and b/res/drawable-xhdpi-v11/notification_icon.png differ diff --git a/res/drawable-xhdpi/copy_link.png b/res/drawable-xhdpi/copy_link.png new file mode 100644 index 00000000..c69eb05a Binary files /dev/null and b/res/drawable-xhdpi/copy_link.png differ diff --git a/res/drawable-xhdpi/icon.png b/res/drawable-xhdpi/icon.png new file mode 100644 index 00000000..041efc6b Binary files /dev/null and b/res/drawable-xhdpi/icon.png differ diff --git a/res/drawable-xhdpi/notification_icon.png b/res/drawable-xhdpi/notification_icon.png new file mode 100644 index 00000000..b3855d61 Binary files /dev/null and b/res/drawable-xhdpi/notification_icon.png differ diff --git a/res/drawable-xhdpi/sharedlink.png b/res/drawable-xhdpi/sharedlink.png new file mode 100644 index 00000000..3879663c Binary files /dev/null and b/res/drawable-xhdpi/sharedlink.png differ diff --git a/res/drawable/arrow_left.png b/res/drawable/arrow_left.png deleted file mode 100644 index c73932b4..00000000 Binary files a/res/drawable/arrow_left.png and /dev/null differ diff --git a/res/drawable/arrow_right.png b/res/drawable/arrow_right.png deleted file mode 100644 index 02e7c0ae..00000000 Binary files a/res/drawable/arrow_right.png and /dev/null differ diff --git a/res/drawable/bookmarks.png b/res/drawable/bookmarks.png deleted file mode 100644 index b92e4f50..00000000 Binary files a/res/drawable/bookmarks.png and /dev/null differ diff --git a/res/drawable/breadcrumb.png b/res/drawable/breadcrumb.png deleted file mode 100644 index b124f349..00000000 Binary files a/res/drawable/breadcrumb.png and /dev/null differ diff --git a/res/drawable/btn_cancel.png b/res/drawable/btn_cancel.png new file mode 100644 index 00000000..0a339bde Binary files /dev/null and b/res/drawable/btn_cancel.png differ diff --git a/res/drawable/calendar.png b/res/drawable/calendar.png deleted file mode 100644 index ee0249b2..00000000 Binary files a/res/drawable/calendar.png and /dev/null differ diff --git a/res/drawable/connection_secure.xml b/res/drawable/connection_secure.xml deleted file mode 100644 index 3e252d32..00000000 --- a/res/drawable/connection_secure.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - diff --git a/res/drawable/dashboard.png b/res/drawable/dashboard.png deleted file mode 100644 index c919de37..00000000 Binary files a/res/drawable/dashboard.png and /dev/null differ diff --git a/res/drawable/download.png b/res/drawable/download.png deleted file mode 100644 index 783ad448..00000000 Binary files a/res/drawable/download.png and /dev/null differ diff --git a/res/drawable/folder.png b/res/drawable/folder.png deleted file mode 100644 index 3edbe257..00000000 Binary files a/res/drawable/folder.png and /dev/null differ diff --git a/res/drawable/home.png b/res/drawable/home.png deleted file mode 100644 index b3fb9bba..00000000 Binary files a/res/drawable/home.png and /dev/null differ diff --git a/res/drawable/ic_hide.png b/res/drawable/ic_hide.png new file mode 100644 index 00000000..8a8144b5 Binary files /dev/null and b/res/drawable/ic_hide.png differ diff --git a/res/drawable/ic_view.png b/res/drawable/ic_view.png new file mode 100644 index 00000000..b57c2499 Binary files /dev/null and b/res/drawable/ic_view.png differ diff --git a/res/drawable/list_selector.xml b/res/drawable/list_selector.xml index d5881ab8..e0e86b34 100644 --- a/res/drawable/list_selector.xml +++ b/res/drawable/list_selector.xml @@ -22,6 +22,6 @@ - + diff --git a/res/drawable/logo_inverted.png b/res/drawable/logo_inverted.png deleted file mode 100644 index d9fd119d..00000000 Binary files a/res/drawable/logo_inverted.png and /dev/null differ diff --git a/res/drawable/main_header_bg.xml b/res/drawable/main_header_bg.xml index 855ba2c1..8cd82e4b 100644 --- a/res/drawable/main_header_bg.xml +++ b/res/drawable/main_header_bg.xml @@ -19,8 +19,8 @@ --> diff --git a/res/drawable/music.png b/res/drawable/music.png deleted file mode 100644 index 4c844425..00000000 Binary files a/res/drawable/music.png and /dev/null differ diff --git a/res/drawable/owncloud_logo_small_white.png b/res/drawable/owncloud_logo_small_white.png deleted file mode 100644 index c0d8a92d..00000000 Binary files a/res/drawable/owncloud_logo_small_white.png and /dev/null differ diff --git a/res/drawable/share.png b/res/drawable/share.png deleted file mode 100644 index 300ce575..00000000 Binary files a/res/drawable/share.png and /dev/null differ diff --git a/res/layout-land/account_setup.xml b/res/layout-land/account_setup.xml index bdff0bfb..88b1ab3c 100644 --- a/res/layout-land/account_setup.xml +++ b/res/layout-land/account_setup.xml @@ -1,12 +1,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:text="@string/uploader_btn_upload_text"/> diff --git a/res/layout/video_layout.xml b/res/layout/video_layout.xml index 8781ad8a..dfa29f7d 100644 --- a/res/layout/video_layout.xml +++ b/res/layout/video_layout.xml @@ -1,7 +1,7 @@ + android:layout_width="match_parent" + android:layout_height="match_parent" > . --> + - - \ No newline at end of file diff --git a/res/menu/file_actions_menu.xml b/res/menu/file_actions_menu.xml index e9c86885..986ce3ed 100644 --- a/res/menu/file_actions_menu.xml +++ b/res/menu/file_actions_menu.xml @@ -19,12 +19,16 @@ --> - - + + + + + - + + diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml index 538dde76..979d7309 100644 --- a/res/menu/main_menu.xml +++ b/res/menu/main_menu.xml @@ -17,12 +17,33 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . --> - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/session_context_menu.xml b/res/menu/session_context_menu.xml deleted file mode 100644 index e488902b..00000000 --- a/res/menu/session_context_menu.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - diff --git a/res/values-af-rZA/strings.xml b/res/values-af-rZA/strings.xml index 6834437f..d037a07e 100644 --- a/res/values-af-rZA/strings.xml +++ b/res/values-af-rZA/strings.xml @@ -1,9 +1,7 @@ - Instellings Instellings + Hulp Gebruikersnaam Wagwoord - Gebruikersnaam - Wagwoord diff --git a/res/values-ak/strings.xml b/res/values-ak/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-ak/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-am-rET/strings.xml b/res/values-am-rET/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-am-rET/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-ar-rSA/strings.xml b/res/values-ar-rSA/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/res/values-ar-rSA/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index 6f0d5099..6aff6937 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -1,28 +1,240 @@ - تسجيل الدخول - الملفات - الموسيقى - المعارف - تعديلات - إرفع + %1$s تطبيق أندرويد + الإصدار %1$s + تحديث الحساب + رفع + محتويات من تطبيقات أخرى الملفات - تعديلات + فتح باستخدام + مجلد جديد + إعدادات + تفاصيل + أرسل عام - عنوان الموقع + المزيد + حسابات + إدارة الحسابات + كلمة سر التطبيق + حماية العميل + تفعيل الرفع الفوري + رفع الصور فور التقاطها بالكاميرا + تفعيل الدخول + يستخدم هذا لتسجيل المشاكل + تاريخ الدخول + هذا يعرض السجلات المسجلة + حذف التاريخ + المساعدة + توصية الى صديق + ملاحظات + الدمغة. + جرب %1$s على جهازك الذكي + تحقق من الخادم + عنوان الخادم https://… إسم المستخدم - كلمات السر + كلمة السر + جديد في %1$s ؟ الملفات - إسم المستخدم - كلمات السر - إرفع - انزال - افتح + اتصال + رفع + لم يتم العثور على أي حساب + لا توجد حسابات %1$s على جهازك. يرجى تهيئة حساب أولاً. + تهيئة + خروج + لا يوجد محتوى للرفع + لم يتم استلام أي محتوى. لا شيء للرفع. + %1$s غير مسموح له بالوصول للمحتوى المشارك + يتم الرفع + لا توجد ملفات في هذا المجلد.\nيمكن إنشاء ملف جديد باختيار \"رفع\" القائمة. + اضغظ على الملف ليتم عرض خيارات أكثر + الحجم : + النوع : + انشئ في : + عُدل في : + تحميل + تحديث الملف + تم تغيير اسم الملف إلى %1$s أثناء الرفع + شارك الرابط + الغاء مشاركة الرابط نعم لا - الغاء + تم + إلغاء التحميل + إلغاء الرفع + إلغاء + حفظ + خروج خطأ - المعارف - شارك - الغى + تحميل ... + خطأ غير معروف. + حول + عدل كلمة السر + حذف الحساب + حساب جديد + الرفع من ... + اسم المجلد + يتم الرفع ... + %1$d%% رفع %2$s + تم الرفع بنجاح + تم رفع %1$s بنجاح + فشل الرفع + لم يكتمل رفع %1$s + يتم التحميل ... + %1$d%% تحميل %2$s + تم التحميل بنجاح + تم تحميل %1$s بنجاح + فشل التحميل + لم يكتمل تحميل %1$s + لم يتم تحميلها بعد + اختر حسابا + فشلت المزامنة. + لم تكتمل مزامنة %1$s + كلمة السر غير صالحة لـ %1$s + هناك تعارض + لم تنجح المزامنة الدائمة لـ %1$d ملفات + لم تنجح المزامنة التلقائية للملفات + لا يمكن مزامنة جهات اتصال %1$d ( %2$d تعارض) + تم نسيان بعض الملفات المحلية + لا يوجد مجلد %1$s بعد الان + نقل الكل + تم نقل جميع الملفات + لم ينجح نقل بعض الملفات + محلي :%1$s + خارجي : %1$s + لا يوجد مساحة كافية لنسخ الملفات المحددة إلى مجلد %1$s . هل ترغب بنقلها بدلاَ من ذلك؟ + يرجى إدخال كلمة السر + أدخل كلمة السر + سيتم طلب PIN في كل مرة يتم فيها تشغيل التطبيق + يرجى إدخال كلمة السر مرة أخرى + إزالة كلمة السر + كلمتا السر غير متطابقتين + كلمه السر غير صحيحة + تمت إزالة كلمه السر + تم تسجيل كلمت السر + مشغل الموسيقى %1$s + %1$s (يتم التشغيل) + %1$s (يتم التحميل) + انتهى تشغيل %1$s + لا يوجد ملف وسائط + لم يتم تقديم أي حساب + الملف ليس في حساب صحيح + ترميز غير مدعّم + لا يمكن قراءة ملف الوسائط + الملف غير مرمز بشكل صحيح + انتهت المهلة أثناء محاولة العرض + لا يمكن بث ملف الوسائط + لا يمكن عرض ملف الوسائط مع عارض الوسائط المستعمل + خطا امني اثناء محاولة عرض %1$s + خطا في المدخلات اثناء محاولة عرض %1$s + خطا غير متوقع اثناء محاولة عرض %1$s + زر الترجيع + زر التشغيل أو الإيقاف + زر التقدم للأمام + محاولة الدخول ... + لا يتوفر اتصال + الاتصال الآمن غير متاح + تم الاتصال + اختبار الاتصال ... + إعداد الخادم غير صحيحة + الحساب لنفس المستخدم والخادم موجود مسبقا على الجهاز + المستخدم المدخل لا يتوافق مع المستخدم الموجود في الحساب + حدث خطأ غير معروف ! + فشل في العثور على المضيف + لم يتم إيجاد الخادم + الخادم أخذ كثيرا من الوقت للرد + رابط غير سليم + فشل في تهيئة SSL + لا يمكن التحقق من هوية خادم SSL + إصدار الخادم غير معروف + لم ينجح الاتصال + نجح الاتصال آمن + اسم المستخدم أو كلمة المرور خاطئة + فشل في التحقق + تم رفض الوصول من قبل الخادم المرخص + حالة غير متوقعة: الرجاء, ادخال عنوان الخادم مرة اخرى + مدة التحقق انتهت , يرجى اعادة التحقق + يرجى ادخال كلمة المرور الحالية + مدة الجلسة انتهت , يرجى اعادة الاتصال + يتم الاتصال بالخادم للتحقق + الخادم لا يدعم طريقة التحقق هذه + %1$s لا يدعم الحسابات المتعددة + جعل الملف محدثا + إعادة التسمية + حذف + هل تود حقاَ إزالة %1$s ؟ + هل ترغب حقا في إزالة %1$s و محتوياته ؟ + محليا فقط + المحتويات المحلية فقط + الحذف من الخادم + محليا و عن بعد + تم الحذف بنجاح + فشل الحذف + أدخل اسما جديدا + لم تنجح إعادة تسمية النسخة المحلية، جرب اسما آخر + لم تكتمل إعادة التسمية + لا يمكن التحقق من الملف الخارجي + تمت مزامنة محتويات الملفات من قبل + رموز ممنوعة: / \\ < > : \" | ? * + انتظر للحظة + خطا غير متوقع : الرجاء اختيار الملف من تطبيق آخر + لم يتم اختيار أي ملف + ارسل الرابط الى ... + تسجيل الدخول باستخدام oAuth2 + الاتصال مع خادم oAuth2 + لا يمكن التحقق من هوية الموقع + شهادة الخادم غير موثوقة + شهادة الخادم منتهية الصلاحية + وقت صلاحية شهادة الخادم لم يحن بعد + الرابط لا يوافق اسم المضيف فى شهاده الحماية + هل تريد أن تثق في هذه الشهادة على اي حال ؟ + لم ينجح حفظ الشهادة + تفاصيل + إخفاء + أصدرت لـ : + أصدرت بواسطة + الاسم الشائع : + منظمة : + الوحدة التنظيمية : + البلد : + الحالة : + المكان : + الصلاحية : + من : + إلى : + التوقيع : + الخوارزمية : + لا يمكن إظهار الشهادة + - لا معلومات عن الخطأ. + هذه مساحة محجوزة + placeholder.txt + صورة PNG + 389 KB + 2012/05/18 12:23 مساء + 12:23:45 + رفع الصور من خلال الـ WiFi فقط + /InstantUpload + تعارض في التحديث + الملف %s غير + الاحتفاظ بالنسختين + استبدال + عدم الرفع + معاينة الصورة + هذه الصورة لا يمكن أن تظهر + فشل في محاولة الرفع الفوري + فشل في الرفع الفوري + ملخص لكل الاخطاء في عملية الرفع الفوري + تحديد الكل + اعادة كل المختارات + حذف كل المختارات من قائمة انتظار الرفع + اعادة المحاولة لرفع الصورة: + تحميل المزيد من الصور + do nothing you are not online for instant upload + رسالة خطا: + الرجاء التاكد من اعدادات الخادم, من الممكن انك تعديت الحد في quota + لم يتمكن من مشاركة الملف او المجلد. يرجى التاكد من وجوده + حدث خطأ ما أثناء محاولة مشاركة هذا الملف أو المجلد + غير قادر على إلغاء مشاركة هذا الملف أو المجلد.لا وجود له + حدث خطأ ما أثناء محاولة إلغاء مشاركة هذا الملف أو المجلد + أرسل + نسخ الرابط + تم النسخ للحافظة diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-az/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index c757504a..8be48850 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -1,2 +1,7 @@ - + + Налады + Так + Не + Памылка + diff --git a/res/values-bg-rBG/strings.xml b/res/values-bg-rBG/strings.xml index aec32fd5..1d7b631c 100644 --- a/res/values-bg-rBG/strings.xml +++ b/res/values-bg-rBG/strings.xml @@ -1,31 +1,78 @@ - ownCloud - Парола: - Потребител: - Вход - Добре дошли - Файлове - Музика - Контакти - Календар - Предпочитани - Настройки Качване + Съдържание от други приложения Файлове + Нова папка Настройки + Изпрати Общи - Уеб адрес + Още + Профили + Своевременно качване на снимки направени с камерата + Помощ + Потребител Парола Файлове - Парола + Свързване Качване + Няма открит профил + Инсталиране Изход + Няма съдържание за качване + Не беше получено съдържание. Няма какво да се качи. + Качване + Натиснете върху файл за да видите повече информация. + Размер: + Тип: + Създаден: + Променен: Изтегляне + Да + Не + ОК Спри качването + Отказ + Запази и Излез Грешка - Контакти - Споделяне + Относно + Промяна на паролата + Изтрий акаунт + Създай акаунт + Качено от ... + Име на папката + Качване ... + %1$d%% Качване %2$s + Качване е успешно + Качването е неуспешно + Качването на %1$s не може да бъде завършено + Сваляне ... + %1$d%% Сваляне %2$s + Свалянето е неуспешно + Свалянето на %1$s не може да бъде завършено + Изберете акаунт + Синхронизацияте е неуспешна + Синхронизацията на %1$s не може да бъде осъществена + Моля въведете Вашия App ПИН + Няма мрежова свързаност + Защитена връзка не е налична + Осъществена връзка + Появи се неизвестна грешка! + Невъзможност за намиране на хоста + Сървърът се забави прекалено много с отговора + Грешен URL + Инициализацията на SSL е неуспешна + Невъзможност за осъществяване на връзка + Осъществена защитена връзка + Дръж файлът обновен Преименуване Премахване + Наистина ли искате да изтриете %1$s ? + Само локално + Премахни от сървъра + Преименуването не може да се осъществи + Изчакайте малко + Не е избран файл + Самоличността на сайта не може да бъде проверена + Изпрати diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml index 6ab3a5b6..8ece22a6 100644 --- a/res/values-bn-rBD/strings.xml +++ b/res/values-bn-rBD/strings.xml @@ -1,39 +1,16 @@ - প্রবেশ - সুস্বাগতম - ফাইল - গানবাজনা - ঠিকানাপঞ্জী - দিনপঞ্জী - ঠিকা - নিয়ামকসমূহ - একাউন্ট সেটআপ - নবোদ্যম আপলোড ফাইল - ডিরেক্টরি তৈরী কর - অনুসন্ধান নিয়ামকসমূহ + পাঠাও সাধারণ - ডিভাইস ট্র্যাকিং - নতুন সেসন যোগ কর - ছবির অঙ্গুলিবীক্ষণ তৈরী কর - একাউন্ট নির্বাচন কর - ডিভাইস ট্র্যাকিং - পরিবর্ধনের মধ্যবিরতি + বেশী একাউন্ট - URL + সহায়িকা ব্যবহারকারি কূটশব্দ - ভুল ঠিকানা প্রদান করা হয়েছে - সেসনের নামটি ভুল ফাইল - আপলোড করার জন্য কোন ফাইল নির্বাচন করা হয় নি - ব্যবহারকারি - কূটশব্দ - ওয়েব ঠিকানা - কূটশব্দ প্রদর্শন করবে ? সংযুক্ত হও আপলোড কোন একাউন্ট খুঁজে পাওয়া গেল না @@ -45,7 +22,6 @@ তৈরীর নির্ঘন্টঃ পরিবর্তিতঃ ডাউনলোড - খোল হ্যাঁ না তথাস্তু @@ -53,8 +29,9 @@ বাতিল সংরক্ষণ কর এবং &প্রস্থান সমস্যা - ডিরেক্টরির নাম + কূটশব্দ পরিবর্তন করুন একাউন্ট নির্বাচন - ভাগাভাগি কর পূনঃনামকরণ + অপসারণ + পাঠাও diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml new file mode 100644 index 00000000..8cb0bfde --- /dev/null +++ b/res/values-bs/strings.xml @@ -0,0 +1,4 @@ + + + Nova fascikla + diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index a566cd67..5ad0b6c8 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -1,67 +1,50 @@ - ownCloud - Contrasenya: - Nom d\'usuari: - Accés - Benvingut al vostre ownCloud - Fitxers - Música - Contactes - Calendari - Adreces d\'interès - Configuració - Configuració de comptes - No hi ha comptes d\'ownCloud en el dispositiu. Per usar l\'aplicació, n\'heu de crear un. - Client ownCloud per Android\n\nversió: %1$s - Sincronitza el compte + %1$s aplicació per Android + versió %1$s + Actualitza compte Puja un fitxer Contingut d\'altres aplicacions Fitxers - Crea una carpeta - Cerca + Obre amb + Carpeta nova Configuració + Detalls + Envia General - Seguiment del dispositiu - Afegeix una sessió nova - Crea miniatures d\'imatges - Seleccioneu un compte - Escolliu quin dels comptes hauria d\'usar l\'aplicació - Seguiment del dispositiu - Permet que owCloud faci el seguiment del dispositiu - ownCloud manté el seguiment d\'aquest dispositiu - Actualitza l\'interval - Actualitza cada %1$s minuts + Més Comptes Gestió de comptes - PIN de l\'aplicació ownCloud - Protegiu el client ownCloud + PIN de l\'aplicació + Protegiu el client Activa la pujada instantània Puja instantàniament les fotografies preses amb la càmera - URL de ownCloud + Habilita el registre + Això s\'usa per registrar problemes + Història del registre + Això mostra els registres desats + Esborra la història + Ajuda + Recomana a un amic + Comentaris + Imprint + Proveu %1$s a un telèfon avançat! + Comprova el servidor + Adreça del servidor https://… Nom d\'usuari Contrasenya - Sóc nou a ownCloud - La URL proporcionada és incorrecta - El nom de la sessió no és correcte + Nou a %1$s? Fitxers - No heu seleccionat cap fitxer per a la pujada - Nom d\'usuari - Contrasenya - Adreça web - Mostra la contrasenya - Connecta amb ownCloud Connecta Puja No s\'ha trobat el compte - No hi ha comptes ownCloud en el dispositiu. Configureu un compte primer + No hi ha comptes %1$s en el dispositiu. Configureu un compte primer Configura Surt No hi ha continguts per pujar No s\'ha rebut cap contingut. Res per pujar - ownCloud no pot accedir al contingut compartit + %1$s no pot accedir al contingut compartit S\'està pujant - Crea una carpeta per les pujades No hi ha fitxers en aquesta carpeta.\nPodeu afegir fitxers a través de l\'opció \"Puja\" del menú. Feu clic en un fitxer per mostrar informació addicional. Mida: @@ -69,10 +52,10 @@ Creat: Modificat: Baixa - Refrescar - Baixa de nou - Obre + Actualitza el fitxer L\'arxiu s\'ha canviat de nom a %1$s durant la càrrega + Enllaç de compartició + Deixa de compartir l\'enllaç Sí No D\'acord @@ -80,9 +63,11 @@ Cancel·la la pujada Cancel·la Desa & Surt - Surt de ownCloud Error + Carregant... + Error desconegut Quant a + Canvia la contrasenya Elimina compte Crea un compte Puja fitxer des de ... @@ -91,80 +76,89 @@ %1$d%% Pujant %2$s La pujada ha estat correcte %1$s s\'ha pujat correctament - %1$d fitxers s\'han pujat correctament La pujada ha fallat La pujada de %1$s no s\'ha pogut acabar - La pujada ha fallat: s\'han pujat %1$d/%2$d fitxers S\'està baixant ... %1$d%% pujant %2$s La baixada ha estat correcte %1$s s\'han baixat correctament La baixada ha fallat La baixada de %1$s no s\'ha pogut acabar + No baixat encara Escolliu el compte - Contactes La sincronització ha fallat La sincronització de %1$s no s\'ha pogut acabar + Contrasenya no vàlida per %1$s S\'han trobat conflictes %1$d mantinguts els arxius que no podien ser sincronitzats Es mantenent els arxius a sincronitzar erronis El contingut de %1$d arxius no es va poder sincronitzar (%2$d conflictes) - Usa connexió segura - ownCloud no pot fer el seguiment de l\'equip. Comproveu la configuració d\'ubicuació + S\'han oblidat alguns fitxers locals + La carpeta %1$s ja no existeix + Mou-los tots + S\'han mogut tots els fitxers + Alguns fitxers no s\'han pogut moure + Local: %1$s + Remot: %1$s + No hi ha prou espai per copiar els fitxers seleccionats a la carpeta %1$s. Els hi voleu moure? Escriviu el PIN de l\'aplicació - Escriviu el nou PIN de l\'aplicació - Escriviu el PIN de l\'aplicació ownCloud + Escriviu el PIN de l\'aplicació es requerirà el PIN cada vegada que s\'iniciï l\'aplicació - Torneu a escriure el PIN de l\'aplicació ownCloud - Elimina el PIN de l\'aplicació ownCloud + Torneu a escriure el PIN de l\'aplicació + Elimina el PIN de l\'aplicació Els PINs de l\'aplicació no coincideixen - El PIN de l\'aplicació ownCloud no és correcte - S\'ha eliminat el PIN de l\'aplicació ownCloud - S\'ha desat el PIN de l\'aplicació ownCloud - - 15 Minuts - 30 Minuts - 60 Minuts - - - 15 - 30 - 60 - + El PIN de l\'aplicació no és correcte + S\'ha eliminat el PIN de l\'aplicació + S\'ha desat el PIN de l\'aplicació + reproductor de música %1$s + %1$s (sonant) + %1$s (carregant) + reproducció acabada %1$s + No s\'han trobat fitxers multimedia + No s\'ha proporcionat cap compte + El fitxer no és en un compte vàlid + El codec multimèdia no és compatible + El fitxer multimèdia no es pot llegir + El fitxer multimèdia no està codificat correctament + Temps exhaurit en intentar reproduir-ho + El fitxer multimèdia no es pot transmetre + El fitxer multimèdia no es pot reproduir amb el reproductor per defecte + Error de seguretat en intentar reproduir %1$s + Error d\'entrada intentant reproduir %1$s + Error inesperat intentant reproduir %1$s + Botó de rebobinat + Botó de reproducció o pausa + Botó de reproducció ràpida S\'està intentant acreditar-vos... Sense connexió de xarxa - No s\'ha detectat cap connexió, comproveu la connexió i torneu a intentar-ho. - Connecta igualment La connexió segura no està disponible. - L\'aplicació no ha pogut establir una connexió segura amb el servidor. Malgrat tot, una connexió no segura està disponible. Podeu continuar o cancel·lar. S\'ha establert la connexió S\'està comprovant la connexió... - La configuració de ownCloud està malformada - Sembla que la instància ownCloud no està configurada correctament. Per més detalls contacteu amb l\'administrador. + La configuració del servidor està malformada + Ja hi ha un compte al dispositiu pel mateix usuari i mateix servidor + L\'usuari introduït no coincideix amb l\'usuari d\'aquest compte S\'ha produït un error desconegut - S\'ha produït un error desconegut. Contacteu els autors i inclogueu els registres del dispositiu. No s\'ha trobat el servidor - No s\'ha trobat el servidor. Comproveu el nom del servidor i la seva disponibilitat i intenteu-ho de nou. - No s\'ha trobat la instància ownCloud - L\'aplicació no ha trobat la instància ownCloud al camí indicat. Comproveu el camí i torneu-ho a intentar. + No s\'ha trobat la instància del servidor El servidor ha trigat massa en respondre La URL esà malformada La inicialització SSL ha fallat No s\'ha pogut verificar la identitat SSL del servidor - Versió del servidor ownCloud desconeguda + Versió del servidor desconeguda No s\'ha pogut establir la connexió S\'ha establert la connexió segura - Detalls de connexió - Login / contrasenya incorrectes - Camí equivocat - Error intern del servidor, codi %1$d - L\'aplicació s\'ha aturat de manera no prevista. Voleu enviar el registre d\'errors? - Envia el registre d\'errors - No envïis el registre d\'errors - Hi ha extensions disponibles! - Sembla que la instància ownCloud té suport per extensions avançades. Voleu veure les extensions disponibles per Android? + Nom d\'usuari o contrasenya incorrectes + Autorització sense èxit + El servidor d\'autenticació us ha denegat l\'accés + Estat inesperat; escriviu l\'adreça de nou + La vostra autorització ha vençut. Acrediteu-vos de nou + Escriviu la contrasenya actual + La sessió ha vençut. Connecteu-vos de nou + S\'està connectant a un servidor d\'autenticació... + El serivdor no permet aquest mètode d\'autenticació + %1$s no permet comptes múltiples + No es pot autenticar en aquest servidor Mantén el fitxer actualitzat - Comparteix Reanomena Elimina Esteu segur que voleu eliminar %1$s? @@ -172,7 +166,7 @@ Només local Només contiguts locals Elimina del servidor - Tant el remot com el local + Remot i local L\'eliminació ha tingut èxit No s\'ha pogut completar l\'eliminació Introdueix un nom nou @@ -180,17 +174,18 @@ El canvi de nom no s\'ha pogut completar L\'arxiu remot no ha pogut ser comprovat Contingut de l\'arxiu ja sincronitzat - La carpeta no s\'ha pogut crear + Caràcters no permesos: / \\ < > : \" | ? * Espereu S\'ha produït un problema inesperat; proveu una altra aplicació per seleccionar el fitxer No heu seleccionat cap fitxer - Avís + Envia l\'enllaç a... + Accés amb oAuth2 + Connectant amb el servidor oAuth2... No s\'ha pogut verificar la identitat del lloc web - El certificat del servidor no està autenticat - El certificat del servidor ha expirat - El certificat del servidor és massa jove - La URL no coincideix el nom del servidor en el certificat - No s\'ha pogut obtenir el certificat del servidor Voleu confiar en aquest certificat igualment? El certificat no s\'ha pogu desar Detalls @@ -208,7 +203,14 @@ A: Signatura: Algoritme: - Això és un text variable + No s\'ha pogut mostrar el certificat. + - No hi ha informació de l\'error + Això és un text variable + placeholder.txt + Imatge PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Puja les fotos només via WiFi /CarregaInstantània Conflicte d\'actualització @@ -216,4 +218,24 @@ Mantén-los ambdós Sobrescriu No el pugis + Visualització prèvia d\'imatge + Auquesta imatge no es pot mostrar + La pujada instantània ha fallat + Fallada de pujades instantànies + Resum de totes les pujades instantànies que han fallat + selecciona-ho tot + reintenta els seleccionats + elimina tots els seleccionats de la cua de pujada + intenta pujar de nou la imatge: + Carrega més imatges + no facis res, no estàs en lína per la pujada instantània + Missatge d\'Error: + Comproveu la configuració del servidor, potser heu excedit la quota. + No es pot compartir aquest fitxer o carpeta. Assegureu-vos que existeix + S\'ha produït un error en intentar compartir aquest fitxer o carpeta + No es pot deixar de compartir aquest fitxer o carpeta. No existeix. + S\'ha produït un error en intentar deixar de compartir aquest fitxer o carpeta + Envia + Copia l\'enllaç + S\'ha copiat al porta-retalls diff --git a/res/values-cs-rCZ/strings.xml b/res/values-cs-rCZ/strings.xml index c6adef82..004dc811 100644 --- a/res/values-cs-rCZ/strings.xml +++ b/res/values-cs-rCZ/strings.xml @@ -1,67 +1,50 @@ - ownCloud - Heslo: - Uživatelské jméno: - Přihlásit - Vítejte ve svém ownCloudu - Soubory - Hudba - Kontakty - Kalendář - Záložky - Nastavení - Nastavit účet - Nemáte nastavené žádné účty ownCloud. Aby bylo možné tuto aplikaci používat, musíte si nějaký vytvořit. - klient ownCloud pro Android\n\nverze: %1$s - Obnovit + %1$s Android aplikace + verze %1$s + Obnovit účet Odeslat Obsah z ostatních aplikací Soubory - Vytvořit adresář - Hledat + Otevřít pomocí + Nová složka Nastavení + Podrobnosti + Odeslat Obecné - Sledování zařízení - Přidat nové sezení - Vytvořit náhledy obrázků - Vybrat účet - Vyberte, který účet má tato aplikace používat. - Sledování zařízení - Povolit ownCloudu sledovat polohu VaÅ¡eho zařízení - Váš ownCloud sleduje toto zařízení - Interval aktualizace - Aktualizovat každých %1$s minut + Více Účty Spravovat účty - PIN aplikace ownCloud - Chraňte svého klienta ownCloud + PIN aplikace + Chraňte svého klienta Povolit okamžité odeslání Okamžitě odesílat vytvořené fotografie - URL ownCloud + Povolit logování + Použito k zaznamenávání problémů + Historie logování + Zobrazuje zaznamenané logy + Smazat historii + Nápověda + Doporučit příteli + Odezva + Imprint + Zkuste %1$s na vaÅ¡em smartphonu! + Zkontrolovat server + Adresa serveru https://... Uživatelské jméno Heslo - Jsem nový na ownCloud. - Zadané neplatné URL - Neplatný název sezení + Nováček s %1$s? Soubory - Žádný soubor nevybrán k odeslání - Uživatelské jméno - Heslo - Webová adresa - Zobrazit heslo? - Připojit k VaÅ¡emu ownCloudu Připojit Odeslat Nenalezen žádný účet - Nemáte žádné ownCloud účty. Vytvořte si, prosím, nejdříve účet. + Nemáte žádné %1$s účty. Vytvořte si, prosím, nejdříve účet. Nastavení Ukončit Žádný obsah k odeslání Neobdržen žádný obsah. Nic k odeslání. - ownCloud nemá právo přistupovat ke sdílenému obsahu + %1$s nemá právo přistupovat ke sdílenému obsahu Odesílání - Vytvořit adresář pro odesílání Ve složce nejsou žádné soubory.\nNové soubory mohou být přidány pomocí volby \"Odeslat\". Více informací získáte klepnutím na soubor. Velikost: @@ -69,10 +52,10 @@ Vytvořen: Upraven: Stáhnout - Obnovit - Znovu stáhnout - Otevřít + Obnovit soubor Soubor byl v průběhu odesílání přejmenován na %1$s + Sdílet odkaz + ZruÅ¡it sdílení odkazu Ano Ne OK @@ -80,94 +63,105 @@ ZruÅ¡it odesílání ZruÅ¡it Uložit a ukončit - Opustit ownCloud Chyba + Načítání ... + Neznámá chyba O aplikaci + Změnit heslo Smazat účet Vytvořit účet - Odeslat z... - Název adresáře + Odeslat z ... + Název složky Odesílám... %1$d%% Odesílám %2$s Odesílání úspěšné %1$s byl úspěšně odeslán - %1$s soubory byly úspěšně odeslány Odesílání selhalo Odesílání %1$s nemohlo být dokončeno - Nahrávání selhalo: %1$d/%2$d souborů odesláno Stahuji ... %1$d%% Stahuji %2$s Stažení úspěšné %1$s byl úspěšně stažen Stažení selhalo Stažení %1$s nemohlo být dokončeno + JeÅ¡tě nestaženo Vybrat účet - Kontakty Synchronizace selhala Synchronizaci %1$s nelze dokončit + Chybné heslo pro %1$s Nalezeny konflikty %1$d souborů z automatické synchronizace nelze synchronizovat Automatická synchronizace souborů selhala Obsah %1$d souborů nemohl být synchronizován (počet konfliktů: %2$d) - Použít zabezpečené spojení - ownCloud nemůže sledovat vaÅ¡e zařízení. Zkontroluje nastavení zjišťování polohy. + Některé místní soubory byly zapomenuty + Složka %1$s již neexistuje + Přesunout vÅ¡e + VÅ¡echny soubory byly přesunuty + Některé soubory nebylo možno přesunout + Místní: %1$s + Vzdálené: %1$s + Nedostatek místa pro zkopírování vybraných souborů do složky %1$s. Přejete si je místo kopírování přesunout? Zadejte PIN aplikace - Zadejte nový PIN aplikace - Zadat PIN ownCloud aplikace + Zadat PIN aplikace Při každém spuÅ¡tění aplikace bude vyžadováno zadání PIN - Zopakovat PIN ownCloud aplikace - Odstranit PIN ownCloud aplikace - PINy se neshodují - Neplatný PIN - PIN ownCloud aplikace odstraněn - PIN ownCloud aplikace uložen - - 15 Minut - 30 Minut - 60 Minut - - - 15 - 30 - 60 - + Zadejte znovu PIN aplikace + Odstranit PIN aplikace + PINy aplikace se neshodují + Neplatný PIN aplikace + PIN aplikace odstraněn + PIN aplikace uložen + Hudební přehrávač %1$s + %1$s (přehrává) + %1$s (načítá) + %1$s přehrávání dokončeno + Nenalezen žádný multimediální soubor + Neposkytnut žádný účet + Soubor není v platném účtu + Nepodporovaný kodek + Multimediální soubor nelze přečíst + Multimediální soubor není správně kódován + VyprÅ¡el čas při pokusu o přehrání + Multimediální soubor nelze proudově odesílat + Multimediální soubor nemůže být přehrán s výchozím přehrávačem + Chyba zabezpečení při pokusu o přehrání %1$s + Chyba vstupu při pokusu o přehrání %1$s + Neočekávaná chyba při pokusu o přehrání %1$s + Tlačítko Přetočit + Tlačítko Přehrát/Pozastavit + Tlačítko Rychle vpřed Zkouším se přihlásit... Žádné síťové spojení - Nebylo nalezeno připojení k síti, zkontrolujte svoje připojení a zkuste to znovu. - I přesto připojit Zabezpečené spojení není k dispozici - Aplikace nemůže navázat zabezpečené spojení se serverem. Ačkoliv nezabezpečené spojení je k dispozici. Můžete pokračovat, či přeruÅ¡it spojení. Spojení navázáno Zkouším spojení... - Neplatné nastavení ownCloudu - Vypadá to že nastavení Vaší instance ownCloud není správné. Kontaktujte svého správce pro více informací. + Neplatné nastavení serveru + Účet pro stejného uživatele a server již v zařízení existuje + Zadaný uživatel neodpovídá uživateli tohoto účtu Nastala neznámá chyba - Nastala neznámá chyba. Prosím kontaktujte autory a připojte záznam z VaÅ¡eho zařízení. Nelze najít hostitele - Nemůžu navázat spojení se serverem. Zkontrolujte zda máje správnou adresu, zda server běží a zkuste to znovu. - Instance ownCloud nenalezena - Aplikace nenalezla na zadané adrese instanci ownCloud. Zkontrolujte cestu a zkuste znovu. + Instance serveru nenalezena Serveru trvalo příliÅ¡ dlouho odpovědět Neplatné URL Inicializace SSL selhala - Neověřená identita SSL serveru - Nerozpoznaná verze serveru ownCloud + Nemohu ověřit SSL identitu serveru + Nerozpoznaná verze serveru Nemohu navázat spojení Zabezpečené spojení navázáno - Podrobnosti přihlášení - Neplatné přihlaÅ¡ovací údaje - Zadána neplatná cesta - Interní chyba serveru, kód %1$d - Aplikace neočekávaně skončila. Chcete odeslat zprávu o chybě? - Odeslat zprávu - Neodesílat zprávu - Rozšíření jsou dostupná! - Vypadá to, že VaÅ¡e instance ownCloud podporuje pokročilá rozšíření. Přejete si zobrazit rozšíření dostupná pro android? + Chybné přihlaÅ¡ovací jméno nebo heslo + Neúspěšné přihlášení + Přístup zamítnut autorizačním serverem + Neočekávaný stav; prosím vložte znovu URL adresu serveru + VaÅ¡e přihlášení vyprÅ¡elo. PřihlaÅ¡te se, prosím, znovu + Zadejte prosím aktuální heslo + VaÅ¡e přihlášení vyprÅ¡elo. PřihlaÅ¡te se, prosím, znovu + Připojuji se k přihlaÅ¡ovacímu serveru... + Server nepodporuje tuto přihlaÅ¡ovací metodu + %1$s nepodporuje více účtů + Není možné provést ověření Udržovat soubor aktuální - Sdílet Přejmenovat Odstranit - opravdu si přejete odstranit %1$s ? + Opravdu si přejete odstranit %1$s ? Opravdu si přejete odstranit %1$s a jeho obsah? Pouze místní Pouze místní obsah @@ -180,17 +174,18 @@ Přejmenování nelze dokončit Vzdálený soubor nemohl být zkontrolován Obsah souboru je již synchronizován - Adresář nelze vytvořit + Zakázané znaky: / \\ < > : \" | ? * Počkejte chvíli Neočekávaný problém - zkuste zvolit soubor jinou aplikací Žádný soubor nebyl vybrán - Varování + Odeslat odkaz ... + Přihlásit se s oAuth2 + Připojuji se k oAuth2 serveru... Identitu stránky nelze ověřit - Certifikát serveru je nedůvěryhodný - Certifikátu serveru vyprÅ¡ela platnost - - Certifikát serveru je příliÅ¡ nový + - Datum platnosti certifikátu je v budoucnosti - URL neodpovídá hodnotě hostname certifikátu - Nelze získat certifikát serveru Přejete si přesto tomuto certifikátu důvěřovat? Certifikát nelze uložit Detaily @@ -208,7 +203,14 @@ Pro: Podpis: Alogritmus: - Zástupný text + Certifikát nemohl být zobrazen. + - Žádné informace o této chybě + Zástupný text + placeholder.txt + Obrázek PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Odesílat obrázky pouze skrze WiFi /InstantUpload Konflikt při aktualizaci @@ -216,4 +218,24 @@ Ponechat oba Přepsat Nenahrávat + Náhled obrázku + Obrázek nemůže být zobrazen + Selhalo Okamžité odeslání + Selhaná okamžitá odeslání + Souhrn vÅ¡ech selhaných okamžitých odeslání + vybrat vÅ¡e + zkusit znovu vybrané + smazat vybrané z fronty k nahrání + zkusit znovu odeslat obrázek: + Načíst více obrázků + nic nedělat nejste připojeni pro okamžité odeslání + Chybová zpráva: + Zkontrolujte prosím nastavení vaÅ¡eho serveru, možná jste překročili kvótu. + Nepodařilo se sdílet tento soubor či složku. Ujistěte se, že existuje. + Při pokusu o sdílení tohoto souboru či složky nastala chyba + Nepodařilo se zruÅ¡it sdílení tohoto souboru nebo složky, protože neexistuje. + Při pokusu o zruÅ¡ení sdílení tohoto souboru či složky nastala chyba + Odeslat + Zkopírovat odkaz + Zkopírováno do schránky diff --git a/res/values-cy-rGB/strings.xml b/res/values-cy-rGB/strings.xml new file mode 100644 index 00000000..cba3dec5 --- /dev/null +++ b/res/values-cy-rGB/strings.xml @@ -0,0 +1,147 @@ + + + Llwytho i fyny + Cynnwys o becynnau eraill + Ffeiliau + Gosodiadau + Anfon + Cyffredinol + Cyfrifon + Rheoli Cyfrifon + PIN Ap + Amddiffyn eich cleient + Galluogi llwytho i fyny\'n syth + Llwytho lluniau dynnwyd â chamera i fyny\'n syth + Cymorth + Imprint + Enw defnyddiwr + Cyfrinair + Ffeiliau + Cysylltu + Llwytho i fyny + Heb ganfod cyfrif + Does dim cyfrifon %1$s ar eich dyfais. Sefydlwch gyfrif yn gyntaf. + Gosod + Gadael + Dim cynnwys i lwytho i fyny + Heb dderbyn cynnwys. Dim cynnwys i lwytho i fyny + Does dim mynediad gan %1$s i gynnwys a rennir + Yn llwytho i fyny + Does dim ffeilau yn y blygell hon.\nGellir ychwanegu rhai newydd drwy ddewis \"Llwytho i fyny\" yn y ddewislen. + Tapiwch ffeil i ddangos gwybodaeth ychwanegol + Maint: + Math: + Crewyd: + Addaswyd: + Llwytho i lawr + Ailenwyd y ffeil i %1$s wrth lwytho i fyny + Ie + Na + Iawn + Diddymu llwytho i lawr + Diddymu llwytho i fyny + Diddymu + Cadw & Gadael + Gwall + Ynglyn â + Dileu cyfrif + Creu cyfrif + Llwytho i fyny o ... + Yn llwytho i fyny ... + %1$d%% Llwytho i fyny %2$s + Llwytho i fyny\'n llwyddiannus + Llwythwyd %1$s i fyny\'n llwyddiannus + Methwyd llwytho i fyny + Methwyd cwblhau llwytho %1$s i fyny + Llwytho i lawr ... + %1$d%% Llwytho i lawr %2$s + Llwyddiant llwytho i lawr + Llwythwyd %1$s i lawr yn llwyddiannus + Methwyd llwytho i lawr + Methwyd cwblhau llwytho %1$s i lawr + Dewiswch gyfrif + Methodd y cydamseriad + Methwyd cwblhau cydamseru %1$s + Canfuwyd gwrthdaro + Methwyd cydamseru\'r ffeil cadw-yn-gydamserol %1$d + Methodd ffeiliau cadw-yn-gydamserol + Methwyd cydamseru cynnwys ffeiliau %1$d (gwrthdaro %2$d ) + Anghofiwyd am rai ffeiliau lleol + Symud y cyfan + Symudwyd pob ffeil + Methwyd symud rhai ffeiliau + Lleol: %1$s + Pell: %1$s + Does dim digon o le i gopïo\'r ffeiliau ddewiswyd i blygell %1$s . Would like to move them into instead? + Cyflwynwch PIN eich Ap + Cyflwynwch PIN eich Ap + Bydd cais am y PIN bob tro mae\'r ap yn cychwyn + Ailgyflwynwch PIN eich Ap + Gwaredwch PIN eich Ap + Nid yw PINau yr Ap yr un fath + PIN Ap anghywir + Gwaredwyd PIN Ap + Cadwyd PIN Ap + Dim cysylltiad rhwydwaith + Nid oes cysylltiad diogel ar gael. + Sefydlwyd y cysylltiad + Cyfluniad gweinydd wedi\'i gamffurfio + Digwyddodd gwall anhysbys! + Methwyd canfod gwesteiwr + Enghraifft o\'r gweinydd heb ei ganfod + Cymrodd y gweinydd rhy hir i ymateb + URL Camffurfiedig + Methwyd ymgychwyn SSL + Fersiwn gweinydd heb ei gydnabod + Methwyd creu cysylltiad + Sefydlwyd cysylltiad diogel + Cadw\'r ffeil yn gyfredol + Ailenwi + Gwaredu + Ydych chi wir am gael gwared ar %1$s ? + Ydych chi wir am waredu %1$s a\'i gynnwys? + Lleol yn unig + Cynnwys lleol yn unig + Gwaredu o\'r gweinydd + Pell a lleol + Gwaredwyd yn llwyddiannus + Methwyd gwaredu + Rhowch enw newydd + Methwyd ailenwi copi lleol; rhowch gynnig ar enw arall + Methwyd cwblhau\'r ailenwi + Methwyd gwirio\'r ffeil bell + Cynnwys y ffeil eisoes wedi cydamseru + Arhoswch eiliad + Gwall annisgwyl ; dewiswch y ffeil o ap gwahanol + Ni ddewiswyd ffeil + Methwyd gwirio hunaniaeth y safle + - Nid yw tystysgrif y gweinydd yn ddibynadwy + - Mae tystysgrif y gweinydd wedi dod i ben + - Mae dyddiadau dilysrwydd tystysgrif y gweinydd yn y dyfodol + - Nid yw\'r URL yn cyfateb i\'r enw gwesteiwr yn y dystysgrif + Ydych chi\'n ymddiried yn y dystysgrif hon beth bynnag? + Methwyd cadw\'r dystysgrif + Manylion + Cuddio + Rhoddwyd i: + Rhoddwyd gan: + Enw cyffredin: + Sefydliad: + Uned sefydliad: + Gwlad: + Sir: + Lleoliad: + Dilysrwydd: + O: + I: + Llofnod: + Algorithm: + Llwytho lluniau i fyny drwy WiFi\'n unig + /LlwythoSyth + Gwrthdaro diweddaru + Nid yw ffeil bell %s wedi cydamseru â\'r ffeil leol. Bydd parhau yn amnewid cynnwys y ffeil ar y gweinydd. + Cadw\'r ddau + Trosysgrifio + Peidio llwytho i fyny + Anfon + diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index d68e05f2..65982033 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -1,67 +1,50 @@ - ownCloud - Kodeord: - Brugernavn: - Log ind - Velkommen til dit ownCloud - Filer - Musik - Kontakter - Kalender - Bogmærker - Indstillinger - Opsæt konto - Der er ingen ownCloud konti pÃ¥ enheden. For at bruge denne App, skal du oprette en. - ownCloud Android klient\n\nversion: %1$s - Opdater + %1$s Android App + version %1$s + Genindlæs konto Upload Indhold fra andre apps Filer - Opret mappe - Søg + Åben med + Ny Mappe Indstillinger + Detaljer + Send Generel - Enheds sporing - Tilføj ny session - Opret billede thumbnails - Vælg en konto - Vælg hvilken af dine brugere denne app skal bruge. - Enhedssporing - Tillad ownCloud at spore denne enheds placering - Din ownCloud sporer denne enhed - Opdateringsinterval - Opdater hvert %1$s minut + Mere Konti Administrer konti - ownCloud App PIN - Beskyt din ownCloud klient + App PIN + Beskyt din klient SlÃ¥ øjeblikkelig upload til Upload billeder taget med kamera med det samme - ownCloud URL + Aktiver Logning + Dette bruges til at logge problemer + Logger Historik + Dette viser de optagne logger + Slet Historik + Hjælp + Anbefal til en ven + Feedback + Imprint + Prøv %1$s pÃ¥ din smartphone! + Check Server + Server addresse https://… Brugernavn Kodeord - Jeg er ny til ownCloud - Forkert URL givet - Forkert session navn + Uvant med %1$s Filer - Ingen fil valgt til at uploade - Brugernavn - Kodeord - Web adresse - Hvis kodeord? - Tilslut til din ownCloud Tilslut Upload Ingen konto fundet - Der er ingen ownCloud brugere pÃ¥ din enhed. Sæt venligst en bruger op først. + Der er ingen %1$s brugere pÃ¥ din enhed. Sæt venligst en bruger op først. Opsætning Afslut Intet indhold at uploade Intet indhold blev modtaget. Intet at uploade. - ownCloud er ikke tilladt adgang til delt indhold + %1$s er ikke tilladt adgang til delt indhold Uploader - Opret mappe til uploads Der er ingen filer i denne mappe.\nNye filer kan tilføjes med menu valgmuligheden \"Upload\". Tryk pÃ¥ en fil for at vise yderligere information. Størelse: @@ -69,10 +52,10 @@ Oprettet: Ændret: Hent - Opdater - Download igen - Åben + Opdater fil Filen blev omdøbt til %1$s under upload + Del link + Ophæv deling Ja Nej OK @@ -80,91 +63,102 @@ Fortryd upload Annuller Gem & Afslut - Forlad ownCloud Fejl + Indlæser... + Ukendt fejl Om + Skift kodeord Slet konto Opret konto Upload fra ... - mappenavn + Mappenavn Uploader ... %1$d%% Uploader %2$s Upload færdig %1$s blev uploadet med success - %1$d filer blev uploadet med success Upload fejlede Upload af %1$s kunne ikke gennemføres - Upload fejlede: %1$d/%2$d filer blev uploadet Downloader ... %1$d%% Downloader %2$s Download fuldført %1$s blev downloadet med success Download fejlede Download af %1$s kunne ikke fuldføres + Endnu ikke downloadet Vælg konto - Kontakter Synkronisering fejlede Synkronisering af %1$s kunne ikke gennemføres + Ugyldig adgangskode for %1$s Konflikter fundet %1$d hold-synkroniseret filer kunne ikke synkroniseres Hold-synkroniseret filer mislykkedes Indholdet af %1$d filer ikke kunne synkroniseres (%2$d konflikter) - Brug sikker forbindelse - ownCloud kan ikke spore din enhed. Tjek venligst dine indstillinger for brug af GPS + Visse lokale filer blev glemt + Mappen %1$s eksistere ikke længere + Flyt alle + Alle filer blev flyttet + Visse filer kunne ikke flyttes + Lokal: %1$s + Fjernplacering: %1$s + Der er ikke tilstrækkelig plads til at kopiere de valgte filer ind i %1$s mappen. Vil du flytte dem i stedet? Indsæt venligst din App PIN - Indsæt venligst din nye App PIN - Indtast ownCloud App PIN + Indtast App PIN PIN koden vil blive anmodet om hver gang applikationen bliver startet - Indtast venligst ownCloud App PIN igen - Fjern din ownCloud App PIN - ownCloud App PIN er ikke ens - Forkert ownCloud App PIN - ownCloud App PIN fjernet - ownCloud App PIN gemt - - 15 Minutter - 30 Minutter - 60 Minutter - - - 15 - 30 - 60 - + Indtast venligst App PIN igen + Fjern din App PIN + App PIN er ikke ens + Forkert App PIN + App PIN fjernet + App PIN gemt + %1$s musikafspiller + %1$s (afspiller) + %1$s (indlæser) + %1$s afspilning færdig + Mediefil ikke fundet + Ingen konto angivet + Fil er ikke en gyldig konto + Ikke-understøttet medie codec + Mediefilen kunne ikke læses + Mediefilen er ikke korrekt kodet + Tiden udløb under forsøg pÃ¥ at afspille + Mediefilen kan ikke streames + Mediefil kan ikke afspilles med tilgængelige medieafspiller + Sikkerhedsfejl ved forsøg pÃ¥ afspilning af + Inputfejl ved forsøg pÃ¥ afspilning af %1$s + Uventet fejl ved forsøg pÃ¥ afspilning af %1$s + Tilbagespolings knap + Afspil eller pause knap + Hurtigt fremad kanp Forsøger at logge ind... Ingen netværksforbindelse - Der blev ikke fundet nogen netværksforbindelse, tjek din internetforbindelse, og prøv igen - Forbind alligevel Sikker forbindelse ikke tilgængelig. - Applikationen kunne ikke oprette en sikker forbindelse med serveren. Selvom en sikker forbindelse ikke er tilgængeligt, kan du vælge at fortsætte eller annullere. Forbindelse oprettet Afprøver forbindelse ... - Misdannet ownCloud konfiguration - Det ser ud til, at din ownCloud instans ikke er konfigureret korrekt. KOntakt din administrator for flere detaljer. + Misdannet server konfiguration + En konto for den samme bruger og server eksisterer allerede pÃ¥ enheden + Den indtastede bruger passer ikke til brugeren for denne konto Ukendt fejl opstod! - Der forekom en ukendt fejl. Kontakt venligst forfattere og inkluder logfiler fra din enhed. Kunne ikke finde host - Kunne ikke finde den indtastede host. Tjek venligst host\'ens navn og om serveren er tilgængelig og prøv derefter igen. - ownCloud instans blev ikke fundet - Applikation kunne ikke finde ownCloud instans pÃ¥ den givne placering. Tjek venligst din placering og prøv igen. + Server instans blev ikke fundet Serveren var for længe om at svare Deform URL SSL initialisering fejlede - Ikke verifiseret SSL servers identitet - Ikke genkendt ownCloud server version + Kunne ikke bekræfte SSl-serverens identitet + Ikke genkendt server version Ikke ikke oprette forbindelse Sikker forbindelse oprettet - Login detaljer - Ugyldig login / password - Forkert sti angivet - Intern serverfejl, kode %1$d - Applikation afsluttede mod forventning. Vil du indsende en fejl rapport? - Send rapport - Send ikke rapport - Udvidelser tilgængelige! - Det ser ud til at dit instans af ownCloud har understøttelse af avancerede tilføjelser. Vil du se udvidelser tilgængelig til Android? + Forkert brugernavn eller kodeord + Mislykket godkendelse + Adgang afvist af autorisationsserver + Uventet tilstand; angiv server-URL\'en igen + Din godkendelse udløb. Gentag godkendelse + Indtast venligst dit nuværende kodeord + Din session udløb. Forbind venligst igen + Forbinder til godkendelsesserver ... + Serveren understøtter ikke denne godkendelsesmetode + %1$s understøtter ikke multiple konti + Kan ikke autentificere mod denne server Hold fil opdateret - Del Omdøb Fjern Er du sikker pÃ¥ at du vil fjerne %1$s ? @@ -180,17 +174,18 @@ Omdøbning kunne ikke gennemføres Ekstern fil kunne ikke kontrolleres Filindholdet allerede synkroniseret - Mappe kunne ikke oprettes + Ugyldige tegn: / \\ < > : \" | ? * Vent et øjeblik Uforventet problem; prøv venligst anden applikation til at vælge filen Ingen fil blev valgt - Advarsel + Send link til ... + Log pÃ¥ med oAuth2 + Forbinder til oAuth2 server... Sidens identitet kunne ikke verificeres - Serverens certifikat er ikke troværdigt - Serverens certifikat er udløbet - Serverens certifikat er for ung - URL adressen stemmer ikke overens med værtsnavnet i certifikatet - Serverens certifikat kunne ikke hentes Stoler du pÃ¥ certifikatet alligevel? Certifikatet kunne ikke gemmes Detaljer @@ -208,7 +203,14 @@ Til: Signatur: Algoritme: - Dette er en pladsholder + Certifikatet kunne ikke vises. + - Ingen information om fejlen + Dette er en pladsholder + stedfortræder.txt + PNG Billede + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Upload billeder kun via WiFi /Øjeblikkelig upload Opdaterings konflikt @@ -216,4 +218,24 @@ Behold begge Overskriv Upload ikke + Billede preview + Dette billede kan ikke vises + Fejlede Umiddelbar upload + Øjeblikkelige uploads mislykkedes + Sammenfatning af alle mislykkede øjeblikkelige uploads + Vælg alle + prøv alle markerede igen + slet alle markerede fra uploadkøen + prøv at uploade billedet igen: + Indlæs flere billeder + gør intet, du er ikke online til øjeblikkelig upload + Fejlmeddelelse + Tjek din serverkonfiguration, mÃ¥ske er din kvota overskredet. + Kan ikke dele denne fil eller mappe. Find venligst ud af om den eksisterer + Der opstod en fejl ved deling af denne fil eller mappe + Kan ikke fjerne delingen af denne fil eller mappe. Den findes ikke. + Der opstod en fejl ved stopning af deling af denne mappe. + Send + Kopier link + Kopieret til udklipsholder diff --git a/res/values-de-rAT/strings.xml b/res/values-de-rAT/strings.xml new file mode 100644 index 00000000..4a150b43 --- /dev/null +++ b/res/values-de-rAT/strings.xml @@ -0,0 +1,8 @@ + + + Einstellungen + Allgemein + Passwort + Herunterladen + Abbrechen + diff --git a/res/values-de-rCH/strings.xml b/res/values-de-rCH/strings.xml new file mode 100644 index 00000000..b0175ea9 --- /dev/null +++ b/res/values-de-rCH/strings.xml @@ -0,0 +1,214 @@ + + + %1$s Android App + Version %1$s + Konto aktualisieren + Datei hochladen + Inhalt von anderen Apps + Dateien + Öffnen mit + Neues Verzeichnis + Einstellungen + Details + Senden + Allgemein + Mehr + Konten + Konten verwalten + App-PIN + Schützen Sie Ihren Client + Aktiviert den sofortigen Upload + Laden Sie Ihre Fotos von der Kamera sofort hoch + Protokollierung aktivieren + Dies wird zur Protokollierung von Problemen genutzt + Protokollierungsverlauf + Dies zeigt die gespeicherten Protokollierungen + Verlauf löschen + Hilfe + Rückmeldungen + Impressum + Server überprüfen + Benutzername + Passwort + Ist %1$s neu für Sie? + Dateien + Verbinden + Hochladen + Kein Konto gefunden + Es sind keine %1$s-Konten auf Ihrem Gerät eingerichtet. Bitte richten Sie zuerst ein Konto ein. + Einrichten + Beenden + Keine Inhalte zum Hochladen vorhanden + Es wurden keine Inhalte empfangen. Es gibt nichts zum Hochladen. + %1$s darf den freigegebenen Inhalt nicht nutzen. + Lade hoch + Es sind keine Dateien im Verzeichnis vorhanden.\nNeue Dateien können mit der Menüfunktion «Hochladen» hinzugefügt werden. + Klicken Sie auf eine Datei für weitere Informationen. + Grösse: + Art: + Erstellt: + Geändert: + Herunterladen + Datei aktualisieren + Datei wurde wärend des Uploads zu %1$s umbenannt + Ja + Nein + OK + Download abbrechen + Upload abbrechen + Abbrechen + Speichern & Schliessen + Fehler + Lade... + Unbekannter Fehler + Über + Passwort ändern + Account löschen + Account erstellen + Dateien hochladen von... + Ordnername + Hochladen... + %1$d%% Hochladen %2$s + Hochladen erfolgreich + %1$s wurde(n) erfolgreich hochgeladen + Hochladen fehlgeschlagen + Hochladen von %1$s konnte nicht abgeschlossen werden + Herunterladen... + %1$d%% Herunterladen %2$s + Herunterladen erfolgreich + %1$s wurde erfolgreich heruntergeladen + Herunterladen fehlgeschlagen + Herunterladen von %1$s konnte nicht abgeschlossen werden + Noch nicht heruntergeladen + Konto auswählen + Synchronisation fehlgeschlagen + Bei der Synchronisation konnte %1$s nicht übertragen werden + Ungültiges Passwort für %1$s + Konflikte gefunden + %1$d Synchronisationsdateien konnten nicht synchronisiert werden. + Synchronisationsdateien konnten nicht synchronisiert werden. + Inhalte von %1$d konnte nicht synchronisiert werden (%2$d Konflikte) + Einige lokale Dateien wurden vergessen + Verschiebe alle + Alle Dateien wurden verschoben + Einige Dateien konnten nicht verschoben werden + Lokal: %1$s + Remote: %1$s + Es steht nicht genügend Speicherplatz zur Verfügung, um die ausgewählten Dateien in den %1$s Ordner zu kopieren. Möchten Sie sie stattdessen verschieben? + Bitte geben Sie Ihre App-PIN ein + Bitte geben Sie Ihre App-PIN ein + PIN-Abfrage erfolgt nach Starten der App. + Bitte geben Sie Ihre App-PIN erneut ein. + App-PIN entfernen + Die App-PINs stimmen nicht überein + Falsche App-PIN + Die App-PIN wurde entfernt + Die App-PIN wurde gespeichert + %1$s Musikplayer + %1$s (abspielend) + %1$s (lädt) + %1$s Wiedergabe beendet + Keine Mediadatei gefunden + Kein Account angegeben + Datei nicht in einem gültigen Account + Nicht unterstützter Media-codec + Mediendatei konnte nicht gelesen werden + Mediendatei nicht korrekt kodiert + Mediendatei kann nicht gestreamt werden + Die Mediendatei kann nicht mit dem vorinstallierten Media Player abgespielt werden + Sicherheitsfehler beim abspielen von %1$s + Eingabefehler beim Versuch %1$s abzuspielen + Unerwarteter Fehler beim Versuch %1$s abzuspielen + Zurückspulen Button + Abspielen oder Pausieren Button + Vorspulen Button + Anmeldungsversuch... + Keine Netzwerkverbindung + Sichere Verbindung nicht verfügbar. + Verbindung hergestellt + Verbindung testen... + Fehlerhafte Server Konfiguration + Ein unbekannter Fehler ist aufgetreten! + Konnte den Host nicht finden. + Server-Installation nicht gefunden + Der Server braucht zu lange für eine Antwort. + Fehlerhafte URL + SSL-Initialisierung fehlgeschlagen. + Unbekannte Server-Version + Konnte keine Verbindung aufbauen. + Sichere Verbindung hergestellt + Falscher Benutzername oder Passwort + Autorisierung nicht erfolgreich + Zugriff durch den Autorisierungsserver abgelehnt + Unerwarteter Zustand; bitte geben Sie die URL des Servers nochmals ein + Bitte geben Sie Ihr aktuelles Passwort ein + Datei aktuell halten + Umbenennen + Löschen + Möchten Sie %1$s wirklich löschen? + Möchten Sie wirklich %1$s und dessen Inhalte entfernen? + Nur lokal + Nur lokale Inhalte + Vom Server entfernen + Lokal und auf dem Server + Erfolgreich gelöscht + Der Löschvorgang konnte nicht beendet werden + Geben Sie einen neuen Namen ein + Die lokale Kopie konnte nicht umbenannt werden. Versuchen Sie es mit einem anderen neuen Namen. + Die Umbenennung konnte nicht abgeschlossen werden. + Die entfernte Datei konnte nicht überprüft werden + Dateiinhalte bereits synchronisiert + Bitte warten Sie einen Moment. + Ein unerwartetes Problem ist aufgetreten. Bitte versuchen Sie, die Datei in einer anderen App zu öffnen. + Es wurde keine Datei ausgewählt. + Verbinde mit dem oAuth2-Server... + Die Identität der Website konnte nicht überprüft werden + - Das Zertifikat des Servers ist nicht vertrauenswürdig + - Das Zertifikat des Servers ist abgelaufen + - Das Gültigkeitsdatum des Serverzertifikats liegt in der Zukunft + - Die Adresse stimmt nicht mit dem im Zertifikat angegebenen Hostnamen überein + Möchten Sie diesem Zertifikat trotzdem vertrauen? + Das Zertifikat konnte nicht gespeichert werden + Details + Ausblenden + Ausgestellt für: + Ausgestellt von: + Üblicher Name: + Organisation: + Organisationseinheit: + Land: + Bundesland: + Ort: + Gültigkeit: + Von: + An: + Signatur: + Algorithmus: + Dies ist ein Platzhalter + platzhalter.txt + PNG Bild + 389 KB + 18.05.2012 12:23 + 12:23:45 + Fotos nur über WiFi hochladen + /SofortUpload + Konflikt beim Update + Serverdatei %s ist nicht synchronisiert mit der lokalen Datei. Weitermachen bedeutet, dass der Inhalt der Datei auf dem Server ersetzt wird. + Beide behalten + Überschreiben + Nicht hochladen + Bildvorschau + Dieses Bild kann nicht angezeigt werden + Sofortige Uploads fehlgeschlagen + Zusammenfassung aller fehlgeschlagenen Uploads + Alle auswählen + Versuche alle ausgewählten erneut + Lösche alle ausgewählten aus der Uploadwarteschlange + Bildupload erneut versuchen: + Lade weitere Bilder + Nicht durchgeführt - Nicht online für sofortigen Upload + Fehlermeldung: + Bitte überprüfen Sie Ihre Serverkonfiguration. Vielleicht ist Ihr Nutzungslimit überschritten. + Senden + In die Zwischenablage kopiert + diff --git a/res/values-de-rDE/strings.xml b/res/values-de-rDE/strings.xml index 77a5a82e..d86403c7 100644 --- a/res/values-de-rDE/strings.xml +++ b/res/values-de-rDE/strings.xml @@ -1,59 +1,43 @@ - ownCloud - Passwort: - Benutzername: - Anmelden - Willkommen - Dateien - Musik - Kontakte - Kalender - Lesezeichen - Einstellungen - Konto einrichten - Auf Ihrem Gerät sind keine Konten eingerichtet. Bitte erstellen Sie ein Konto, um diese App zu nutzen. - %1$s Android App\n\nVersion: %2$s - Aktualisieren + %1$s Android App + Version %1$s + Konto aktualisieren Datei hochladen Inhalt von anderen Apps Dateien - Ordner anlegen - Suche + Öffnen mit + Neues Ordner Einstellungen + Details + Senden Allgemein - Gerät verfolgen - Neue Sitzung hinzufügen - Bildvorschau erstellen - Account auswählen - Bitte wählen Sie, welches Konto von der App verwendet werden soll. - Gerät verfolgen - Geräteverfolgung in dieser App aktivieren - Ihre App verfolgt dieses Gerät - Aktualisierungsintervall - Alle %1$s Minuten aktualisieren + Mehr Konten Konten verwalten App-PIN Schützen Sie Ihren Client Aktiviert den sofortigen Upload Laden Sie Ihre Fotos von der Kamera sofort hoch - URL + Protokollierung aktivieren + Dies wird zur Protokollierung von Problemen genutzt + Protokollierungsverlauf + Dies zeigt die gespeicherten Protokollierungen + Verlauf löschen + Hilfe + Empfehlen Sie dies einem Freund + Rückmeldungen + Impressum + Probieren Sie %1$s auf Ihrem Smartphone! + Server überprüfen + Server-Adresse https://… Benutzername Passwort - Ich bin neu bei %1$s - Falsche URL angegeben - Falscher Sitzungsname + Ist %1$s neu für Sie? Dateien - Sie haben keine Datei zum Hochladen ausgewählt - Benutzername - Passwort - Internetadresse - Passwort anzeigen? - Mit Ihrer %1$s verbinden Verbinden Hochladen - Wähle Zielverzeichnis: + Wähle Zielordner: Kein Konto gefunden Es sind keine %1$s-Konten auf Ihrem Gerät eingerichtet. Bitte richten Sie zuerst ein Konto ein. Einrichten @@ -62,7 +46,6 @@ Es wurden keine Inhalte empfangen. Es gibt nichts zum Hochladen. %1$s darf den freigegebenen Inhalt nicht nutzen. Lade hoch - Verzeichnis für hochzuladene Dateien erstellen Es sind keine Dateien im Verzeichnis vorhanden.\nNeue Dateien können mit der Menüfunktion \"Hochladen\" hinzugefügt werden. Klicken Sie auf eine Datei für weitere Informationen. Größe: @@ -70,10 +53,10 @@ Erstellt: Geändert: Herunterladen - Aktualisieren - Neu laden - Öffnen + Datei aktualisieren Datei wurde wärend des Uploads zu %1$s umbenannt + Link teilen + Link nicht mehr teilen Ja Nein OK @@ -81,11 +64,11 @@ Upload abbrechen Abbrechen Speichern & Schließen - %1$s verlassen Fehler - Wird geladen … + Lade … Unbekannter Fehler Über + Passwort ändern Account löschen Account erstellen Dateien hochladen von... @@ -94,10 +77,8 @@ %1$d%% Hochladen %2$s Hochladen erfolgreich %1$s wurde(n) erfolgreich hochgeladen - %1$d Datei(en) wurde(n) erfolgreich hochgeladen Hochladen fehlgeschlagen Hochladen von %1$s konnte nicht abgeschlossen werden - Hochladen fehlgeschlagen: %1$d/%2$d Dateien wurden hochgeladen Herunterladen... %1$d%% Herunterladen %2$s Herunterladen erfolgreich @@ -106,17 +87,24 @@ Herunterladen von %1$s konnte nicht abgeschlossen werden Noch nicht heruntergeladen Konto auswählen - Kontakte Synchronisation fehlgeschlagen Bei der Synchronisation konnte %1$s nicht übertragen werden + Ungültiges Passwort für %1$s Konflikte gefunden %1$d Synchronisationsdateien konnten nicht synchronisiert werden. Synchronisationsdateien konnten nicht synchronisiert werden. Inhalte von %1$d konnte nicht synchronisiert werden (%2$d Konflikte) - Sichere Verbindung benutzen - %1$s kann Ihr Gerät nicht verfolgen. Bitte überprüfen Sie Ihre Standorteinstellungen. + Einige lokale Dateien wurden vergessen + %1$d Dateien aus dem %2$s Verzeichnis konnten nicht kopiert werden nach + Mit Version 1.3.16 werden Dateien die von diesem Gerät aus hochgeladen werden in den lokalen Ordner %1$s kopiert um Datenverlust zu vermeiden, wenn eine einzelne Datei mit mehreren Accounts synchronisiert wird.\n\nInfolge dieser Änderung wurden alle Dateien, die mit vorherigen Versionen dieser App hochgeladen wurden, in den Ordner %2$s verschoben. Jedoch ist während der Account-Synchronisation ein Fehler aufgetreten, der das Abschließen dieses Vorgangs verhindert. Sie können die Datei(en) entweder wie sie sind belassen und den Link zu %3$s entfernen oder die Datei(en) in den %1$s Ordner verschieben und den Link zu %4$s beibehalten.\n\nUnten befindet sich eine Liste der lokalen Datei(en) und der mit ihnen verbundenen Remote-Datei(en) in %5$s. + Das Verzeichnis %1$s existiert nicht mehr + Verschiebe alle + Alle Dateien wurden verschoben + Einige Dateien konnten nicht verschoben werden + Lokal: %1$s + Remote: %1$s + Es steht nicht genügend Speicherplatz zur Verfügung, um die ausgewählten Dateien in den %1$s Ordner zu kopieren. Möchten Sie sie stattdessen verschieben? Bitte geben Sie Ihre App-PIN ein - Bitte geben Sie Ihre neue App-PIN ein Bitte geben Sie Ihre App-PIN ein PIN-Abfrage erfolgt nach Starten der App. Bitte geben Sie Ihre App-PIN erneut ein. @@ -125,70 +113,55 @@ Falsche App-PIN Die App-PIN wurde entfernt Die App-PIN wurde gespeichert - "%1$s Musik Player" - "%1$s (wird abgespielt)" - "%1$s (wird geladen)" - "%1$s Wiedergabe beendet" - Keine Media-Datei gefunden - Ungültiger Account - Datei ist nicht in einem gültigen Account - Nicht unterstützter Medien-Codec - Media-Datei konnte nicht gelesen werden - Die Media-Datei ist noch nicht kodiert - Zeitüberschreitung ist beim Abspielen aufgetreten - Media-Datei konnte nicht gestreamt werden - Media-Datei kann nicht vom Standard Player wiedergegeben werden - Sicherheits-Fehler ist beim Abspielen aufgetreten %1$s - Eingabe-Fehler ist beim Abspielen aufgetreten %1$s - Unerwarteter Fehler bei der Wiedergabe %1$s - Zurückspulen - Wiedergabe oder Pause - Vorspulen - - - 15 Minuten - 30 Minuten - 60 Minuten - - - 15 - 30 - 60 - + %1$s Musikplayer + %1$s (abspielend) + %1$s (lädt) + %1$s Wiedergabe beendet + Keine Mediadatei gefunden + Kein Account angegeben + Datei nicht in einem gültigen Account + Nicht unterstützter Media-codec + Mediendatei konnte nicht gelesen werden + Mediendatei nicht korrekt kodiert + Wartezeit beim Abspielversuch abgelaufen + Mediendatei kann nicht gestreamt werden + Die Mediendatei kann nicht mit dem vorinstallierten Media Player abgespielt werden + Sicherheitsfehler beim abspielen von %1$s + Eingabefehler beim Versuch %1$s abzuspielen + Unerwarteter Fehler beim Versuch %1$s abzuspielen + Zurückspulen Button + Abspielen oder Pausieren Button + Vorspulen Button Anmeldungsversuch... Keine Netzwerkverbindung - Es konnte keine Netzwerkverbindung gefunden werden, bitte überprüfen Sie Ihre Internetverbindung. - Trotzdem verbinden Sichere Verbindung nicht verfügbar. - Die App konnte keine sichere Verbindung zum Server herstellen. Eine unsichere Verbindung ist verfügbar. Möchten Sie fortfahren oder abbrechen? Verbindung hergestellt Verbindung testen... Fehlerhafte Server Konfiguration - Es scheint, als wäre Ihre Server-Installation nicht richtig konfiguriert. Bitte kontaktieren Sie Ihren Administrator, um weitere Details zu erhalten. + Ein Benutzerkonto für den gleichen Benutzer und Server existiert auf diesem Gerät bereits + Der eingegebene Benutzer passt nicht zu dem Benutzer dieses Benutzerkontos Ein unbekannter Fehler ist aufgetreten! - Ein unbekannter Fehler ist aufgetreten. Bitte kontaktieren Sie Ihren Administrator unter Zu­hil­fe­nah­me der Log-Dateien Ihres Gerätes. Konnte den Host nicht finden. - Konnte den eingetragenen Host nicht finden. Bitte prüfen Sie den Hostnamen und die Verfügbarkeit des Servers und versuchen es erneut. Server-Installation nicht gefunden - Die App konnte den Server unter dem angegebenen Pfad nicht finden. Bitte überprüfen Sie den Pfad und versuchen es erneut. Der Server braucht zu lange für eine Antwort. Fehlerhafte URL SSL-Initialisierung fehlgeschlagen. - Nichtüberprüfte SSL-Server-Identität + SSL-Server-Identität konnte nicht überprüft werden Unbekannte Server-Version Konnte keine Verbindung aufbauen. Sichere Verbindung hergestellt - Anmeldedetails - Benutzername oder Passwort ungültig - Falsche Pfadangabe - Interner Server Fehler, Fehlercode %1$d - Die Anwendung ist abgestürzt. Möchten Sie einen Bericht senden? - Bericht senden - Keinen Bericht senden - Erweiterung verfügbar! - Scheinbar unterstützt Ihr Server weitere Erweiterungen. Möchten Sie die verfügbaren Erweiterungen für Android sehen? + Falscher Benutzername oder Passwort + Autorisierung nicht erfolgreich + Zugriff durch den Autorisierungsserver abgelehnt + Unerwarteter Zustand; bitte geben Sie die URL des Servers nochmals ein + Ihre Autorisierung ist abgelaufen. Bitte Autorisierung nochmals durchführen + Bitte geben Sie Ihr aktuelles Passwort ein + Ihre Sitzung ist abgelaufen. Bitte Anmeldung nochmals durchführen + Verbinde mit dem Authentifizierung-Server… + Der Server unterstützt diese Authentifizierung-Methode nicht + %1$s unterstützt nicht mehrere Benutzerkonten + Die Authentifizierung gegenüber dem Server konnte nicht durchgeführt werden Datei aktuell halten - Teilen Umbenennen Löschen Möchten Sie %1$s wirklich löschen? @@ -204,17 +177,19 @@ Die Umbenennung konnte nicht abgeschlossen werden. Die entfernte Datei konnte nicht überprüft werden Dateiinhalte bereits synchronisiert - Das Verzeichnis konnte nicht erstellt werden. + Verzeichnis konnte nicht erstellt werden + Verbotene Zeichen: / \\ < > : \" | ? * Bitte warten Sie einen Moment. Ein unerwartetes Problem ist aufgetreten. Bitte versuchen Sie, die Datei in einer anderen App zu öffnen. Es wurde keine Datei ausgewählt. - Warnung + Link senden an ... + Anmelden mit oAuth2 + Verbinde mit dem oAuth2-Server… Die Identität der Website konnte nicht überprüft werden - Das Zertifikat des Servers ist nicht vertrauenswürdig - Das Zertifikat des Servers ist abgelaufen - Das Gültigkeitsdatum des Serverzertifikats liegt in der Zukunft - Die Adresse stimmt nicht mit dem im Zertifikat angegebenen Hostnamen überein - Das Zertifikat des Servers konnte nicht abgerufen werden Möchten Sie diesem Zertifikat trotzdem vertrauen? Das Zertifikat konnte nicht gespeichert werden Details @@ -232,7 +207,14 @@ An: Signatur: Algorithmus: - Dies ist ein Platzhalter + Das Zertifikat konnte nicht gezeigt werden. + - Keine Informationen über den Fehler + Dies ist ein Platzhalter + platzhalter.txt + PNG Bild + 389 KB + 18.05.2012 12:23 + 12:23:45 Fotos nur über WiFi hochladen /SofortUpload Konflikt beim Update @@ -241,16 +223,24 @@ Überschreiben Nicht hochladen Bildvorschau - Das Bild kann nicht angezeigt werden - "Nicht genug Speicherplatz um das Bild anzuzeigen - - Fehlgeschlagene Sofortuploads - Fehlgeschlagene Sofortuploads - Auflistung aller fehlgeschlagenen Softuploads + Dieses Bild kann nicht angezeigt werden + %1$s konnte nicht in den lokalen %2$s Ordner kopiert werden + Sofort-Upload fehlgeschlagen + Sofortige Uploads fehlgeschlagen + Zusammenfassung aller fehlgeschlagenen Uploads Alle auswählen - Ausgewählte wiederholen - Ausgewählte löschen - Versuche ausgewählte erneut hochzuladen - Load more Picrures - do nothing you are not online for instant upload + Versuche alle ausgewählten erneut + Lösche alle ausgewählten aus der Uploadwarteschlange + Bildupload erneut versuchen: + Lade weitere Bilder + Nicht durchgeführt - Nicht online für sofortigen Upload + Fehlermeldung: + Bitte überprüfen Sie Ihre Serverkonfiguration. Vielleicht ist Ihr Nutzungslimit überschritten. + Die Freigabe der Datei oder des Ordners ist nicht möglich. Bitte stellen Sie sicher, dass diese existiert. + Es ist ein Fehler beim Freigeben der Datei oder des Ordners aufgetreten. + Das Entfernen der Freigabe für die Datei oder den Ordner ist nicht möglich. Diese existieren nicht. + Es ist ein Fehler beim Entfernen der Freigabe für diese Datei oder den Ordner aufgetreten. + Senden + Link kopieren + In die Zwischenablage kopiert diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 8013575d..56fd4b43 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1,59 +1,43 @@ - ownCloud - Passwort: - Benutzername: - Anmelden - Willkommen - Dateien - Musik - Kontakte - Kalender - Lesezeichen - Einstellungen - Account einrichten - Auf Deinem Gerät sind keine Konten eingerichtet. Bitte erstelle ein Konto, um diese App zu nutzen. - %1$s Android App\n\nVersion: %2$s - Aktualisieren + %1$s Android-App + Version %1$s + Konto aktualisieren Datei hochladen Inhalt von anderen Apps Dateien - Ordner anlegen - Suche + Öffnen mit + Neuer Ordner Einstellungen + Details + Senden Allgemein - Gerät verfolgen - Neue Sitzung hinzufügen - Bildvorschau erstellen - Account auswählen - Bitte wähle, welches Konto von der App verwendet werden soll. - Gerät verfolgen - Geräteverfolgung in dieser App aktivieren - Deine App verfolgt dieses Gerät - Aktualisierungsintervall - Alle %1$s Minuten aktualisieren + Mehr Konten Konten verwalten App-PIN Schütze Deinen Client Aktiviert den sofortigen Upload Lade Deine Fotos von der Kamera sofort hoch - URL + Protokollierung aktivieren + Dies wird zur Protokollierung von Problemen genutzt + Protokollierungsverlauf + Dies zeigt die gespeicherten Protokollierungen + Verlauf löschen + Hilfe + Empfehle dies einem Freund + Rückmeldungen + Impressum + Probiere %1$s auf Deinem Smartphone! + Überprüfe den Server + Server-Adresse https://… Benutzername Passwort - Ich bin neu bei %1$s - Falsche URL angegeben - Falscher Sitzungsname + Ist %1$s neu für dich? Dateien - Du hast keine Datei zum Hochladen ausgewählt - Benutzername - Passwort - Internetadresse - Passwort anzeigen? - Mit Deiner %1$s verbinden Verbinden Hochladen - Wähle Zielverzeichnis: + Wähle Zielordner: Kein Account gefunden Es sind keine %1$s-Accounts auf Deinem Gerät eingerichtet. Bitte richte zuerst ein Konto ein. Einrichten @@ -62,7 +46,6 @@ Es wurden keine Inhalte empfangen. Es gibt nichts zum Hochladen. %1$s darf den freigegebenen Inhalt nicht nutzen. Lade hoch - Ordner für hochzuladene Dateien erstellen Es sind keine Dateien im Verzeichnis vorhanden.\nNeue Dateien können mit der \"Hochladen\" Menüfunktion hinzugefügt werden. Klicken Sie auf eine Datei für weitere Informationen. Größe: @@ -70,10 +53,10 @@ Erstellt: Geändert: Herunterladen - Aktualisieren - Neu laden - Öffnen + Datei aktualisieren Datei wurde wärend des Uploads zu %1$s umbenannt + Link Teilen + Link nicht mehr freigeben Ja Nein OK @@ -81,9 +64,11 @@ Upload abbrechen Abbrechen Speichern & schließen - %1$s verlasse Fehler + Lädt ... + Unbekannter Fehler Über + Passwort ändern Account löschen Account erstellen Dateien hochladen von... @@ -92,28 +77,34 @@ %1$d%% Hochladen %2$s Hochladen erfolgreich %1$s wurde(n) erfolgreich hochgeladen - %1$d Datei(en) wurde(n) erfolgreich hochgeladen Hochladen fehlgeschlagen Hochladen von %1$s konnte nicht abgeschlossen werden - Hochladen fehlgeschlagen: %1$d/%2$d Dateien wurden hochgeladen Herunterladen... %1$d%% Herunterladen %2$s Herunterladen erfolgreich %1$s wurde erfolgreich heruntergeladen Herunterladen fehlgeschlagen Herunterladen von %1$s konnte nicht abgeschlossen werden + Noch nicht Heruntergeladen Account auswählen - Kontakte Synchronisation fehlgeschlagen Bei der Synchronisation konnte %1$s nicht übertragen werden + Falsches Passwort für %1$s Konflikte gefunden %1$d synchron zu haltende Dateien konnte nicht synchronisiert werden. Synchron halten schlug fehl. Inhalte von %1$d konnte nicht synchronisiert werden (%2$d Konflikte) - Sichere Verbindung benutzen - %1$s kann Dein Gerät nicht verfolgen. Bitte überprüfe Deine Standorteinstellungen + Einige lokale Dateien wurden vergessen + %1$d Dateien aus dem %2$s Verzeichnis konnten nicht kopiert werden nach + Mit Version 1.3.16 werden Dateien die von diesem Gerät aus hochgeladen werden in den lokalen Ordner %1$s kopiert um Datenverlust zu vermeiden, wenn eine einzelne Datei mit mehreren Accounts synchronisiert wird.\n\nInfolge dieser Änderung wurden alle Dateien, die mit vorherigen Versionen dieser App hochgeladen wurden, in den Ordner %2$s verschoben. Jedoch ist während der Account-Synchronisation ein Fehler aufgetreten, der das Abschließen dieses Vorgangs verhindert. Du kannst die Datei(en) entweder wie sie sind belassen und den Link zu %3$s entfernen oder die Datei(en) in den %1$s Ordner verschieben und den Link zu %4$s beibehalten.\n\nUnten befindet sich eine Liste der lokalen Datei(en) und der mit ihnen verbundenen Remote-Datei(en) in %5$s. + Das Verzeichnis %1$s existiert nicht mehr + Verschiebe alle + Alle Dateien wurden verschoben + Einige Dateien konnten nicht verschoben werden + Lokal: %1$s + Remote: %1$s + Es steht nicht genügend Speicherplatz zur Verfügung, um die ausgewählten Dateien in den %1$s Ordner zu kopieren. Möchtest Du sie stattdessen verschieben? Bitte gib Deine App-PIN ein - Bitte gib eine neue App-PIN ein Bitte gib Deine App-PIN ein PIN-Abfrage erfolgt nach Starten der App. Bitte gib Deine App-PIN erneut ein. @@ -122,50 +113,55 @@ Falsche App-PIN Die App-PIN wurde entfernt Die App-PIN wurde gespeichert - - 15 Minuten - 30 Minuten - 60 Minuten - - - 15 - 30 - 60 - + %1$s Musik Player + %1$s (playing) + %1$s (loading) + %1$s Beendet + Keine Meidendatei gefunden + Kein Konto angeben + Datei ist nicht in einem gültigen Account + Codierung wird nicht unterstützt + Fehler beim Lesen der Datei + Mediendatei nicht korrekt encodiert + Wartezeit beim Abspielversuch abgelaufen + Mediendatei konnte nicht gestreamt werden + Diese Mediendatei konnte nicht mit dem Standardplayer geöffnet werden + Sicherheitsfehler beim Versuch %1$s zu spielen + Eingabefehler beim Versuch %1$s zu spielen + Ein unerwarteter Fehler ist aufgetreten beim Versuch %1$s abzuspielen + Zurückspielen Knopf + Play-/Pause Knopf + Vorspulen Knopf Anmeldungsversuch... Keine Netzwerkverbindung - Es konnte keine Netzwerkverbindung gefunden werden, bitte überprüfe Deine Internetverbindung. - Trotzdem verbinden Sichere Verbindung nicht verfügbar. - Die App konnte keine sichere Verbindung zum Server herstellen. Eine nicht sichere Verbindung ist nichtsdestotrotz verfügbar. Möchtest Du fortfahren oder abbrechen? Verbindung hergestellt Verbindung testen... Fehlerhafte Server Konfiguration - Es scheint, als wäre Deine Server-Installation nicht richtig konfiguriert. Bitte kontaktiere Deinen Administrator, um weitere Details zu erhalten. + Ein Benutzerkonto für den gleichen Benutzer und Server existiert auf diesem Gerät bereits + Der eingegebene Benutzer passt nicht zu dem Benutzer dieses Benutzerkontos Ein unbekannter Fehler ist aufgetreten! - Ein unbekannter Fehler ist aufgetreten. Bitte kontaktiere Deinen Administrator unter Zu­hil­fe­nah­me der Log-Dateien Deines Gerätes. Konnte den Host nicht finden. - Konnte den eingetragenen Host nicht finden. Bitte prüfe den Hostnamen und die Verfügbarkeit des Servers und versuche es erneut. Server-Installation nicht gefunden - Die App konnte den Server unter dem angegebenen Pfad nicht finden. Bitte überprüfe den Pfad und versuche es erneut. Der Server braucht zu lange für eine Antwort. Fehlerhafte URL SSL-Initialisierung fehlgeschlagen. - Nichtüberprüfte SSL-Server-Identität + SSL-Server-Identität konnte nicht überprüft werden Unbekannte Server-Version Konnte keine Verbindung aufbauen. Sichere Verbindung hergestellt - Anmeldedetails - Benutzername oder Passwort ungültig - Falsche Pfadangabe - Interner Server Fehler, Fehlercode %1$d - Die Andwendung ist abgestürzt. Möchtest Du einen Bericht senden? - Bericht senden - Keinen Bericht senden - Erweiterung verfügbar! - Scheinbar unterstützt Dein Server weitere Erweiterungen. Möchtest Du die verfügbaren Erweiterungen für Android sehen? + Benutzername oder Passwort stimmen nicht! + Autorisierung nicht erfolgreich + Zugriff durch den Autorisierungsserver abgelehnt + Unerwarteter Zustand; bitte gib die URL des Servers nochmals ein + Deine Autorisierung ist abgelaufen. Bitte Autorisierung nochmals durchführen + Bitte gib dein aktuelles Passwort ein + Deine Sitzung ist abgelaufen. Bitte Anmeldung nochmals durchführen + Verbinde mit dem Authentifizierung-Server… + Der Server unterstützt diese Authentifizierung-Methode nicht + %1$s unterstützt nicht mehrere Benutzerkonten + Die Authentifizierung gegenüber dem Server konnte nicht durchgeführt werden Datei aktuell halten - Teilen Umbenennen Löschen Möchtest Du %1$s wirklich löschen? @@ -181,17 +177,19 @@ Die Umbenennung konnte nicht abgeschlossen werden. Die entfernte Datei konnte nicht überprüft werden Dateiinhalte bereits synchronisiert - Das Verzeichnis konnte nicht erstellt werden. + Verzeichnis konnte nicht erstellt werden + Verbotene Zeichen: / \\ < > : \" | ? * Bitte warte einen Moment. Ein unerwartetes Problem ist aufgetreten. Bitte versuche, die Datei in einer anderen App zu öffnen Es wurde keine Datei ausgewählt. - Warnung + Link senden an ... + Anmelden mit oAuth2 + Verbinde mit dem oAuth2-Server. Die Identität der Website konnte nicht überprüft werden - Das Zertifikat des Servers ist nicht vertrauenswürdig - Das Zertifikat des Servers ist abgelaufen - Das Gültigkeitsdatum des Serverzertifikats liegt in der Zukunft - Die Adresse stimmt nicht mit dem im Zertifikat angegebenen Hostnamen überein - Das Zertifikat des Servers konnte nicht abgerufen werden Möchtest Du diesem Zertifikat trotzdem vertrauen? Das Zertifikat konnte nicht gespeichert werden Details @@ -209,7 +207,14 @@ Bis: Signatur: Algorithmus: - Dies ist ein Platzhalter + Das Zertifikat konnte nicht gezeigt werden. + - Keine Informationen über den Fehler + Dies ist ein Platzhalter + platzhalter.txt + PNG-Bild + 389 KB + 18.5.2012 12:23 + 12:23:45 Fotos nur über WiFi hochladen /SofortUpload Konflikt beim Update @@ -217,16 +222,25 @@ Beide behalten Überschreiben Nicht hochladen - - Fehlgeschlagene Sofortuploads - Fehlgeschlagene Sofortuploads - Auflistung aller fehlgeschlagenen Softuploads - Alle auswählen - Ausgewählte wiederholen - Ausgewählte löschen - Versuche ausgewählte erneut hochzuladen + Bildvorschau + Dieses Bild kann nicht angezeigt werden + %1$s konnte nicht in den lokalen %2$s Ordner kopiert werden + Sofort-Upload fehlgeschlagen + SofortUpload fehlgeschlagen + Übersicht aller fehlgeschlagenen SofortUploads + Alles auswählen + Alles auswählen erneut versuchen + Auswahl aus der Upload Warteschlange entfernen + Hochladen des Bildes erneut versuchen: Weitere Bilder laden - Upload nicht gestarted, Sie sind nicht online für ein Softupload - Fehlermeldung: - Bitte überprüfen sie ihre Serverkonfiguration, möglicherweise ist ihre Upload Limit überschritten + Nicht durchgeführt - Nicht online für sofortigen Upload + Fehlermeldung: + Bitte überprüfe Deine Servereinstellungen. Eventuell ist Dein Nutzungslimit überschritten. + Die Freigabe der Datei oder des Ordners ist nicht möglich. Bitte stelle sicher, dass diese existiert. + Es ist ein Fehler beim Freigeben der Datei oder des Ordners aufgetreten. + Das Entfernen der Freigabe für die Datei oder den Ordner ist nicht möglich. Diese existieren nicht. + Es ist ein Fehler beim Entfernen der Freigabe für diese Datei oder den Ordner aufgetreten. + Senden + Link kopieren + In die Zwischenablage kopiert diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index faac70df..21285e1d 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -1,67 +1,50 @@ - ownCloud - Συνθηματικό: - Όνομα Χρήστη: - Σύνδεση - Καλώς ήλθατε στο ownCloud - Αρχεία - Μουσική - Επαφές - Ημερολόγιο - Σελιδοδείκτες - Ρυθμίσεις - Ρύθμιση Λογαριασμού - Δεν υπάρχουν λογαριασμοί ownCloud στη συσκευή σας. Για να χρησιμοποιήσετε την εφαρμογή, πρέπει να δημιουργήσετε ένα. - ownCloud εφαρμογή για Android\n\nέκδοση: %1$s - Ανανέωση + %1$s Εφαρμογή για Android + έκδοση %1$s + Ανανέωση λογαριασμού Μεταφόρτωση Περιεχόμενο από άλλες εφαρμογές Αρχεία - Δημιουργία καταλόγου - Αναζήτηση + Άνοιγμα με + Νέος φάκελος Ρυθμίσεις + Λεπτομέρειες + Αποστολή Γενικά - Παρακολούθηση συσκευής - Προσθήκη νέας συνεδρίας - Δημιουργία μικρογραφιών εικόνων - Επιλογή λογαριασμού - Επιλέξτε, ποιόν από τους λογαρισμούς σας θα χρησιμοποιήσει η εφαρμογή. - Παρακολούθηση συσκευής - Επιτρέψτε στο ownCloud να ανιχνεύει την τοποθεσία στης συσκευής σας - Το ownCloud ανιχνεύει την τοποθεσία αυτής της συσκευής - Περίοδος ανανέωσης - Ενημέρωση κάθε %1$s λεπτά + Περισσότερα Λογαριασμοί Διαχείριση λογαριασμών - PIN της εφαρμογής ownCloud - Προστατέψτε την ownCloud εφαρμογή + PIN της εφαρμογής + Προστατέψτε την εφαρμογή Ενεργοποιήστε την άμεση μεταφόρτωση Άμεση μεταφόρτωση των φωτογραφιών που τραβάει η φωτογρ. μηχανή - ownCloud URL + Ενεργοποίηση Καταγραφής Ιστορικού + Χρησιμοποιείται για την καταγραφή προβλημάτων + Ιστορικό Καταγραφής + Εδώ μπορείτε να δείτε το καταγεγραμμένο ιστορικό + Διαγραφή Ιστορικού + Βοήθεια + Προτείνετε σε ένα φίλο + Σχόλια + Αποτύπωμα + Δοκιμάστε %1$s στο κινητό σας! + Έλεγχος Διακομιστή + Διεύθυνση εξυπηρέτη https://… Όνομα χρήστη Συνθηματικό - Είμαι νέος στο ownCloud - Εσφαλμένη διεύθυνση URL - Εσφαλμένο όνομα συνεδρίας + Νέος στο %1$s; Αρχεία - Δεν επιλέχθηκε αρχείο για μεταφόρτωση - Όνομα χρήστη - Συνθηματικό - Διεύθυνση ιστοσελίδας - Προβολή κωδικού; - Σύνδεση στο ownCloud σας Σύνδεση Μεταφόρτωση Δεν βρέθηκε λογαριασμός - Δεν υπάρχουν λογαριασμοί ownCloud στη συσκευή σας. Παρακαλώ ρυθμίστε πρώτα ένα λογαριασμό. + Δεν υπάρχουν λογαριασμοί %1$s στη συσκευή σας. Παρακαλώ ρυθμίστε πρώτα ένα λογαριασμό. Ρύθμιση Κλείσιμο Δεν υπάρχει περιεχόμενο για να μεταφορτώσετε Δεν ελήφθη περιεχόμενο. Δεν υπάρχει τίποτα να μεταφορτώσετε. - Το ownCloud δεν επιτρέπεται να έχει πρόσβαση στο κοινόχρηστο περιεχόμενο + Το %1$s δεν επιτρέπεται να έχει πρόσβαση στο κοινόχρηστο περιεχόμενο Μεταφόρτωση - Δημιουργία καταλόγου για μεταφόρτωση Δεν υπάρχουν αρχεία σε αυτόν τον φάκελο.\nΝέα αρχεία μπορούν να προστεθούν με την επιλογή \"Μεταφόρτωση\" του μενού. Αγγίξτε κάποιο αρχείο για να προβάλετε περισσότερες πληροφορίες. Μέγεθος: @@ -69,10 +52,10 @@ Δημιουργήθηκε: Τροποποιήθηκε: Λήψη - Ανανέωση - Κατέβασμα ξανά - Άνοιγμα + Ανανέωση αρχείου Το αρχείο μετονομάστηκε σε %1$s κατά την μεταφόρτωση + Διαμοιρασμός συνδέσμου + Ακύρωση διαμοιρασμού συνδέσμου Ναι Όχι ΟΚ @@ -80,91 +63,102 @@ Ακύρωση αποστολής Άκυρο Αποθήκευση & Έξοδος - Κλείσιμο Σφάλμα + Φόρτωση ... + Άγνωστο σφάλμα Σχετικά + Αλλαγή συνθηματικού Διαγραφή λογαριασμού Δημιουργία λογαριασμού Μεταφόρτωση από ... - Όνομα καταλόγου + Όνομα φακέλου Μεταφορτώνεται ... %1$d%% Μεταφορτώνονται %2$s Η μεταφόρτωση ολοκληρώθηκε επιτυχώς Το %1$s μεταφορτώθηκε με επιτυχία - %1$d αρχεία μεταφορτώθηκαν με επιτυχία Η μεταφόρτωση απέτυχε Η μεταφόρτωση του %1$s δεν ήταν δυνατόν να ολοκληρωθεί - Αποτυχία μεταφόρτωσης: %1$d/%2$d αρχεία μεταφορτώθηκαν Κατέβασμα ... %1$d%% Λαμβάνονται %2$s Το κατέβασμα ολοκληρώθηκε επιτυχώς %1$s αρχεία λήφθηκαν με επιτυχία Το κατέβασμα απέτυχε Η λήψη του %1$s δεν μπόρεσε να ολοκληρωθεί με επιτυχία + Δεν έχει κατέβει ακόμα Επιλογή λογαριασμού - Επαφές Ο συγχρονισμός απέτυχε Ο συγχρονισμός του %1$s δεν μπόρεσε να ολοκληρωθεί + Λάθος κωδικός για %1$s Βρέθηκαν διενέξεις %1$d αρχεία σε αναμονή συγχρονισμού, δεν μπόρεσαν να συγχρονιστούν Τα αρχεία σε αναμονή συγχρονισμού απέτυχαν Τα περιεχόμενα των %1$d αρχείων δεν μπόρεσαν να συγχρονιστούν (%2$d διενέξεις) - Χρήση ασφαλούς σύνδεσης - Το ownCloud δεν μπορεί να ανιχνεύσει την τοποθεσία της συσκευής. Παρακαλώ ελέγξτε τις ρυθμίσεις σύνδεσης. + Ορισμένα τοπικά αρχεία ξεχάστηκαν + Ο φάκελος %1$s δεν υπάρχει πια + Μετακινηση ολων + Ολα τα αρχεια μετακινηθηκαν + Μερικα αρχεια δεν μπορεσαν να μετακινηθουν + Τοπικα: %1$s + Remote: %1$s + Δεν υπαρχει αρκετος χωρος για να αντιγραφθουν τα επιλεγμενα αρχεια στον χωρο αποθηκευσης %1$s. Θελετε να μετακινηθουν μερικα? Παρακαλώ, εισάγετε το PIN σας - Παρακαλώ, εισάγετε το νέο PIN σας - Εισάγετε το PIN της εφαρμογής ownCloud + Εισάγετε το PIN της εφαρμογής Το PIN θα ζητείται κάθε φορά στην εκκίνηση - Παρακαλώ επαναεισάγετε το PIN της εφαρμογής ownCloud - Αφαιρέστε τον PIN της εφαρμογής ownCloud - Δεν ταιριάζουν τα PIN της εφαρμογής ownCloud - Εσφαλμένο PIN της εφαρμογής ownCloud - Αφαιρέθηκε το PIN της εφαρμογής ownCloud - Το PIN της εφαρμογής ownCloud αποθηκεύτηκε - - 15 Λεπτά - 30 Λεπτά - 60 Λεπτά - - - 15 - 30 - 60 - + Παρακαλώ επαναεισάγετε το PIN της εφαρμογής + Αφαιρέστε τον PIN της εφαρμογής + Δεν ταιριάζουν τα PIN της εφαρμογής + Εσφαλμένο PIN της εφαρμογής + Αφαιρέθηκε το PIN της εφαρμογής + Το PIN της εφαρμογής αποθηκεύτηκε + %1$s αναπαραγωγή μουσικής + %1$s (αναπαραγωγή) + %1$s (φόρτωση) + %1$s αναπαραγωγή τελείωσε + Δεν βρέθηκε αρχείο πολυμέσων + Δεν δόθηκε λογαριασμός + Το αρχείο δεν βρίσκεται σε έγκυρο λογαριασμό + Αυτή η μορφή κωδικοποιήσης πολυμέσων δεν υποστηρίζεται + Το αρχείο πολυμέσων δεν μπόρεσε να διαβαστεί + Το αρχείο πολυμέσων δεν είναι σωστά κοδικοποιημένο + Λήξη χρόνου κατά την προσπάθεια αναπαραγωγής + Το αρχείο πολυμέσων δεν μπορεί να μεταδοθεί + Το αρχείο πολυμέσων δεν μπορεί να αναπαραχθεί με την παρεχόμενη εφαρμογή αναπαραγωγής πολυμέσων + Σφάλμα ασφαλείας κατά την προσπάθεια αναπαραγωγής του %1$s + Σφάλμα εισόδου κατά την προσπάθεια αναπαραγωγής του %1$s + Απροσδόκτο σφάλμα κατά την προσπάθεια αναπαραγωγής του %1$s + Κουμπί επαναφοράς + Κουμπί αναπαραγωγής ή παύσης + Κουμπί προώθησης Προσπάθεια σύνδεσης... Δεν υπάρχει σύνδεση στο δίκτυο - Δεν ανιχνεύθηκε σύνδεση δικτύου, ελέγξτε την σύνδεση στο Internet και προσπαθήστε ξανά. - Συνδεθείτε παρ\' αυτά Μη διαθέσιμη ασφαλής σύνδεση. - Αδυναμία καθιέρωσης ασφαλούς σύνδεσης με τον διακομιστή. Παρόλο που είναι διαθέσιμη μη ασφαλή σύνδεση. Μπορείτε να συνεχίσετε ή να ακυρώσετε. Επετεύχθη σύνδεση Έλεγχος σύνδεσης... - Λανθασμένες ρυθμίσεις ownCloud - Φαίνεται ότι το ownCloud δεν είναι σωστά ρυθμισμένο. Παρακαλώ επικοινωνήστε με τον διαχειριστή σας, για περισσότερες πληροφορίες. + Λανθασμένες ρυθμίσεις + Ένας λογαριασμός για τον ίδιο χρήστη και διακομιστή υπάρχει ήδη στη συσκευή + Ο χρήστης που εισάγατε δεν ταιριάζει με το χρήστη αυτού του λογαριασμού Παρουσιάστηκε άγνωστο σφάλμα - Παρουσιάστηκε άγνωστο σφάλμα. Παρακαλώ επικοινωνήστε με τους προγραμματιστές και συμπεριλάβετε το ιστορικό από την συσκευή σας. Δεν βρέθηκε υπολογιστής - Δεν βρέθηκε ο ζητούμενος υπολογιστής. Παρακαλώ ελέγξτε το όνομα του υπολογιστή και την διαθεσιμότητα του εξυπηρετητή και προσπαθήστε πάλι. - Δεν βρέθηκε στιγμιότυπο ownCloud - Η εφαρμογή δεν βρήκε να υπάρχει ownCloud στον δοσμένο μονοπάτι. Παρακαλώ ελέγξτε το μονοπάτι και ξαναπροσπαθήστε. + Δεν βρέθηκε στιγμιότυπο server σας Ο εξυπηρετητής αργεί πολύ να απαντήσει Κακώς διατυπωμένο URL Η αρχικοποίηση του SLL απέτυχε - Μη επιβεβαιωμένη ταυτότητα SSL του εξυπηρετητή - Μη αναγνωρίσιμη έκδοση διακομιστή ownCloud + Αδυναμία επιβεβαίωσης την ταυτότητα SSL του διακομιστή + Μη αναγνωρίσιμη έκδοση διακομιστή server σας Δεν ήταν δυνατή η σύνδεση Επιτεύχθηκε ασφαλής σύνδεση - Λεπτομέρειες σύνδεσης - Μη έγκυρο συνθηματικό εισόδου - Δόθηκε εσφαλμένη διαδρομή - Εσωτερικό σφάλμα διακομιστή, κωδικός %1$d - Η εφαρμογή τερματίστηκε ξαφνικά. Θα θέλατε να υποβάλετε αναφορά σφάλματος; - Αποστολή αναφοράς - Να μην αποσταλεί αναφορά - Διαθέσιμες επεκτάσεις! - Φαίνεται ότι το ownCloud σας υποστηρίζει προηγμένες επεκτάσεις. Θα θέλατε να δείτε τις διαθέσιμες επεκτάσεις για το android; + Λάθος όνομα χρήστη ή κωδικός + Η πιστοποίηση απέτυχε + Ο διακομιστής πιστοποίησης αρνήθηκε την πρόσβαση + Απρόοπτη κατάσταση - παρακαλώ εισάγετε τη διεύθυνση URL του διακομιστή ξανά + Η εξουσιοδότησή σας έληξε. Παρακαλώ εξουσιοδοτείστε ξανά + Παρακαλώ είσάγετε τον τρέχοντα κωδικό + Η συνεδρία σας έληξε. Παρακαλώ συνδεθείτε ξανά + Σύνδεση με το διακομιστή πιστοποίησης σε εξέλιξη... + Ο διακομιστής δεν υποστηρίζει αυτή τη μέθοδο πιστοποίησης + Ο %1$s δεν υποστηρίζει πολλαπλούς λογαριασμούς + Δεν είναι δυνατός ο έλεγχος ταυτότητας με αυτόν τον διακομιστή Διατήρηση αρχείου ενημερωμένo - Διαμοιρασμός Μετονομασία Αφαίρεση Θέλετε να αφαιρέσετε το %1$s ; @@ -180,17 +174,18 @@ Η μετονομασία δεν ήταν επιτυχής Αδυναμία ελέγχου του απομακρυσμένου αρχείου Τα περιεχόμενα του αρχείου έχουν ήδη συγχρονιστεί - Ο κατάλογος δεν ήταν δυνατόν να δημιουργηθεί + Μη-επιτρεπόμενοι χαρακτήρες: / \\ < > : \" | ? * Παρακαλούμε περιμένετε Απροσδόκητο σφάλμα, δοκιμάστε με άλλη εφαρμογή Δεν επιλέχθηκαν αρχεία - Προειδοποίηση + Αποστολή συνδέσμου σε ... + Σύνδεση με oAuth2 + Σύνδεση με το διακομιστή oAuth2 σε εξέλιξη... Η ταυτότητα της σελίδας δεν μπορεί να εγκριθεί - Το πιστοποιητικό του διακομιστή δεν είναι αξιόπιστο - Το πιστοποιητικό του διακομιστή έχει λήξει - Το πιστοποιητικό του διακομιστή είναι πολύ νέο - Η διεύθυνση URL δεν ταιριάζει με το όνομα στο πιστοποιητικό - Το πιστοποιητικό του διακομιστή δεν μπόρεσε να ανακτηθεί Θέλετε να θεωρείται έμπιστο το πιστοποιητικό αυτό ούτως ή άλλως; Το πιστοποιητικό δεν ήταν δυνατόν να αποθηκευτεί Λεπτομέρειες @@ -208,7 +203,14 @@ Μέχρι Υπογραφή: Αλγόριθμος: - Αυτό είναι ένα σημείο κράτησης θέσης + Δεν μπορεί να εμφανιστεί το πιστοποιητικό. + - Καμία πληροφορία σχετικά με το σφάλμα + Αυτό είναι ένα σημείο κράτησης θέσης + placeholder.txt + Εικόνα PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Μεταφόρτωση εικόνων μόνο μέσω WiFi /InstantUpload Ενημέρωση σύγκρουσης @@ -216,4 +218,24 @@ Διατήρηση και των δύο Αντικατάσταση Δεν μεταφορτώθηκε + Προεπισκόπηση εικόνας + Αυτή η εικόνε δεν μπόρεσε να προβληθεί + Αποτυχημένη στιγμιαία φόρτωση + Αποτυχημένες στιγμιαίες φορτώσεις + Σύνοψη όλων των αποτυχημένων φορτώσεων + επιλογή όλων + επανάληψη για όλα τα επιλεγμένα + διαγραφή όλων των επιλεγμένων από τη λίστα προς μεταφόρτωση + επανάληψη προσπάθειας μεταφόρτωσης της εικόνας: + Φόρτωση περισσότερων εικόνων + μην κάνετε τίποτε, δεν είστε συνδεμένος για άμεση μεταφόρτωση + Μήνυμα Αποτυχίας: + Παρακαλώ ελέγξτε τις ρυθμίσεις του διακομιστή σας, ίσως έχετε υπερβεί τη διαθέσιμη μερίδα σας. + Αδυναμία διαμοιρασμού αυτού του αρχείου ή φακέλου. Παρακαλώ βεβαιωθείτε ότι υπάρχει + Ένα σφάλμα προέκυψε κατά την προσπάθεια διαμοιρασμού αυτού του αρχείου ή φακέλου + Ήταν αδύνατη η ακύρωση διαμοιρασμού αυτού του αρχείου ή φακέλου. Δεν υπάρχει. + Ένα σφάλμα προέκυψε κατά τη διάρκεια ακύρωσης διαμοιρασμού αυτού του αρχείου ή φακέλου + Αποστολή + Αντιγραφή συνδέσμου + Αντιγραφθηκε στο clipboard diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml new file mode 100644 index 00000000..27817ea1 --- /dev/null +++ b/res/values-en-rGB/strings.xml @@ -0,0 +1,246 @@ + + + %1$s Android App + version %1$s + Refresh account + Upload + Content from other apps + Files + Open with + New folder + Settings + Details + Send + General + More + Accounts + Manage Accounts + App PIN + Protect your client + Enable instant uploads + Instantly upload photos taken by camera + Enable Logging + This is used to log problems + Logging History + This shows the recorded logs + Delete History + Help + Recommend to a friend + Feedback + Imprint + Try %1$s on your smartphone! + Check Server + Server address https://… + Username + Password + New to %1$s? + Files + Connect + Upload + Choose upload folder: + No account found + There are no %1$s accounts on your device. Please setup an account first. + Setup + Quit + No content to upload + No content was received. Nothing to upload. + %1$s is not allowed to access the shared content + Uploading + There are no files in this folder.\nNew files can be added with the \"Upload\" menu option. + Tap on a file to display additional information. + Size: + Type: + Created: + Modified: + Download + Refresh file + File was renamed to %1$s during upload + Share link + Unshare link + Yes + No + OK + Cancel download + Cancel upload + Cancel + Save & Exit + Error + Loading … + Unknown error + About + Change password + Delete account + Create account + Upload from … + Folder name + Uploading … + %1$d%% Uploading %2$s + Upload succeeded + %1$s was successfully uploaded + Upload failed + Upload of %1$s could not be completed + Downloading … + %1$d%% Downloading %2$s + Download succeeded + %1$s was successfully downloaded + Download failed + Download of %1$s could not be completed + Not downloaded yet + Choose account + Synchronisation failed + Synchronisation of %1$s could not be completed + Invalid password for %1$s + Conflicts found + %1$d kept-in-sync files could not be synced + Kept-in-sync files failed + Contents of %1$d files could not be synced (%2$d conflicts) + Some local files were forgotten + %1$d files out of the %2$s folder could not be copied into + As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronisation. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s folder and retain the link to %4$s.\n\nListed below are the local file(s), and the remote file(s) in %5$s they were linked to. + Folder %1$s does not exist anymore + Move all + All files were moved + Some files could not be moved + Local: %1$s + Remote: %1$s + There is not enough space to copy the selected files into the %1$s folder. Would you like to move them instead? + Please, insert your App PIN + Enter your App PIN + The PIN will be requested every time the app is started + Please, re-enter your App PIN + Remove your App PIN + The App PINs are not the same + Incorrect App PIN + App PIN removed + App PIN stored + %1$s music player + %1$s (playing) + %1$s (loading) + %1$s playback finished + No media file found + No account provided + File not in a valid account + Unsupported media codec + Media file could not be read + Media file not correctly encoded + Timed out whilst trying to play + Media file cannot be streamed + Media file cannot be played with the stock media player + Security error trying to play %1$s + Input error trying to play %1$s + Unexpected error trying to play %1$s + Rewind button + Play or pause button + Fast-forward button + Trying to login… + No network connection + Secure connection unavailable. + Connection established + Testing connection… + Malformed server configuration + An account for the same user and server already exists on the device + The entered user does not match the user of this account + Unknown error occurred! + Couldn\'t find host + Server instance not found + The server took too long to respond + Malformed URL + SSL initialisation failed + Couldn\'t verify SSL server\'s identity + Unrecognised server version + Couldn\'t establish connection + Secure connection established + Wrong username or password + Unsuccessful authorisation + Access denied by authorisation server + Unexpected state; please, enter the server URL again + Your authorisation expired. Please, authorise again + Please enter the current password + Your session expired. Please connect again + Connecting to authentication server… + The server does not support this authentication method + %1$s does not support multiple accounts + Cannot authenticate against this server + Keep file up to date + Rename + Remove + Do you really want to remove %1$s ? + Do you really want to remove %1$s and its contents ? + Local only + Local contents only + Remove from server + Remote and local + Removal succeeded + Removal failed + Enter a new name + Local copy could not be renamed; try a different name + Rename could not be completed + Remote file could not be checked + File contents already synchronised + Folder could not be created + Forbidden characters: / \\ < > : \" | ? * + Wait a moment + Unexpected problem; please select the file from a different app + No file was selected + Send link to … + Log in with oAuth2 + Connecting to oAuth2 server… + The identity of the site could not be verified + - The server certificate is not trusted + - The server certificate expired + - The server certificate valid dates are in the future + - The URL does not match the hostname in the certificate + Do you want to trust this certificate anyway? + The certificate could not be saved + Details + Hide + Issued to: + Issued by: + Common name: + Organisation: + Organisational unit: + Country: + State: + Location: + Validity: + From: + To: + Signature: + Algorithm: + The certificate could not be shown. + - No information about the error + This is a placeholder + placeholder.txt + PNG Image + 389 KB + 2012/05/18 12:23 PM + 12:23:45 + Upload pictures via WiFi only + /InstantUpload + Update conflict + Remote file %s is not synchronised with local file. Continuing will replace content of file on server. + Keep both + Overwrite + Don\'t upload + Image preview + This image cannot be shown + %1$s could not be copied to %2$s local folder + Failed InstantUpload + Failed instant uploads + Summary of all failed instant uploads + select all + retry all selected + delete all selected from uploadqueue + retry uploading the image: + Load more Pictures + do nothing you are not online for instant upload + Failure Message: + Please check your server configuration, perhaps your quota is exceeded. + Unable to share this file or folder. Please, make sure it exists + An error occurred while trying to share this file or folder + Unable to unshare this file or folder. It does not exist. + An error occurred while trying to unshare this file or folder + Send + Copy link + Copied to clipboard + diff --git a/res/values-eo/strings.xml b/res/values-eo/strings.xml index adac8674..00d3034a 100644 --- a/res/values-eo/strings.xml +++ b/res/values-eo/strings.xml @@ -1,76 +1,42 @@ - ownCloud - Pasvorto: - Uzantonomo: - Ensaluti - Bonvenon al via ownCloud - Dosieroj - Muziko - Kontaktoj - Kalendaro - Legosignoj - Agordo - Agordi konton - Estas neniu konto de ownCloud en via aparato. Por povi uzi ĉi tiun aplikaĵon, vi devas krei iun. - Android-aplikaĵo %1$s\n\neldono: %2$s - Refreŝigi Alŝuti dosieron Enhavo el aliaj aplikaĵoj Dosieroj - Krei dosierujon - Serĉi + Nova dosierujo Agordo + Detaloj + Sendi Ĝeneralo - Spurado de aparato - Aldoni novan sesion - Krei bildetojn - Elekti konton - Elektu kiun el viaj kontoj la aplikaĵo uzu. - Spurado de aparato - Kapabligi ownCloud-on spuri la lokon de via aparato - Ĝisdatiga intervalo - Ĝisdatigi je ĉiuj %1$s minutoj + Pli Kontoj Administri kontojn - PIN de ownCloud-aplikaĵo - Protekti vian ownCloud-klienton + PIN de App-aplikaĵo + Protekti vian klienton Kapabligi tujan alŝuton Tuje alŝuti fotojn faritajn per fotilo - URL de ownCloud + Helpo Uzantonomo Pasvorto - Mi estas nova ĉe ownCloud - Malĝusta URL doniĝis - Malĝusta sesionomo Dosieroj - Neniu dosiero estas elektita por alŝuto - Uzantonomo - Pasvorto - TTT-adreso - Ĉu montri la pasvorton? - Konekti al via ownCloud Konekti Alŝuti Neniu konto troviĝis - Estas neniu ownCloud-konto en via aparato. Bonvolu agordi konton unue. + Estas neniu $1%s-konto en via aparato. Bonvolu agordi konton unue. Agordi Forlasi Neniu enhavo alŝutota Neniu enhavo riceviĝis. Nenio alŝutota. - ownCloud ne estas permesata aliri la kunhavigitan enhavon + $1%s ne estas permesata aliri la kunhavigitan enhavon Alŝutante - Krei dosierujon por alŝuto Neniu dosiero estas en ĉi tiu dosierujo.\nNovajn dosierojn vi povas aldoni per la menuero “Alŝuti”. Grando: Tipo: Kreita je: Modifita je: Elŝuti - Refreŝigi - Reelŝuti - Malfermi La dosiero alinomiĝis al %1$s dum alŝuto + Konhavigi ligilon Jes Ne Akcepti @@ -78,9 +44,10 @@ Nuligi alŝuton Nuligi Konservi kaj forlasi - Forlasi ownCloud-on Eraro + Nekonata eraro Pri + Ŝanĝi la pasvorton Forigi konton Krei konton Alŝuti dosieron el... @@ -89,10 +56,8 @@ %1$d%% Alŝutante %2$s Alŝuto sukcesis %1$s sukcese alŝutiĝis - %1$d dosieroj sukcese alŝutiĝis Alŝuto malsukcesis Alŝuto de %1s ne eblis plenumiĝi - Alŝuto malsukcesis: %1$d el %2$d dosieroj alŝutiĝis Elŝutante... %1$d%% Elŝutante %2$s Elŝuto sukcesis @@ -100,64 +65,36 @@ Elŝuto malsukcesis Elŝuto de %1$s ne eblis plenumiĝi Elekti konton - Kontaktoj Sinkronigo malsukcesis Sinkronigo de %1$s ne povis plenumiĝi Konfliktoj troviĝis %1$d ade sinkronigataj dosieroj ne povis sinkroniĝi Malsukcesis ada sinkronigo de dosieroj Enhavoj de %1$d dosieroj ne povis sinkroniĝi (%2$d konfliktas) - Uzi sekuran konekton - ownCloud ne povas spuri vian aparaton. Bonvolu kontroli vian lokagordon. Bonvolu enigi vian PIN-on de aplikaĵo - Bonvolu enigi vian novan PIN-on de aplikaĵo - Enigu PIN-on de ownCloud-aplikaĵo - Bonvolu reenigi PIN-on de ownCloud-aplikaĵo - Forigu vian PIN-on de ownCloud-aplikaĵo - La du PIN-oj de ownCloud-aplikaĵo malsamas - Malĝusta PIN de ownCloud-aplikaĵo - PIN de ownCloud-aplikaĵo foriĝis - PIN de ownCloud-aplikaĵo konserviĝis - - 15 minutoj - 30 minutoj - 60 minutoj - - - 15 - 30 - 60 - + Enigu PIN-on de aplikaĵo + Bonvolu reenigi PIN-on de aplikaĵo + Forigu vian PIN-on de aplikaĵo + La du PIN-oj de aplikaĵo malsamas + Malĝusta PIN de aplikaĵo + PIN de aplikaĵo foriĝis + PIN de aplikaĵo konserviĝis Provante ensaluti... Neniu reta konekto - Neniu reta konekto detektiĝis; kontrolu vian Interretan konekton kaj provu denove. - Konekti ĉiuokaze Sekura konekto ne haveblas. Konekto stariĝis Testante konekton... - Malĝuste formita ownCloud-agordo + Malĝuste formita servilo-agordo Nekonata eraro okazis - Nekonata eraro okazis. Bonvolu kontakti verkintojn kaj inkluzivigi registrojn el via aparato. Ne eblis trovi gastigon - Apero de ownCloud ne troviĝis - La aplikaĵo ne povis trovi ownCloud-aperon en la vojo donita. Bonvolu kontroli vian vojon kaj provi denove. + Apero de servilo ne troviĝis La servilo tro malfruis por respondi Malĝuste formita URL SSL-ekigo malsukcesis - Nekontrolita idento de SSL-servilo - Nerekonita eldono de ownCloud-servilo + Nerekonita eldono de servilo Ne eblis starigi konekton Sekura konekto stariĝis - Ensalutaj detaloj - Nevalida ensalutonomo/pasvorto - Malĝusta vojo doniĝis - Ena servila eraro: kodo %1$d - Aplikaĵo finis neatendite. Ĉu vi volus sendi paneoraporton? - Sendi raporton - Ne sendi raporton - Kromaĵoj haveblas! Teni dosieron ĝisdatigita - Kunhavigi Alinomigi Forigi Ĉu vi vere volas forigi %1$s? @@ -173,17 +110,14 @@ Alinomigo ne povis plenumiĝi Malloka dosiero ne povis kontroliĝi Dosierenhavoj jam sinkroniĝis - Dosierujo ne povis kreiĝi Atendu momenton Neatendita problemo; bonvolu provi alian aplikaĵon por elekti la dosieron Neniu dosiero elektiĝis - Averto La idento de la ejo ne povis kontroliĝi - La servila atestilo ne fidindas - La servila atestilo eksvalidiĝis - La servila atestilo tro junas - La URL ne kongruas kun la gastignomo en la atestilo - La servila atestilo ne povis ekhaviĝi Ĉu vi volas fidi ĉi tiun atestilon ĉiuokaze? La atestino ne povis konserviĝi Detaloj @@ -199,11 +133,13 @@ Al: Subskribo: Algoritmo: - Ĉi tio estas lokokupilo + Ĉi tio estas lokokupilo Alŝuti bildojn nur per WiFi Alŝuta konflikto La malloka dosiero %s estas ne sinkronigita kun loka dosiero. Se la ago daÅ­rus, enhavo de la dosiero en la servilo anstataÅ­iĝus. Konservi ambaÅ­ AnstataÅ­igi Ne alŝuti + Sendi + Kopiita en la tondejon diff --git a/res/values-es-rAR/strings.xml b/res/values-es-rAR/strings.xml index b3df23d8..cd61dcd5 100644 --- a/res/values-es-rAR/strings.xml +++ b/res/values-es-rAR/strings.xml @@ -1,56 +1,40 @@ - ownCloud - Password: - Nombre de usuario: - Entrar - Bienvenido - Archivos - Música - Contactos - Calendario - Marcadores - Configuración - Configurar cuenta - No hay cuentas en tu dispositivo. Para usar esta aplicación, tenés que crear una. - %1$s para Android\n\nversión: %2$s - Recargar + App Android %1$s + versión %1$s + Actualizar cuenta Subir Contenido de otras aplicaciones Archivos - Crear directorio - Buscar + Abrir con + Nueva Carpeta Configuración + Detalles + Mandar General - Seguimiento del dispositivo - Agregar una sesión nueva - Crear miniaturas de las imágenes - Elegir una cuenta - Elegí cual de tus cuentas esta aplicación debería usar. - Rastreo de dispositivo - Habilitar la localización de tu dispositivo - La aplicación está haciendo un seguimiento de este dispositivo - Intervalo de actualización - Actualizar cada %1$s minutos + Más Cuentas Gestionar cuentas PIN de aplicación Protejé tu cliente Habilitar la subida inmediata Subir inmediatamente las fotos sacadas con la cámara - URL + Habilitar registro + Esto es usado para registrar problemas + Historia del Registro + Esto muestra los registros grabados + Eliminar Historial + Ayuda + Recomendar a un amigo + Sugerencias + Imprint + ¡Intento %1$s en tu teléfono inteligente! + Verificar Servidor + Dirección del servidor https://... Nombre de usuario Contraseña - Soy nuevo en %1$s - La URL es incorrecta - Nombre de sesión equivocado + ¿Sos nuevo para %1$s? Archivos - No se seleccionaron archivos para subir - Nombre de usuario - Contraseña - Dirección web - Mostrar contraseña? - Conectate a tu %1$s Conectar Subir No se encontraron cuentas @@ -61,7 +45,6 @@ No se recibió ningún contenido. No hay nada para subir. %1$s no está autorizado para acceder al contenido compartido Subiendo - Crear directorio para subir No hay archivos en este directorio.\nPodés agregar archivos a través de la opción del menú \"Subir\". Pulsá sobre un archivo para mostrar información adicional. Tamaño: @@ -69,10 +52,9 @@ Creado: Modificado: Descargar - Recargar - Volver a descargar - Abrir + Actualizar archivo El archivo fue renombrado como %1$s durante la subida + Compartir vínculo Sí No Aceptar @@ -80,39 +62,45 @@ Cancelar subida Cancelar Guardar y salir - Salir de %1$s Error + Cargando ... + Error desconocido Acerca de + Cambiar contraseña Eliminar cuenta Crear cuenta Subir desde ... - Nombre del directorio + Nombre de la carpeta Subiendo... %1$d%% Subiendo %2$s Subido con éxito %1$s se ha subido con éxito - %1$d archivos se han subido con éxito Error al subir el archivo No se pudo completar la subida de %1$s - Error al subir: %1$d/%2$d archivos fueron subidos Descargando ... %1$d%% descargando %2$s Descarga completa %1$s fue descargado con éxito Error al descargar No fue posible completar la descarga de %1$s + No descargado Elegí una cuenta - Contactos Error en la sincronización No se pudo completar la sincronización de %1$s + Contraseña no válida para %1$s Se encontraron conflictos No se pudieron sincronizar %1$d archivos Fallo la sincronización de archivos %1$d archivos no pudieron ser sincronizados (%2$d conflictos) - Usar conexión segura - %1$s no puede rastear tu dispositivo. Verificá la configuración de localización + Algunos archivos locales fueron olvidados + El directorio %1$s ya no existe + Mover todos + Todos los archivos fueron movidos + Algunos archivos no pudieron ser movidos + Local: %1$s + Remote: %1$s + No hay suficiente espacio para copiar los archivos seleccionados al directorio %1$s. Querés moverlos ahí? Por favor, escribí el PIN de la aplicación - Por favor, escribí el nuevo PIN de la aplicación Ingresá el PIN de la aplicación Se te pedirá el PIN cada vez que esta app sea iniciada. Por favor, ingresá nuevamente el PIN de la aplicación @@ -121,50 +109,54 @@ El PIN de la aplicación es incorrecto El PIN de la aplicación fue borrado El PIN de la aplicación fue almacenado - - 15 minutos - 30 minutos - 60 minutos - - - 15 - 30 - 60 - + Reproductor de música %1$s + %1$s (reproduciendo) + %1$s (cargando) + %1$s reproducción finalizada + No se encuentra archivo de medio + No se proporcionó cuenta + El archivo no está en una cuenta válida + Codec no soportado + El archivo de medios no pudo ser leído + Archivo no está correctamente codificado + Tiempo expirado mientras se intentaba reproducir + Archivo de medios no puede ser transmitido + El archivo de medios no se puede reproducir con el reproductor de medios por defecto + Error de seguridad al intentar reproducir %1$s + Error de entrada al intentar reproducir %1$s + Error inesperado intentando reproducir %1$s + Botón rebobinado + Botón de reproducción o pausa + Botón avance rápido Intentado iniciar sesión... Sin conexión de red - No se ha detectado una conexión de red, revisá tu conexión a internet e intentá nuevamente. - Conectar de todos modos Conexión segura no disponible. - La aplicación no pudo establecer una conexión segura al servidor. Aunque no haya una conexión segura disponible, podés continuar o cancelar. Conexión establecida Probando conexión... Configuración de servidor en formato incorrecto - Parece ser que la instancia de tu servidor no está configurada correctamente. Comunicate con el administrador para más detalles. + Una cuenta para el mismo usuario y servidor ya existe en el dispositivo + El usuario ingresado no concuerda con el usuario de esta cuenta ¡Ocurrió un error desconocido! - Ocurrió un error desconocido. Por favor, comunicate con los autores e incluí los registros de tu dispositivo. No fue posible encontrar la dirección - No se pudo encontrar el servidor. Consultá si el servidor está disponible, su dirección, e intentalo nuevamete. Instancia de servidor no encontrada - La aplicación no pudo encontrar la instancia del servidor en la ruta de acceso dada. Por favor verificá la ruta de acceso e intentalo nuevamente. El servidor tardó demasiado en responder URL no válida Error al inicializar el SSL - No se puede verificar la Identidad del servidor SSL + No se ha podido verifica la identidad SSL del servidor No es posible reconocer la versión del servidor No fue posible establecer la conexión Conexión segura establecida - Detalles de inicio de sesión - Nombre / contraseña incorrectos - Ruta incorrecta - Error interno en el servidor, código %1$d - La aplicación finalizó inesperadamente. ¿Querés mandar un reporte de error? - Enviar reporte - No enviar reporte - ¡Hay extensiones disponibles! - Parece que tu instancia de servidor soporta extensiones avanzadas. ¿Querés ver las extensiones disponibles para Android? + Nombre de usuario o contraseña no válidas + Autorización no satisfactoria + Acceso denegado por el servidor de autorización + Estado inesperado; por favor, introducí la URL del servidor de nuevo + Tu autorización ha expirado. Por favor, obtenga una nueva autorización + Por favor, ingresá tu contraseña actual + Su sesión ha expirado. Por favor, conéctese de nuevo + Conectando al servidor de autenticación... + El servidor no soporta este método de autenticación + %1$s no soporta múltiples cuentas Mantener el archivo actualizado - Compartir Renombrar Borrar ¿Estás seguro que querés borrar %1$s? @@ -180,17 +172,17 @@ No se pudo cambiar el nombre No pudo comprobarse el archivo remoto Ya está sincronizado - No fue posible crear el directorio + Caracteres prohibidos: / \\ < > : \" | ? * Esperá un momento Problema inesperado; por favor, intentá con otra aplicación para abrir el archivo No se seleccionó ningún archivo - Alerta + Iniciando sesión con oAuth2 + Conectando al servidor oAuth2... La identidad del sitio no pudo ser verificada - El certificado del servidor no es confiable - El certificado del servidor caducó - El certificado del servidor es demasiado nuevo - La URL no se condice con el nombre del servidor en el certificado - No se pudo obtener el certificado del servidor ¿Confiás en el certificado de todos modos? No fue posible guardar el certificado Detalles @@ -208,7 +200,12 @@ Para: Firma: Algoritmo: - Este es un texto temporario + Este es un texto temporario + marcadordeposición.txt + Imagen PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Subir imágenes solamente a través de WiFi /SubidasInstantáneas Conflicto en la actualización @@ -216,4 +213,19 @@ Mantener ambas Sobrescribir No subir + Previsualización de imagen + No se puede mostrar la imagen + Falló InstantUpload + Error en Cargas instantánea + Resumen de todas las cargas instantáneas con error + Seleccionar todos + Reintentar con todos los seleccionados + Eliminar todo de la cola de subida + Reintentar subida de imagen: + Cargar mas imágenes + No hacer nada, no estás conectado para subida instantánea + Mensaje de error: + Por favor, revisá la configuración de servidor, posiblemente tu cuota se haya excedido. + Mandar + Copiado al portapapeles diff --git a/res/values-es-rCL/strings.xml b/res/values-es-rCL/strings.xml new file mode 100644 index 00000000..3cc00c08 --- /dev/null +++ b/res/values-es-rCL/strings.xml @@ -0,0 +1,30 @@ + + + Subir + Archivos + Configuración + General + Cuentas + Usuario + Clave + Archivos + Conectar + Subir + No se encuentra la cuenta + Subiendo + Seleccione un archivo para desplegar información adicional. + Tamaño: + Tipo: + Creado: + Modificado: + Descargar + Si + No + OK + Cancelar + Error + Elija una cuenta + Por favor, ingreses su PIN de aplicación + Ingrese su PIN de aplicación + Por favor, reingrese su PIN de aplicación + diff --git a/res/values-es-rMX/strings.xml b/res/values-es-rMX/strings.xml new file mode 100644 index 00000000..ab6ed1e5 --- /dev/null +++ b/res/values-es-rMX/strings.xml @@ -0,0 +1,231 @@ + + + App Android %1$s + versión %1$s + Actualizar cuenta + Subir archivo + Contenido de otras aplicaciones + Archivos + Abrir con + Nueva carpeta + Ajustes + Detalles + Enviar + General + Más + Cuentas + Gestionar cuentas + PIN de aplicación + Proteja su cliente + Habilita la subida instantánea + Subir instantáneamente las fotos tomadas por la cámara + Habilitar registro + Esto es usado para registrar problemas + Historia del Registro + Esto muestra los registros grabados + Eliminar Historial + Ayuda + Recomendar a un amigo + Mensajes de retroalimentación + Imprint + Prueba %1$s en tu smarthphone! + Compruebe el servidor. + Dirección del servidor https://… + Nombre de usuario + Contraseña + New to %1$s? + Archivos + Conectar + Subir + No se encontraron cuentas + No hay cuentas de %1$s en tu dispositivo. Por favor configura una cuenta primero. + Configuración + Salir + No hay contenido para subir + Ningún contenido ha sido recibido. No hay nada que subir. + %1$s no está autorizado para acceder al contenido compartido + Enviando + No hay archivos en esta carpeta.\nPuedes añadir nuevos archivos con la opción \"Subir\" del menú. + Pulsa sobre un archivo para mostrar información adicional. + Tamaño: + Tipo: + Creado: + Modificado: + Descargar + Actualizar archivo + El archivo fue renombrado como %1$s durante la subida + Enlace compartido + Sí + No + Aceptar + Cancelar descarga + Cancelar subida + Cancelar + Guardar & Salir + Error + Cargando ... + Error desconocido + Acerca de + Cambiar contraseña + Eliminar cuenta + Crear cuenta + Subir + Nombre de la carpeta + Subiendo... + %1$d%% Subiendo %2$s + Subido con éxito + %1$s se ha subido con éxito + Error en la subida + La subida de %1$s no se pudo completar + Descargando ... + %1$s Descargada de %2$s + Descarga completa + %1$s se ha descargado con éxito + Falló la descarga + La descarga de %1$s no se pudo completar + No descargado + Elige una cuenta + Falló la sincronización + La sincronización de %1$s s no se pudo completar + Contraseña no válida para %1$s + Se encontraron conflictos + Falló la sincronización de contenidos de %1$d archivos + Fallos en la sincronización de contenidos + Los contenidos de %1$d archivos no fueron sincronizados (%2$d conflictos) + Algunos archivos locales se han perdido + La carpeta local %1$s no existe. + Mover todo + Todos los archivos fueron movidos + Algunos archivos no han podido ser movidos + Local: %1$s + Remoto: %1$s + No hay suficiente espacio para copiar los archivos seleccionados en la carpeta %1$s. ¿Quiere moverlos en lugar de copiarlos? + Por favor, inserta tu PIN de aplicación + Introduzca un PIN para la aplicación + Se solicitará el PIN cada vez que se inicie la aplicación + Repita el PIN para la aplicación, por favor + Borre su PIN de aplicación + Los PIN introducidos no son iguales + PIN de aplicación incorrecto + PIN de aplicación borrado + PIN de aplicación guardado + Reproductor de música %1$s + %1$s (reproduciendo) + %1$s (cargando) + %1$s reproducción finalizada + No se encuentra archivo de medio + No se ha proporcionado cuenta + El archivo no esta en una cuenta valida + Codec No Soportado + El archivo de medios no pudo ser leído + Archivo no codificado correctamente + Tiempo de espera agotado en el intento de reproducción + Archivo de medio no puede ser transmitido + El archivo de medios no se puede reproducir con el reproductor de medios por defecto + Error de seguridad al intentar reproducir %1$s + Error de entrada al intentar reproducir %1$s + Error inesperado intentando reproducir %1$s + Botón Rebobinado + Botón de reproducción o pausa + Botón avance rápido + Intentado iniciar sesión... + Sin conexión de red + Conexión segura no disponible. + Conexión establecida + Probando conexión... + Configuración de servidor en formato incorrecto + Una cuenta para el mismo usuario y servidor ya existen en el dispositivo + El usuario introducido no concuerda con el usuario de esta cuenta + Ocurrió un error desconocido + No se pudo encontrar la dirección + Instancia de servidor no encontrada + El servidor ha tardado demasiado en responder + URL no válida + Falló la inicialización SSL + No fue posible verificar la identidad del servidor SLL + No se reconoce la versión del servidor + No se ha podido establecer la conexión + Conexión segura establecida + Nombre de usuario o contraseña incorrecta + Autorización no satisfactoria + Acceso denegado por servidor de autorización + Estado inesperado; por favor, introduzca la URL del servidor de nuevo + Su autorización ha expirado. Por favor, autorice de nuevo + Por favor, introduzca la contraseña actual. + Su sesión ha expirado. Favor de conectarse de nuevo + Conectando al servidor de autenticación... + El servidor no soporta este método de autenticación + %1$s no soporta cuentas múltiples + Mantener el archivo actualizado + Renombrar + Borrar + ¿Está seguro que desea borrar %1$s ? + ¿Desea elimiar %1$s y sus descendientes? + Sólo local + Sólo archivos locales + Eliminar del servidor + Tanto remoto como local + Borrado correctamente + El borrado no pudo ser completado + Introduzca un nombre nuevo + No se pudo cambiar el nombre de la copia local, trata con un nombre differente + No se pudo cambiar el nombre + No pudo comprobarse el archivo remoto + Ya está sincronizado + Carácteres ilegales: / \\ < > : \" | ? * + Espere un momento + Problema inesperado; por favor, prueba otra app para seleccionar el archivo + No fué seleccionado ningún archivo + Ingresar con oAuth2 + Conectando al servidor oAuth2... + La identidad del sitio no puede ser verificada + - El certificado del servidor no es de confianza + - El certificado del servidor expiró + - El certificado del servidor es demasiado reciente + - La URL no coincide con el nombre de dominio del certificado + ¿Confías de todas formas en este certificado? + El certificado no pudo ser guardado + Detalles + Ocultar + Emitido para: + Emitido por: + Nombre común: + Organización: + Unidad organizativa + Pais: + Estado: + Ubicación: + Validez: + De: + A: + Firma: + Algoritmo: + Esto es un marcador de posición + marcadordeposición.txt + Imagen PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 + Subir imágenes sólo via WiFi + /SubidasInstantáneas + Conflicto en la actualización + El archivo remoto %s no está sincronizado con el archivo local. Si continúa, se reemplazará el contenido del archivo en el servidor. + Mantener ambas + Sobrescribir + No subir + Previsualización de imagen + No se puede mostrar la imagen + Carga instantánea fallida + Cargas instantáneas fallidas + Resumen de todas las cargas instantáneas fallidas + Seleccionar todos + Reintentar todos los seleccionados + Eliminar todo de la cola de subida + Reintentar subida de imagen: + Cargar mas imágenes + No hacer nada no está conectado para subida instantánea + Mensaje de error: + Por favor revise su configuración de servidor, posiblemente su cuota se haya excedido. + Enviar + Copiado al portapapeles + diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 5b8691a4..7136e032 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -1,67 +1,51 @@ - ownCloud - Contraseña: - Nombre de usuario: - Inicio de sesión - Bienvenido - Archivos - Música - Contactos - Calendario - Marcadores - Ajustes - Configuración de cuenta - No hay cuentas en tu dispositivo. Para usar esta aplicación, necesitas crear una. - %1$s para Android\n\nversión: %2$s - Sincronización de cuenta - Subir archivo + %1$s para Android + versión %1$s + Actualizar cuenta + Subir Contenido de otras aplicaciones Archivos - Crear directorio - Buscar - Ajustes + Abrir con + Nueva carpeta + Configuración + Detalles + Enviar General - Rastreo de dispositivo - Agregar nueva sesión - Crear miniaturas de imagen - Seleccionar una cuenta - Elige, cuál de tus cuentas la aplicación debería usar. - Rastreo de dispositivo - Permitir la localización de tu dispositivo - La app realiza un seguimiento de este dispositivo - Intervalo de actualización - Actualizar cada %1$s minutos + Más Cuentas Gestionar cuentas PIN de aplicación Proteja su cliente - Habilita la subida instantánea + Activar la subida instantánea Subir instantáneamente las fotos tomadas por la cámara - URL + Habilitar registro + Esto es usado para registrar problemas + Historia del Registro + Esto muestra los registros grabados + Eliminar Historial + Ayuda + Recomendar a un amigo + Mensajes de retroalimentación + Imprint + ¡Prueba %1$s en tu teléfono inteligente! + Compruebe el servidor. + Dirección del servidor https://… Nombre de usuario Contraseña - Soy nuevo en %1$s - La URL dada es incorrecta - Nombre de sesión incorrecto + New to %1$s? Archivos - No se seleccionaron archivos para enviar - Nombre de usuario - Contraseña - Dirección web - ¿Mostrar contraseña? - Conectar a tu %1$s Conectar Subir - No se encontraron cuentas + Escoger carpeta de carga: + No se encontró la cuenta No hay cuentas de %1$s en tu dispositivo. Por favor configura una cuenta primero. Configuración Salir No hay contenido para subir Ningún contenido ha sido recibido. No hay nada que subir. %1$s no está autorizado para acceder al contenido compartido - Enviando - Crear directorio para envío + Subiendo... No hay archivos en esta carpeta.\nPuedes añadir nuevos archivos con la opción \"Subir\" del menú. Pulsa sobre un archivo para mostrar información adicional. Tamaño: @@ -69,10 +53,10 @@ Creado: Modificado: Descargar - Actualizar - Volver a descargar - Abrir + Actualizar archivo El fichero fue renombrado como %1$s durante la subida + Compartir + Descompartir Sí No Aceptar @@ -80,39 +64,46 @@ Cancelar subida Cancelar Guardar & Salir - Salir de %1$s Error + Cargando ... + Error desconocido Acerca de + Cambiar contraseña Eliminar cuenta Crear cuenta - Subir - Nombre de directorio + Subir desde... + Nombre de la carpeta Subiendo... %1$d%% Subiendo %2$s Subido con éxito %1$s se ha subido con éxito - %1$d archivos se han subido con éxito Error en la subida La subida de %1$s no se pudo completar - Error de subida: %1$d/%2$d archivos fueron cargados Descargando ... - %1$s Descargada de %2$s + %1$d%% Descargado de %2$s Descarga completa %1$s se ha descargado con éxito Falló la descarga La descarga de %1$s no se pudo completar + No descargado Elige una cuenta - Contactos Falló la sincronización La sincronización de %1$s s no se pudo completar + Contraseña no válida para %1$s Se encontraron conflictos Falló la sincronización de contenidos de %1$d ficheros Fallos en la sincronización de contenidos - Los contenidos de %1$d ficheros no fueron sincronizados (%2$d conflictos) - Usar conexión segura - %1$s no puede rastear tu dispositivo. Por favor chequea tu configuración de localización + Los contenidos de %1$d ficheros no se han sincronizado (%2$d conflictos) + Algunos archivos locales se han perdido + %1$d archivos en la carpeta %2$s no pudieron ser copiados a + La carpeta local %1$s no existe. + Mover todo + Todos los archivos fueron movidos + No se han podido mover algunos archivos + Local: %1$s + Remoto: %1$s + No hay suficiente espacio para copiar los archivos seleccionados en la carpeta %1$s. ¿Quiere moverlos en lugar de copiarlos? Por favor, inserta tu PIN de aplicación - Por favor, inserta tu nuevo PIN de aplicación Introduzca un PIN para la aplicación Se solicitará el PIN cada vez que se inicie la aplicación Repita el PIN para la aplicación, por favor @@ -121,54 +112,59 @@ PIN de aplicación incorrecto PIN de aplicación borrado PIN de aplicación guardado - - 15 minutos - 30 minutos - 60 minutos - - - 15 - 30 - 60 - + Reproductor de música %1$s + %1$s (reproduciendo) + %1$s (cargando) + %1$s reproducción finalizada + No se encuentra archivo de medio + No se ha proporcionado cuenta + El archivo no esta en una cuenta valida + Codec No Soportado + El archivo de medios no pudo ser leído + Archivo no codificado correctamente + Tiempo de espera agotado en el intento de reproducción + Archivo de medio no puede ser transmitido + El archivo de medios no se puede reproducir con el reproductor de medios por defecto + Error de seguridad al intentar reproducir %1$s + Error de entrada al intentar reproducir %1$s + Error inesperado intentando reproducir %1$s + Botón Rebobinado + Botón de reproducción o pausa + Botón avance rápido Intentado iniciar sesión... Sin conexión de red - No se ha detectado una conexión de red, chequea tu conexión a internet e intenta nuevamente. - Conectar de todos modos Conexión segura no disponible. - La aplicación no pudo establecer una conexión segura al servidor. Aunque no haya una conexión segura disponible, puedes continuar o cancelar. Conexión establecida Probando conexión... Configuración de servidor en formato incorrecto - Parece que tu servidor no está correctamente configurado. Contacta a tu administrador para más detalles. + Una cuenta para el mismo usuario y servidor ya existen en el dispositivo + El usuario introducido no concuerda con el usuario de esta cuenta Ocurrió un error desconocido - Ocurrió un error desconocido. Por favor, contacta a los autores e incluye los registros de tu dispositivo. No se pudo encontrar la dirección - No se pudo encontrar el dirección introducida. Consulte la disponibilidad del servidor y la dirección e intenta nuevamete. Instancia de servidor no encontrada - La aplicación no pudo encontrar la instancia del servidor en la ruta de acceso dada. Por favor, compruebe la ruta de acceso e inténtelo de nuevo. El servidor ha tardado demasiado en responder URL no válida Falló la inicialización SSL - Identidad del Servidor SSL no verificada + No fue posible verificar la identidad del servidor SLL No se reconoce la versión del servidor No se ha podido establecer la conexión Conexión segura establecida - Detalles de inicio de sesión - Nombre / contraseña incorrectos - Ruta errónea - Error interno en el servidor, código %1$d - La aplicación finalizó inesperadamente. ¿Desea enviar un reporte de error? - Enviar reporte - No enviar reporte - ¡Extensiones disponibles! - Parece que su servidor soporta extensiones avanzadas. ¿Desea ver las extensiones disponibles para Android? + Nombre de usuario o contraseña incorrecta + Autorización no satisfactoria + Acceso denegado por servidor de autorización + Estado inesperado; por favor, introduzca la URL del servidor de nuevo + Su autorización ha expirado. Por favor, autorice de nuevo + Por favor, introduzca la contraseña actual. + Tu sesión ha expirado. Por favor, intenta conectarte de nuevo + Conectando al servidor de autenticación... + El servidor no soporta este método de autenticación + %1$s no soporta cuentas múltiples + No puede autenticarse en este servidor. Mantener el archivo actualizado - Compartir Renombrar Borrar ¿Está seguro que desea borrar %1$s ? - ¿Desea elimiar %1$s y sus descendientes? + ¿Desea eliminar %1$s y sus descendientes? Sólo local Sólo ficheros locales Eliminar del servidor @@ -178,19 +174,21 @@ Introduzca un nombre nuevo No se pudo cambiar el nombre de la copia local, trata con un nombre differente No se pudo cambiar el nombre - No pudo comprobarse el fichero remoto + No se ha podido comprobar el fichero remoto Ya está sincronizado - El directorio no pudo ser creado + No se pudo crear la carpeta + Carácteres ilegales: / \\ < > : \" | ? * Espere un momento Problema inesperado; por favor, prueba otra app para seleccionar el archivo - No fué seleccionado ningún archivo - Atención + No hay ficheros seleccionados. + Enviar enlace a... + Ingresar con oAuth2 + Conectando al servidor oAuth2... La identidad del sitio no puede ser verificada - El certificado del servidor no es de confianza - El certificado del servidor expiró - El certificado del servidor es demasiado reciente - La URL no coincide con el nombre de dominio del certificado - No se pudo obtener el certificado del servidor ¿Confías de todas formas en este certificado? El certificado no pudo ser guardado Detalles @@ -200,7 +198,7 @@ Nombre común: Organización: Unidad organizativa - Pais: + País: Estado: Ubicación: Validez: @@ -208,12 +206,40 @@ A: Firma: Algoritmo: - Es un marcador de posición - Subir imágenes sólo via WiFi + No se ha podido mostrar el certificado + - No hay información acerca del error + Esto es un marcador de posición + marcadordeposición.txt + Imagen PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 + Subir imágenes sólo cuando hay WiFi /SubidasInstantáneas Conflicto en la actualización El archivo remoto %s no está sincronizado con el archivo local. Si continúa, se reemplazará el contenido del archivo en el servidor. - Mantener ambas + Mantener ambos Sobrescribir No subir + Previsualización de imagen + No se puede mostrar la imagen + %1$s no pudo ser copiado a la carpeta local %2$s + Carga instantánea fallida + Cargas instantáneas fallidas + Resumen de todas las cargas instantáneas fallidas + Seleccionar todos + Reintentar todos los seleccionados + Eliminar todo de la cola de subida + Reintentar subida de imagen: + Cargar mas imágenes + No hacer nada no está conectado para subida instantánea + Mensaje de error: + Por favor revise su configuración de servidor, posiblemente su cuota se haya excedido. + No es posible compartir este archivo o carpeta. Asegúrese de que existe. + Ocurrió un error al tratar de compartir este archivo o carpeta + No se puede dejar de compartir este archivo o carpeta. No existe. + Ocurrió un error al tratar de ya no compartir este archivo o carpeta + Enviar + Copiar enlace + Copiado al portapapeles diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml index 400aeb4f..022345d1 100644 --- a/res/values-et-rEE/strings.xml +++ b/res/values-et-rEE/strings.xml @@ -1,67 +1,51 @@ - ownCloud - Parool: - Kasutajanimi: - Logi sisse - Teretulemast sinu enda ownCloudi - Failid - uusika - ntaktid - Kalender - Järjehoidjad - Seaded - Konto seadistamine - Sinu seadmes pole ühtegi ownCloudi kontot. Selle rakenduse kasutamiseks pead sa ühe looma. - ownCloud Androidi klient\n\nversioon: %1$s - Värskenda + %1$s Android App + versioon %1$s + Värskenda kontot Lae fail üles Sisu teistest rakendustest Failid - Loo kaust - Otsi + Ava rakendusega + Uus kaust Seaded + Üksikasjad + Saada Üldine - Seadme jälgimine - Lisa uus sessioon - Loo pisipildid - Vali konto - Vali, milliseid sinu kontosid peaks rakendus kasutama. - Seadme jälgimine - Luba ownCloudil jälgida oma seadme asukohta - Sinu ownCloud jälgib seda seadet - Uuendamise intervall - Uuenda iga %1$s minuti tagant + Rohkem Kontod Halda kontosid - ownCloud rakenduse PIN - Kaitse oma ownCloudi klienti + Rakenduse PIN + Kaitse oma klienti Luba kohene üleslaadimine Lae kaamera poolt tehtud fotod automaatselt üles - ownCloudi URL + Luba logimine + Kasutatakse probleemide logimiseks + Logi ajalugu + See näitab salvestatud logisid + Kustuta ajalugu + Abiinfo + Soovita sõbrale + Tagasiside + Impressum + Proovi oma nutitelefonil rakendust %1$s! + Kontrolli serverit + Serveri aadress https://... Kasutajanimi Parool - Olen uus ownCloudi kasutaja - Anti vale URL - Vale sessiooni nimi + Uus %1$s kasutaja? Failid - Üleslaadimiseks pole ühtegi faili laetud - Kasutajanimi - Parool - Veebiaadress - Näita parooli? - Ühenda oma ownCloudiga Ühenda Lae üles + Vali kataloog serveris: Kontot ei leitud - Selles seadmes pole ühtegi ownCloudi kontot. Palun seadista esmalt konto. + Selles seadmes pole ühtegi %1$si kontot. Palun seadista esmalt konto. Seadista Lõpeta Pole sisu, mida üles laadida Sisu ei saadud. Pole midagi üles laadida. - ownCloudile pole lubatud ligipääs jagatud sisule + %1$sile pole lubatud ligipääs jagatud sisule Üleslaadimine - Loo üleslaadimise jaoks kaust Selles kaustas pole ühtegi faili.\nUusi faile saab lisada kasutades menüü valikut \"Lae üles\". Lisainfo vaatamiseks vajuta failile. Suurus: @@ -69,9 +53,10 @@ Loodud: Muudetud: Lae alla - Värskenda - Lae uuesti alla - Ava + Värskenda faili + Fail nimetati üleslaadimise käigus ümber %1$ + Jaga linki + Tühista lingi jagamine Jah Ei OK @@ -79,9 +64,11 @@ Tühista üleslaadimine Loobu Salvesta & Välju - Lahku ownCloudist Viga + Laadimine ... + Tundmatu viga Info + Muuda parooli Kustuta konto Loo konto Lae fail üles kohast ... @@ -90,77 +77,91 @@ %1$d%% üleslaadimine %2$s Üleslaadimine oli edukas %1$s laeti üles - %1$d faili laeti üles Üleslaadimine ebaõnnestus %1$s üleslaadimise lõpetamine ebaõnnestus - Üleslaadimine ebaõnnestus: %1$d/%2$d faili laeti üles Allalaadimine ... %1$d%% allalaadimine %2$s Allalaadimine oli edukas %1$s laeti alla Allalaadimine ebaõnnestus %1$s allalaadimist ei õnnestunud lõpetada + Allalaadimata Vali konto - Kontaktid Sünkroniseerimine ebaõnnestus %1$s sünkroniseerimise lõpetamine ebaõnnestus + Vigane parool %1$s jaoks Leite konflikte - Kasuta turvalist ühendust - ownCloud ei saa sinu seadet jälgida. Palun kontrolli oma asukoha seadeid + %1$d sünkroniseeritavad faile ei suudeta sünkroniseerida + Sünkroniseeritavad failid ebaõnnestusid + Faili %1$d sisu ei suudeta sünkroniseerida (konflikt %2$d) + Osad kohalikud faili ununesid + %1$d faili %2$s kataloogis ei saa kopeerida + Alates versioonist 1.3.16 failid, mis on üles laaditud kopeeritakse kohalikku kataloogi %1$s vältimaks andmete kadu vältimaks andmete kadu juhul, kui ühte faili sünkroniseeritakse mitmelt kontolt.\n\nSelle muudatusega seoses kõik failid, mis on üles laetud rakenduse vanemate versioonidega, kopeeriti kataloogi %2$s. Selle tegevuse peatas viga, mis tekkis konto sünkroniseerimise käigus. Sa saad jätta faili(d) nagu nad on ning eemaldata viite %3$s või tõsta faili(d) %1$s kataloogi ja säilitada viite %4$s. \n\nAllpool on loend kohalikest failidest ning serveris asuvatest failidest %5$s, millele nad viitavad. + Kausta %1$s pole enam olemas + Tõsta kõik ümber + Kõik failid tõsteti ümber + Mõningaid faile ei saa ümber tõsta + Kohalik: %1$s + Serveris: %1$s + Pole piisavalt ruumi kopeerimaks valitud faile kataloogi %1$s. Soovid kopeerimise asemel neid ümber tõsta? Palun sisesta oma rakenduse PIN - Palun sisesta oma rakenduse uus PIN - Palun ownCloudi rakenduse PIN + Sisesta oma rakenduse PIN PIN-i nõutakse iga kord, kui rakendus käivitatakse - Palun sisesta oma ownCloudi rakenduse PIN uuesti - Eemalda oma ownCloud rakenduse PIN - Mõlemad ownCloudi rakenduse PIN-id pole samad - Vigane ownCloudii rakenduse PIN - ownCloud rakenduse PIN on eemaldatud - ownCloud rakenduse PIN on salvestatud - - 15 minutit - 30 minutit - 60 minutit - - - 15 - 30 - 60 - + Palun sisesta oma rakenduse PIN uuesti + Eemalda oma rakenduse PIN + Mõlemad rakenduse PIN-id pole samad + Vigane rakenduse PIN + Rakenduse PIN on eemaldatud + Rakenduse PIN on salvestatud + %1$s muusika mängija + %1$s (mängib) + %1$s (laeb) + %1$s esitus lõpetatud + Meedia faili ei leitud + Ühtegi kontot pole antud + Fail ei kuulu õigele kontole + Mittetoetatud meedia koodek + Ei suuda lugeda meediafaili + Meediafail pole korralikult kodeeritud + Esitamise ajal aegunud + Ei suuda voogesitada meediafaili + Meediafaili ei saa mängida Stock Media Player-iga + Turbe viga %1$s esitamisel + Sisendi viga %1$s esitamisel + Ootamatu viga %1$s esitamisel + Tagasikerimise nupp + Mängimise või pausi nupp + Kiire kerimise nupp Proovitakse sisse logida... Võrguühendust pole - Internetiühendust ei leitud, kontrolli oma interneti ühendust ja proovi uuesti. - Ühenda sellegipoolest Turvaline ühendus pole saadaval - Rakendus ei suutnud serveriga turvalist ühendust luua. Kuid saadaval on mitteturvaline ühendus. Sa võid jätkata või loobuda. Saadi ühendus Ühenduse testimine... - Vigases vormingus ownCloud seadistus - Näib, et sinu ownCloud pole korralikult seadistatud. Lisainfo saamiseks võta ühendust oma administraatoriga. + Vigases vormingus server seadistus + Sama konto kasutaja ja server on juba selles seadmes olemas + Sisestatud kasutaja ei kattu selle konto kasutajaga Tekkis tundmatu tõrge! - Tekkis tundmatu tõrge. Palun võta autoritega ühendust ja kaasa oma seadme logid. Hosti ei leitud - Sisestatud hosti ei leitud. Palun kontrolli hostiinme ja serveri kättesaadavust ning proovi seejärel uuesti. - ownCloud protsessi ei leitud - Rakendus antud asukohast ownCloundi. Palun kontrolli asukohta ja proovi uuesti. + server protsessi ei leitud Serveri vastus võttis liiga kaua aega Vigases vormingus URL SSL-i käivitamine ebaõnnestus - Kinnitamata SSL serveri identiteet - Tundmatu ownCloud serveri versioon + Serveri SSL identiteedi tuvastamine ebaõnnestus + Tundmatu serveri versioon Ühenduse loomine ebaõnnestus Saavutati turvaline ühendus - Sisselogimise andmed - Vigane kasutajanimi /parool - Sisestati vale asukoht - Sisemine serveri viga, kood %1$d - Rakdnus suleti ootamatult. Kas sa sooviksid saata kokkujooksmise kohta teavitust arendajale? - Saada veateade - Ära saada veateadet - Saadaval on lisaprogrammid! - Näib, et sinu ownCloud toetab laienduste kasutamist. Kas sa sooviksid näha androidile saada olevaid lisaprogramme ? + Vale kasutajanimi või parool + Autoriseerimine ebaõnnestus + Ligipääs keelatud autoriseeriva serveri poolt + Ootamatu seisund; palun sisesta serveri URL uuesti + Sinu autoriseerimine aegus. Palun autoriseeri uuesti + Palun sisesta kehtiv parool + Sinu sessioon aegus. Palun ühenda uuesti + Autentimisserveriga ühendumine ... + See server ei toeta seda autentimise viisi + %1$s ei toeta mitme konto kasutamist + Ei suuda autoriseerida selle serveriga. Hoia faili ajakohasena - Jaga Nimeta ümber Eemalda Oled sa kindel, et soovid %1$s eemaldada ? @@ -168,42 +169,78 @@ Ainult kohalik Ainult kohalik sisu Eemalda serverist - Mõlemad nii eemalolev kui ka kohalik + Eemalolev ja kohalik Eemaldamine oli edukas - Eemaldamist ei suudetud lõpetada + Eemaldamine ebaõnnestus Sisesta uus nimi Kohalikku faili ei saa ümber nimetada; proovi teist uut nime Ümbernimetamine ebaõnnestus Mujaloleva faili kontrollimine ebaõnnestus Faili sisu on juba sünkroniseeritud - Kausta loomine ebaõnnestus + Kataloogi ei saa tekitada + Keelatud sümbolid: / \\ < > : \" | ? * Oota hetk Ootamatu tõrge ; palun kasuta faili valimiseks mõnda teist rakendust Ühtegi faili pole valitud - Hoiatus + Saada link + Logi sisse oAuth2-ga + oAuth2 serveriga ühendumine... Saidi identiteeti ei suudetud kinnitada - Serveri sertifikaat pole usaldusväärne - Serveri sertifikaat on aegunud - - Serveri sertifikaat on liiga noor + - Serveri sertifikaat kehtivad kuupäevad on alles tulevikus - URL ei kattu sertifikaadis oleva hostinimega - Serveri sertifikaati ei õnnestunud hankida Kas sa soovid siiski seda sertifikaati usaldada? Sertifikaadi salvestamine ebaõnnestus Üksikasjad Peida + Väljastatud: + Väljastaja: + Üldnimetus: Organisatsioon: Organisatsiooni üksus: Riik: Maakond: Asukoht: Kehtivus: + Saatja: + Saaja: Allkiri: Algoritm: - See on kohahoidja + Ei suuda kuvada sertifikaati. + - Vea kohta puudub info + See on kohahoidja + placeholder.txt + PNG pilt + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Lae pilte üles ainult läbi WiFi /InstantUpload Uuenduse konflikt + Serveris asuvat faili ei sünkroniseeritud kohaliku failiga. Jätkates kirjutatakse serveris asuva faili sisu üle. Säilita mõlemad Kirjuta üle Ära uuenda + Pildi eelvaade + Seda pilti ei saa näidata + %1$s ei suudetud kopeerida kohalikku kataloogi %2$s + Ebaõnnestunud kohene üleslaadimine + Ebaõnnestunud kohesed üleslaadimised + Kõikide ebaõnnestunud üleslaadimiste kokkuvõte + vali kõik + proovi uuesti kõik valitud + eemalda kõik valitud üleslaadimise järjekorrast + proovi uuesti pilti üles laadida: + Lae veel pilte + ära tee midagi, sa pole võrku ühendatud koheseks üleslaadimiseks + Veateade: + Palun kontrolli oma serveri seadeid, võib-olla on mahulimiit ületatud. + Antud faili või kausta ei saa jagada. Tee kindlaks, et see on olemas + Faili või kausta jagamisel esines viga + Antud faili või kausta jagamist pole võimalik tühistada + Faili või kausta jagamise tühistamisel esines viga + Saada + Kopeeri link + Kopeeritud lõikepuhvrisse diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml index c757504a..64bd780c 100644 --- a/res/values-eu-rES/strings.xml +++ b/res/values-eu-rES/strings.xml @@ -1,2 +1,5 @@ - + + Deskargatu + Ezeztatu + diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 7b3f7608..d9925f5f 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -1,67 +1,50 @@ - ownCloud - Pasahitza: - Erabiltzaile izena: - Saioa hasi - Ongi etorria zure ownCloudera - Fitxategiak - Musika - Kontaktuak - Egutegia - Laster-markak - Ezarpenak - Konfiguratu kontua - Ez dago ownCloud konturik zure gailuan. Programa hau erabiltzeko, kontu bat sortu behar duzu. - ownCloud Android bezeroa\n\nBertsioa: %1$s - Birkargatu + %1$s Android Aplikazioa + %1$s bertsioa + Freskatu kontua Igo fitxategia Beste app-en edukia Fitxategiak - Sortu karpeta - Bilatu + Ireki honekin + Karpeta berria Ezarpenak + Xehetasunak + Bidali Orokorra - Gailuaren jarraipena - Gehitu saio berria - Sortu irudien koadro txikiak - Aukeratu kontu bat - Aukeratu aplikazioak ze kontu erabiliko duen. - Gailuaren jarraipena - Gaitu ownCloud zure gailuaren kokapena jarraitzeari - ownCloudek gailu honen jarraipena egiten du - Eguneraketa tartea - Eguneratu %1$s minuturo + Gehiago Kontuak Kontuak kudeatu - ownCloud App PIN - Babestu zure ownCloud bezeroa + App PIN + Babestu zure bezeroa Gaitu berehalako igoera Igo berehala kamerak ateratako argazkiak - ownCloud URL + Gaitu erregistroa + Arazoen erregistroa gordetzeko erabiltzen da + Erregistro historia + Honek gordetako erregistroak bistaratzen ditu. + Ezabatu historia + Laguntza + Lagun bati aholkatu + Oharrak + Imprint + Probatu %1$s zure telefono adimentsuan! + Egiaztatu zerbitzaria + Zerbitzariaren helbidea https:// Erabiltzaile izena Pasahitza - Berria naiz ownCloud-en - Emandako URLa gaizki dago - Saio izen okerra + Berria %1$s-n? Fitxategiak - Ez duzu igotzeko fitxategirik hautatu - Erabiltzaile izena - Pasahitza - Web helbidea - Bistaratu pasahitza? - Konektatu zure ownCloudera Konektatu Igo Ez da konturik aurkitu - Zure gailuan ez dago ownCloud konturik. Mesedez konfiguratu kontu bat lehenengo. + Zure gailuan ez dago %1$s konturik. Mesedez konfiguratu kontu bat lehenengo. Konfiguratu Irten Ez dago igotzeko edukirik Ez da edukirik jaso. Ez dago ezer igotzeko. %1$s-(e)k ez du baimenik elkarbanatutako edukian sartzeko Igotzen - Sortu igotzeko karpeta bat Ez dago fitxategirik karpeta honetan.\nFitxategi berriak \"Igo\" menu aukerarekin gehi daitezke. Sakatu fitxategi baten gainean informazio gehiago lortzeko Tamaina: @@ -69,10 +52,10 @@ Sortuta: Aldatuta: Deskargatu - Birkargatu - Birdeskargatzen - Ireki + Freskatu fitxaegia Fitxategiaren izena %1$sra aldatu da igotzean + Elkarbanatu lotura + Lotura partekatzeari utzi Bai Ez Ados @@ -80,9 +63,11 @@ Ezeztatu igoera Ezeztatu Gorde eta Irten - Utzi ownCloud Errorea + Kargatzen ... + Errore ezezaguna Honi buruz + Aldatu pasahitza Ezabatu kontua Sortu kontua Igo fitxategia hemendik ... @@ -91,80 +76,88 @@ %1$d%% Igotzen %2$s Igotzea ongi burutu da %1$s ongi igo da - %1$d fitxategi ongi igo dira igotzeak huts egin du %1$s fitxategiaren igoera ezin izan da burutu - Igoerak huts egin du: %1$d/%2$d fitxategi igo dira Deskargatzen ... %1$d%% Deskargatzen %2$s Deskarga ongi burutu da %1$s ongi deskargatu da Deskargak huts egin du %1$s fitxategiaren deskarga ezin izan da burutu + Oraindik deskargatu gabe Hautatu kontua - Kontaktuak Sinkronizazioak huts egin du %1$s fitxategiaren sinkronizazioa ezin da burutu + Okerreko pasahitza %1$s-rako Gatazkak aurkituak sinkronizatu beharreko %1$d fitxategiak ezin dira sinkronizatu edukien sinkronizazioak huts egin du %1$d fitxategien edukiak ezin dira sinkronizatu (%2$d gatazka) - Erabili konexio segurua - ownCloudek ezin du zure gailua jarraitu. Mesedez begiratu zure kokapen ezarpenak + Bertako fitxategi batzuk ahaztu dira + %1$s karpeta dagoeneko ez da existitzen + Mugitu denak + Fitxategi guztiak mugitu dira + Fitxategi batzuk ezin dira mugitu + Bertakoa: %1$s + Urrunekoa: %1$s + Ez dago leku nahikorik hautatutako fitxategiak %1$s karpetan kopiatzeko. Nahi al duzu kopiatu ordez bertara mugitzea? Mesedez, sartu zure aplikazioaren PINa - Mesedez, sartu zure aplikazioaren PIN berria - Sartu ownCloud aplikazioaren PINa + Sartu aplikazioaren PINa PINa aplikazioa abiarazten den bakoitzean eskatuko da - Sartu berriz ownCloud aplikazioarenPINa, mesedez - Ezabatu zure ownCloud aplikazioaren PINa - ownCloud aplikazioko bi PINak ez dira berdinak - ownCloud aplikazioaren PINa ezda zuzena - ownCloud aplikazioaren PINa kendu da - ownCloud aplikazioaren PINa gorde da - - 15 Minutu - 30 Minutu - 60 Minutu - - - 15 - 30 - 60 - + Sartu berriz aplikazioarenPINa, mesedez + Ezabatu zure aplikazioaren PINa + Aplikazioko bi PINak ez dira berdinak + Aplikazioaren PINa ezda zuzena + Aplikazioaren PINa kendu da + Aplikazioaren PINa gorde da + %1$s musika erreproduzigailua + %1$s (jotzen) + %1$s (kargatzen) + %1$s erreprodukzioa amaitua + Ez da euskarri fitxategia aurkitu + Ez da konturik eman + Fitxategiak ez du baliozko kontu bat + Onartzen ez de euskarri kodeka + Euskarri fitxategia ezin da bihurtu + Euskarri fitxategia ezin da kodetu + Erreproduzitzen saiatzean denbora iraungitu da + Euskarri fitxategia ezin da jariotu + Euskarri fitxategia ezin erreproduzitu stock euskarri erreproduzigailuarekin + Segurtasun errorea %1$s erreproduzitzen saiatzean + Sarrera errorea %1$s erreproduzitzen saiatzean + Ezusteko errorea %1$s erreproduzitzen saiatzean + Atzeratu botoia + Erreproduzitu edo pausatu botoia + Azkar aurreratu botoia Saioa hasten saiatzen... Ez dago sare konexiorik - Ez da sare konexiorik antzeman, egiaztatu zure interneteko konexioa eta saiatu berriz. - Konektatu hala ere Konexio segurua ez dago eskuragarri - Aplikazioak ezin izan du zerbitzariarekin konexio seguru bat ezarri. Hala ere konexio ez segurua egin daiteke. Jarraitu edo ezeztatu dezakezu. Konexioa ezarri da Konexioa probatzen... - gaizki egindako ownCloud konfigurazioa - Badirudi zure ownCloud instantzia ez dagoela ongi konfiguratuta. Jarri zure harremanetan administradorearkin informazio gehiago izateko. + gaizki egindako server konfigurazioa + Erabiltzaile eta zerbitzari hauendako dagoeneko kontu bat existitzen da gailu honetan + Sartutako erabiltzaileak ez du bat egiten kontu honetako erabiltzailearekin Errore ezezagun bat gertatu da - Errore ezezagun bat gertatu da. Mezedez jarri harremanetan egileekin eta zure gailuko log-ak erantsi. Ezin izan da hostalaria aurkitu - Ezin da sartutako hostalaria aurkitu. Mezedez egiaztatu hostalari izena eta zerbitzariaren eskuragarritasuna eta saiatu berriz. - ez da ownClouden instalaziorik aurkitu - Programak ezin du ownCloud instantzia aurkitu emandako bidean. Mesedez egiaztatu bidea eta saiatu berriz. + ez da serveren instalaziorik aurkitu Zerbitzariak denbora asko hartu du erantzuteko Gaizki sortutako URLa SSL abiaratzeak huts egin du - Egiaztatu gabeko SSL zerbitzariaren identitaea - ownCloud zerbitzari bertsio ezezaguna + Ezin izan da SSL zerbitzariaren identitaea egiaztatu + server zerbitzari bertsio ezezaguna Ezin izan da konexioa egin Konexio segurua ezarri da - Saio hasierako ezarpenak - Erabiltzaile / pasahitz baliogabea - Okerreko bidea emanda - Barne zerbitzari errorea, kodea: %1$d - Programa ezustekoan bukatu da. Nahiko zenuke huts egitearen berri eman? - Bidali txostena - Ez bidali txostena - Hedapenak eskuragarri! - Badirudi zure ownCloud instantziak aukera aurreratuak eskuragarri dituela. Nahiko zenuke aukera horiek zure androidean ikustea? + Okerreko erabiltzaile izen edo pasahitza + Baimena ez da lortu + Sarrera autorizazio zerbitzariak ukatua + Egoera esperogabea, mesedez idatzi berriz zerbitzari URLa + Zure baimena iraungitu da.\nMesedez, baimendu berriz + Mesedez, sartu oraingo pasahitza + Zure saioa iraungitu da. Mesdez konektatu berriro + Konektatzen autentikazio zerbitzarira... + Zerbitzariak ez du autentikazio metodo hau onartzen + %1$s ez du kontu anitzak onartzen Mantendu fitxategia eguneratuta - Elkarbanatu Berrizendatu Ezabatu Nahi duzu %1$s ezabatu? @@ -180,17 +173,18 @@ Izen aldaketa ezin izan da burutu Urruneko fitxategia ezin izan da arakatu Fitxategi edukiak dagoeneko sinkronizaturik - Karpeta ezin da sortu + Debekatutako karaktereak: / \\ < > : \" | ? * Itxaron momentu bat Ezusteko arazoa; mesedez, saiatu beste app batekin fitxategia hautatzeko Ez da fitxategirik hautatu - Abisua + Bidali lotura honi... + Saioa hasi oAuth2-rekin + Konektatzen oAuth2 zerbitzarira... Lekuaren identitatea ezin da egiaztatu - Zerbitzariaren ziurtagiria ez da fidagarria - Zerbitzariaren ziurtagiria iraungi da - Zerbitzariaren ziurtagiria oso gaztea da - URLa ez dator bat ziurtagiriaren hostalari izenarekin - Zerbitzariaren ziurtagiria ezin da lortu Nahi duzu, hala ere, ziurtagiriaz fidatu? Ziurtagiria ezin da gorde Xehetasunak @@ -208,7 +202,12 @@ Noiz arte: Sinadura: Algoritmoa: - Hau leku-marka da + Hau leku-marka da + kokalekua.txt + PNG Irudia + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Igo irudiak bakarrik WIFIren bidez /BerehalakoIgoerak Igoera konfliktoa @@ -216,4 +215,24 @@ Mantendu biak Gainidatzi Ez igo + Irudi aurreikuspena + Ezin da irudi hau erakutsi + UnekoIgoerak huts egin du + Uneko igoerek huts egin dute + Huts egindako igoeren laburpena + Hautatu dena + Berriz saiatu hautatutakoak + kendu hautatutakoak igoera-ilaratik + Berriz saiatu irudia igotzen: + Kargatu irudi gehiago + ez egin ezer ez zaude on-line uneko igoerarentzat + Hutsegite mezua: + Egiaztatu zure konfigurazioa, agian zure kuota muga gainditu duzu. + Ezin izan da karpeta edo fitxategi hau partekatu. Mesedez, ziurtatu existitzen dela + Errore bat egon da fitxategaia edo karpeta partekatzerakoan + Ezin izan da karpeta edo fitxategi honen partekatzeari utzi. Ez da existitzen. + Errore bat egon da fitxategaia edo karpeta partekatzeari uzterakoan + Bidali + Lotura kopiatu + Arbelera kopiatua diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 50fb8e34..e5e0469c 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -1,89 +1,236 @@ - ownCloud - رمز عبور: - نام کاربری: - ورود - به ownCloud خود خوش‌آمدید - پرونده‌ها - موزیک - ارتباط‌ها - تقویم - نشانک‌ها - تنظیمات - نصب حساب کاربری - حساب کاربری ownCloud در دستگاه شما وجود ندارد. برای استفاده از این برنامه می‌بایست یکی ایجاد کنید. - بازنمایی + %1$s برنامه اندروید + %1$s نسخه + بازنمایی حساب کاربری بارگزاری + محتوا از دیگر برنامه ها پرونده‌ها - ایجاد پوشه - جست‌و‌جو + باز کردن با + پوشه جدید تنظیمات + جزئیات + ارسال عمومی - ردیابی دستگاه - ایجاد جلسه جدید - ایجاد پیش نمایش تصاویر - انتخاب یک حساب - ردیابی دستگاه - برنامه را فعال کنید تا محل دستگاه شما پیگیری شود - به روز رسانی بازه + بیش‌تر حساب‌ها - آدرس ownCloud + مدیریت حسابها + PIN برنامه + حفاظت از مشتری + فعال کردن بارگذاری فوری + فورا عکسهایی را که با دوربین گرفته شده است را آپلود کن + فعال کردن ورود + این برای مشکلات ورود استفاده شده است. + تاریخچه ورود + این وقایع ثبت شده را نمایش می دهد. + حذف تاریخچه + راه‌نما + پیشنهاد دادن به یک دوست + باز خورد + مهر زدن + %1$s را بر روی گوشی هوشمند خود امتحان کنید. + چک کردن سرور + آدرس سرور https://… نام کاربری رمز عبور + جدید نسبت به %1$s? پرونده‌ها - نام کاربری - رمز عبور - آدرس وب - نمایش رمز عبور؟ اتصال بارگزاری + هیچ حسابی یافت نشد + هیچ حسابی در %1$s بر روی دستگاه شما موجود نیست.لطفا اول یک حساب ترتیب دهید. نصب خروج هیچ مطلبی بارگزاری نشده‌ است هیچ مطلبی دریافت نشده است. هیچ‌‌چیزی بارگزاری نشده. + %1$s اجازه ی دسترسی به محتوای مشترک را نمی دهد در حال بارگزاری - ایجاد دایرکتوری برای بارگذاری + هیچ فایل در این پوشه نیست.\nفایل های جدید می توانند بوسیله ی گزینه ی \"بارگزاری\" در لیست اضافه کنید. + روی هر فایل کلیک کنید تا اطلاعات اضافی نمایش داده شود. اندازه نوع: ایجاد شده توسط: تغییر یافته توسط: بارگیری - بازنمایی - بارگزاری دوباره + بازنمایی فایل + فایل در هنگام بارگزاری به %1$s تغییر نام یافت + اشتراک گذاشتن لینک + لغو اشتراک گذاشتن لینک بله نه باشه + قطع دانلود متوقف کردن بار گذاری منصرف شدن + ذخیره سازی و خروج خطا + بارگذاری ... + خطای نامشخص درباره + تغییر گذر واژه حذف حساب ساخت حساب بارگزاری از ... + نام پوشه در حال بارگزاری ... + %1$d%% در حال بارگزاری %2$s بارگزاری موفقیت‌آمیز بود + %1$s با موفقیت بار گذاری شد بارگزاری ناموفق بود + بارگزاری %1$s نتوانست به طور کامل انجام شود در حال بارگیری ... + %1$d%% در حال دانلود %2$s بارگیری موفقیت‌آمیز بود + %1$s با موفقیت دانلود شد بارگیری ناموفق بود - ارتباط‌ها + دانلود %1$s نمی توانست به طور کامل انجام شود. + دانلود هنوز به پایان نرسیده است. + حساب کاربری را انتخاب کنید + همگام سازی ناموفق + همگام سازی %1$s نتوانست به طور کامل انجام شود + رمز عبور نامعتبر برای %1$s + ناسازگاری ها یافت شدند + %1$d پرونده ها نمیتوانند همگام سازی شوند. + همگام سازی پرونده ها ناموفق بود. + محتوای %1$d فایل ها نمی توانند همگام باشند(%2$d ناسازگاری) + بعضی از فایلهای محلی فراموش شده اند + پوشه %1$s دیگر وجود ندارد + انتقال همه + همه ی فایل ها جا به جا شدند + بعضی از فایل ها نمی توانند انتقال یابند + محلی: %1$s + دور از دسترس: %1$s + فضایی به اندازه ی کافی برای کپی کردن فایل ها در پوشه ی %1$s نیست.آیا می خواهید آنها را به جای دیگری انتقال دهید؟ + لطفا PIN برنامه خودتان را وارد کنید + PIN برنامه را وارد کنید + هر زمان که برنامه آغاز شود PIN درخواست خواهد شد. + لطفا PIN برنامه خودتان را دوباره وارد کنید + PIN برنامه خودتان را حذف کنید + PIN های برنامه یکسان نیستند + PIN برنامه نادرست است + PIN برنامه حذف شده است + PIN برنامه ذخیره شده است + %1$s پخش کننده موسیقی + %1$s ( در حال پخش موسیقی ) + %1$s (درحال بارگذاری) + %1$s پخش به پایان رسید. + هیچ رسانه ای یافت نشد. + هیچ حسابی ارائه نشده است. + پرونده در حساب کاربری معتبر نیست. + کدک رسانه پشتیبانی نشده است. + فایل رسانه نمیتواند خوانده شود. + فایل رسانه به شکل صحیح رمزگذاری نشده است. + در حین تلاش برای پخش کردن، مهلت به پایان رسیده است + فایل رسانه نمیتواند جریان داشته باشد. + فایل رسانه با استفاده از پخش کننده stock media player قابل پخش نیست. + خطای امنیتی، تلاش برای پخش %1$s + خطای وارد کردن، تلاش برای پخش %1$s + خطای غیرمنتظره، تلاش برای پخش %1$s + دکمه عقبگرد + دکمه پخش یا توقف + دکمه رو به جلو سریع + تلاش برای ورود... + هیچ ارتباطی به شبکه موجود نیست اتصال امن در دسترس نیست اتصال برقرار شد آزمایش اتصال... - خطای ناشناخته رخ داده است! - جزئیات ورود - ارسال گزارش - گزارش را ارسال نکن - اشتراک‌گزاری + پیکربندی سرور ناقص است + یک اکانت با همین نام کاربری و سرور بر روی این دستگاه موجود می‌باشد. + نام کاربری وارد شده با نام کاربری این اکانت مطابقت ندارد + خطای نامشخص رخ داده است! + میزبان را نمیتواند پیدا نماید. + نمونه ی سرور یافت نشد + زمان زیادی برای پاسخ سرور صورت گرفت. + آدرس ناقص + مقداردهی SSL ناموفق بود. + نمی‌توان اصالت SSL سرور را احراز نمود + نسخه ی سرور ناشناخته + نمیتوان ارتباط برقرار نمود. + ایجاد ارتباط ایمن + نام کاربری یا رمز ورود اشتباه است + اجازه ناموفق + دسترسی توسط سرور احراز هویت رد شده است. + حالت غیرمنتظره؛ لطفا آدرس URL سرور را مجددا وارد نمایید. + احراز هویت شما منقضی شده است. لطفا، مجددا احراز هویت فرمایید. + لطفاً رمز کاربری فعلی را وارد نمایید + اتصال به سرور احراز هویت... + سرور این نوع احراز هویت را پشتیبانی نمی‌کند + %1$s چند اکانته بودن را پشتیبانی نمی‌کند + فایل را به روز نگه دار تغییرنام حذف + آیا واقعا می خواهید %1$s حذف شود؟ + آیا واقعا می خواهید %1$s و محتوای آن را حذف کنید؟ + فقط محلی + فقط محتوای محلی پاک کردن از سرور + راه دور و محلی + حذف با موفقیت انجام شد + حذف نا موفق بود + نام جدید وارد کنید + کپی محلی نمی تواند تغییر نام پیدا کند، لطفا نام دیگری انتخاب نمایید. + نامگذاری نمی تواند به طور کامل انجام شود + پرونده های دور از دسترس نمی توانند بررسی شوند. + محتوای فایل قبلا همگام شده + کاراکترهای ممنوع: / \\ < > : \" | ? * لحظه‌ای صبر کنید + مشکل غیر متقربه، لطفا پرونده را از یک برنامه متفاوت انتخاب کنید. هیچ پرونده‌ای انتخاب نشده است - اخطار + ارسال لینک به ... + ورود با oAuth2 + اتصال به سرور oAuth2 ... + هویت این سایت نمی تواند تایید شود + - گواهی سرور نامعتبر است + - گواهی سرور انقضا یافته + زمان اعتبار گواهی نامه سرویس دهنده فرانرسیده است + URL با آدرس هاست موجود در گواهی نامه مطابقت ندارد. + آیا می خواهید در هر صورت به این مدرک اطمینان کنید؟ + گواهی نمی تواند ذخیره شود جزییات پنهان کردن + صدور به: + صدور بوسیله: + نام مشترک: + سازمان + واحد سازمان کشور: + ایالت + مکان: + اعتبار: + از: + به: + امضا: + الگوریتم: + این یک حفره است. + placeholder.txt + تصویربا فرمت PNG + 389 کیلو بایت + 2012/05/18 12:23 بعد از ظهر + 12:23:45 تصاویر را فقط از طریق wifi بارگذاری کن + آپلود فوری + بارگذاری ناسازگار + فایل های دور از دسترس %s با فایل های محلی همگام نشده اند. ادامه کار، محتوا را بر روی فایل در سرور جایگزین خواهد کرد. + نگهداشتن هر دو + دوباره نویسی + آپلود نکن + پیش نمایش تصویر + این تصویر نمی تواند نمایش داده شود. + آپلود فوری انجام نشد + آپلود های فوری انجام نشدند. + خلاصه ای از تمام ارسال های فور ی ناموفق. + انتخاب همه + تلاش مجدد بر روی همه موارد انتخاب شده + حذف تمام انتخاب شده ها از صف ارسال. + تلاش برای آپلود کردن تصویر: + بارگذاری تصاویر بیشتر + هیچ کاری انجام ندهید، شما برای آپلود فوری آنلاین نیستید. + پیغام عدم موفقیت: + لطفا پیکربندی سرورتان را بررسی کنید، شاید سهمیه شما بیش از حد شده باشد. + به اشتراک گذاری این فایل یا پوشه ممکن نیست. لطفاً از وجود آن اطمینان حاصل نمایید + در حین اشتراک گذاری این فایل یا پوشه خطایی رخ داده است + حذف اشتراک گذاری این فایل یا پوشه ممکن نیست. لطفاً از وجود آن اطمینان حاصل نمایید + در حین حذف اشتراک گذاری این فایل یا پوشه خطایی رخ داده است + ارسال + کپی به کلیپ بورد diff --git a/res/values-fi-rFI/strings.xml b/res/values-fi-rFI/strings.xml index 280ca694..8f9c28ed 100644 --- a/res/values-fi-rFI/strings.xml +++ b/res/values-fi-rFI/strings.xml @@ -1,67 +1,47 @@ - ownCloud - Salasana: - Käyttäjätunnus: - Kirjaudu - Tervetuloa ownCloudiin - Tiedostot - Musiikki - Yhteystiedot - Kalenteri - Kirjanmerkit - Asetukset - Aseta tili - Laitteessasi ei ole ownCloud-tilejä. Luo tili käyttääksesi tätä sovellusta. - ownCloudin Android-sovellus\n\nversio: %1$s - Päivitä + %1$s-Android-sovellus + versio %1$s + Päivitä tili Lähetä tiedosto Sisältö toisista sovelluksista Tiedostot - Luo kansio - Etsi + Avaa sovelluksella + Uusi kansio Asetukset + Tiedot + Lähetä Yleiset - Laitteen jäljitys - Lisää uusi istunto - Luo kuvista pikkukuvat - Valitse tili - Valitse sovelluksen käyttämät tilit. - Laitteen jäljitys - Salli ownCloudin jäljittää laitteesi sijainti - ownCloud jäljittää tätä laitetta - Päivitysväli - Päivitä %1$s minuutin välein + Enemmän Tilit Tilien hallinta - ownCloud-sovelluksen PIN-koodi - Suojaa ownCloud-asiakasohjelmasi + Sovelluksen PIN-koodi + Suojaa Asiakasohjelmasi Käytä välitöntä lähetystä Lähetä kameralla otetut kuvat välittömästi - ownCloud-osoite + Käytä lokitusta + Tämä näyttää tallennetut lokit + Poista historia + Ohje + Suosittele kaverille + Palaute + Kokeile %1$sia älypuhelimellasi! + Tarkista palvelin + Palvelinosoite https://… Käyttäjätunnus Salasana - Olen uusi ownCloudin kanssa - Väärä osoite - Väärä istuntonimi + Onko %1$s uusi tuttavuus sinulle? Tiedostot - Tiedostoa ei valittu lähetettäväksi - Käyttäjätunnus - Salasana - Verkko-osoite - Näytä salasana - Yhdistä omaan ownCloudiin Yhdistä Lähetä Tiliä ei löytynyt - Laitteelle ei ole asetettu ownCloud-tiliä. Luo tili ensin. + Laitteelle ei ole asetettu %1$s-tiliä. Luo tili ensin. Asetukset Lopeta Ei sisältöä ladattavaksi Sisältöä ei saatu. Ei lähetettävää palvelimelle. - ownCloudilla ei ole oikeuksia jaettuun sisältöön + %1$silla ei ole oikeuksia jaettuun sisältöön Lähetetään - Luo kansio latausta varten Tässä kansiossa ei ole tiedostoja.\nUusia tiedostoja voi lisätä käyttäen valikon \"Lähetä\"-toimintoa. Napauta tiedostoa nähdäksesi lisätietoja. Koko: @@ -69,10 +49,9 @@ Luotu: Muokattu: Lataa - Päivitä - Lataa uudelleen - Avaa + Päivitä tiedosto Tiedoston nimeksi muutettiin %1$s siirron yhteydessä + Jaa linkki Kyllä Ei OK @@ -80,9 +59,11 @@ Peru lähetys Peru Tallenna ja poistu - Jätä ownCloud Virhe + Ladataan… + Tuntematon virhe Tietoja + Vaihda salasana Poista tili Luo tili Lataus täältä: @@ -91,80 +72,77 @@ %1$d%% Lähetetään palvelimelle %2$s Lähetys onnistui %1$s lähetettiin onnistuneesti - %1$d tiedostoa lähetettiin onnistuneesti Lähetys epäonnistui %1$s :n lähetys palvelimelle jäi kesken - Lähetys epäonnistui: %1$d/%2$d tiedostoa lähetettiin Ladataan... %1$d%% ladataan palvelimelta %2$s Lataus tänne onnistui %1$s ladattu palvelimelta onnistuneesti Lataus epäonnistui %1$s :n latausta ei pystytty suorittamaan loppuun asti + Ei vielä ladattu Valitse tili - Yhteystiedot Synkronointi epäonnistui Kohteen %1$s synkronointia ei voitu suorittaa loppuun + Virheellinen salasana tilille %1$s Ristiriitoja löytynyt %1$d \"pidä synkronoituna\" tiedostoja ei voitu synkronoida \"Pidä synkronoituna\" epäonnistui Hakemiston %1$d tiedostoja ei voitu synkronoida (%2$d konfliktia) - Käytä salattua yhteyttä - ownCloud ei voi jäljittää laitettasi. Tarkista laitteesi sijaintiasetukset + Kansiota %1$s ei ole enää olemassa + Siirrä kaikki + Kaikki tiedostot siirrettiin + Joidenkin tiedostojen siirtäminen epäonnistui + Paikallinen: %1$s + Etä: %1$s Aseta sovelluksesi PIN - Anna uuden sovelluksesi PIN-koodi - Anna ownCloud-sovelluksen PIN + Anna sovelluksen PIN PIN kysytään joka kerta, kun sovellus käynnistetään - Anna ownCloud-sovelluksen PIN uudestaan - Poista ownCloud-sovelluksen PIN - ownCloud-sovelluksen PIN-koodit eivät täsmää - Väärä ownCloud-sovelluksen PIN - ownCloud-sovelluksen PIN poistettu - ownCloud-sovelluksen PIN-koodi tallennettu - - 15 minuuttia - 30 minuuttia - 60 minuuttia - - - 15 - 30 - 60 - + Anna sovelluksen PIN uudestaan + Poista sovelluksen PIN + Sovelluksen PIN-koodit eivät täsmää + Väärä sovelluksen PIN + Sovelluksen PIN poistettu + Sovelluksen PIN-koodi tallennettu + %1$s (toistetaan) + %1$s (ladataan) + Mediatiedostoa ei löytynyt + Tiliä ei määritetty + Tiedosto ei ole kelvollisella tilillä + Mediatiedoston luku ei onnistunut + Aikakatkaisu toistoa yrittäessä + Mediatiedostoa ei voi suoratoistaa + Taaksepäin kelaus -painike + Toisto tai keskeytys -painike + Eteenpäin kelaus -painike Yritetään kirjautua... Ei verkkoyhteyttä - Verkkoyhteyttä ei havaittu. Tarkista internetyhteyden tila ja yritä uudelleen. - Yhdistä silti Salattu yhteys ei ole käytettävissä. - Sovellus ei voinut luoda salattua yhteyttä palvelimeen. Kuitenkin salaamaton yhteys on olemassa. Jatka sitä käyttäen tai lopeta tähän. Yhteys muodostettu Testataan yhteyttä... - Väärin tehdyt ownCloud-asetukset - Vaikuttaa ettei ownCloud ole oikein määritetty. Ota yhteyttä ylläpitäjään. + Väärin tehdyt palvelin-asetukset + Laitteella on jo tili samalle käyttäjälle ja palvelimelle Tuntematon virhe - Tuntematon virhe tapahtui. Ota yhteyttä tekijöihin ja liitä mukaan lokitiedostot laitteestasi. Isäntää ei löydy - Annettua isäntää ei löydy. Tarkista nimi ja onko palvelin käytettävissä, yritä sitten uudestaan. - ownCloud-instanssia ei löydetty - Sovellus ei löydä ownCloud-instanssia annetusta polusta. Tarkista polku ja yritä uudestaan. + Palvelin-instanssia ei löydetty Palvelimen vastaus viipyy liian kauan Väärin annettu osoite SSL:n alustus epäonnistui - SSL-palvelimen identiteettiä ei voida varmistaa - Tuntematon ownCloud-palvelimen versio + Tuntematon palvelimen versio Yhteyden muodostus epäonnistui Salattu yhteys muodostettu - Kirjautumistiedot - Virheellinen tunnus tai salasana - Väärä polku - Sisäinen palvelinvirhe, koodi %1$d - Sovellus sulkeutui yllättäen. Haluatko lähettää vikailmoituksen? - Lähetä ilmoitus - Älä lähetä ilmoitusta - Laajennuksia saatavilla! - ownCloud-instanssisi näyttäisi tukevan laajennuksia. Haluatko nähdä Androidille saatavilla olevat laajennukset? + Väärä käyttäjätunnus tai salasana + Epäonnistunut valtuutus + Valtuutuspalvelun esti käytön + Odottamaton tila; anna palvelimen osoite uudelleen + Valtuutus vanhentui. Valtuuta uudelleen + Anna nykyinen salasana + Istunto vanhentui. Yhdistä uudelleen + Yhdistetään tunnistautumispalvelimeen… + Palvelin ei tue tätä tunnistautumistapaa + %1$s ei tue useita tilejä + Tunnistautuminen palvelimeen ei onnistunut Pidä tiedosto ajan tasalla - Jaa Nimeä uudelleen Poista Haluatko poistaa kohteen %1$s? @@ -180,17 +158,18 @@ Nimen muutos epäonnistui Etäpään tiedostoa ei voitu tarkistaa Tiedoston sisältö on jo synkronoitu - Kansion luonti epäonnistui + Kielletyt merkit: / \\ < > : \" | ? * Odota hetki Odottamaton ongelma; kokeile valita tiedosto toisella sovelluksella Tiedostoa ei valittu - Varoitus + Lähetä linkki… + Kirjaudu oAuth2:lla + Yhdistetään oAuth2-palvelimeen… Sivuston identiteetin vahvistaminen ei onnistunut - Palvelimen varmenteeseen ei luoteta - Palvelimen varmenne on vanhentunut - Palvelimen varmenteen kelvolliset päivät ovat tulevaisuudessa - Osoite ei vastaa sertifikaatissa mainittua isäntänimeä - Palvelimen varmenteen noutaminen epäonnistui Haluatko silti luottaa tähän varmenteeseen? Varmenteen tallennus epäonnistui Tiedot @@ -208,7 +187,10 @@ Päättyen: Allekirjoitus: Algoritmi: - Tämä on paikkavaraus + Varmennetta ei voi näyttää. + - Ei lisätietoja virheestä + PNG-kuva + 389 kt Lähetä kuvat vain WiFi-verkossa /InstantUpload Päivitysristiriita @@ -216,4 +198,15 @@ Säilytä molemmat Korvaa Älä lähetä + Kuvan esikatselu + Kuvaa ei voi näyttää + valitse kaikki + yritä uudelleen kaikkia valittuja + Lataa lisää kuvia + Virheviesti: + Valitettavasti tämän tiedoston tai kansion jakaminen ei onnistu. Varmista että se on olemassa + Virhe tiedoston tai kansion jakamista yrittäessä + Lähetä + Kopioi linkki + Kopioitu leikepöydälle diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml deleted file mode 100644 index 7e10d0fe..00000000 --- a/res/values-fi/strings.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - asetukset - asetukset - Käyttäjätunnus - Käyttäjätunnus - avata - diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-fr-rCA/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 9a0eaad4..0180642d 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -1,67 +1,51 @@ - ownCloud - Mot de passe : - Utilisateur : - Connexion - Bienvenue dans votre ownCloud - Fichiers - Musique - Contacts - Calendrier - Marque-pages - Paramètres - Paramètres du compte - Aucun compte ownCloud n\'est encore paramétré. Pour utiliser cette application, il vous faut en ajouter un. - client ownCloud pour Android\n\nversion : %1$s - Synchronisation du compte - Téléverser un fichier + %1$s Android App + version %1$s + Actualiser le compte + Téléverser Contenu d\'une autre application Fichiers - Créer un répertoire - Rechercher + Ouvrir avec + Nouveau dossier Paramètres - Généraux - Traçage de l\'appareil - Ajouter une nouvelle session - Créer les miniatures - Sélectionnez un compte - Choisissez quel compte utiliser - Traçage de l\'appareil - Permettre à ownCloud de suivre votre position - Votre ownCloud trace actuellement cet appareil - Intervalle de mise à jour - Mettre à jour toutes les %1$s minutes + Détails + Envoyer + Général + Plus Comptes Gestion des comptes utilisateur - Utilisation d\'un code de sécurité ownCloud - Protéger l\'accès aux données maniplulées par le client ownCloud + Utilisation d\'un code de sécurité + Protéger l\'accès aux données maniplulées par le client Activer le téléversement instantané Import instantané des photos prises par la caméra - URL ownCloud + Activer les logs + Utilisé pour enregistrer les problèmes dans les logs + Historique des logs + Cela affiche les logs enregistrés + Supprimer l\'historique + Aide + Recommander à un ami + Commentaires + Empreinte + Essayez %1$s sur votre smartphone ! + Vérifier le serveur + Adresse du serveur https://... Nom d\'utilisateur Mot de passe - Je suis nouveau sur ownCloud - L\'URL spécifiée n\'est pas correcte - Le nom de session spécifié n\'est pas correct + Nouveau dans %1$s ? Fichiers - Aucun fichier n\'est sélectionné pour le téléversement - Nom d\'utilisateur - Mot de passe - Adresse web - Afficher le mot de passe ? - Me connecter à mon ownCloud Connecter Téléverser + Sélectionner le dossier d\'envoi : Aucun compte n\'a été trouvé - Aucun compte ownCloud n\'a été trouvé. Veuillez commencer par en configurer un. + Aucun compte %1$s n\'a été trouvé. Veuillez commencer par en configurer un. Paramètres Quitter Rien à envoyer Aucun contenu reçu. Rien à envoyer - ownCloud n\'est pas autorisé à accéder au contenu partagé + %1$s n\'est pas autorisé à accéder au contenu partagé Téléversement - Créer un répertoire pour le téléversement Ce répertoire ne contient aucun fichier.\nDe nouveaux fichiers peuvent être importés en cliquant sur le bouton « Téléverser un fichier » du menu des options Effleurez un fichier pour afficher les informations complémentaires Taille : @@ -69,10 +53,10 @@ Créé le : Modifié le : Télécharger - Rafraîchir - Télécharger à nouveau - Ouvrir + Actualiser le fichier Le fichier a été renommé en %s pendant le téléversement + Partager le lien + Ne plus partager ce lien Oui Non OK @@ -80,9 +64,11 @@ Annuler l\'envoi Annuler Sauvegarder & Quitter - Quitter ownCloud Erreur + Chargement ... + Erreur Inconnue À propos de + Changer de mot de passe Effacer ce compte Créer un compte Téléverser un fichier depuis… @@ -91,80 +77,90 @@ Envoi du fichier %2$s en cours, %1$d%% effectués Téléversement réussi Le fichier %1$s a été envoyé avec succès - %1$d fichiers ont été envoyés avec succès Échec de l\'envoi L\'envoi de %1$s a échoué - Échec de l\'envoi : %1$d sur %2$d fichiers ont été importés Téléchargement en cours… Téléchargement en cours de %2$s, %1$d%% effectués Téléchargement réussi %1$s a été téléchargé avec succès Le téléchargement a échoué Le téléchargement de %1$s a échoué + Pas encore téléchargé Choisissez un compte - Contacts La synchronisation a échoué La synchronisation de %1$s ne peut pas être complétée + Mot de passe invalide pour %1$s Des conflits ont été trouvés %1$d fichiers à garder synchronisés n\'ont put être synchronisé La synchronisation des fichiers a échoué Le contenu de %1$d fichiers n\'a put être synchronisé (%2$d conflits) - Utiliser une connexion sécurisée - ownCloud ne peut pister votre appareil, veuillez vérifier vos paramètres de localisation + Certains fichiers locaux ont été oubliés + %1$d fichiers du dossier %2$s n\'ont pas pu être copiés dans + Le dossier %1$s n\'existe plus + Tout déplacer + Tous les fichiers ont été déplacés + Certains fichiers n\'ont pu être déplacés + Local: %1$s + Distant: %1$s + Il n\'y a pas assez de place disponible pour copier les fichiers sélectionnés dans le dossier %1$s. Voulez-vous les déplacer à la place ? Veuillez saisir votre code de sécurité - Veuillez saisir votre nouveau code de sécurité - Veuillez saisir votre code de sécurité ownCloud + Veuillez saisir votre code de sécurité Le code PIN vous sera demandé à chaque lancement de l\'application - Veuillez saisir à nouveau votre code de sécurité ownCloud - Retirer le code de sécurité ownCloud + Veuillez saisir à nouveau votre code de sécurité + Retirer le code de sécurité Les deux codes saisis ne concordent pas - code de sécurité ownCloud incorrect - code de sécurité ownCloud retiré - code de sécurité ownCloud enregistré - - 15 minutes - 30 minutes - 60 minutes - - - 15 - 30 - 60 - + Code de sécurité incorrect + Code de sécurité retiré + Code de sécurité enregistré + %1$s lecteur de musique + %1$s (lecture) + %1$s (chargement) + %1$s pourcentage de lecture finie + Fichier média introuvable + Aucun compte n\'a été trouvé + Le fichier n\'est pas dans un compte valide + Le codec de ce média n\'est pas supporté + Le fichier média ne peut pas être lu + Le fichier média n\'est pas correctement encodé + Délai dépassé pour la lecture du morceau. + Le fichier média ne peut pas être diffusé + Fichier média ne peut être joué avec le stock de media player + taux %1$s erreurs de sécurité essayant de jouer + Taux %1$s d\'erreurs de saisie essayant de jouer + Taux %1$s d\'erreurs inattendues essayant de jouer + Bouton de rem-bobinage + Bouton de Lecture ou de Pause + Bouton d\'avance rapide Tentative de connexion … Pas de connexion réseau - Aucune connexion réseau n\'a été trouvée, veuillez vérifier votre connectivité à internet puis réessayez. - Se connecter malgré tout Connexion sécurisée non disponible - L\'application ne peut établir de connexion sécurisée avec le serveur. Cependant, une connexion non sécurisée est disponible. Vous pouvez continuer en utilisant celle-ci ou annuler. Connexion établie Test de la connexion… - Configuration ownCloud erronée - Il semblerait que votre instance ownCloud ne soit pas correctement configurée. Veuillez contacter votre administrateur pour plus de détails. + Configuration du serveur erronée + Un compte pour le même utilisateur et serveur existe déjà sur ce périphérique + L\'utilisateur entré ne correspond pas à l\'utilisateur de ce compte Une erreur inconnue s\'est produite - Une erreur inconnue s\'est produite, merci de bien vouloir contacter les auteurs du programme en y incluant les messages d\'erreurs. Impossible de trouver l\'hôte - Impossible de trouver l\'hôte spécifié. Veuillez vérifier le nom du serveur, qu\'il est disponible, et essayer à nouveau. - Aucune instance ownCloud n\'a été trouvée - L\'application n\'a pu trouver une instance ownCloud à cette adresse. Veuillez vérifier l\'adresse et essayez à nouveau + Aucune instance du serveur n\'a été trouvée Le serveur met trop longtemps à répondre Adresse invalide Échec de l\'initialisation SSL - L\'identité SSL du serveur n\'a pu être vérifiée - La version ownCloud du serveur n\'est pas reconnue + Impossible de vérifier l\'identité du serveur SSL. + La version du serveur n\'est pas reconnue Impossible d\'établir la connexion Connexion sécurisée établie - Détails des informations de connexion - Login / mot de passe invalides - Le chemin fourni est erroné - Erreur interne du serveur, code %1$d - L\'application s\'est fermée de façon inattendue, voulez-vous envoyer un rapport d\'erreur ? - Envoyer un rapport - Ne rien envoyer - Des extensions sont disponibles ! - Il semblerait que votre ownCloud supporte les extensions avancées. Voulez-vous afficher les extensions disponibles pour android ? + Nom d\'utilisateur ou mot de passe invalide + Echec d\'autorisation + Accès refusé par le serveur d\'autorisation + État inattendu ; veuillez entrer à nouveau l\'URL du serveur + Votre autorisation a expiré. Merci de vous authentifier à nouveau + Veuillez saisir le mot de passe courant + Votre session a expiré. Merci de vous reconnecter + Connexion au serveur d\'authentification... + Le serveur ne supporte pas cette méthode d\'authentification + %1$s ne supporte pas les comptes multiples + Impossible de s\'authentifier sur ce serveur Maintenir le fichier à jour - Partager Renommer Supprimer Voulez-vous vraiment supprimer %1$s ? @@ -180,17 +176,19 @@ Renommage impossible Le fichier distant n\'a pu être vérifié Le contenu des fichiers est déjà synchronisé - Création du répertoire impossible + Le dossier n\'a pas pu être créé + Caractères interdits : / \\ < > : \" | ? * Veuillez patienter Problème inattendu ; veuillez essayer une autre app pour la sélection du fichier Aucun fichier sélectionné - Attention + Envoyer un lien à… + Connexion avec aAuth2. + Connexion au serveur aAuth2... L\'identité du site ne peut être vérifiée - Le certificat du serveur n\'est pas sûr - Le certificat du serveur a expiré - Le certificat du serveur n\'est pas encore valide - L\'URL ne correspond pas au nom d\'hôte du certificat - Impossible d\'obtenir le certificat du serveur Voulez-vous tout de même faire confiance à ce certificat ? Impossible de sauvegarder le certificat Détails @@ -208,7 +206,14 @@ À : Signature : Algorithme : - Ceci est un espace réservé + Impossible d\'afficher le certificat. + - Aucune information sur l\'erreur + Ceci est un espace réservé + placeholder.txt + Image PNG + 389 Ko + 18/05/2012 12:23 + 12:23:45 Ne téléverser les images que via une connexion WiFi /TéléversementInstantané Conflit de mise à jour @@ -216,4 +221,25 @@ Garder les deux versions Écraser Ne pas téléverser + Prévisualisation de l\'image + Cette image ne peut pas être affichée + %1$s n\'a pas pu être copié dans le dossier local %2$s + Échec du téléversement instantané + Téléchargements instantanés échoués + Résumé de tous les téléchargements instantanés échoués + Tous sélectionner + réessayer de tous sélectionner + Supprimez tous les sélectionnés de la file d\'attente de téléchargement + Ré-essayer de charger l\'image: + Charger plus d\'images + Ne rien faire vous n\'êtes pas connecté pour le téléchargement instantané + Message d\'échec: + Veuillez vérifier la configuration de votre serveur, peut-être que votre quota est dépassé. + Impossible de partager ce fichier ou répertoire. Vérifiez qu’il est bien présent + Une erreur est survenue lors de la tentative de partage de ce fichier ou répertoire + Impossible d’annuler le partage de ce fichier ou répertoire : il n’existe pas. + Une erreur est survenue lors de la tentative d’annulation du partage de ce fichier ou répertoire + Envoyer + Copier le lien + Copié dans le presse-papiers diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index 4cf5de2a..9dfe0bbc 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -1,58 +1,43 @@ - ownCloud - Contrasinal: - Nome de usuario: - Acceso - Benvido/a - Ficheiros - Música - Contactos - Calendario - Marcadores - Preferencias - Activar a conta - Non hai ningunha conta configurada no seu dispositivo. Para empregar o aplicativo necesita crear unha. - O aplicativo %1$s para Android\n\nversión: %2$s - Actualizar + Aplicativo Android %1$s + versión %1$s + Actualizar a conta Enviar Contido doutros aplicativos Ficheiros - Crear un directorio - Buscar + Abrir con + Novo cartafol Preferencias + Detalles + Enviar Xeral - Seguimento do dispositivo - Engadir unha nova sesión - Crear miniaturas das imaxes - Escoller unha conta - Escolla cal das súas contas ten que usar o aplicativo. - Rastrexamento do dispositivo - Permitir que este aplicativo rexistre as posicións do seu dispositivo - Este aplicativo mantén o rexistro de posicións do seu dispositivo - Intervalo de actualizacións - Actualizar cada %1$s minutos + Máis Contas Xestionar as contas PIN do aplicativo Protexe o seu cliente Activar o envío instantáneo Enviar instantaneamente as fotos tiradas coa cámara - URL + Activar o rexistro + Isto empregase para rexistrar os problemas + Historial do rexistro + Isto amosa os rexistros gravados + Eliminar o historial + Axuda + Recomendar a un amigo + Comentarios + Impresión + Tente %1$s no seu teléfono intelixente! + Comprobar o servidor + Enderezo do servidor https://… Nome de usuario Contrasinal - Son novo en %1$s - Deuse un enderezo incorrecto - Nome de sesión incorrecto + Novo en %1$s? Ficheiros - Non se escolleron ficheiros para enviar - Nome de usuario - Contrasinal - Enderezo web - Amosar o contrasinal? - Conectar co seu %1$s Conectar Enviar + Escolla o cartafol de envío: Non se atoparon contas Non hai contas de %1$s no seu dispositivo. Cree unha nova conta primeiro. Instalación @@ -61,7 +46,6 @@ Non se recibiu contido. Non hai nada para enviar. %1$s non ten permiso para acceder ao contido compartido Enviando - Crear un directorio para os envíos Non hai ficheiros neste cartafol.\nOs novos ficheiros pódense engadir ca opción do menú «Enviar». Prema nun ficheiro para que amose a información adicional. Tamaño: @@ -69,10 +53,10 @@ Creado: Modificado: Descargar - Actualizar - Descargar de novo - Abrir + Actualizar o ficheiro O ficheiro foi renomeado a %1$s durante o envío + Ligazón para compartir + Deixar de compartir a ligazón Si Non Aceptar @@ -80,39 +64,47 @@ Cancelar o envío Cancelar Gardar e saír - Abandonar %1$s Erro + Cargando ... + Produciuse un erro descoñecido Sobre + Cambiar o contrasinal Eliminar a conta Crear unha conta Enviar desde… - Nome do directorio + Nome do cartafol Enviando… %1$d%% enviando %2$s Enviado correctamente %1$s foi enviado correctamente - %1$d ficheiros foron enviados correctamente Produciuse un fallou no envío Non foi posíbel completar o envío de %1$s - Produciuse un fallou no envío: enviáronse %1$d/%2$d ficheiros Descargando… %1$d%% descargando %2$s Completouse a descarga %1$s descargouse correctamente Produciuse un fallo na descarga Non foi posíbel completar a descarga de %1$s + Non descargado aínda Escoller unha conta - Contactos Produciuse un fallo na sincronización Non foi posíbel completar a sincronización de %1$s + Contrasinal incorrecto para %1$s Atopáronse conflictos - Non foi posíbel sincronizar %1$d ficheiros «kept-in-sync» - Produciuse un fallou ao sincronizar ficheiros «kept-in-sync» + %1$d ficheiros «manter sincronizados» non foi posíbel sincronizalos + Produciuse unha falla no mantemento sincronizado de ficheiros Non foi posíbel sincronizar o contido de %1$d ficheiros (%2$d conflitos) - Empregar a conexión segura - %1$s non pode rastrexar o seu dispositivo. Comprobe as configuracións de localización. + Algúns ficheiros locais foron esquecidos + Non é posíbel copiar %1$d ficheiros do cartafol %2$s en + 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 deste aplicativo 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). + O cartafol %1$s xa non existe + Mover todo + Foron movidos todos os ficheiros + Algúns ficheiros non puideron seren movidos + Local: %1$s + Remoto: %1$s + Non hai espazo abondo para copiar os ficheiros seleccionados no cartafol %1$s. Quere movelos no canto de copialos? Insira o seu PIN do aplicativo - Insira o seu novo PIN do aplicativo Introduza o seu PIN do aplicativo Pediráselle o PIN cada vez que se inicie o aplicativo Volva a introducir o seu PIN do aplicativo @@ -121,50 +113,55 @@ PIN do aplicativo incorrecto O PIN do aplicativo foi retirado Almacenouse o PIN do aplicativo - - 15 minutos - 30 minutos - 60 minutos - - - 15 - 30 - 60 - - Tentando acceder… + %1$s reprodutor musical + %1$s (reproducindo) + %1$s (cargando) + Rematou a reprodución de %1$s + Non se atopan ficheiros multimedia + Non foi fornecida unha conta + O ficheiro non está nunha conta axeitada + Códec multimedia non admitido + Non é posíbel ler o ficheiro multimedia + O ficheiro multimedia non está codificado correctamente + Esgotouse o tempo de espera tentando reproducir + Non é posíbel enviar como fluxo o ficheiro multimedia + Non é posíbel reproducir o ficheiro multimedia co reprodutor «stock» + Produciuse un erro de seguranza tentando reproducir %1$s + Produciuse un erro de entrada tentando reproducir %1$s + Produciuse un erro non agardado tentando reproducir %1$s + Botón de retroceso + Botón de reprodución/pausa + Botón de avance rápido + Intentando acceder... Sen conexión de rede - Non se detectaron conexións de rede. Comprobe a conexión a Internet e tenteo de novo. - Conectar igualmente Non hai conexión seguras dispoñíbeis. - O aplicativo non puido facer unha conexión segura co servidor. Inda que non é segura, hai unha conexión posíbel. Pode continuar ou cancelala. Estabeleceuse a conexión - Comprobando a conexión… + Comprobando a conexión... Configuración errada do servidor - Semella que a súa instancia do servidor non está configurada correctamente. Contacte co seu administrador para máis detalles. + Xa existe unha conta do mesmo usuario e servidor neste dispositivo + O usuario que introduciu non coincide co usuario desta conta Produciuse un erro descoñecido! - Produciuse un erro descoñecido. Contacte cos autores e infórmeos do fallo cos rexistros de erro do seu dispositivo. Non foi posíbel atopar a máquina - Non foi posíbel atopar a máquina indicada. Comprobe o nome da máquina e tenteo de novo. Non se atopou unha instancia do servidor - O aplicativo non atopou instancias do servidor na ruta indicada. Comprobe a ruta e tenteo de novo. O servidor tardou demasiado en responder URL incorrecto Produciuse un fallo ao iniciar o SSL - A identidade SSL do servidor non foi verficada + Non foi posíbel verificar a identidade do servidor SSL Versión do servidor non recoñecida Non é posíbel estabelecer a conexión Estabeleceuse unha conexión segura - Detalles do acceso - Usuario ou contrasinal incorrectos - Deuse unha ruta incorrecta - Produciuse un erro interno do servidor, código %1$d - O aplicativo deixou de funcionar de xeito inesperado. Quere enviar un informe desta quebra? - Enviar un informe - Non enviar un informe - Extensión dispoñíbel! - Semella que a súa instancia do servidor admite extensións avanzadas. Quere ver se hai extensións dispoñíbeis para Android ? + Nome de usuario ou contrasinal incorrecto + A autorización non foi aceptada + O acceso foi denegado polo servidor de autorización + Estado inesperado, introduza de novo o enderezo URL do servidor + A súa autorización caducou. Autorícese de novo + Introduza o contrasinal actual + A súa sesión caducou. Acceda de novo + Conectando co servidor de autenticación… + O servidor non admite este método de autenticación + %1$s non admite contas múltipes + Non é posíbel autenticalo neste servidor Manter actualizado o ficheiro - Compartir Renomear Retirar Confirma que quere retirar %1$s ? @@ -180,17 +177,19 @@ Non foi posíbel completar a operación de cambio de nome Non foi posíbel comprobar o ficheiro remoto Os contidos do ficheiro xa están sincronizados - Non foi posíbel crear o directorio + Non foi posíbel crear o cartafol + Caracteres non permitidos: / \\ < > : \" | ? * Agarde un chisco Produciuse un erro non agardado. Seleccione o ficheiro con outro aplicativo diferente Non se escolleu ningún ficheiro - Aviso + Enviar a ligazón a ... + Acceder con oAuth2 + Conectando co servidor oAuth2… Non foi posíbel verificar a identidade do sitio - O certificado do servidor non é de confianza - O certificado do servidor caducou - As datas de validez do certificado están son do futuro - O URL non coincide co nome de máquina no certificado - Non foi posíbel obter o certificado do servidor Aínda así, quere fiar neste certificado igualmente? Non foi posíbel gardar o certificado Detalles @@ -208,7 +207,14 @@ A: Sinatura: Algoritmo: - Isto é unha marca de posición + Non é posíbel amosar o certificado. + - Non hai información sobre este erro + Isto é un marcador de posición + marcador_de_posición.txt + Imaxe PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Enviar imaxes só medinte WiFi /EnvíoInstantáneo Conflito de actualización @@ -216,4 +222,25 @@ Manter ambos Sobrescribir Non enviar + Vista previa da imaxe + Esta imaxe non pode ser amosada + Non foi posíbel copiar %1$s no cartafol local %2$s + produciuse un fallo de EnvíoInstantáneo + Envíos instantáneos fallados + Resumo de todos os envíos instantáneos fallados + seleccionar todo + tentar de novo todo o seleccionado + eliminar todo o seleccionado da cola de envío + tentar de novo o envío da imaxe: + Cargar máis imaxes + non facer nada que non estea en liña para o envío instantáneo + Mensaxe de fallo: + Comprobe a configuración do seu servidor. é probábel que xa excedera a cota. + Non foi posíbel compartir este ficheiro ou cartafol. Asegurese de que existe. + Produciuse un erro ao tentar compartir este ficheiro ou cartafol. + Non foi posíbel deixar de compartir este ficheiro ou cartafol xa que non existe. + Produciuse un erro ao tentar deixar de compartir este ficheiro ou cartafol + Enviar + Copiar a ligazón + Copiado no portapapeis. diff --git a/res/values-he/strings.xml b/res/values-he/strings.xml index 15a26118..f8d370f4 100644 --- a/res/values-he/strings.xml +++ b/res/values-he/strings.xml @@ -1,56 +1,23 @@ - ownCloud - ססמה: - שם משתמש: - התחברות - ברוך בואך - קבצים - מוזיקה - אנשי קשר - יומן - סימניות - הגדרות - הגדרת חשבון - לא הוגדר חשבון בהתקן שלך. כדי להשתמש ביישום זה עליך ליצור אחד. - יישום האנדרויד של %1$s\n\nגרסה: %2$s - רענון העלאה תוכן מיישומים אחרים קבצים - יצירת תיקייה - חיפוש הגדרות + פרטים + שליחה כללי - מעקב אחר מכשירים - הוספת הפעלה חדשה - יצירת תמונות ממוזערות - נא לבחור בחשבון - נא לבחור באיזה מהחשבונות שלך היישום אמור להשתמש. - מעקב אחר מכשירים - יש להפעיל יישום זה כדי לעקוב אחר מיקום ההתקן שלך - יישום זה עוקב אחר ההתקן - מרווח בין עדכונים - לעדכן בכל %1$s דקות + יותר חשבונות ניהול חשבונות קוד יישום הגנה על הלקוח שלך הפעלת העלאות מהירות העלאה מהירה של תמונות שמצולמות במצלמה שלך - כתובת + עזרה שם משתמש ססמה - %1$s חדש לי - הכתובת שצוינה שגויה - שם ההפעלה שגוי קבצים - לא נבחר שום קובץ להעלאה - שם משתמש - ססמה - כתובת אתר - האם להציג את הססמה? - התחברות ל־%1$s שלך התחברות העלאה לא נמצא חשבון @@ -61,7 +28,6 @@ לא התקבל תוכן. אין מה להעלות. ל־%1$s אין הרשאה לגשת לתוכן המשותף שלך בהעלאה - יצירת תיקייה להעלאה אין קבצים בתיקייה זו.\nניתן להוסיף קבצים חדשים בעזרת האפשרות „העלאה“ מהתפריט. יש לגעת בקובץ כדי להציג פרטים נוספים. גודל: @@ -69,9 +35,6 @@ מועד היצירה: מועד השינוי: הורדה - רענון - הורדה מחדש - פתיחה שם הקובץ השתנה ל־ %1$s במהלך ההעלאה כן לא @@ -80,9 +43,11 @@ ביטול ההעלאה ביטול לשמור ולצאת - לעזוב את %1$s שגיאה + בטעינה … + שגיאה בלתי ידועה על אודות + שינוי ססמה מחיקת חשבון יצירת חשבון העלאה מהמיקום … @@ -91,10 +56,8 @@ %1$d%% בהעלאה %2$s ההעלאה הצליחה %1$s נשלח בהצלחה - %1$d קבצים נשלחו בהצלחה ההעלאה נכשלה אין אפשרות להשלים את ההעלאה של %1$s - ההעלאה נכשלה: %1$d/%2$d קבצים נשלחו בהורדה … %1$d%% בהורדה %2$s ההורדה הצליחה @@ -102,17 +65,19 @@ ההורדה נכשלה לא ניתן להשלים את ההורדה של נא לבחור בחשבון - אנשי קשר הסנכרון נכשל לא ניתן להשלים את הסנכרון של נמצאו התנגשויות לא ניתן לסנכרן %1$d קבצים שהוגדרו לסנכרון קבצים שהוגדרו לסנכרון נכשלו לא ניתן לסנכרם את תוכנם של %1$d מהקבצים (%2$d התנגשויות) - להשתמש בחיבור מאובטח - ל־%1$s אין אפשרות לעקוב אחר ההתקן שלך. נא לבדוק את הגדרות המיקום שלך + חלק מהקבצים המקומיים נשכחו + להעביר הכול + כל הקבצים הועברו + לא ניתן להעביר חלק מהקבצים + מקומי: %1$s + מרוחק: %1$s נא להזין את קוד היישום שלך - נא להזין את הקוד החדש של היישום שלך נא להזין את קוד היישום שלך תופיע בקשה לקוד בכל פעם שהיישום מופעל נא להזין את קוד היישום שלך מחדש @@ -121,50 +86,30 @@ קוד היישום שגוי קוד היישום הוסר קוד היישום אוחסן - - רבע שעה - חצי שעה - שעה - - - 15 - 30 - 60 - + נגנן המוזיקה %1$s + %1$s (מתנגן) + %1$s (בטעינה) + לא נמצא קובץ מדיה + לא צוין חשבון + הקובץ אינו בחשבון תקני + מקודד המדיה אינו נתמך + לא ניתן לקרוא את קובץ המדיה מתבצע ניסיון כניסה… אין חיבור לאינטרנט - לא נבחר חיבור לאינטרנט, נא לבדוק את החיבור שלך לאינטרנט ולנסות שוב. - להתחבר בכל זאת אין חיבור מוצפן זמין. - היישום לא יכול לקיים חיבור מאובטח לשרת. עם זאת קיים חיבור שאינו מאובטח. ניתן להמשיך או לבטל. החיבור נוצר החיבור נבדק… תצורת השרת פגומה - מסתבר כי עותק השרת שלך אינו מוגדר נכון. נא ליצור קשר עם המנהל שלך לקבלת פרטים נוספים. אירעה שגיאה בלתי ידועה! - אירעה שגיאה בלתי ידועה. נא ליצור קשר עם התמיכה ולכלול את קובצי הרישום (log) מההתקן שלך. לא ניתן למצוא את המארח - לא ניתן למצוא את המארח שהוזן. נא לבדוק את שם המארח ואת זמינות השרת ולנסות שוב. לא נמצא מופע שרת - היישום לא הצליח למצוא עותק שרת בנתיב שצוין. נא לבדוק את הנתיב שלך ולנסות שוב. לשרת לקח יותר מדי זמן להגיב כתובת שגויה הפעלת ה־SSL נכשלה - זהות ה־SSL של השרת לא מאומתת גרסה השרת אינה מזוהה לא ניתן ליצור את החיבור נוצר חיבור מאובטח - פרטי הכניסה - שם משתמש / ססמה שגויים - הנתיב שצוין שגוי - שגיאת שרת פנימית, קוד - היישום הושבת באופן בלתי צפוי. האם לשלוח דוח קריסה? - שליחת דוח - לא לשלוח דוח - יש הרחבות זמינות! - מסתבר כי השרת שלך תומך בהרחבות מתקדמות. האם ברצונך לצפות בהרחבות הזמינות לאנדרויד? לשמור על קובץ עדכני - שתף שינוי שם הסרה האם אכן להסיר את @@ -180,17 +125,14 @@ לא ניתן להשלים את פעולת שינוי השם לא ניתן לבדוק את הקובץ המרוחק תוכן הקובץ כבר מסונכרן - לא ניתן ליצור את התיקייה נא להמתין רגע תקלה בלתי צפויה ; נא לבחור בקובץ מיישום אחר לא נבחרו קבצים - אזהרה לא ניתן לאמת את זהות האתר - תעודת השרת אינה מהימנה - תוקף תעודת השרת פג - תוקף תעודת השרת מתייחס לתאריכים בעתיד - הכתובת אינה תואמת לשם המארח בתעודה - לא ניתן לקבל את תעודת השרת האם לתת אמון בתעודה זו בכל אופן? לא ניתן לשמור את התעודה פרטים @@ -208,7 +150,9 @@ סיום: חתימה: אלגוריתם: - זהו ממלא מקום + זהו ממלא מקום + תמונת PNG + 389 ק״ב העלאת תמונות דרך WiFi בלבד /InstantUpload התנגשות עדכון @@ -216,4 +160,13 @@ להשאיר את שניהם לשכתב לא להעלות + הסיכום של כל ההעלאות המהירות שנכשלו + לבחור הכול + לנסות שוב את כל הנבחרים + למחוק את כל הנבחרים מתור ההעלאה + לנסות להעלות את התמונה מחדש: + טעינת תמונות נוספות + הודעת התקלה: + נא לבדוק את תצורת שרת שלך, יתכן שחרגת מהמיכסה שלך. + שליחה diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index e70625ec..d5597756 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -1,9 +1,13 @@ - सेटिंग्स + अपलोड सेटिंग्स + भेजें + सामान्य + सहयोग प्रयोक्ता का नाम पासवर्ड - प्रयोक्ता का नाम - पासवर्ड + अपलोड + त्रुटि + भेजें diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 5db57d5b..48b18b5d 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -1,46 +1,26 @@ - ownCloud - Lozinka: - Korisničko ime: - Prijava - Datoteke - Muzika - Kontakti - Kalendar - ZabiljeÅ¡ke - Postavke - Osvježi Učitaj Datoteke - Kreiraj direktorij - pretraži Postavke Općenito - Dodaj novo pretraživanje - Izaberi račun + viÅ¡e Korisnićki računi - ownCloud URL + Pomoć Korisničko ime Lozinka Datoteke - Korisničko ime - Lozinka - Web adresa - Prikaži lozinku? Poveži Učitaj Izlaz Preuzimanje - Osvježi - Osvježi Da + Ne Prekini upload Odustani GreÅ¡ka - Kontakti + Izmjena lozinke Trying to login… - Podijeli Promjeni ime Makni diff --git a/res/values-hu-rHU/strings.xml b/res/values-hu-rHU/strings.xml index 396995fd..c80a6941 100644 --- a/res/values-hu-rHU/strings.xml +++ b/res/values-hu-rHU/strings.xml @@ -1,56 +1,40 @@ - ownCloud - Jelszó: - Felhasználói név: - Belépés - Üdv - Fájlok - Zene - Címjegyzék - Naptár - Könyvjelzők - Beállítások - Fiók beállítása - Nincs beállított fiók a készülékén. Ahhoz, hogy használja ezt a programot, létre kell hozzon egyet. - %1$s Android alkalmazás\nverzió: %2$s - Frissítés + %1$s Android Alkalmazás + verzió %1$s + Fiók frissítése Feltöltés Más alkalmazásokból származó tartalom Fájlok - Mappa létrehozása - Keresés + Megnyitás a következővel + Új mappa Beállítások + Részletek + Küldjük el Általános - Eszközkövetés - Új munkafolyamat létrehozása - Bélyegkép készítése - Válasszon egy felhasználónevet - Válassza ki, hogy a program milyen fiókot használjon. - Eszközkövetés - Engedélyezze ennek a programnak a helykövetést - Ez a program eltárolja ennek a készüléknek az útvonalát - A frissítés gyakorisága - Minden %1$s percben + Több Fiókok Fiókok kezelése Alkalmazás PIN Védje meg az alkalmazást Az azonnali feltöltés engedélyezése Az eszköz által készített fényképek azonnali feltöltése - URL + Naplózás engedélyezése + Ez használható a problémák naplózásához + Naplózás előzménye + Ez megjeleníti a rögzitett eseményeket + Elözmények törlése + Súgó + Ajánlja egy barátjának + Visszajelzés + Impresszum + Próbálja ki %1$s-t az okostelefonján! + Szerver állapot ellenörzés + Kiszolgáló címe https://... Felhasználói név Jelszó - Új kapcsolat: %1$s - A megadott cím helytelen - A megadott munkafolyamatnév érvénytelen + Új vagy a %1$s területen? Fájlok - A feltöltéshez nincs fájl kiválasztva - Felhasználói név - Jelszó - Web cím - A jelszó megjelenjen? - Kapcsolódás ehhez: %1$s Kapcsolódás Feltöltés Nincs ilyen felhasználói fiók @@ -61,7 +45,6 @@ Nem jött tartalom. Nincs mit feltölteni. %1$s nem jogosult a megosztott tartalom elérésére Feltöltés - Könyvtár létrehozása a feltöltésekhez Ebben a mappában nincsenek állományok. A \"Feltöltés\" menüpont segítségével tölthet föl fájlokat. Érintsen meg egy fájlt a további információkért. Méret: @@ -69,10 +52,10 @@ Készült: Módosítva: Letöltés - Frissítés - Ismételt letöltés - Megnyitás + File frissítése A feltöltés során az állmányt erre neveztük át: %1$s + Megosztás hivatkozással + Megosztás visszavonása Igen Nem OK @@ -80,9 +63,11 @@ A feltöltés megszakítása Mégsem Mentés & Kilépés - %1$s elhagyása Hiba + Betöltés ... + Ismeretlen hiba Rólunk + A jelszó megváltoztatása Fiók törlése Fiók létrehozása Feltöltés innen ... @@ -91,28 +76,32 @@ %1$d%% Feltöltés %2$s A feltöltés sikerült %1$s sikeresen fel lett töltve - %1$d fájl sikeresen fel lett töltve A feltöltés nem sikerült %1$s fájl feltöltése sikertelen - Hibás feltöltés: %1$d/%2$d fájl feltöltve Letöltés ... %1$d%% Letöltés %2$s A letöltés sikeres %1$s sikeresen letöltve A letöltés sikertelen A letöltésből %1$s nem lett befejezve + Még nem töltötte le Válasszon azonosítót - Címtár A szinkronizálás sikertelen %1$s szinkronizációját nem sikerült befejezni + Érvénytelen jelszó a következőhöz %1$s Ütközések vannak %1$d szinkronizálandó állományokat nem sikerült szinkronizálni A szinkronizálandó fájlokat nem sikerült szinkronizálni %1$d fájl szinkronizálása nem sikerült (%2$d ütközés) - Biztonságos kapcsolat használata - %1$s nem tudja követni az eszközét. Kérem ellenőrizze a helybeállításait + Néhány helyi fájlt figyelmen kívül hagytunk + A %1$s mappa már nem létezik + Helyezzük át mindet + Az összes fájlt áthelyeztük + Egyes fájlokat nem sikerült áthelyezni + Helyi: %1$s + Távoli: %1$s + Nincs elég hely ahhoz, hogy a kiválasztott fájlokat a %1$s mappába másoljuk. Akkor inkább helyezzük át őket oda? Kérem adja meg az alkalmazás PIN-kódját - Kérem adja meg az alkalmazás új PIN-kódját Az alkalmazás PIN-kódja A PIN-t kötelező lesz megadni az alkalmazás minden indításakor Kérem, adja meg újra az alkalmazás PIN-kódját @@ -121,50 +110,55 @@ Rossz a megadott PIN Az alkalmazás PIN-ját eltávolítottuk Az alkalmazás PIN-jét eltároltuk - - 15 perc - 30 perc - 60 perc - - - 15 - 30 - 60 - + %1$s zene lejátszó + %1$s (lejátszás) + %1$s (betöltés) + %1$s lejátszás véget ért + Nincs média fájl találat + Nincs felhasználói fiók + A fájl nincs egy felhasználó fiókban + Nem támogatott média kodek + Media fájl nem olvasható + Médiafájl kódolása nem megfelelő + A lejátszás közben időtúllépés történt + A média fájlt nem lehet streamelni. + A média fájlt nem tudja lejátszani a jelenlegi médialejátszó + Biztonsági hiba amikor megpróbálja lejátszani a %1$s + Bemeneteli hiba amikor megpróbálja lejátszani a %1$s + Váratlan hiba amikor megpróbálja lejátszani a %1$s + Következő button + Lejátszás vagy pillanat állj gomb + Gyors előre gomb Bejelentkezés... Nincs hálózati kapcsolat - Nem található hálózati kapcsolat, ellenőrizze az internetkapcsolatát, és próbálja újra. - Csatlakozás mindenképpen Nem érhető el biztonságos kapcsolat. - Az alkalmazás nem tudott titkosított kapcsolatot kialakítani a kiszolgálóval. A nem titkosított kapcsolat elérhető. Folytathatja vagy kiléphet. A kapcsolat létrejött Kapcsolat tesztelése... Hibás a kiszolgáló beállítása - Úgy tűnik a kiszolgáló nincs megfelelően beállítva. Lépjen kapcsolatba a rendszergazdával további információkért. + Egy bejelentkezési beállítás már létezik ugyanehhez a kiszolgálóhoz és felhasználóhoz + A megadott felhasználó nem azonos ezzel a belépési jogosultsággal Ismeretlen hiba történt! - Ismeretlen hiba történt. Értesítse a terméktámogatást csatolva a az eszközének a naplóbejegyzéseit. A kiszolgáló nem található - Nem található a keresett kiszolgáló. Ellenőrizze a nevét és a szerver elérhetőségét majd próbálja újra. Kiszolgáló nem található - A program nem találta a kiszolgálót a megadott útvonalon. Kérem ellenőrizze az útvonalat, majd próbálja újra. A kiszolgáló túl sokára válaszolt Hibás URL Nem sikerült az SSL kapcsolat felépítése - Nem ellenőrizhető az kiszolgáló biztonsági tanúsítványa + Az SSL kiszolgáló tanúsítványát nem sikerült ellenőrizni Ismeretlen változat a kiszolgálón A kapcsolat nem hozható létre Létrejött a titkosított kapcsolat - Belépési adatok - Érvénytelen felhasználónév / jelszó - Rossz a megadott útvonal - Belső hiba történt a kiszolgálón, hibakód: %1$d - A program összeomlott. El akar küldeni egy összeomlási jelentést? - Jelentés küldése - Ne küldjön jelentést - Kiegészítők állnak rendelkezésre! - Úgy tűnik a programja támogatja a haladó kiterjesztéseket. Látni akarja az androidon elérhető kiterjesztéseket? + Hibás felhasználónév vagy jelszó + Sikertelen azonosítás + Hozzáférés megtagadva az azonsítást végző szerver által + Nem várt állapot; kérlek, menj a szerver URL-jére újra. + A jogosultsága lejárt. Kérjük jelentkezzen be ismét! + Kélek, írd be a jelenlegi jelszavadat + Lejárt a munkamenetének érvényessége. Kérjük jelentkezzen be ismét! + Kapcsolódás a felhasználóazonosítást végző kiszolgálóhoz... + A kiszolgáló nem támogatja ezt a felhasználóazonosítási módszert + %1$s nem támogat több bejelenkezési jogosultságot + Nem hiteleíthető a szerverrel szemben Automatikusan frissítse a fájlokat - Megosztás Átnevezés Eltávolítás Tényleg szeretné törölni ezt: %1$s ? @@ -180,17 +174,18 @@ Az átnevezés nem sikerült A távoli fájl nem volt ellenőrizhető Az állományok már szinkonizálva vannak - A mappa nem hozható létre + Nem megendedett karakterek: / \\ < > : \" | ? * Egy pillanat... Váratlan hiba; válassza ki a fájlt más programból Egy fájl sincs kiválasztva - Figyelmeztetés + Hivatkozás küldése ... + Bejelentkezés oAuth2-vel + Kapcsolódás az oAuth2 szerverhez... A kiszolgálót nem sikerült azonosítani - A kiszolgáló tanúsítványa nem megbízható - A kiszolgáló tanúsítványának lejárt az érvényessége - A kiszolgáló tanúsítványa most még nem érvényes - Az URL nem egyezik a tanúsítványban szereplő kiszolgálónévvel - A kiszolgáló tanúsítványa nem érhető el Mégis megbízik ebben a tanúsítványban? A tanúsítvány nem menthető el Részletek @@ -208,7 +203,14 @@ Eddig a dátumig: Aláírás: Algoritmus: - Ez egy helykitöltő + A tanúsítvány nem megjeleníthető. + - Nincs információ a hibáról + Ez egy helyőrző + helyörző.txt + PNG kép + 389 KB + 2012/05/18 12:23 + 12:23:45 Képeket csak WiFi kapcsolaton keresztül töltsünk föl /InstantUpload Frissítési ütközés @@ -216,4 +218,24 @@ Mindkettő megtartása Felülírás Ne töltsük föl + Előnézeti kép + Ez a kép nem jelenik meg + Sikertelen azonnali feltöltés\" + Sikertelen Azonnali feltöltés + Összefoglaló az összes sikertelen instant feltöltésről + Összes kijelölése + újra az összes kiválasztott + törölje az összes kiválasztottat a feltöltési sorból + újra feltölteni a képet: + További képek betöltése + nem vagyunk online üzemmódban az azonnali feltöltéshez + Hibaüzenet + Kérjük ellenőrizd a szerver konfigurációt, mert lehet, hogy a kvótát túllépted. + A mappa nem osztható meg. Lehet, hogy nem is létezik. + Hiba lépett fel a mappa megosztásakor + Nem lehet a megosztást megszüntetni. A mappa vagy fájl nem létezik. + Hiba lépett fel a mappa megosztásának visszavonásakor + Küldjük el + Link másolása + Bemásolva a vágólapra diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/res/values-hu/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/res/values-ia/strings.xml b/res/values-ia/strings.xml index 02512b92..80deb00d 100644 --- a/res/values-ia/strings.xml +++ b/res/values-ia/strings.xml @@ -1,20 +1,21 @@ - Files - Musica - Contactos - Configurationes Incargar Files Configurationes + Invia + General + Plus + Adjuta Nomine de usator Contrasigno Files - Nomine de usator - Contrasigno + Connecte Incargar Discargar Cancellar - Contactos - Compartir + Error + Error Incognite + Cambiar contrasigno + Invia diff --git a/res/values-id-rID/strings.xml b/res/values-id-rID/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/res/values-id-rID/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/res/values-id/strings.xml b/res/values-id/strings.xml index 6823f046..41d20dc0 100644 --- a/res/values-id/strings.xml +++ b/res/values-id/strings.xml @@ -1,36 +1,226 @@ - ownCloud - Password: - Nama Pengguna: - login - Selamat Datang - Berkas - Musik - kontak - Kalendar - Bookmark - pengaturan - Mengatur Akun - Tidak ada akun yang diatur di alat anda. Dalam rangka menggunakan App ini, anda hrus membuatnya. + %1$s Apl Android + versi %1$s + Segarkan akun + Unggah + Konten dari apl lain Berkas + Bukan dengan + Folder baru pengaturan + Rincian + Kirim umum - tautan - nama pengguna - kata kunci + Lainnya + Akun + Kelola Akun + PIN Apl + Lindungi klien Anda + Aktifkan unggah instan + Unggah langsung foto yang diambil oleh kamera + Aktifkan Pencatatan + Ini digunakan untuk mencatat masalah + Riwayat Catatan + Ini menampilkan catatan yang disimpan + Riwayat Hapus + Bantuan + Rekomendasikan ke teman + Umpan balik + Imprint + Coba %1$s pada smartphone Anda! + Periksa Server + Alamat server https://… + Nama Pengguna + Sandi + Baru di %1$s? Berkas - nama pengguna - kata kunci - unduh - buka + Sambungkan + Unggah + Tidak ada akun yang ditemukan + Belum ada akun %1$s pada perangkat Anda. Silahkan buat akun terlebih dahulu. + Pengaturan + Keluar + Tidak ada konten untuk diunggah + Tidak ada konten yang diterima. Tidak ada yang diunggah + %1$s tidak diizinkan mengakses konten berbagi + Mengunggah + Tidak ada berkas dalam folder ini.\nBerkas baru dapat ditambahkan dengan opsi menu \"Unggah\". + Sentuh pada berkas untuk menampilkan informasi tambahan + Ukuran: + Tipe: + Dibuat: + Diubah: + Unduh + Segarkan berkas + Berkas diubah namanya menjadi %1$s saat pengunggahan + Bagikan tautan Ya + Tidak + Oke + Batal mengunduh Batal mengunggah - batal - kesalahan - kontak - berbagi - hilangkan - peringatan - sembunyikan + Batal + Simpan & Keluar + Kesalahan + Memuat ... + Galat tidak diketahui + Tentang + Ubah sandi + Hapus akun + Buat akun + Unggah dari... + Nama folder + Mengungggah... + %1$d%% Mengunggah %2$s + Berhasil mengunggah + %1$s berhasil diunggah + Gagal mengunggah + Unggah %1$s tidak selesai + Mengunduh... + %1$d%% Mengunduh %2$s + Berhasil mengunduh + %1$s berhasil diunduh + Gagal Mengunduh + Mengunduh %1$s tidak selesai + Belum diunduh + Pilih akun + Sinkronisasi gagal + Sinkronisasi %1$s tidak selesai + Sandi salah untuk %1$s + Konflik ditemukan + Konten berkas %1$d tidak dapat disinkronasikan (%2$d konflik) + Beberapa berkas lokal terlupakan + Folder %1$s tidak ada lagi + Pindahkan semua + Semua berkas sudah dipindahkan + Beberapa berkas tidak dapat dipindahkan + Lokal: %1$s + Jauh: %1$s + Silakan masukkan PIN Apl + Masukkan PIN Apl + PIN akan selalu diminta setiap kali apl dijalankan + Silakan masukkan ulang PIN Apl + Hapus PIN Apl + PIN Apl tidak sama + PIN Apl salah + PIN Apl dihapus + PIN Apl disimpan + Pemutar musik %1$s + %1$s (dimainkan) + %1$s (sedang dimuat) + Tidak ditemukan berkas media + Tidak ada akun yang diberikan + Brkas tidak didalam akun yang sah + Kodek media tidak didukung + Berkas media tidak dapat dibaca + Berkas media tidak di enkode dengan benar + Waktu habis saat mencoba untuk main + Berkas media tidak bisa dialirkan + Berkas media tidak dapat dimainkan dengan pemutar media + Kesalahan keamanan saat mencoba memutar %1$s + Kesalahan masukkan saat mencoba memutar %1$s + Kesalahan tak terduga saat mencoba memutar %1$s + Tombol mundur + Tombol main dan jeda + Tombol maju + Mencoba untuk masuk... + Tidak ada koneksi internet + Sambungan aman tidak tersedia + Sambungan dibuat + Pengetesan koneksi ... + Konfigurasi server cacat + Akun untuk pengguna dan server yang sama sudah ada dalam perangkat + Pengguna yang dimasukkan tidak cocok dengan pengguna akun ini + Terjadi kesalahan yang tidak diketahui! + Tidak menemukan host + Instansi server tidak ditemukan + Server terlalu lama merespon + Format URL salah + Menginisiasi SSL gagal + Tidak dapat memverifikasi identitas server SSL + Versi server tidak dikenal + Tidak dapat membuat koneksi + Sambungan aman dibuat + Nama pengguna dan sandi salah + Otorisasi tidak berhasil + Akses ditolak oleh server otorisasi + Keadaan tak terduga, silahkan masukkan URL server yang lagi + Otorisasi Anda telah berakhir. Silakan mengotorisasi lagi + Silakan mesukkan sandi saat ini + Sesi Anda telah berakhir. Silakan masuk kembali + Menyambungkan ke server otentikasi... + Server tidak mendukung medote otentikasi ini + %1$s tidak mendukung banyak akun + Biarkan berkas tetap terbaru + Ubah nama + Hapus + Apakah Anda yakin ingin menghapus %1$s ? + Apakah Anda benar-benar ingin menghapus %1$s beserta isinya? + Lokal saja + Konten lokal saja + Hapus dari server + Jarak jauh dan lokal + Penghapusan berhasil + Penghapusan gagal + Masukkan nama baru + Salinan lokal tidak dapat diubah nama, coba dengan nama yang berbeda + Mengubah nama tidak selesai + Berkas jauh tidak dapat diperiksa + Isi berkas sudah diselaraskan + Karakter yang dilarang: / \\ < > : \" | ? * + Tunggu sejenak + Masalah tidak terduga, silahkan pilih berkas dari apl yang berbeda + Tidak ada berkas yang terpilih + Masuk dengan oAuth2 + Menyambungkan ke server oAuth2... + Identitas situs tidak dapat diverfikasi + - Sertifikat server tidak terpercaya + - Sertifikat server kadaluarsa + - Tanggal sertifikat server yang valid adalah di masa depan + - URL tidak cocok dengan nama host dalam sertifikat + Apakah Anda percaya dengan sertifikat ini? + Sertifikat tidak dapat disimpan + Rincian + Sembunyikan + Diterbitkan untuk: + Diterbitkan oleh: + Nama panggilan: + Organisasi: + Unit Organisasi: + Negara: + Negara bagian: + Lokasi: + Masa berlaku: + Dari: + Untuk: + Tanda tangan: + Algoritma: + Ini adalah placeholder + placeholder.txt + Gambar PNG + 389 KB + 18/05/2012 12:23 PM + 12:23:45 + Hanya unggah gambar via WiFi + /UnggahInstan + Perbarui benturan + Berkas jauh %s tidak sinkron dengan berkas lokal. Melanjutkan akan menggantikan konten berkas di server. + Biarkan keduannya + Timpa + Jangan mengunggah + Pratilik gambar + Gambar ini tidak dapat ditampilkan + UnggahInsatan Gagal + Unggahan instan gagal + Ringkasan dari semua unggahan instan yang gagal + pilih semua + Ulangi semua yang terpilih + hapus semua yang terpilih dari antrian unggahan + ulangi unggah gambar: + Muat Gambar selengkapnya + Tidak melakukan apapun, Anda tidak sedang online + Pesan Kegagalan: + Silakan periksa konfigurasi server Anda, kemungkinan kuota terlampaui. + Kirim diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index 7d27649c..87659b30 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -1,28 +1,24 @@ - Skrár - Tónlist - Bókamerki - Stillingar Senda inn Skrár Stillingar - URL + Senda + Meira + Hjálp Notendanafn Lykilorð Skrár - Notendanafn - Lykilorð Senda inn Niðurhal - Opna Já Nei Hætta við innsendingu Hætta við Villa - Deila + Breyta lykilorði + Nafn möppu Endurskýra Fjarlægja - Aðvörun + Senda diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index a5ecd84c..6f4584a5 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -1,67 +1,51 @@ - ownCloud - Password: - Nome utente: - Accedi - Benvenuto nel tuo ownCloud - File - Musica - Contatti - Calendario - Segnalibri - Impostazioni - Configurazione account - Non ci sono account ownCloud su questo dispositivo. Per utilizzare questa applicazione, devi crearne uno. - %1$s per Android\n\nversione: %2$s - Sincronizza account + Applicazione Android di %1$s + versione %1$s + Aggiorna account Carica un file Contenuti da altre applicazioni File - Crea cartella - Cerca + Apri con + Nuova cartella Impostazioni + Dettagli + Invia Generale - Tracciamento dispositivo - Aggiungi nuova sessione - Crea anteprime immagini - Scegli un account - Scegli quale dei tuoi account sarà utilizzato dall\'applicazione - Tracciamento del dispositivo - Abilita ownCloud a tracciare la posizione del tuo dispositivo - Il tuo ownCloud tiene traccia di questo dispositivo - Intervallo di aggiornamento - Aggiorna ogni %1$s minuti + Altro Account Gestisci account - PIN ownCloud App - Proteggi il tuo client ownCloud + PIN App + Proteggi il tuo client l\'applicazione Caricamento immediato Carica immediatamente le foto dalla fotocamera - URL ownCloud + Abilita registrazione log + Usato per registrare i problemi nei log + Cronologia di registrazione log + Mostra i log registrati + Elimina la cronologia + Aiuto + Consiglia ad un amico + Segnalazioni + Imprint + Prova %1$s sul tuo smartphone! + Verifica server + Indirizzo server https://... Nome utente Password - Sono nuovo di ownCloud - URL fornito errato - Nome di sessione errato + Prima volta su %1$s? File - Nessun file selezionato per l\'invio - Nome utente - Password - Indirizzo web - Mostrare la password? - Connettiti al tuo ownCloud Connetti Carica + Scegliere la cartella da caricare: Nessun account trovato - Non ci sono account ownCloud sul tuo dispositivo. Configura prima un account. + Non ci sono account %1$s sul tuo dispositivo. Configura prima un account. Configurazione Esci Nessun contenuto da caricare Non è stato ricevuto alcun contenuto. Niente da caricare. - ownCloud non è abilitato ad accedere al contenuto condiviso + %1$s non è abilitato ad accedere al contenuto condiviso Caricamento in corso - Crea cartella per il caricamento Non ci sono file in questa cartella.\nNuovi file possono essere aggiunti con l\'opzione di menu \"Carica\". Tocca un file per visualizzare informazioni aggiuntive. Dimensione: @@ -69,10 +53,10 @@ Creato: Modificato: Scarica - Aggiorna - Scarica nuovamente - Apri + Aggiorna file Il file è stato rinominato in %1$s durante il caricamento + Condividi collegamento + Rimuovi condivisione collegamento Sì No OK @@ -80,91 +64,104 @@ Annulla invio Annulla Salva ed esci - Esci da ownCloud Errore + Caricamento in corso... + Errore sconosciuto Informazioni + Modifica la password Elimina account Crea account Carica file da... - Nome cartella + Nome della cartella Caricamento in corso... %1$d%% Caricamento di %2$s Caricamento effettuato %1$s è stato caricato correttamente - %1$d file sono stati caricati correttamente Caricamento non riuscito Il caricamento di %1$s non può essere completato - Caricamento non riuscito: %1$d/%2$d file sono stati caricati Scaricamento in corso... %1$d%% Scaricamento di %2$s Scaricamento effettuato %1$s è stato scaricato correttamente Scaricamento non riuscito Scaricamento di %1$s non è stato completato + Non ancora scaricato Scegli account - Contatti Sincronizzazione non riuscita La sincronizzazione di %1$s non può essere completata + Password non valida per %1$s Conflitti rilevati %1$d file non possono essere sincronizzati Sincronizzazione dei file non riuscita I contenuti di %1$d file non possono essere sincronizzati (%2$d conflitti) - Usa connessione sicura - ownCloud non può tracciare questo dispositivo. Controlla le impostazioni di posizionamento + Alcuni file locali sono stati trascurati + i file %1$d della cartella %2$s non possono essere copiati + Nella versione 1.3.16, i file caricati da questo dispositivo vengono copiati nella cartella locale %1$s per prevenire la perdita dei dati quando un singolo file è sincronizzato con diversi account.\n\nA causa di questo cambiamento, tutti i file caricati nelle versioni precedenti di quest\'applicazione sono stati copiati nella cartella %2$s. Tuttavia, un errore non ha permesso il completamento di questa operazione durante la sincronizzazione dell\'account. Puoi, dunque, sia lasciare i (il) file come sono e rimuovere il collegamento a %3$s, o spostare i (il) file nella cartella %1$s e mantenere il collegamento a %4$s.\n\nIn basso sono elencati i (il) file locali, e i (il) file rimossi in %5$s a cui sono collegati. + La cartella %1$s non esiste più + Sposta tutto + Tutti i file sono stati spostati + Alcuni file non possono essere spostati + Locale: %1$s + Remoto %1$s + Non c\'è spazio sufficiente per copiare i file selezionati nella cartella %1$s. Vuoi invece spostarli nella cartella? Inserisci il PIN dell\'applicazione - Inserisci il nuovo PIN dell\'applicazione - Inserisci il PIN di ownCloud + Inserisci il PIN di l\'applicazione Il PIN sarà richiesto ad ogni avvio dell\'applicazione - Inserisci nuovamente il PIN di ownCloud - Rimuovi il PIN di ownCloud - I PIN di ownCloud non corrispondono - PIN di ownCloud non corretto - PIN di ownCloud rimosso - PIN di ownCloud memorizzato - - 15 minuti - 30 minuti - 60 minuti - - - 15 - 30 - 60 - + Inserisci nuovamente il PIN di l\'applicazione + Rimuovi il PIN di l\'applicazione + I PIN di l\'applicazione non corrispondono + PIN di l\'applicazione non corretto + PIN di l\'applicazione rimosso + PIN di l\'applicazione memorizzato + Lettore musicale %1$s + %1$s (in riproduzione) + %1$s (in caricamento) + Riproduzione di %1$s terminata + Nessun file multimediale trovato + Nessun account fornito + Il file non è in un account valido + Codificatore multimediale non supportato + Il file multimediale non può essere letto + File multimediale non codificato correttamente + La riproduzione ha richiesto troppo tempo + Il file multimediale non può essere trasmesso + Il file multimediale non può essere riprodotto con il lettore integrato + Errore di sicurezza durante il tentativo di riprodurre %1$s + Errore di input durante il tentativo di riprodurre %1$s + Errore inatteso durante il tentativo di riprodurre %1$s + Pulsante Riavvolgi + Pulsante Riproduci o pausa + Pulsante Avanti veloce Tentativo di accesso in corso... Nessuna connessione di rete - Non sono state rilevate connessioni di rete, controlla la tua connessione a Internet e prova ancora. - Connetti comunque Connessione sicura disponibile. - L\'applicazione non è in grado di stabilire una connessione sicura al server. È comunque disponibile una connessione non sicura. Puoi continuare o annullare. Connessione stabilita - Test della connessione in corso... - Configurazione non corretta di ownCloud - Sembra che l\'istanza di ownCloud non sia configurata correttamente. Contatta il tuo amministratore per ulteriori dettagli. + Prova di connessione in corso... + Configurazione non corretta di il server + Esiste già un account su questo dispositivo per lo stesso utente e server + L\'utente digitato non corrisponde all\'utente di questo account Errore sconosciuto - Si è verificato un errore sconosciuto. Contatta gli autori e includi i log del tuo dispositivo. Impossibile trovare l\'host - Impossibile trovare l\'host digitato. Controlla il nome dell\'host e la raggiungibilità del server e prova ancora. - Istanza di ownCloud non trovata - L\'applicazione non può trovare un\'istanza di ownCloud al percorso specificato. Controlla il percorso e prova ancora. + Istanza di il server non trovata Il server ha richiesto troppo tempo per rispondere URL non valido Inizializzazione SSL non riuscita - Identità SSL del server non verificata - Versione del server ownCloud non riconosciuta + Impossibile verificare l\'identità SSL del server + Versione del server il server non riconosciuta Impossibile stabilire la connessione Connessione sicura stabilita - Dettagli di accesso - Credenziali di accesso non valide - Il percorso fornito non è valido - Errore interno del server, codice %1$d - Applicazione terminata in modo inatteso. Vuoi inviare una segnalazione del problema? - Invia segnalazione - Non inviare la segnalazione - Estensioni disponibili! - Sembra che la tua istanza di ownCloud supporti le estensioni avanzate. Vuoi vedere le estensioni disponibili per Android? + Nome utente o password errati + Autorizzazione non riuscita + Accesso negato dal server di autorizzazione + Stato inatteso; inserisci nuovamente l\'URL del server + Autorizzazione scaduta. Richiedi l\'autorizzazione + Inserisci la password attuale + Sessione scaduta. Riconnettiti + Connessione al server di autenticazione in corso... + Il server non supporta questo metodo di autenticazione + %1$s non supporta account multipli + Impossibile eseguire l\'autenticazione su questo server Tieni aggiornato il file - Condividi Rinomina Rimuovi Vuoi davvero rimuovere %1$s? @@ -180,17 +177,19 @@ La rinomina non può essere completata Il file remoto non può essere controllato Contenuti del file già sincronizzati - La cartella non può essere creata + La cartella non potrebbe essere creata + Caratteri proibiti: / \\ < > : \" | ? * Attendi Problema inatteso; prova un\'altra applicazione per selezionare il file Non è stato selezionato alcun file - Avviso + Invia collegamento a... + Accesso con oAuth2. + Connessione al server oAuth2 in corso... L\'identità del sito non può essere verificata - Il certificato del server non è affidabile - Il certificato del server è scaduto - Il certificato del server è troppo recente - L\'URL non corrisponde al nome host nel certificato - Impossibile ottenere il certificato del server Vuoi fidarti comunque di questo certificato? Il certificato non può essere salvato Dettagli @@ -208,7 +207,14 @@ A: Firma: Algoritmo: - Questo è un segnaposto + Il certificato non può essere mostrato. + - Nessuna informazione sull\'errore + Questo è un segnaposto + segnaposto.txt + Immagine PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Carica le immagini solo via WiFi /InstantUpload Conflitto di aggiornamento @@ -216,4 +222,25 @@ Mantieni entrambi Sovrascrivi Non inviare + Anteprima dell\'immagine + Questa immagine non può essere mostrata + %1$s non può essere copiato nella cartella locale %2$s + Caricamento istantaneo non riuscito + Caricamenti istantanei non riusciti + Riepilogo dei caricamenti istantanei non riusciti + seleziona tutto + riprova tutti i selezionati + elimina tutti i selezionati dalla coda di caricamento + riprova a caricare l\'immagine: + Carica altre immagini + non fare niente, non sei collegato per i caricamenti istantanei + Messaggio d\'errore: + Controlla la configurazione del server, forse hai superato la tua quota. + Impossibile condividere il file o la cartella. Assicurati che esista. + Si è verificato un errore durante il tentativo di condivisione del file o della cartella + Impossibile rimuovere dalla condivisione il file o la cartella. Non esiste. + Si è verificato un errore durante il tentativo di rimuovere la condivisione del file o della cartella + Invia + Copia collegamento + Copiato negli appunti diff --git a/res/values-ja-rJP/strings.xml b/res/values-ja-rJP/strings.xml index 383f7eba..1304d854 100644 --- a/res/values-ja-rJP/strings.xml +++ b/res/values-ja-rJP/strings.xml @@ -1,78 +1,61 @@ - ownCloud - パスワード: - ユーザー名: - ログイン - ownCloudへようこそ - ファイル - ミュージック - 連絡先 - カレンダー - ブックマーク - 設定 - アカウントを設定 - このデバイスには、ownCloudのアカウントがありません。このアプリケーションを使用するにはアカウントを作成してください。 - ownCloud Android クライアント\n\nバージョン: %1$s - 同期 - ファイルをアップロード + %1$s アンドロイドアプリ + バージョン %1$s + アカウントを同期 + アップロード 他アプリからのコンテンツ ファイル - ディレクトリを作成 - 検索 + 次で開く + 新しいフォルダー 設定 + 詳細 + 送信 一般 - デバイスの追跡 - 新しいセッションを追加 - サムネイル画像を作成 - アカウントを選択 - 利用するアカウントを選んでください。 - デバイスの追跡 - デバイスの位置情報をownCloudに通知 - ownCloudはこのデバイスを追跡します - 更新間隔 - %1$s 分間隔で更新 + もっと見る アカウント アカウント管理 - ownCloudアプリのパスワード - ownCloudクライアントを保護する + アプリのパスワード + クライアントを保護する 自動アップロードを有効 カメラで撮影した画像を自動アップロード - ownCloud URL + ログを有効にする + これは問題をログに記録するのに使用します。 + ログ履歴 + これは記録されたログを表示します + 履歴を削除 + ヘルプ + 友達に推薦 + フィードバック + インプリント + スマートフォンで %1$s を試してください! + サーバーを確認する + サーバーアドレス https://… ユーザー名 パスワード - 私は、ownCloudの新規ユーザーです。 - 誤ったURLです - 誤ったセッション名です + %1$sは初めてですか? ファイル - アップロードのためのファイルが選択されていません - ユーザー名 - パスワード - WEBアドレス - パスワードを表示しますか? - ownCloudに接続 接続 アップロード アカウントが見つかりません - デバイスにownCloudのアカウントがありません。まず最初にアカウントを登録してください。 + デバイスに%1$sのアカウントがありません。まず最初にアカウントを登録してください。 設定 終了 アップロードするコンテンツはありません コンテンツを受信しませんでした。アップロードするものはありません。 - ownCloudで共有コンテンツへのアクセスが許可されていません。 + %1$sで共有コンテンツへのアクセスが許可されていません。 アップロード中 - アップロード用のディレクトリを作成 - このフォルダにはファイルがありません。\n\"アップロード\" メニューで新しいファイルを追加することができます。 + このフォルダーにはファイルがありません。\n\"アップロード\" メニューで新しいファイルを追加することができます。 ファイルをタップすると追加情報が表示されます。 サイズ: タイプ: 作成: 更新: ダウンロード - 同期 - 再ダウンロード - 開く + ファイルを同期 アップロード中にファイル名を %1$s に変更しました + URLで共有 + 未共有のリンク はい いいえ OK @@ -80,98 +63,108 @@ アップロードをキャンセル キャンセル 保存して終了 - ownCloudを終了 エラー - ownCloudについて + 読込中... + 不明なエラー + について + パスワードを変更 アカウントを削除 アカウントを作成 アップロード … - ディレクトリ名 + フォルダー名 アップロード中... %1$d%% アップロード中 %2$s アップロード完了 %1$s は正常にアップロードされました - %1$d 個のファイルは正常にアップロードされました アップロードに失敗 %1$s のアップロードが完了しませんでした - アップロードに失敗: %1$d/%2$d ファイルがアップロードされました ダウンロード中 ... %1$d%% ダウンロード中 %2$s ダウンロードに成功 %1$s は正常にダウンロードされました ダウンロードに失敗 %1$s のダウンロードは完了しませんでした + 未ダウンロード アカウントを選択 - コンタクト 同期失敗 %1$s の同期が完了しませんでした。 + 1$sの無効なパスワード 競合が見つかりました %1$d 同期ファイルを同期できませんでした ファイルの同期に失敗しました %1$d ファイルのコンテンツを同期できませんでした(%2$d の競合) - 暗号通信を利用 - ownCloudがあなたのデバイスの位置情報を取得出来ません。位置情報設定を確認してください。 + 一部のローカルファイルが忘れられています + フォルダー %1$s はもう存在しません + 全て移動 + 全てのファイルは移動されました + 一部のファイルは移動できませんでした + ローカル: %1$s + リモート: %1$s + %1$s フォルダーに選択されたファイルをコピーするのに十分な空き領域がありません。コピーする代わりに、それらを移動させますか? アプリのパスワードを入力してください - 新しいアプリのパスワードを入力してください - ownCloudアプリのパスワードを入力してください + アプリのパスワードを入力してください アプリ開始時に毎回PINが要求されます。 - ownCloudアプリのパスワードを再入力してください - ownCloudアプリのパスワードを削除 - ownCloudアプリのパスワードが一致しません - 無効なownCloudアプリのパスワードです - ownCloudアプリのパスワードを削除しました - ownCloudアプリのパスワードを保存しました - - 15分 - 30分 - 60分 - - - 15 - 30 - 60 - + アプリのパスワードを再入力してください + アプリのパスワードを削除 + アプリのパスワードが一致しません + 無効なアプリのパスワードです + アプリのパスワードを削除しました + アプリのパスワードを保存しました + %1$s ミュージックプレーヤー + %1$s (プレイ中) + %1$s (読込中) + %1$s プレイバック終了 + メディアファイルが見つかりませんでした + アカウントが提供されていません + ファイルが有効なアカウントにありません + サポートされていないメディアコーデック + メディアファイルを読み込めませんでした + メディアファイルが正確にエンコードされていません + 再生中にタイムアウトが発生しました + メディアファイルをストリーミングできません + メディアファイルをStock Media Playerで再生できません + 再生時にセキュリティエラー %1$s + 再生時に入力エラー %1$s + 再生時に予期しないエラー %1$s + 巻き戻しボタン + 再生/一時停止ボタン + 早送りボタン ログイン中... ネットワークに接続されていません - ネットワーク接続が見つかりません、インターネット接続を確認してからお試しください。 - 接続する 暗号化通信が利用できません。 - サーバーとの暗号化通信を確立できませんでした。しかし、通常の通信が利用できます。続けますか? 接続が確立しました 接続をテスト中... - ownCloudサーバーの間違った設定 - ownCloudサーバー設定が正しくないようです。詳しくは管理者にご相談ください。 + サーバー設定が間違っています + 同じユーザーとサーバーのアカウントがデバイス上にすでに存在します + 入力されたユーザーはこのアカウントのユーザーと一致しません 不明なエラーに発生しました - 不明なエラーが発生しました。制作者に連絡して、あなたのデバイスのログを送付してください。 ホストが見つかりませんでした - 入力されたホストが見つかりません。ホスト名とサーバが利用できるものかどうか確認して、再度試してください。 - ownCloudのインスタンスが見つかりませんでした - 指定されたパスでownCloudのサーバが見つかりませんでした。パスを確認してもう一度試してみてください。 - サーバーからの反応がありません。 + サーバーのインスタンスが見つかりません + サーバーからの反応がありません 不明なURL形式 SSLの初期化に失敗しました - 未認証のSSLサーバID - 認識出来ないownCloudサーバのバージョンです + SSLサーバー識別子を確認できませんでした + 認識できないサーバーのバージョンです 接続を確立できませんでした 暗号化通信を確立しました - ログインの詳細 - 無効なログイン/パスワード - 誤ったパスが指定されました - 内部サーバエラー、コード %1$d - アプリケーションが予期せず終了しました。クラッシュレポートを送ってもよろしいでしょうか? - レポートを送信 - レポートを送信しない - 拡張機能が利用可能です! - ownCloudサーバーは拡張機能がサポートされているようです。androidで利用できる拡張機能を見ますか? + 間違ったユーザー名もしくはパスワード + 認証失敗 + 認証サーバーによってアクセスが拒否されました + 予期しない状態。もう一度サーバーのURLを入力してください + 認証情報は有効期限切れです。再度認証を行ってください。 + 現在のパスワードを入力してください + セッションの有効期限切れです。再度接続してください。 + 認証サーバーに接続中 ... + サーバーはこの認証方式をサポートしていません + %1$s は複数アカウントをサポートしていません ファイルを最新に保つ - 共有 名前を変更 削除 本当に %1$s を削除してもよろしいですか? 本当に %1$s およびそのコンテンツを削除してもよろしいですか? ローカルのみ ローカルコンテンツのみ - サーバから削除 + サーバーから削除 リモートとローカルの両方 削除に成功しました 削除を完了できませんでした @@ -180,17 +173,18 @@ 名前の変更ができませんでした リモートファイルをチェックできませんでした ファイルコンテンツはすでに同期されています - ディレクトリを作成できませんでした + 使用できない文字: / \\ < > : \" | ? * しばらくお待ちください 予期せぬ問題;他のアプリでファイルを選択してみてください。 ファイルは選択されていません - 警告 + リンクを送信… + oAuth2でログイン + oAuth2サーバーに接続中... サイトの識別子を確認できませんでした - - サーバ証明書は信頼されていません - - サーバ証明書は有効期限切れです - - サーバ証明書は若すぎます + - サーバー証明書は信頼されていません + - サーバー証明書は有効期限切れです + - サーバー証明書の有効期限は未来のものです - URLは証明書内のホスト名と一致しません - サーバ証明書を取得できませんでした この証明書を信頼してもよろしいですか? 証明書は保存できませんでした 詳細 @@ -208,12 +202,37 @@ 先: 署名: アルゴリズム: - これはプレースホルダです + これはプレースホルダです + placeholder.txt + PNG 画像 + 389 KB + 2012/05/18 12:23 PM + 12:23:45 WiFi経由でのみ写真をアップロード /InstantUpload - 更新の競合 - リモートファイル %s はローカルファイルと同期していません。続行すると、サーバ上のファイルを置き換えます。 + 更新が競合 + リモートファイル %s はローカルファイルと同期していません。続行すると、サーバー上のファイルを置き換えます。 両方を保持 上書き アップロードしない + イメージプレビュー + この画像は表示できません + インスタントアップロードに失敗 + インスタントアップロードに失敗 + 全ての失敗したインスタントアップロードの要約 + すべて選択 + すべての選択を再試行 + アップロードキューからすべての選択を削除 + 画像のアップロードを再試行: + 更に画像を読み込む + オンラインでなく、インスタントアップロードのために何もしません + 失敗メッセージ: + サーバー設定を確認してください。クォータサイズを超えている可能性があります。 + このファイルまたはフォルダーは共有できません。存在しているか確認してください。 + このファイルまたはフォルダーを共有する際にエラーが発生しました + このファイルもしくはフォルダは存在しないため、共有を解除できません。 + このファイルまたはフォルダーの共有を解除する際にエラーが発生しました + 送信 + リンクをコピー + クリップボードにコピー diff --git a/res/values-jv/strings.xml b/res/values-jv/strings.xml new file mode 100644 index 00000000..4ab64314 --- /dev/null +++ b/res/values-jv/strings.xml @@ -0,0 +1,4 @@ + + + Njipuk + diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml index f1b1a5bf..8aeca933 100644 --- a/res/values-ka-rGE/strings.xml +++ b/res/values-ka-rGE/strings.xml @@ -1,32 +1,154 @@ - ფაილები - მუსიკა - კონტაქტები - ბუქმარკები - პარამეტრები ატვირთვა + კონტენტი სხვა აპლიკაცისებიდან ფაილები + ახალი ფოლდერი პარამეტრები + გაგზავნა ზოგადი - URL + უფრო მეტი + ანგარიში + ანგარიშების მენეჯმენტი + აპლიკაციის PIN–ი + თქვენი კლიენტის დაცვა + ჩართე ეგრევე ატვირთვის ფუნქცია + კამერით გადაღებული ფოტოების ეგრევე ატვირთვა + დახმარება + უკუკავშირი + ბეჭედი მომხმარებლის სახელი პაროლი ფაილები - მომხმარებლის სახელი - პაროლი + დაკავშირება ატვირთვა + ანგარიში ვერ მოიძებნა + %1$s ანგარიში არ არის თქვენს მოწყობილობაში. გთხოვთ ჯერ დააყენოთ ის. + დაყენება გამოსვლა + არ არის კონტენტი ასატვირთად + კონტენტი არ იქნა მიღებული. არაფერია ასატვირთად. + %1$s–ი არ დაიშვება გაზიარებული კონტენტის სანახავად + მიმდინარეობს ატვირთვა + ამ ფოლდერში ფაილები არ არის.⏎\nახალი ფაილები შეიძლება დაემატოს \"ატვირთვა\" მენიუს ოფციით. + დააჭირეთ ფაილს დამატებითი ინფორმაციის გამოსაჩენად. + ზომა: + ტიპი: + შექმნილია: + მოდიფიცირებულია: ჩამოტვირთვა - გახსნა + ფაილ %1$s–ზე გადარქმეულ იქნა სახელი ატვირთვის დროს კი + არა + დიახ + ჩამოტვირთვის შეჩერება ატვირთვის გაუქმება გაუქმება + შენახვა &გამოსვლა შეცდომა - კონტაქტები - გაზიარება + უცნობი შეცდომა + ჩვენს შესახებ + პაროლის შეცვლა + ანგარიშის წაშლა + ანგარიშის შექმნა + ატვირთე აქედან... + ფოლდერის სახელი + მიმდინარეობს ატვირთვა... + %1$d%% მიმდინარეობს ატვირთვა %2$s + ატვირთვა განხორციელდა + %1$s–ი წარმატებით ატვირთულ იქნა + ატვირთვა ვერ განხორციელდა + %1$s ატვირთვა ვერ დასრულდა + ჩამოტვირთვა... + %1$d%% ჩამოტვირთვა %2$s + გადმოწერა დასრულდა + %1$s წარმატებით ჩამოიქაჩა + ჩამოტვირთვა ვერ განხორციელდა + ჩამოტვირთვა %1$s–ის ვერ დასრულდა + აირჩიეთ ანგარიში + შეცდომა სინქრონიზაციისას + %1$s –ის სინქრონიზაცია ვერ დასრულდა + კონფლიქტი წარმოიშვა + %1$d kept-in-sync ფაილების სინქრონიზაცია არ შეიძლება + Kept-in-sync ფაილები ვერ მოხერხდა + %1$d ფაილების კონტენტის სინქრონიზაცია ვერ მოხერხდა (%2$d კონფლიქტი) + რამოდენიმე ლოკალური ფაილი დაკარგულია + გადაიტანე ყველა + ყველა ფაილები გადატანილია + რამოდენიმე ფაილის გადატანა ვერ მოხერხდა + ლოკალური: %1$s + დაშორებული: %1$s + ადგილი არ არის მონიშნული ფაილების დასაკოპირებლად %1$s ფოლდერში. გინდათ თქვენ გადაიტანოთ ისინი ამის მაგივრად? + გთხოვთ, ჩასვათ თქვენი აპლიკაციის PIN–ი + შეიყვანეთ თქვენი აპლიკაციის PIN–ი + PIN–ი მოთხოვნილი იქნება აპლიკაციის ყოველ ჩართვაზე + გთხოვთ შეიყვანოთ თქვენი აპლიკაციის PIN–ი ხელთავიდან + წაშალეთ თქვენი აპლიკაციის PIN–ი + აპლიკაციის PIN–ი არ არის იგივე + არასწორი აპლიკაციის PIN–ი + აპლიკაციის PIN–ი წაიშალა + აპლიკაციის PIN–ი დამახსოვრებულ იქნა + ქსელური კავშირი არ არის + დაცული კავშირი არ არსებობს. + კავშირი დამყარდა + არასწორი სერვერის კონფიგურაცია + აღმოჩენილია უცნობი შეცდომა! + ჰოსტი ვერ მოიძებნა + სერვერის ინსტანცია ვერ მოიძებნა + სერვერმა გვიპასუხა ძალიან გვიან + არასწორი URL + შეცდომა SSL ინიციალიზაციისას + ამოუცნობი სერვერის ვერსია + კავშირის დამყარება ვერ მოხერხდა + დაცული კავშირი დამყარდა + არ განაახლო ფაილი გადარქმევა წაშლა - გაფრთხილება + ნამდვილად გინდათ წაშალოთ %1$s ? + თქვენ აუცილებლად გინდათ %1$s–ის წაშლა თავისი კონტენტით? + მხოლოდ ლოკალური + მხოლოდ ლოკალური კონტენტი + სერვერიდან წაშლა + დაშორებული და ლოკალური + წაშლა შარმატებით დასრულდა + წაშლა წარუმატებლად დამთავრდა + შეიყვანეთ ახალი სახელი + ლოკალურ კოპიაზე სახელის გადარქმევა ვერ მოხერხდა: გთხოვთ მიუთითოთ სხვა სახელი + გადარქმევა ვერ დასრულდა წარმატებით + დაშორებული ფაილის შემოწმება ვერ მოხერხდა + ფაილების კონტენტის სინქრონიზაცია ვერ მოხერხდა + გთხოვთ მოითმინოთ + მოულოდნელი პრობლემა: გთხოვთ აირჩიოთ ფაილი სხვადასხვა აპლიკაციიდან + ფაილი არ ყოფილა მონიშნული + საიტის იდენტობა ვერ დამოწმდა + – სერვერის სერთიფიკატები არ არის ცნობიადი + – სერვერის სერთიფიკატი ვადაგასულია + –სერვერის სერთიფიკატი არის დაშვებადი მომავალში + – URL არ ემთხვევა სერთიფიკატში არსებულ ჰოსტნეიმს + გინდათ მაინც ენდოთ მოცემულ სერთიფიკატს? + სერთიფიკატის შენახვა ვერ მოხერხდა + დეტალური ინფორმაცია დამალვა + გაცემულია: + გაცემულია მიერ: + ზოგადი სახელი: + ორგანიზაცია: + ორგანიზაციის განყოფილება: + ქვეყანა: + შტატი: + ადგილმდებარეობა: + მოქმედების ვადა: + დან: + ვისზე: + სიგნატურა: + ალგორითმი: + ატვირთეთ სურათები მხოლოდ WiFi–ით + /ეგრევე ატვირთვა + კონფლიქტი განახლებისას + დაშორებული ფაილი %s არ არის სინქრონიზირებული ლოკალურ ფაილთან. ოპერაციის გაგრძელება გამოიწვევს სერვერზე ფაილის შეცვლას. + დატოვე ორივე + გადააწერე + არ ატვირთო + გაგზავნა + კოპირებულია კლიპბორდში diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index 09a564b1..6538c7d9 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -1,9 +1,8 @@ - ფაილები ფაილები + შველა პაროლი ფაილები - პაროლი გადმოწერა diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml new file mode 100644 index 00000000..eee11b00 --- /dev/null +++ b/res/values-km/strings.xml @@ -0,0 +1,49 @@ + + + ផ្ទុក​ឡើង + ឯកសារ + ថត​ថ្មី + ការកំណត់ + ផ្ញើ + ទូទៅ + ច្រើន​ទៀត + គណនី + ជំនួយ + ឈ្មោះ​អ្នកប្រើ + ពាក្យសម្ងាត់ + ឯកសារ + ភ្ជាប់ + ផ្ទុក​ឡើង + រកមិនឃើញ​គណនី + គ្មាន %1$s គណនី​លើម៉ាស៊ីន​របស់អ្នកទេ។ សូមរៀបចំគណនីមួយជាមុនសិន។ + ដំឡើង + ចាក់ចេញ + កំពុង​ផ្ទុក​ឡើង + ចុចមួយ​លើឯកសារ ដើម្បី​បង្ហាញ​ព័ត៌មាន​បន្ថែម។ + ទំហំ៖ + ប្រភេទ៖ + បាន​បង្កើត៖ + បាន​កែ​សម្រួល៖ + ទាញយក + ព្រម + ទេ + OK + លើកលែង + រក្សាទុក & ចាកចេញ + កំហុស + មិន​ស្គាល់​កំហុស + ប្តូរ​ពាក្យសម្ងាត់ + ឈ្មោះ​ថត + ជ្រើស​គណនី + សូម ដាក់​បញ្ចូល App PIN របស់អ្នក + បញ្ចូល App PIN របស់អ្នក + សូម បញ្ចូល App PIN របស់អ្នក​ម្តង​ទៀត + លុប App PIN របស់​អ្នក + App PIN ទាំងនេះ​មិនដូចគ្នាទេ + App PIN មិន​ត្រឹម​ត្រូវទេ + App PIN បាន​លុបចេញហើយ + App PIN បាន​យក​មកវិញ + គ្មានបណ្តាញ​តភ្ជាប់ទេ + ដកចេញ + ផ្ញើ + diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-kn/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 43e4931e..3de59f61 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -1,56 +1,40 @@ - ownCloud - 암호: - 사용자 이름: - 로그인 - 환영합니다 - 파일 - 음악 - 연락처 - 달력 - 책갈피 - 설정 - 계정 설정 - 이 장치에 ownCloud 계정이 설정되어 있지 않습니다. 이 앱을 사용하려면 계정을 설정하십시오. - %1$s Android 앱\n\n버전: %2$s - 새로 고침 + %1$s 안드로이드 앱 + 버전 %1$s + 계정 새로고침 업로드 다른 앱의 콘텐츠 파일 - 디렉터리 만들기 - 검색 + 로 열기 + 새 폴더 설정 + 세부내용 + 보내기 일반 - 장치 추적 - 새 세션 추가 - 미리 보기 그림 만들기 - 계정 선택 - 이 앱에서 사용할 계정을 선택하십시오. - 장치 추적 - ownCloud에서 현재 위치 추적하기 - 이 앱에서 장치 위치를 추적합니다 - 업데이트 주기 - %1$s분마다 업데이트 + 더 중요함 계정 계정 관리 앱 암호 내 클라이언트 보호 자동 업로드 사용 카메라로 촬영한 사진 자동 업로드 - URL + 로깅 허용 + 이건 로그 문제에 사용됩니다 + 로그 기록 + 여기서 기록된 로그를 보여줍니다 + 역사 삭제하기 + 도움말 + 친구들에게 권하기 + 피드백 + 임프린트 + %1$s 을 스마트폰에서 사용해보세요! + 서버 확인 + 서버 주소 https://… 사용자 이름 암호 - %1$s의 새로운 사용자입니다 - 잘못된 주소를 입력하였습니다 - 세션 이름이 잘못되었습니다 + %1$s의 새로운 사용자입니까? 파일 - 업로드할 파일이 선택되지 않았습니다 - 사용자 이름 - 암호 - 웹 주소 - 암호 보이기 - 내 %1$s에 접속 접속 업로드 계정 없음 @@ -61,7 +45,6 @@ 받은 콘텐츠가 없습니다. 업로드할 항목이 없습니다. %1$s에서 공유된 콘텐츠에 접근할 수 없습니다 업로드 중 - 업로드할 디렉터리 만들기 이 폴더에 파일이 없습니다.\n메뉴 옵션의 \"업로드\" 항목을 사용하여 새 파일을 업로드할 수 있습니다. 파일을 누르면 추가 정보가 표시됩니다. 크기: @@ -69,10 +52,9 @@ 만든 날짜: 수정한 날짜: 다운로드 - 새로 고침 - 다시 다운로드 - 열기 + 파일 새로고침 업로드 중 파일 이름을 %1$s(으)로 변경하였습니다 + 링크 공유 예 아니요 확인 @@ -80,39 +62,45 @@ 업로드 취소 취소 저장하고 끝내기 - %1$s 떠나기 오류 + 불러오는 중... + 알수없는 오류 정보 + 암호 변경 계정 삭제 계정 만들기 다음에서 업로드... - 디렉터리 이름 + 폴더 이름 업로드 중... %1$d%% %2$s 업로드 중 업로드 성공 %1$s을(를) 업로드하였습니다 - 파일 %1$d개를 업로드하였습니다 업로드 실패 %1$s을(를) 업로드할 수 없었습니다 - 업로드 실패: 파일 %2$d개 중 %1$d개를 업로드하였습니다 다운로드 중... %1$d%% %2$s 다운로드 중 다운로드 성공 %1$s을(를) 다운로드하였습니다 다운로드 실패 %1$s을(를) 다운로드할 수 없었습니다 + 아직 다운로드 되지 않았습니다 계정 선택 - 연락처 동기화 실패 %1$s와(ê³¼) 동기화할 수 없었습니다 + %1$s에 대한 비밀번호가 틀립니다 충돌하는 항목 발견됨 동기화된 파일 중 %1$d개를 동기화할 수 없었습니다 파일을 동기화할 수 없었습니다 파일 %1$d개의 내용을 동기화할 수 없었습니다 (충돌 %2$d개) - 암호화된 연결 사용 - %1$s에서 장치 위치를 추적할 수 없습니다. 위치 설정을 확인하십시오. + 몇몇 로컬 파일이 사라졌습니다. + %1$s 폴더가 존재하지 않습니다. + 모두 옮김 + 모든 파일 옮김 + 몇몇 파일을 옮기지 못했습니다. + 로컬: %1$s + 원격: %1$s + 선택한 파일을 %1$s폴더에 넣기에는 공간이 충분하지 않습니다. 다른곳으로 옮기시겠습니까? 앱 암호를 입력하십시오 - 새 앱 암호를 입력하십시오 앱 암호를 입력하십시오 앱을 시작할 때마다 암호를 물어봅니다 앱 암호를 다시 입력하십시오 @@ -121,50 +109,54 @@ 앱 암호가 잘못되었습니다 앱 암호가 삭제되었습니다 앱 암호가 저장되었습니다 - - 15분 - 30분 - 60분 - - - 15 - 30 - 60 - + %1$s 음악 재생기 + %1$s (재생중) + %1$s (불러오는 중) + %1$s 재생 완료됨 + 미디어 파일을 찾을수 없습니다 + 준비된 계정이 없습니다 + 유효한 계정의 파일이 아닙니다 + 지원하지 않는 미디어 코덱 + 미디어 파일을 읽을수 + 미디어 파일이 제대로 인코드 되지 않았습니다 + 재생 시도 중 시간이 초과됨 + 미디어 파일을 스트리밍 할수 없습니다 + 내장된 미디어 플레이어에서는 이 미디어 파일을 재생할수 없습니다 + %1$s 를 재생하는 중에 보안오류가 발생함 + %1$s 를 재생하는 중에 입력 에러가 발생함 + %1$s 를 재생하던 중에 알수 없는 오류가 발생함 + 되감기 버튼 + 재생 혹은 일시정지 버튼 + 빨리감기 버튼 로그인 중... 네트워크에 연결할 수 없습니다 - 네트워크 연결을 시도할 수 없습니다. 인터넷 연결을 확인하고 다시 시도하십시오. - 그래도 접속하기 암호화된 연결을 사용할 수 없습니다. - 서버와 암호화된 통신을 시도할 수 없었습니다. 암호화되지 않은 연결을 사용할 수 있습니다. 그래도 접속하거나 취소할 수 있습니다. 연결됨 연결 테스트 중... 서버 설정이 잘못됨 - 서버 인스턴스가 잘못 설정된 것 같습니다. 서버 관리자에게 문의하십시오. + 같은 사용자와 서버에 대한 계정이 이미 존재합니다 + 입력된 사용자가 이 계정의 사용자와 일치하지 않음 알 수 없는 오류가 발생하였습니다! - 알 수 없는 오류가 발생하였습니다. 지원 요청을 할 때 장치에 저장된 로그를 같이 업로드해 주십시오. 호스트를 찾을 수 없음 - 입력한 호스트를 찾을 수 없습니다. 호스트 이름과 서버 상태를 확인한 후 다시 시도해 주십시오. 서버 인스턴스를 찾을 수 없음 - 지정한 경로에서 서버 인스턴스를 찾을 수 없습니다. 경로를 확인하고 다시 시도해 주십시오. 서버 응답 시간이 초과되었습니다 잘못된 URL SSL 초기화 오류 - 확인되지 않은 SSL 서버 인증서 + SSL 서버의 신원을 확인할수 없습니다 확인할 수 없는 서버 버전 연결을 수립할 수 없음 암호화된 연결 사용 중 - 로그인 정보 잘못된 로그인/암호 - 경로가 잘못되었습니다 - 내부 서버 오류, 코드 %1$d - 프로그램이 예상치 못하게 종료되었습니다. 충돌 보고서를 전송하시겠습니까? - 보고서 보내기 - 보내지 않기 - 확장 기능을 사용할 수 있습니다! - 현재 서버 인스턴스에서 확장 기능을 지원하는 것 같습니다. 안드로이드용 확장 기능을 검색해 보시겠습니까? + 권한부여가 성공적으로 이뤄지지 않았습니다 + 권한 서버로 부터 접근이 거부되었습니다 + 뜻밖의 상태; 다시 서버 주소를 입력해주십시오 + 인증이 만료되었습니다. 다시 인증해주세요 + 현재 암호를 + 세션이 만료되었습니다. 다시 접속해주세요 + 인증 서버에 접속하는 중... + 서버에서 이 인증 방법을 지원하지 않습니다. + %1$s 에서는 다중 계정을 지원하지 않습니다 파일을 최신 정보로 유지 - 공유 이름 바꾸기 삭제 %1$s을(를) 삭제하시겠습니까? @@ -180,17 +172,17 @@ 이름을 변경할 수 없었습니다 원격 파일을 확인할 수 없었습니다 파일 내용이 이미 동기화되었습니다 - 디렉터리를 만들 수 없었습니다 + 사용할수 없는 문자들: / \\ < > : \" | ? * 잠시 기다려 주십시오 예상하지 못한 오류입니다. 다른 앱에서 파일을 선택하십시오 선택한 파일 없음 - 경고 + oAuth2로 로그인하기 + oAuth2 서버에 연결중... 사이트 인증서를 확인할 수 없었습니다 - 서버 인증서를 신뢰할 수 없습니다 - 서버 인증서가 만료되었습니다 - 서버 인증서의 유효 기간이 시작되지 않았습니다 - 인증서의 URLê³¼ 입력한 URL이 일치하지 않습니다 - 서버 인증서를 가져올 수 없었습니다 이 인증서를 신뢰하시겠습니까? 인증서를 저장할 수 없었습니다 자세히 @@ -208,7 +200,12 @@ 끝: 서명: 알고리즘: - 자리 비움자 + 이것은 플레이스홀더입니다 + placeholder.txt + PNG 그림 + 389 KB + 2012/05/18 12:23 PM + 12:23:45 WiFi 사용 중일때만 사진 업로드 /InstantUpload 업데이트 충돌 @@ -216,4 +213,20 @@ 모두 저장 덮어쓰기 업로드하지 않음 + 그림 미리보기 + 이 그림을 볼수 없습니다 + 자동 업로드가 실패했습니다 + 자동 업로드 실패함 + 실패된 자동 업로드 전체 요약 + 전체 선택 + 전체 선택 재시도 + 선택된 업로드 큐 전체 삭제 + 이미지 업로드 재시도: + 더 많은 사진 불러오기 + 현재 온라인이 아니셔서 자동 업로드를 할수 없습니다 + 실패 메시지: + 서버 설정을 확인해주세요, 아마 업로드 제한을 초과하셨을겁니다. + 보내기 + 링크 복사 + 클립보드로 복사됨 diff --git a/res/values-ku-rIQ/strings.xml b/res/values-ku-rIQ/strings.xml index 379374c1..957ee5b3 100644 --- a/res/values-ku-rIQ/strings.xml +++ b/res/values-ku-rIQ/strings.xml @@ -1,18 +1,30 @@ - مۆسیقا - دڵخوازه‌کان - ده‌ستكاری بارکردن + په‌ڕگەکان ده‌ستكاری - ناونیشانی به‌سته‌ر + گشتی + هەژمارەکان + یارمەتی ناوی به‌کارهێنه‌ر وشەی تێپەربو - ناوی به‌کارهێنه‌ر - وشەی تێپەربو + په‌ڕگەکان + بەستنەوە بارکردن + هیچ هەژمارێک نه‌دۆزرایه‌وه‌ + جێگیرکردن + دەرچوون + بارکردن + قەبارە: + جۆر: + درووستبووە: + گۆردراو: داگرتن - کردنه‌وه + بەڵێ + نەخێر + باشە + لابردن + پاشکەوتکردن & دەرچوون هه‌ڵه - ئاگاداری + ناوی بوخچه diff --git a/res/values-large-land/bools.xml b/res/values-large-land/bools.xml new file mode 100644 index 00000000..9feccd8a --- /dev/null +++ b/res/values-large-land/bools.xml @@ -0,0 +1,22 @@ + + + + + true + \ No newline at end of file diff --git a/res/values-large/bools.xml b/res/values-large/bools.xml deleted file mode 100644 index e1436057..00000000 --- a/res/values-large/bools.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - true - \ No newline at end of file diff --git a/res/values-lb/strings.xml b/res/values-lb/strings.xml index 2569e5dc..771896a6 100644 --- a/res/values-lb/strings.xml +++ b/res/values-lb/strings.xml @@ -1,49 +1,86 @@ - ownCloud - Passwuert: - Benotzernumm: - Login - Dateien - Musek - Kontakter - Kalenner - Bookmarks - Astellungen - Account erstellen + Versioun %1$s Eroplueden Dateien - Dossier erstellen - Sichen + Opmaachen mat Astellungen + Detailer + Schécken Allgemeng - Nei Sessioun bäisetzen - Account auswielen + Méi Accounten + App PIN + Hëllef + Feedback Benotzernumm Passwuert Dateien - Benotzernumm - Passwuert - Passwuert uweisen? Verbannen Eroplueden Keen Account fonnt Setup Erausgoen + Eroplueden Gréisst: Typ: Erstallt: Geännert: Download - Opmaachen + Link deelen Jo Nee OK Upload ofbriechen Ofbriechen + Späicher & géi raus Fehler - Kontakter - Deelen - Warnung + Et ass en onbekannte Fehler opgetrueden + Iwwer + Passwuert änneren + Account läschen + Account erstellen + Dossiers Numm: + Gett eropgelueden ... + Eroplueden färdeg + Eroplueden huet net geklappt + Eroflueden huet net geklappt + Wiel en Account aus + Alles bewegen + Gëff w.e.g. däin App PIN an + Gëff däin App PIN an + Gëff däin App PIN w.e.g. nei an + Huel däin App PIN raus + Ongültegen App PIN + App PIN geläscht + App PIN gespaichert + Keng Netzwierk Verbindung + Keng geséchert Verbindung verfügbar. + Verbindung hiergestallt + Ongülteg Server Konfiguratioun + Et ass en onbekannte Fehler opgetrueden! + Server Instanz net fonnt + Ëm-benennen + Läschen + Nemmen Lokal + Vum Server läschen + Detailer + Verstoppen + Land: + Staat: + Uert: + Gültegkeet: + Vun: + Fir: + Signatur: + PNG Bild + 389 KB + 2012/05/18 12:23 + 12:23:45 + Béid halen + Iwwerschreiwen + Net eroplueden + all auswielen + Fehler Message: + Schécken diff --git a/res/values-lt-rLT/strings.xml b/res/values-lt-rLT/strings.xml index 0df5da8e..c1ac11f9 100644 --- a/res/values-lt-rLT/strings.xml +++ b/res/values-lt-rLT/strings.xml @@ -1,140 +1,188 @@ - ownCloud - Slaptažodis: - Prisijungimo vardas: - Prisijungti - Sveiki prisijungę prie savo OwnCloud - Failai - Muzika - Kontaktai - Kalendorius - Žymekliai - Nustatymai - Nustatyti paskyrą - JÅ«sų prietaise nėra nė vienos ownCloud paskyros. Kad galėtumėte naudoti Å¡ią programą, turite sukurti paskyrą. - Atnaujinti + %1$s Android programėlė + versija %1$s + Atnaujinti paskyrą Ä®kelti Turinys iÅ¡ kitų programų Failai - Kurti aplanką - PaieÅ¡ka + Atverti naudojant + Naujas aplankas Nustatymai - Pridėti naują sesiją - Sukurti paveikslėlių miniatiÅ«ras - Pasirinkite paskyrą - Pasirinkite kurias jÅ«sų paskyras programa turėtų naudoti. - Atnaujinimo intervalas + Informacija + Siųsti + Bendras + Daugiau Paskyros Tvarkyti paskyras - ownCloud programos PIN kodas - Apsaugokite savo ownCloud klientą + App programos PIN kodas + Apsaugokite savo klientą + Ä®jungti momentinius įkėlimus IÅ¡ karto nusiųsti nufotografuotas nuotraukas - ownCloud URL + Ä®jungti žurnalo raÅ¡ymą + Tai naudojama problemų žurnalo vedimui + Žurnalų istorija + Čia rodomi įraÅ¡yti žurnalai + IÅ¡trinti Istoriją + Pagalba + Rekomenduoti draugui + Atsiliepimai + Imprint + IÅ¡bandykite %1$s savo iÅ¡maniajame telefone! + Patikrinti Serverį + Serverio adresas Prisijungimo vardas Slaptažodis - Neteisingas URL - Neteisingas sesijos pavadinimas Failai - Nusiuntimui nepasirinktas joks failas - Prisijungimo vardas - Slaptažodis - Web adresas - Rodyti slaptažodį? - Prisijunkite prie savo ownCloud Prisijungti Ä®kelti Paskyrų nerasta + JÅ«sų įrenginyje nėra nė vienos %1$s paskyros. PraÅ¡ome pirmiausia susikurti paskyrą. + Nustatymai IÅ¡eiti + Nėra įkeltino turinio + nebuvo gauta turinio. Nėra įkeltino turinio + %1$s neleidžiama prieiti prie turinio, kuriuo dalijamasi IÅ¡siunčiama - Kurti aplanką iÅ¡siuntimui + Å iame aplanke failų nėra.\nNauji failai gali bÅ«ti pridėti naudojant \"Ä®kelti\" funkciją meniu. + Palieskite failą, kad parodyti papildomą informaciją. Dydis: Tipas: Sukurta: Modifikuota: Atsisiųsti - Atnaujinti - Atnaujinti - Atidaryti + Atnaujinti failą + Ä®kėlimo metu failas buvo pervadintas į %1$s + Dalintis nuoroda Taip Ne Gerai + AtÅ¡aukti parsiuntimą AtÅ¡aukti siuntimą AtÅ¡aukti + IÅ¡saugoti ir IÅ¡eiti Klaida + Ä®keliama... + Neatpažinta klaida Apie + Pakeisti slaptažodį IÅ¡trinti paskyrą Sukurti paskyrą Ä®kelti iÅ¡ ... - Aplanko pavadinimas + Katalogo pavadinimas Ä®keliama ... + %1$d%% Siunčiama %2$s + Nusiuntimas pavyko + %1$s buvo sėkmingai nusiųstas Nusiuntimas nepavyko + Nepavyko baigti %1$s nusiuntimo Atsiunčiama... + %1$d%% Atsiunčiama %2$s Atsiuntimas pavyko + %1$s buvo sėkmingai atsiųstas Atsiuntimas nepavyko + Nepavyko baigti %1$s atsiuntimo + Dar neatsiųsta Pasirinkite paskyrą - Kontaktai Sinchronizacija nepavyko - Naudoti saugų prisijungimą - ownCloud negali sekti jÅ«sų prietaiso. Patinkrinkite vietos nustatymo paslaugas + %1$s sinchronizacija nepavyko + Netinkamas slaptažodis %1$s + Rastas konfliktas + Nepavyko sinchronizuoti %1$d failų turinio (%2$d konfliktų) + Keli vietiniai failai buvo užmirÅ¡ti + Aplankas %1$s nebeegzistuoja + Perkelti visus + Visi failai buvo perkelti + Kai kurių failų negalima perkelti + Vietinis: %1$s + Nuotolinis: %1$s PraÅ¡ome įvesti savo programos PIN kodą - PraÅ¡ome įvesti naują programos PIN kodą - Ä®veskite ownCloud programos PIN kodą + Ä®veskite taikymas programos PIN kodą PIN bus praÅ¡omas kiekvieną kartą paleidus programą - PraÅ¡ome pakartoti ownCloud PIN kodą - PaÅ¡alinti ownCloud programos PIN kodą - Abu ownCloud programos PIN kodai nesutampa - Neteisingas ownCloud programos PIN kodas - ownCloud programos PIN kodas paÅ¡alintas - ownCloud programos PIN kodas iÅ¡saugotas - - 15 Minučių - 30 Minučių - 60 Minučių - - - 15 - 30 - 60 - + PraÅ¡ome pakartoti taikymas PIN kodą + PaÅ¡alinti taikymas programos PIN kodą + Abu taikymas programos PIN kodai nesutampa + Neteisingas taikymas programos PIN kodas + Taikymas programos PIN kodas paÅ¡alintas + Taikymas programos PIN kodas iÅ¡saugotas + %1$s muzikos grotuvas + %1$s (grojama) + %1$s (įkeliama) + %1$s sąraÅ¡as baigtas + Nerasta medija failų + Nenurodyta paskyra + Failas yra netinkamoje sąskaitoje + Nepalaikomas kodekas + Nenuskaitomas medijos failas + Netinkamai užkoduotas medijos failas + Baigėsi laukimo laikas bandant paleisti + Medijos failas negali bÅ«ti transliuojamas + Medijos failas negali bÅ«ti atkurtas naudojant gamyklinį grotuvą + Saugumo klaida bandant paleisti %1$s + Ä®vesties klaida bandant paleisti %1$s + Netikėta klaida bandant paleisti %1$s + Grojimo arba pauzės mygtukas Bandoma prisijungti... Nėra tinklo ryÅ¡io - Vis tiek prisijungti Saugus prisijungimas negalimas. RyÅ¡ys užmegztas IÅ¡bandomas prisijungimas... - Sugadinta ownCloud konfigÅ«racija + Sugadinta serverio konfigÅ«racija + To paties vartotojo ir serverio paskyra jau egzistuoja Å¡iame įrenginyje Ä®vyko nežinoma klaida! + Nepavyko rasti mazgo + Å is serveris netinkamas Serveris per ilgai neatsako URL adrese yra klaidų Nepavyko aktyvuoti SSL - Nežinoma ownCloud serverio versija + Nepatikrinta SSL serverio tapatybė + Nežinoma serverio versija Nepavyko užmegzti ryÅ¡io Užmegztas saugus ryÅ¡ys - Prisijungimo duomenys - Programa netikėtai sustojo. Ar norite nusiųsti Å¡io lūžimo ataskaitą programos kÅ«rėjams? - Siųsti ataskaitą - Nesiųsti ataskaitos - Yra naujų papildinių! - Dalintis + Neteisingas naudotojo vardas arba slaptažodis + Autorizacija nesėkminga + Autorizacijos serveris draudžia priėjimą + Netikėta bÅ«sena, praÅ¡ome įvesti serverio URL dar kartą + Autorizacijos laikas baigėsi. PraÅ¡ome autorizuotis iÅ¡ naujo + PraÅ¡ome įvesti dabartinį slaptažodį + JÅ«sų sesijos laikas baigėsi. PraÅ¡ome prisijungti iÅ¡ naujo + Jungiamasi prie autentikacijos serverio... + Serveris nepalaiko Å¡io autentikacijos metodo + %1$s nepalaiko kelių paskyrų iÅ¡ karto + Laikyti failą naujinamą Pervadinti PaÅ¡alinti + Ar tikrai norite paÅ¡alinti %1$s ? + Ar tikrai norite paÅ¡alinti %1$s ir ten esantį turinį? + Tik vietiniai + Tik vietinis turinys PaÅ¡alinti iÅ¡ serverio + Nutolę ir vietiniai PaÅ¡alinta sėkmingai PaÅ¡alinti nepavyko + Ä®veskite naują pavadinimą Lokalios kopijos pervadinti nepavyko, pabandykite kitą pavadinimą Pervadinti nepavyko - Aplanko sukurti nepavyko + Nutolę failai negalėjo bÅ«ti patikrinti + Failo turinys jau sunchronizuotas Truputį palaukite + Netikėta problema ; praÅ¡ome pasirinkti failą iÅ¡ kitos programėlės Joks failas nebuvo pasirinktas - Ä®spėjimas + Siųsti nuorodą asmeniui ... + Prisijungti naudojant oAuth2 + Jungiamasi prie oAuth2 serverio... + Serverio tapatybė negali bÅ«ti patikrinta - Nepatikimas serverio sertifikatas - Serverio sertifikatas galiojimo laikas pasibaigęs - Serverio sertifikatas yra pernelyg naujas - URL neatitinka adreso, kuris yra nurodytas sertifikate - Negalima gauti serverio sertifikato Ar vis tiek norite priimti šį sertifikatą? Sertifikatas negali bÅ«ti iÅ¡saugotas + Informacija + Slėpti + IÅ¡duota kam: + IÅ¡davė: + Bendras pavadinimas: Organizacija: Organizacijos padalinys: Å alis: @@ -145,8 +193,27 @@ Iki: ParaÅ¡as: Algoritmas: + PNG paveikslėlis + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Ä®kelti nuotraukas tik kai prisijungiama per WiFi + Atnaujinimo konfliktas + Nutolęs failas %s nėra sinchronizuotas su vietiniu failu. Jei tęsite, failas serveryje bus pakeistas. Palikti abu PerraÅ¡yti Nebesiųsti + Paveikslėlio peržiÅ«ra + Neįmanoma parodyti paveikslėlio + Nepavykę momentiniai įkėlimai + Suvestinė visų nepavykusių momentinių įkėlimų + pažymėti viską + bandyti dar kartą su visais pažymėtais + bandyti iÅ¡ naujo nusiųsti paveikslėlį: + Ä®kelti daugiau Nuotraukų + Klaidos praneÅ¡imas: + Patikrinkite savo serverio nustatymus, tikėtina jog virÅ¡ijote savo limitą. + Siųsti + Kopijuoti nuorodą + Nukopijuota į talpyklę diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index b449d662..a38a62a2 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -1,56 +1,23 @@ - ownCloud - Parole: - Lietotājvārds: - IerakstÄ«ties - Laipni lÅ«gti - Datnes - MÅ«zika - Kontakti - Kalendārs - GrāmatzÄ«mes - IestatÄ«jumi - IestatÄ«t kontu - Uz šīs ierÄ«ces nav iestatÄ«ta konta. Lai lietotu Å¡o lietotni, tāds ir jāizveido. - %1$s Android lietotne\n\nversija: %2$s - Atsvaidzināt AugÅ¡upielādēt Saturs no citām lietotnēm Datnes - Izveidot direktoriju - Meklēt + Jauna mape IestatÄ«jumi + SÅ«tÄ«t VispārÄ«gi - IerÄ«ces izsekoÅ¡ana - Pievienot jaunu sesiju - Izveidot attēlu sÄ«ktēlus - Izvēlieties kontu - Izvēlieties, kuru no kontiem jÅ«su lietotnei vajadzētu izmantot - Ierīču izsekoÅ¡ana - Aktivējiet Å¡o lietotni, lai izsekotu savas ierÄ«ces atraÅ¡anās vietu - Å Ä« lietotne izseko Å¡o ierÄ«ci - Atjaunināšanas intervāls - Atjaunināt katras %1$s minÅ«tes + Vairāk Konti PārvaldÄ«t kontus Lietotnes PIN Aizsargā savu klientu Aktivēt tÅ«lÄ«tējo augÅ¡upielādēšanu Nekavējoties augÅ¡upielādēt kameras uzņemtos attēlus - URL + PalÄ«dzÄ«ba Lietotājvārds Parole - Esmu %1$s iesācējs - Ir dota nepareiza adrese - Nepareizs sesijas nosaukums Datnes - Neviena datne nav izvēlēta augÅ¡upielādei - Lietotājvārds - Parole - TÄ«mekļa adrese - RādÄ«t paroli? - Savienoties ar savu %1$s Savienoties AugÅ¡upielādēt Nav atrastu kontu @@ -61,7 +28,6 @@ Nav saņemts nekāds saturs. Nav ko augÅ¡upielādēt. %1$s nedrÄ«kst piekļūt koplietotajam saturam AugÅ¡upielādē - Izveidot direktoriju augÅ¡upielādēšanai Å ajā mapē nav datņu.\nJaunas datnes var pievienot ar izvēlnes opciju “AugÅ¡upielādēt” Uzsitiet uz datnes, lai redzētu papildinformāciju. Izmērs: @@ -69,9 +35,6 @@ Izveidota: Modificēta: Lejupielādēt - Atsvaidzināt - Atkārtoti lejupielādēt - Atvērt Datne tika pārsaukta uz %1$s augÅ¡upielādes laikā Jā Nē @@ -80,21 +43,20 @@ Atcelt augÅ¡upielādi Atcelt Saglabāt un iziet - Pamest %1$s Kļūda + Ielādē Par + MainÄ«t paroli Dzēst kontu Izveidot kontu AugÅ¡upielādēt no... - Direktorijas nosaukums + Mapes nosaukums AugÅ¡upielādē ... %1$d%% augÅ¡upielādē %2$s AugÅ¡upielāde ir veiksmÄ«ga %1$s tika veiksmÄ«gi augÅ¡upielādēts - %1$d datnes tika veiksmÄ«gi augÅ¡upielādētas Neizdevās augÅ¡upielādēt Nevarēja pabeigt %1$s augÅ¡upielādēšanu - Neizdevās augÅ¡upielādēt: tika augÅ¡upielādētas %1$d/%2$d datnes Lejupielādē ... %1$d%% lejupielādē %2$s Lejupielāde beidzās veiksmÄ«gi @@ -102,17 +64,13 @@ Neizdevās lejupielādēt Nevarēja pabeigt %1$s lejupielādēšanu Izvēlieties kontu - Kontakti Neizdevās sinhronizēties Nevarēja pabeigt %1$s sinhronizēšanu Ir atrasti konflikti Nevarēja sinhronizēt %1$d kept-in-sync datnes Kept-in-sync datnes cieta neveiksmi Nevarēja sinhronizēt %1$d datņu saturu (%2$d konflikti) - Lietot droÅ¡o savienojumu - %1$s nevar izsekot Å¡o ierÄ«ci. LÅ«dzu, pārbaudiet savus vietas iestatÄ«jumus LÅ«dzu, ierakstiet savu lietotnes PIN - LÅ«dzu, ierakstiet savu jauno lietotnes PIN Ievadiet savu lietotnes PIN PIN tiks pieprasÄ«ts katrā lietotnes palaiÅ¡anas reizē LÅ«dzu, vēlreiz ievadiet savu lietotnes PIN @@ -121,50 +79,20 @@ Nepareizs lietotnes PIN Lietotnes PIN ir izņemts Lietotnes PIN ir noglabāts - - 15 minÅ«tes - 30 minÅ«tes - 60 minÅ«tes - - - 15 - 30 - 60 - - Mēģina ierakstÄ«ties... Nav tÄ«kla savienojumu - Nav atklātu tÄ«kla savienojumu. Pārbaudiet interneta savienojumus un mēģiniet vēlreiz. - Tomēr savienoties Nav pieejams droÅ¡s savienojums. - Lietotne nevar izveidot droÅ¡u savienojumu ar serveri. Ir pieejams nedroÅ¡s savienojums. JÅ«s varat turpināt vai atcelt. Savienojums ir izveidots - Testē savienojumu... Slikti formatēta servera konfigurācija - Izskatās, ka jÅ«su servera instance nav pareizi konfigurēta. Sazinieties ar servera administratoru, lai uzzinātu vairāk. GadÄ«jās nezināma kļūda! - GadÄ«jās nezināma kļūda. LÅ«dzu, sazinieties ar atbalstu un pievienojiet žurnālus no savas ierÄ«ces. Nevarēja atrast datoru - Nevarēja atrast ievadÄ«to datoru. LÅ«dzu, pārliecinieties ka datora nosaukums ir ievadÄ«ts pareizi un ka serveris ir pieejams. Servera instance nav atrasta - Lietotne uz dotā ceļa nevarēja atrast servera instanci. LÅ«dzu, pārbaudiet ceļu un mēģiniet vēlreiz. Serveris pārāk ilgi neatbildēja Slikti formatēts URL Neizdevās inicializēt SSL - NepārbaudÄ«ta SSL servera identitāte NeatpazÄ«ta servera versija Nevarēja izveidot savienojumu Ir izveidots droÅ¡s savienojums - SÄ«kāka informācija par ierakstīšanos - NederÄ«gs lietotājvārds/parole - Dots nepareizs ceļš - Iekšējā servera kļūda, kods %1$d - Lietotne negaidÄ«ti beidza darbu. Vai vēlaties iesniegt kļūdas ziņojumu? - SÅ«tÄ«t ziņojumu - NesÅ«tÄ«t ziņojumu - Ir pieejams paplaÅ¡inājums! - Izskatās, ka jÅ«su servera instance atbalsta papildu paplaÅ¡inājumus. Vai vēlaties redzēt paplaÅ¡inājumus, kas ir pieejami uz android? Uzturēt datni aktuālu - DalÄ«ties Pārsaukt Izņemt Vai tiešām vēlaties izņemt %1$s? @@ -180,17 +108,14 @@ Nevarēja pabeigt pārsaukÅ¡anu Nevarēja atzÄ«mēt attālinātas datnes Datnes saturs jau ir sinhronizēts - Nevarēja izveidot direktoriju UzgaidÄ«t brÄ«di NegaidÄ«ta problēma; lÅ«dzu, izvēlieties datni no citas lietotnes Netika izvēlēta neviena datne - BrÄ«dinājums Å Ä«s vietnes identitāti nevarēja pārbaudÄ«t - Nav uzticÄ«bas servera sertifikātam - Servera sertifikātam ir beidzies termiņš - Servera sertifikāta datumi ir nākotnē - URL sertifikātā neatbilst datora nosaukumam - Nevarēja saņemt sertifikātu Vai tomēr uzticēties sertifikātam? Nevarēja saglabāt sertifikātu SÄ«kāka informācija @@ -208,7 +133,6 @@ Kam: Paraksts: Algoritms: - Å is ir vietturis Attēlus augÅ¡upielādēt tikai caur WiFi /TÅ«lÄ«tējaAugÅ¡upielāde Atjaunināšanas konflikts @@ -216,4 +140,5 @@ Paturēt abas PārrakstÄ«t NeaugÅ¡upielādēt + SÅ«tÄ«t diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index bdaf9360..dc166886 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -1,61 +1,104 @@ - ownCloud - Лозинка: - Корисник: - Најава - Добредојде - Датотеки - Музика - Контакти - Календар - Обележувачи - Параметри - Постави сметка - Нема конфигурирано сметка на овој уред, за да можете да ја користите апликацијава треба да направите сметка. - Освежи Подигни Датотеки - Создади папка - Барај + Отвори со Параметри + Детали: + Прати Општо - Следење на уред - Додади нова сесија - Направи мали слики - Избери сметка - Избери која од вашите сметки - Следење на уред - Овозможи ја оваа апликација за да ја следите локацијата на вашиот уред - Оваа апликација го следи вашиот уред - Интервал на ажурирање - Ажурирај секои %1$s минути + Повеќе Сметки - Адреса + Управување со сметки + Апликативен ПИН + Заштитете го вашиот клиент + Овозможи логирање + Помош + Препорачај на пријател + Повратен одговор Корисничко име Лозинка - Дадена неточна адреса - Неточно име на сесија Датотеки - Корисничко име - Лозинка + Поврзи се Подигни + Не е пронајдена сметка + Нагодување Прекини Големина: Тип: Создадено: Изменето: Преземање - Отвори + Сподели ја врската Да Не Во ред Откажи прикачување Откажи Грешка - Контакти - Сподели + Вчитувам ... + Непозната грешка + За + Смени лозинка + Избриши ја сметката + Креирај сметка + Име на папка + Преземање... + Преземањето е успешно + Преземањето не беше успешно + Одбери сметка + Синхронизацијата беше неуспешна + Пронајден е конфликт + Префрли ги сите + Ве молам внесето го вашиот апликативен ПИН + Внесето го вашиот апликативен ПИН + Ве молам повторно внесето го вашиот апликативен ПИН + Одстранете го го вашиот апликативен ПИН + Апликативните ПИН-ови не се исти + Грешен апликативен ПИН + Апликативниот ПИН е одстранет + Апликативниот ПИН е снимен + Обиди се да се најавиш... + Нема мрежна конекција + Ја тестирам врската... + Серверската инстанца не е пронајдена + На серверот му треба премногу време за да одговори + Неуспешна SSL иницијализација + Верзијата на серверот не е препознаена + Погрешно корисничко име или лозинка + Неуспешна авторизација + Внесете ја вашата тековна лозинка: Преименувај Отстрани - Предупредување + Само локално + Отстрани од серверот + Далечинско и локално + Одстранувањето е успешно + Одстранувањето е неуспешно + Внеси ново име + Почекајте малку + Неочекуван проблем ; ве молам одберете датотека од друга апликација + Нема избрано датотека + Најави се со oAuth2 + Детали + Сокриј + Издаден на: + Издаден од: + Организација + Земја: + Држава: + Локација: + Валидност: + Од: + До: + Потпис: + Алгоритам: + 389 KB + 2012/05/18 12:23 PM + 12:23:45 + Конфликт при надградбата + Задржи ги и двете + Препиши + избери се + Прати diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-ml-rIN/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-ml/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-mn/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml index d98ccc29..56da371e 100644 --- a/res/values-ms-rMY/strings.xml +++ b/res/values-ms-rMY/strings.xml @@ -1,58 +1,27 @@ - ownCloud - Kata laluan: - Nama pengguna: - Log masuk - Selamat datang ke ownCloud - Fail-fail - Muzik - Hubungi - Kalendar - Tanda - Tetapan-tetapan - Set akaun - Peranti anda tidak mempunyai akaun ownCloud. Anda perlu membina satu akaun untuk menggunakan aplikasi ini - Refresh Muat naik Fail-fail - Bina direktori - Cari Set Umum - Jejak peranti - Tambah sesi baru - Bina lakaran kecil imej - Pilih akaun - Pilih akaun yang mana aplikasi ini akan gunakan - Peranti sedang dijejaki - Kemaskini setiap %1$s minit + Lanjutan Akaun - URL ownCloud + PIN App + Bantuan Nama pengguna Kata laluan - URL yang diberikan adalah salah Fail-fail - Nama pengguna - Kata laluan - Alamat sesawang - Tunjuk password? - Berhubung dengan ownCloud anda Berhubung Muat naik Tiada akaun dijumpai - Device anda tidak mempunyai sebarang akaun ownCloud. Mohon bina akaun dahulu. + Device anda tidak mempunyai sebarang akaun %1$s. Mohon bina akaun dahulu. Berhenti Memuatnaik - Bina direktori untuk proses upload Saiz Jenis Telah dibina: Telah diubah: Muatturun - Refresh - Refresh - Buka Ya Tidak OK @@ -60,9 +29,18 @@ Batal Simpan & Keluar Ralat - Nama direktori + Mengenai + Ubah kata laluan + Padam akaun + Muatnaik berjaya + Muatnaik gagal + Muatturun.... Pilih akaun - Hubungi - Kongsi - Amaran + Masukkan PIN App anda + Sila, memasukkan semula PIN App anda + PIN App disimpan + Tiada sambungan rangkaian + Namakan + Buang + Lokal sahaja diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index b304e63e..4318b6af 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -1,13 +1,10 @@ - ဖိုင်များ - ပြက္ခဒိန် ဖိုင်များ + အကူအညီ သုံးစွဲသူအမည် စကားဝှက် ဖိုင်များ - သုံးစွဲသူအမည် - စကားဝှက် ဒေါင်းလုတ် ဟုတ် မဟုတ်ဘူး diff --git a/res/values-nb-rNO/strings.xml b/res/values-nb-rNO/strings.xml index d86ae9d4..d536abbd 100644 --- a/res/values-nb-rNO/strings.xml +++ b/res/values-nb-rNO/strings.xml @@ -1,62 +1,61 @@ - ownCloud - Passord: - Brukernavn: - Logg inn - Velkommen til din ownClound - Filer - Musikk - Kontakter - Kalender - Bokmerker - Innstillinger - Sett opp konto - Det finnes ingen ownClound kontoer for din enhent. For Ã¥ bruker denne appen mÃ¥ du opprette en, - Oppdater + %1$s Andriod app + versjon %1$s + Oppdater konto Last opp Innhold fra andre applikasjoner Filer - Opprett katalog - Søk + Åpne med + Ny mappe Innstillinger + Detaljer + Send Generelt - Enhetssporing - Legg til ny sesjon - Opprett miniatyrbilder - Velg konto - Enhetssporing - Oppdateringsfrekvens - Oppdater hvert %1$s minutt + Mer Kontoer + HÃ¥ndter kontoer + PIN kode + Beskytt klienten din + Aktiver direkte opplastinger Last opp bilder tatt med kamera øyeblikkelig - URL + Aktiver loggføring + Denne er brukt til Ã¥ loggføre problemer + Loggføringshistorikk + Denne viser de lagrede loggene + Slett historikk + Hjelp + Anbefal til en venn + Tilbakemelding + Avtrykk + Prøv %1$s pÃ¥ smarttelefonen din! + Sjekk server + Serveradresse https://... Brukernavn Passord + Ny med %1$s? Filer - Ingen fil valgt for opplasting - Brukernavn - Passord - Internettadresse - Vise passord? Koble til Last opp Ingen konto funnet + Det finnes ingen %1$s kontoer for din enhet. For Ã¥ bruker denne appen mÃ¥ du først opprette en. Oppsett Avslutt Intet innhold Ã¥ laste opp Intet innhold ble mottatt. Intet Ã¥ laste opp. + %1$s har ikke tilgang til det delte innholdet Laster opp - Opprett mappe for opplasting + Det finnes ikke filer i mappen.\nNye filer kan legges til med \"last opp\" i menyen. Trykk pÃ¥ en fil for Ã¥ vise ekstra informasjon. Størrelse: Type: Opprettet: Endret: Last ned - Oppdater - Last ned igjen - Åpne + Oppdater fil + Filnavnet ble endret til %1$s under opplasting + Del lenke + Avslutt deling av lenke Ja Nei OK @@ -65,81 +64,175 @@ Avbryt Lagre og avslutt Feil + Laster... + Ukjent feil Om + Endre passord Slett konto Opprett konto Last opp fra... - Katalognavn + Mappenavn Laster opp... %1$d%% Laster opp %2$s Opplasting fullført + %1$s ble lastet opp Opplasting feilet Opplasting av %1$s kunne ikke fullføres - Opplasting feilet: %1$d/%2$d filer ble lastet opp Laster ned... %1$d%% Laster ned %2$s Nedlasting fullført + %1$s ble lastet ned Nedlasting feilet Nedlasting av %1$s kunne ikke fullføres + Ikke lastet ned enda Velg konto - Kontakter Synkronisering feilet Synkronisering av %1$s kunne ikke fullføres + Ugyldig passord for %1$s Konflikter funnet - Bruk sikker tilkobling + %1$d hold-i-synk filer kunne ikke synkroniseres + Hold i synk filer mislyktes + Innholdet av %1$d filer kunne ikke synkroniseres (%2$d konflikter) + Noen lokale filer ble glemt + Mappen %1$s finnes ikke lengere + Flytt alle + Alle filer ble flyttet + Noen filer kunne ikke fjernes + Lokal: %1$s + Ekstern: %1$s + Det er ikke nok plass til Ã¥ kopiere de valgte filene til %1$s mappe. Vil du flytte dem i stedet? Vennligst tast inn din App-PIN - Vennligst tast inn ny App-PIN - - 15 minutter - 30 minutter - 60 minutter - - - 15 - 30 - 60 - + Skriv inn din PIN kode + PIN koden vil bli ettersourt hver gang appen starter + Vennligst tast inn din PIN kode pÃ¥ nytt + Fjern din PIN kode + PIN kodene du tastet er ulike + Feil PIN kode + PIN kode fjernet + PIN kode lagret + %1$s musikkspiller + %1$s (spiller) + %1$s (laster) + %1$s avspilling avsluttet + Ingen mediafil funnet + Ingen konto angitt + Filen er ikke i en gyldig konto + Mediakodek er ikke støttet + Mediafilen kunne ikke leses + Mediafilen er ikke riktig kodet + Tidsavbrudd under avspillingsforsøk + Mediafilen kan ikke strømmes + Mediafilen kan ikke spilles med standard mediaspiller + Sikkerhetsfeil under avspilling av %1$s + Inputfeil under avspilling av %1$s + Uforventet feil under avspilling av %1$s + Spol tilbake + Spill eller pause + Spol fremover Prøver Ã¥ logge inn... Ingen nettverkstilkobling - Koble til likevel Sikker tilkobling ikke tilgjengelig. Tilkobling opprettet - Tester tilkobling... + Tester tilgang... + Feil i server konfigurasjon + En konto for samme bruker og server finnes allerede pÃ¥ enheten + Den innskrevne brukeren matcher ikke brukeren av denne kontoen Ukjent feil oppstod! Fant ikke tjener - Fant ikke spesifisert tjener. Vennligst sjekk tjenernavnet og server-tilgjengeligheten, og prøv pÃ¥ nytt. + Finner ikke server instans Serveren brukte for lang tid pÃ¥ Ã¥ svare Feil formatert URL Oppstart av SSL feilet - Uverifisert SSL-servers identitet + Kunne ikke verifisere SSL-serverens identitet + Ukjent server versjon Klarte ikke Ã¥ opprette tilkobling Sikker tilkobling opprettet - Innloggingsdetaljer - Ugyldig brukernavn / passord - Send rapport - Ikke send rapport - Utvidelser tilgjengelig! + Feil brukernavn eller passord + Mislykket autorisasjon + Tilgang nektet av autorisasjonsserver + Uforventet tilstand; vennligst skriv inn serveradressen en gang til + Autorisasjonen din har gÃ¥tt ut. Vennligt autoriser igjen + Vennligst skriv inn gjeldende passord + Sesjonen din har gÃ¥tt ut. Vennligst koble til igjen + Kobler til autorisasjonsserver... + Serveren støtter ikke denne autorisasjonsmetoden + %1$s støtter ikke flere kontoer Hold filen oppdatert - Del Endre navn Fjern Er du sikker pÃ¥ at du vil fjerne %1$s ? + Vil du virkelig fjerne %1$s og dens innhold? Kun lokalt + Kun lokalt innhold Fjern fra server + Ekstern og lokal + Fjerning var vellykket + Fjerning mislyktes + Skriv inn et nytt navn + Lokal kopi kunne ikke endre navn; prøv et annet navn Klarte ikke Ã¥ endre navn - Mappe kunne ikke opprettes + Eksterne filer kunne ikke sjekkes + filinnhold er allerede synkronisert + Forbudte tegn: / \\ < > : \" | ? * Vent et øyeblikk + Uforventet problem; vennligst velg filen fra en annen applikasjon Ingen fil ble valgt - Advarsel + Send lenke til ... + Logg inn med oAuth2 + Kobler til oAuth2 server... Identiteten til siden kunne ikke verifiseres - Serverens sertifikat er ikke til Ã¥ stole pÃ¥ - Serverens sertifikat er utløpt + - Server-sertifikatets gyldige datoer er i fremtiden + - Nettadressen samsvarer ikke med vertsnavnet i sertifikatet + Vil du stole pÃ¥ dette sertifikatet likevel? + Sertifikatet kunne ikke lagres Detaljer Skjul + Utstedt til: + Utstedt av: + Vanlig navn: + Organisasjon: + Organisasjonsenhet: Land: + Stat: + Sted: + Gyldighet: Fra: Til: Signatur: - Dette er en plassholder + Algoritme: + Dette er en plassholder + placeholder.txt + PNG bilde + 389 KB + 18.05.2012 12:23 + 12:23:45 + Kun last opp bilder via WiFi + /Direkteopplasting + Oppdateringskonflikt + Ekstern fil %s er ikke synkronisert med lokal fil. Hvis du fortsetter vil det erstatte innhold pÃ¥ serveren. Behold begge + Overskriv + Ikke last opp + BildeforhÃ¥ndsvisning + Dette bildet kan ikke vises + Misslyktes direkteopplasting + Mislykket direkteopplastinger + Oppsumering av alle mislykkede direkteopplastinger + velg alle + forsøk alle valgte pÃ¥ nytt + slett alle valgte fra opplastingskø + forsøk Ã¥ laste opp bildet pÃ¥ nytt: + Last flere bilder + ikke gjør noe nÃ¥r du ikke er online for direkteopplasting + Feilmelding: + Vennligst sjekk serverkonfigurasjon, kanskje kvoten din er brukt opp. + Klarte ikke Ã¥ dele denne filen eller mappen. Sjekk at den eksisterer. + Det skjedde en feil under deling av denne filen eller mappen + Klarte ikke Ã¥ avslutte delingen av denne filen eller mappen. Den eksisterer ikke. + En feil oppstod ved avslutting av delingen av denne filen eller mappen + Send + Kopier lenke + Kopiert til utklippstavlen diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-ne/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index fcb9489b..ba84c25e 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -1,67 +1,51 @@ - ownCloud - Wachtwoord: - Gebruikersnaam: - Inloggen - Welkom in je ownCloud - Bestanden - Muziek - Contactpersonen - Kalender - Bladwijzers - Instellingen - Account configureren. - Er zijn nog geen ownCloud accounts op dit apparaat. Als je deze app wilt gebruiken zul je een account moeten aanmaken. - ownCloud Android client\n\nversie: %1$s - Verversen + %1$s Android App + versie %1$s + Account verversen Uploaden Inhoud van andere apps Bestanden - Creëer map - Zoeken + Open met + Nieuwe map Instellingen + Details + Versturen Algemeen - Apparaat volgen - Voeg nieuwe sessie toe - Maak afbeeldingsminiaturen - Selecteer een account - Kies welke accounts de app mag gebruiken. - Apparaat volgen - Sta toe dat ownCloud je apparaat locatie volgt - ownCloud houdt dit apparaat in de gaten - Update interval - Ververs elke %1$s minuten + Meer Accounts Beheer accounts - ownCloud App PIN - Beveilig je ownCloud client + App PIN + Beveilig je client Schakel direct uploaden in Upload afbeeldingen van camera automatisch - ownCloud URL + Loggen aanzetten + Dit wordt gebruikt om problemen te loggen. + Log-geschiedenis + Dit toont de bijgehouden logs + Verwijder Geschiedenis + Help + Aanbevelen bij een vriend + Feedback + afdruk + Probeer %1$s op uw smartphone! + Controleer server + Serveradres https://… Gebruikersnaam Wachtwoord - Ik ben nieuw op ownCloud - Onjuiste URL - Onjuiste sessienaam + Nieuw bij %1$s? Bestanden - Geen bestand geselecteerd voor upload - Gebruikersnaam - Wachtwoord - Webadres - Wachtwoord tonen? - Verbinden met je ownCloud Verbinden Uploaden + Kies upload map: Geen account gevonden - Er zijn nog geen ownCloud accounts op je apparaat. Stel eerst een account in. + Er zijn nog geen %1$s accounts op je apparaat. Stel eerst een account in. Configureren Afsluiten Geen inhoud om te uploaden Er werd geen inhoud ontvangen. Niets om te uploaden. - ownCloud is niet toegestaan om toegang te hebben tot de publieke inhoud + %1$s is niet toegestaan om toegang te hebben tot de publieke inhoud Uploaden - Maak nieuwe map voor upload Er zitten geen bestanden in deze map.\nNieuwe bestanden kunnen worden toegevoegd via de \"Upload\" menukeuze. Druk op een bestand om extra informatie weer te geven Grootte: @@ -69,10 +53,10 @@ Aangemaakt: Aangepast: Download - Verversen - Opnieuw downloaden - Open + Bestand verversen Bestand was hernoemt naar %1$s tijdens het uploaden + Deel link + Link niet meer delen Ja Nee OK @@ -80,91 +64,104 @@ Upload afbreken Annuleren Opslaan & Afsluiten - Verlaat ownCloud Fout + Laden... + Onbekende fout Over + Wijzig wachtwoord Verwijder account Maak account aan Upload van ... - Map naam + Mapnaam Uploaden ... %1$d%% Uploaden %2$s Upload succesvol %1$s is met succes ge-upload - %1$d bestanden succesvol ge-upload Upload mislukt Upload van %1$s kon niet voltooid worden - Uploaden mislukt: %1$d/%2$d bestanden zijn geüpload Downloaden ... %1$d%% Aan het downloaden %2$s Downloaden gelukt %1$s werd succesvol gedownload Downloaden mislukt Download van %1$s kon niet worden voltooid + Nog niet gedownload Kies account - Contactpersonen Synchronisatie mislukt Synchronisatie van %1$s kon niet worden voltooid + Ongeldig wachtwoord voor %1$s Conflicten gevonden %1$d in-synch-houden bestanden konden niet worden gesync\'d In-sync-houden bestanden mislukt Inhoud van %1$d bestanden kon niet worden gesynchroniseerd (%2$d conflicten) - Gebruik veilige verbinding - ownCloud kan het apparaat niet volgen. Configureer de locatie instellingen (location settings). + Een paar lokale bestanden werden vergeten + %1$d bestanden uit de %2$s map konden niet worden gekopieerd naar + Vanaf versie 1.3.16, worden bestanden die vanaf dit apparaat worden ge-uploaded ook gekopieerd naar de lokale %1$s map om gegevensverlies te voorkomen als een enkel bestand wordt gesynchroniseerd met meerdere accounts.\nDoor deze aanpassing werden alle bestanden die met een eerdere versie zijn ge-uploaded gekopieerd naar de %2$s map. Maar een fout voorkwam het succesvol afronden van deze actie tijdens het synchroniseren. U kunt de/het bestand(en) laten staan zoals ze nu zijn en de link naar %3$s verwijderen, of u kunt de bestanden verplaatsen naar de %1$s map en de link naar %4$s laten staan.\nHieronder staan de lokale bestanden en de externe bestanden in %5$s waar ze naar verwezen. + Map %1$s bestaat niet meer + Alle verplaatsen + Alle bestanden zijn verplaatst + Een paar bestanden konden niet worden verplaatst + Lokaal: %1$s + Extern: %1$s + Er is niet genoeg ruimte om de gekopieerde bestanden ook te plaatsen in map %1$s. Wilt u ze erheen verplaatsten? Voer App PIN in - Voer nieuwe App PIN in Voer App PIN in De PIN wordt steeds opnieuw gevraagd als de app wordt gestart Voer App PIN opnieuw in Verwijder App PIN App PIN\'s komen niet overeen - Foutieve ownCloud applicatie PIN - ownCloud App PIN verwijderd - ownCloud App PIN opgeslagen - - 15 minuten - 30 minuten - 60 mintuen - - - 15 - 30 - 60 - + Foutieve applicatie PIN + App PIN verwijderd + App PIN opgeslagen + %1$s muziekspeler + %1$s (speelt) + %1$s (laden) + %1$s playback geëindigd + Geen mediabestand gevonden + Geen account opgegeven + Bestand niet in een geldig account + Niet ondersteunde media codec + Mediabestand kon niet worden gelezen + Mediabestand niet goed gecodeerd + Time-out tijdens het spelen + Mediabestand kan niet worden gestreamd + Mediabestand kan niet worden afgespeeld met de standaard mediaplayer + Beveiligingsfout bij afspelen %1$s + Invoerfout bij afspelen %1$1s + Onverwachte fout bij afspelen %1$s + terugpoel knop + Speel of pauze knop + Doorspoel knop Proberen om in te loggen... Geen netwerkverbinding - Er kon geen netwerkverbinding worden gevonden, controleer je internet verbinding en probeer het opnieuw. - Toch verbinden Veilige verbinding niet beschikbaar. - De applicatie kon geen beveiligde verbinding met de server maken. Er is echter wel een onbeveiligde verbinding beschikbaar. Je kunt verdergaan of annuleren. Verbinding tot stand gebracht Probeer verbinding... - Foutieve ownCloud configuratie - Het lijkt erop dat je ownCloud instantie niet correct geconfigureerd is. Neem contact op met je beheerder voor meer details. + Foutieve server configuratie + Er bestaat al een account voor deze gebruiker en server op dit apparaat. + De opgegeven gebruiker komt niet overeen met de gebruiker van dit account Onbekende fout opgetreden! - Er heeft zich een onbekend fout voorgedaan. Neem alstublieft contact op met de beheerders en voeg de logs toe van uw apparaat. Kon geen host vinden - Kon de ingevoerde host niet vinden. Controleer de hostnaam en servertoegankelijkheid en probeer opnieuw. - ownCloud instantie niet gevonden - De applicatie kon geen instantie van ownCloud vinden op de opgegeven locatie. Controleer het pad en probeer opnieuw. + Server instantie niet gevonden De server had te lang nodig om te reageren Misvormde URL SSL initialisatie mislukt - Niet geverifieerde SSL server identiteit - Onherkende ownCloud server versie + Kon de identiteit van de SSL server niet verifiëren + Onherkende server versie Kon verbinding niet tot stand brengen. Veilige verbinding tot stand gebracht - Inloggegevens - login / password niet juist - Verkeerde pad opgegeven - Interne server fout, code %1$d - De applicatie is onverwacht beëindigd. Wilt je een crash report versturen? - Rapport verzenden - Rapport niet verzenden - Uitbreidingen aanwezig! - Zo te zien ondersteunt je ownCloud instantie geavanceerde uitbreidingen. Wilt je deze uitbreidingen in Android zichtbaar maken? + Verkeerde gebruikersnaam of wachtwoord + Authorisatie niet succesvol + Toegang geweigerd door authorizatieserver + Onverwachte toestand; voer a.u.b de server-URL nogmaals in. + Uw autorisatie is verstreken. Autoriseer opnieuw. + Geef het huidige wachtwoord op + Uw sessie is verstreken. Verbind opnieuw + Verbinden met authenticatieserver... + De server ondersteunt deze authenticatiemethode niet + %1$s ondersteunt het gebruik van meerdere accounts niet + Kan niet authenticeren tegen deze server Houd bestand actueel - Delen Hernoemen Verwijderen Wilt je werkelijk %1$s verwijderen? @@ -181,16 +178,18 @@ Extern bestand kon niet worden gecontroleerd Bestandsinhoud is al gesynchroniseerd Map kon niet worden aangemaakt + Verboden tekens: / \\ < > : \" | ? * Even geduld Onverwacht probleem; probeer een andere app om het bestand te selecteren Er werd geen bestand geselecteerd - Waarschuwing + Verstuur link naar ... + Inloggen met oAuth2 + Verbinden met oAuth2-server. De identiteit van de site kan niet worden gecontroleerd - Het server certificaat wordt niet vertrouwd - Het server certificaat is verlopen - Het server certificaat is te recent - De URL komt niet overeen met de hostname in het certificaat - Het server certificaat kon niet opgevraagd worden Wil je dit certificaat alsnog vertrouwen? Het certificaat kon niet worden opgeslagen Details @@ -208,7 +207,14 @@ Aan: Handtekening: Algoritme: - Dit is een gereserveerde ruimte + Het certificaat kon niet worden getoond. + - Geen informatie over de fout + Dit is een plaatshouder + plaatshouder.txt + PNG Afbeelding + 389 KB + 2012/05/18 12:23 + 12:23:45 Upload afbeeldingen alleen via WiFi /InstantUpload Update conflict @@ -216,4 +222,25 @@ Beide bewaren Overschrijven Niet uploaden + Afbeelding voorbeeld + Deze afbeelding kan niet weergegeven worden + %1$s kon niet worden gekopieerd naar de %2$s lokale map + Mislukt InstantUpload + Mislukte directe uploads + Samenvatting van alle mislukte directe uploads + alles selecteren + opnieuw proberen alles te selecteren + verwijderen alle geselecteerde van de upload wachtrij + probeer de afbeelding opnieuw te uploaden: + Laadt meer Afbeeldingen + doe niks, u bent niet online voor directe upload + Mislukkings Bericht: + Controleer uw server instellingen, misschien is uw quota overschreden. + Kan dit bestand of deze map niet delen. Controleer of dit object wel bestaat. + Er trad een fout op bij uw poging dit bestand of deze map te delen + Kan delen van dit bestand of deze map niet beëindigen. Het object bestaat niet. + Er trad een fout op bij uw poging het delen van dit bestand of deze map te beëindigen + Versturen + Link kopiëren + Gekopieerd naar het klembord diff --git a/res/values-nn-rNO/strings.xml b/res/values-nn-rNO/strings.xml index e9e4e64d..31c13ff4 100644 --- a/res/values-nn-rNO/strings.xml +++ b/res/values-nn-rNO/strings.xml @@ -1,20 +1,85 @@ - Filer - Musikk - Kotaktar - Innstillingar + Oppdater konto Last opp + Innhald frÃ¥ andre program Filer Innstillingar + Send + Generelt + Meir + Kontoar + Last opp kamerabilete med ein gong du tek dei + Hjelp + Impressum Brukarnamn Passord Filer - Brukarnamn - Passord + Kopla til Last opp + Fann ingen konto + Oppsett + Avslutt + Inga innhald Ã¥ lasta opp + Mottok ikkje noko innhald. Ingenting Ã¥ lasta opp. + Lastar opp + Trykk pÃ¥ ei fil for Ã¥ visa meir informasjon. + Storleik: + Type: + Oppretta: + Endra: Last ned - Kanseller + Ja + Nei + Greitt + Avbryt nedlasting + Avbryt opplasting + Avbryt + Lagra & avslutt Feil - Kotaktar + Om + Endra passord + Slett konto + Opprett konto + Last opp frÃ¥ … + Lastar opp … + %1$d%% lastar opp %2$s + Opplasting fullført + Feil ved opplasting + Klarte ikkje Ã¥ lasta %1$s ferdig opp + Lastar ned … + %1$d%% lastar ned %2$s + Feil ved nedlasting + Klarte ikkje Ã¥ lasta %1$s ferdig ned + Vel konto + Feil ved synkronisering + Klarte ikkje Ã¥ synkronisera ferdig %1$s + Ver venleg og skriv inn programpinkoden + Inga nettilkopling + Trygg tilkopling ikkje tilgjengeleg. + Tilkopling oppretta + Ein ukjend feil oppstod! + Klarte ikkje Ã¥ finna tenaren + Tenaren brukte for lang tid pÃ¥ Ã¥ svara + Ugyldig URL + Feil ved SSL-oppstart + Klarte ikkje Ã¥ oppretta ei tilkopling + Trygg tilkopling oppretta + Hald fila oppdatert + Endra namn + Fjern + Vil du verkeleg fjerna %1$s? + Berre lokalt + Fjern frÃ¥ tenaren + Klarte ikkje Ã¥ fullføra omdøyping + Vent litt + Inga fil valt + Klarte ikkje Ã¥ stadfesta identiteten til nettstaden + – Tenarsertifikatet er ikkje klarert + – Tenarsertifikatet er utløpt + – URL-en stemmer ikkje med tenarnamnet i sertifikatet + Vil du stola pÃ¥ dette sertifikatet uansett? + Klarte ikkje Ã¥ lagra sertifikatet + Berre last opp bilete over WiFi + Send diff --git a/res/values-oc/strings.xml b/res/values-oc/strings.xml index 64bf6653..ed189936 100644 --- a/res/values-oc/strings.xml +++ b/res/values-oc/strings.xml @@ -1,43 +1,16 @@ - ownCloud - Senhal : - Nom o escais : - Login - Planvengut dins mon ownCloud - Fichièrs - Musica - Contactes - Calendièr - Marcapaginas - Configuracion - Compte de configuracion - Refresca Amontcarga Fichièrs - Crea un repertòri - Cèrca Configuracion General - Traqua lo daquòs - Apond una session novèla - Crea imatgonels - Selecciona un compte - Causís, quines comptes las apps poirián utilizar. - Traqua lo daquòs + Mai d\'aquò Comptes Maneja comptes - URL del ownCloud + Ajuda Nom d\'usancièr Senhal - As donat un URL fals - Nom de session pas corrècte Fichièrs - Non d\'usancièr - Senhal - Adreiça web - Mòstra lo senhal ? - Connecta a ton ownCloud Connecta Amontcarga Cap de compte trobat @@ -49,9 +22,6 @@ Creat : Modificat : Avalcarga - Refresca - Torna avalcargar - Dubrís Òc Non D\'accòrdi @@ -59,20 +29,13 @@ Annula Error A prepaus + Cambia lo senhal Escafa lo compte Crea un compte Avalcarga dempuèi ... - Nom de repertòri Al avalcargar ... Capitada d\'avalcargar Causís lo compte - Contactes - Utiliza una conneccion segura Dintras ton PIN d\'App, se te plai - Detalhs de login - Rapòrt mandat - Mandes pas de rapòrt - Extensions disponibles ! - Parteja Torna nomenar diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml new file mode 100644 index 00000000..87ce75b1 --- /dev/null +++ b/res/values-pa/strings.xml @@ -0,0 +1,122 @@ + + + %1$s ਐਡਰਾਇਡ ਐਪ + ਵਰਜਨ %1$s + ਅਕਾਊਂਟ ਤਾਜ਼ਾ ਕਰੋ + ਅੱਪਲੋਡ + ਹੋਰ ਐਪਸ ਤੋਂ ਸਮੱਗਰੀ + ਫਾਇਲਾਂ + ਇਸ ਨਾਲ ਖੋਲ੍ਹੋ + ਸੈਟਿੰਗ + ਵੇਰਵ + ਭੇਜੋ + ਆਮ + ਅਕਾਊਂਟ + ਲਾਗ ਰੱਖਣਾ ਚਾਲੂ + ਲਾਗ ਰੱਖਣ ਅਤੀਤ + ਅਤੀਤ ਹਟਆਓ + ਯੂਜ਼ਰ-ਨਾਂ + ਪਾਸਵਰ + %1$s ਲਈ ਨਵੇਂ ਹੋ? + ਫਾਇਲਾਂ + ਕੁਨੈਕਟ + ਅੱਪਲੋਡ + ਕੋਈ ਅਕਾਊਂਟ ਨਹੀਂ ਲੱਭਾ + ਸੈਟਅੱਪ + ਬਾਹਰ + ਅੱਪਲੋਡ ਕਰਨ ਲਈ ਕੋਈ ਸਮੱਗਰੀ ਨਹੀਂ + ਕੋਈ ਸਮੱਗਰੀ ਨਹੀਂ ਮਿਲੀ। ਅੱਪਲੋਡ ਕਰਨ ਲਈ ਕੁਝ ਨਹੀਂ ਹੈ। + ਅੱਪਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ + ਹੋਰ ਜਾਣਕਾਰੀ ਵੇਖਣ ਲਈ ਫਾਇਲ ਉੱਤੇ ਛੂਹੋ + ਆਕਾਰ: + ਕਿਸਮ: + ਬਣਾਈ: + ਸੋਧ ਕੀਤੀ: + ਡਾਊਨਲੋਡ + ਫਾਇਲ ਤਾਜ਼ਾ ਕਰੋ + ਹਾਂ + ਨਹੀਂ + ਠੀਕ ਹੈ + ਡਾਊਨਲੋਡ ਕਰਨਾ ਰੱਦ ਕਰੋ + ਅੱਪਲੋਡ ਰੱਦ ਕਰੋ + ਰੱਦ ਕਰੋ + ਸੰਭਾਲੋ ਅਤੇ ਬੰਦ ਕਰੋ + ਗਲਤੀ + ...ਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ + ਅਣਜਾਣ ਗਲਤੀ + ਇਸ ਬਾਰੇ + ਪਾਸਵਰਡ ਬਦਲੋ + ਅਕਾਊਂਟ ਹਟਾਓ + ਅਕਾਊਂਟ ਬਣਾਓ + ... ਤੋਂ ਅੱਪਲੋਡ + ... ਅੱਪਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ + %1$d%% ਅੱਪਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ %2$s + ਅੱਪਲੋਡ ਸਫ਼ਲ ਹੈ + ਅੱਪਲੋਡ ਫੇਲ੍ਹ ਹੈ + %1$s ਦੇ ਅੱਪਲੋਡ ਨੂੰ ਠੀਕ ਤਰ੍ਹਾਂ ਪੂਰਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ + ... ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ + ਹਾਲੇ ਡਾਊਨਲੋਡ ਨਹੀਂ + ਅਕਾਊਂਟ ਚੁਣੋ + ਸੈਕਰੋਨਾਈਜ਼ ਕਰਨ ਲਈ ਫੇਲ੍ਹ + %1$s ਲਈ ਗਲਤ ਪਾਸਵਰਡ + ਸਭ ਭੇਜੋ + ਸਭ ਫਾਇਲਾਂ ਨੂੰ ਭੇਜਿਆ ਗਿਆ + ਕੁਝ ਫਾਇਲਾਂ ਨੂੰ ਭੇਜਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ + ਲੋਕਲ: %1$s + ਰਿਮੋਟ: %1$s + ਆਪਣਾ ਐਪ ਪਿੰਨ ਦਿਉ ਜੀ + %1$s ਸੰਗੀਤ ਪਲੇਅਰ + %1$s (ਚੱਲਦਾ ਹੈ) + %1$s (ਲੋਡ ਹੁੰਦਾ ਹੈ) + %1$s ਚੱਲਣਾ ਬੰਦ + ਕੋਈ ਮੀਡਿਆ ਫਾਇਲ ਨਹੀਂ ਲੱਭੀ + ...ਲਾਗਇਨ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਜਾਰੀ + ਕੋਈ ਨੈੱਟਵਰਕ ਕੁਨੈਕਸ਼ਨ ਨਹੀਂ ਹੈ + ਸੁਰੱਖਿਅਤ ਕੁਨੈਕਸ਼ਨ ਉਪਲੱਬਧ ਨਹੀਂ ਹੈ। + ਕੁਨੈਕਸ਼ਨ ਬਣਾਇਆ ਗਿਆ ਹੈ + ...ਕੁਨੈਕਸ਼ਨ ਟੈਸਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ + ਅਣਜਾਣ ਗਲਤੀ ਆਈ ਹੈ! + ਹੋਸਟ ਨਹੀਂ ਲੱਭਿਆ ਜਾ ਸਕਿਆ + SSL ਸ਼ੁਰੂ ਕਰਨ ਲਈ ਫੇਲ੍ਹ ਹੈ + ਸੁਰੱਖਿਅਤ ਕੁਨੈਕਸ਼ਨ ਬਣਾਇਆ ਗਿਆ ਹੈ + ਗਲਤ ਯੂਜ਼ਰ-ਨਾਂ ਜਾਂ ਪਾਸਵਰਡ + ਆਪਣਾ ਮੌਜੂਦਾ ਪਾਸਵਰਡ ਦਿਉ ਜੀ + ਫਾਇਲ ਨੂੰ ਅੱਪ ਟੂ ਡੇਟ ਰੱਖੋ + ਨਾਂ ਬਦਲੋ + ਹਟਾਓ + ਕੀ ਤੁਸੀਂ %1$s ਨੂੰ ਹਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ? + ਕੇਵਲ ਲੋਕਲ + ਸਰਵਰ ਤੋਂ ਹਟਾਓ + ਰਿਮੋਟ ਤੇ ਲੋਕਲ + ਪਲ਼ ਭਰ ਲਈ ਉਡੀਕੋ + ਕੋਈ ਫਾਇਲ ਨਹੀਂ ਚੁਣੀ ਗਈ + ਇਹ ਸਾਈਟ ਦੀ ਪਛਾਣ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ ਹੈ + - ਸਰਵਰ ਸਰਟੀਫਿਕੇਟ ਭਰੋਸੇਯੋਗ (ਟਰੱਸਟਡ) ਨਹੀਂ ਹੈ + - ਸਰਵਰ ਸਰਟੀਫਿਕੇਟ ਦੀ ਮਿਆਦ ਪੁੱਗ ਗਈ ਹੈ + ਸਰਟੀਫਿਕੇਟ ਨੂੰ ਸੰਭਾਲਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ + ਵੇਰਵਾ + ਓਹਲੇ + ਜਾਰੀ ਕੀਤਾ: + ਜਾਰੀ ਕਰਤਾ: + ਆਮ ਨਾਂ: + ਸੰਗਠਨ: + ਦੇਸ਼: + ਸੂਬਾ: + ਟਿਕਾਣਾ: + ਵੈਧਤਾ: + ਵਲੋਂ: + ਵੱਲ: + ਦਸਤਖਤ: + ਐਲਗੋਰਿਥਮ: + PNG ਚਿੱਤ + 389 KB + 2012/05/18 12:23 PM + 12:23:45 + ਤਸਵੀਰਾਂ ਨੂੰ ਵਾਈ-ਫਾਈ ਰਾਹੀਂ ਹੀ ਅੱਪਲੋਡ ਕਰੋ + ਦੋਵੇਂ ਰੱਖੋ + ਉੱਤੇ ਲਿਖੋ + ਅੱਪਲੋਡ ਨਾ ਕਰੋ + ਚਿੱਤਰ ਝਲਕ + ਸਭ ਚੁਣੋ + ਭੇਜੋ + diff --git a/res/values-pl-rPL/strings.xml b/res/values-pl-rPL/strings.xml deleted file mode 100644 index 8bd245c8..00000000 --- a/res/values-pl-rPL/strings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - Nazwa użytkownika - Muzyka - Ustawienia - Ustawienia - Nazwa użytkownika - Nazwa użytkownika - Otwórz - diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 0c071269..0481e542 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -1,58 +1,43 @@ - ownCloud - Hasło: - Nazwa użytkownika: - Zaloguj - Witaj - Pliki - Muzyka - Kontakty - Kalendarz - Zakładki - Ustawienia - Załóż konto - Nie wykryto kont ownCloud. Aby korzystać z tej aplikacji, musisz utworzyć konto. - %1$s Android App⏎ ⏎ wersja: %2$s - Odśwież + %1$s Aplikacja Android + wersja %1$s + Odśwież konto Wyślij plik Zasoby innych aplikacji Pliki - Nowy katalog - Szukaj + Otwórz za pomocą + Nowy folder Ustawienia + Szczegóły + Wyślij Ogólne - Śledzenie urządzenia - Dodaj nową sesję - Utwórz miniaturki obrazów - Wybierz konto - Wybierz konto, z którego ma korzystać aplikacja. - Śledzenie urządzeń - Włącz śledzenie lokalizacji urządzenia przez tę aplikację - Ta aplikacja śledzi to urządzenie - Odstęp aktualizacji - Aktualizuj co %1$ minut + Więcej Konta Zarządzaj kontami PIN aplikacji Chroń klienta Włącz natychmiastowe wysyłanie Natychmiast wysyłaj zdjęcia zrobione aparatem - URL + Włączone Logi + To jest używane do logów problemów + Zapisuj historię + To pokazuje zapisane logi + Usuń histrorię + Pomoc + Poleć znajomemu + Wsparcie + Stopka + Wypróbuj %1$s na swoim smartphonie! + Sprawdź serwer + Adres serwera https://... Nazwa użytkownika Hasło - Jestem nowym użytkownikiem %1$s - Podano niepoprawny adres - Niepoprawna nazwa sesji + Nowe %1$s? Pliki - Nie wybrano plików do wysłania - Nazwa użytkownika - Hasło - Adres internetowy - Wyświetlić hasło? - Połącz z %1$s Połącz Wyślij + Wybierz folder do wysłania: Nie znaleziono konta Nie wykryto kont %1$s na twoim urządzeniu. Załóż konto. Ustawienia @@ -61,7 +46,6 @@ Nie otrzymano danych. Nie ma nic do wysłania. %1$s nie ma dostępu do udostępnionych treści Wysyłanie - Stwórz katalog do wysyłania Nie ma plików w tym katalogu.⏎ Nowe pliki można dodać wybierając w menu Wyślij. Dotknij plik aby wyświetlić dodatkowe informacje Rozmiar: @@ -69,10 +53,10 @@ Utworzono: Zmodyfikowano: Pobierz - Odśwież - Pobierz ponownie - Otwórz + Odśwież plik Podczas wysyłania nazwa pliku została zmieniona na %1$s + Udostępnij link + Anuluj udostępnianie Tak Nie OK @@ -80,39 +64,47 @@ Anuluj wysyłanie Anuluj Zapisz i wyjdź - Opuść %1$s Błąd + Wczytuję ... + Nieznany błąd O aplikacji + Zmień hasło Usuń konto Utwórz konto Wyślij plik z … - Nazwa katalogu + Nazwa folderu Wysyłam… %1$d%% Wysyłanie %2$s Wysyłanie zakończone powodzeniem Wysyłanie %1$s zakończone sukcesem - Wysłano %1$d plików Wysyłanie nie powiodło się Wysyłanie %1$s nie powiodło się - Wysyłanie nieudane: wysłano %1$d/%2$d plików Pobieranie … %1$d%% Pobieranie %2$s Pobieranie zakończone Pobrano %1$s plików Pobieranie nieudane Pobieranie %1$s nie powiodło się + Nie poprane jeszcze Wybierz konto - Kontakty Błąd synchronizacji Nie można było ukończyć synchronizacji %1$s + Niepoprawne hasło dla %1$s Znaleziono konflikty %1$d synchronizowanych plików nie może zostać zsynchronizowanych Synchronizacja plików nie powiodła się Zawartość %1$d plików nie może zostać synchronizowana (%2$d konfliktów) - Użyj bezpiecznego połączenia - %1$s nie może śledzić urządzenia. Sprawdź ustawienia lokalizacji + Niektóre lokalne pliki zostały zgubione. + %1$d plików z folderu %2$s nie udało się się przekopiować + Od wersji 1.3.16, pliki kopiowane z tego urządzenia są kopiowane do lokalnego folderu %1$s aby zapobiec utracie danych gdy pojedynczy plik jest synchronizowany z wieloma kontami.\n\nZ powodu tej zmiany, wszystkie pliku przesłane w poprzednich wersjach tej aplikacji zostały skopiowane do folderu %2$s. Jednakże, podczas synchronizacji konta pojawił się się błąd uniemożliwiający dokończenie tej operacji. Możesz albo pozostawić plik(i) tak jak są i usunąć link do %3$s, albo przenieść plik(i) do folderu %1$s i pozostawić link do %4$s.\n\nPoniżej lokalny plik(i) i zdalny plik(i) w %5$s do których były podłączone. + Folder %1$s nie istnieje. + Przenieś wszystko + Wszystkie pliki zostały przeniesione + Niektóre pliki nie mogły być przeniesione + Lokalna ścieżka: %1$s + Zdalna ścieżka: %1$s + Nie ma wystarczająco miejśca, aby skopiować zaznaczone pliki do folderu %1$s. Chciałbyś je przenieść? Podaj PIN aplikacji - Podaj nowy PIN aplikacji Wpisz PIN aplikacji Kod PIN będzie wymagany za każdym razem, gdy aplikacja będzie uruchamiana. Ponownie wpisz PIN aplikacji @@ -121,50 +113,55 @@ Niepoprawny PIN aplikacji Usunięto PIN aplikacji Zapisano PIN aplikacji - - 15 minut - 30 minut - 60 minut - - - 15 - 30 - 60 - + %1$s odtwarzacz muzyki + %1$s (odtwarzane) + %1$s (wczytywane) + %1$s odtwarzanie zakończone + Nie znaleziono plików multimedialnych + Nie znaleziono konta + Plik na nieprawidłowym koncie + Nieobsługiwany kodek multimediów + Błąd odczytu pliku multimedialnego + Błąd kodowania pliku multimedialnego + Upłynął limit czasu podczas próby odtwarzania + Ni udało się przesłać pliku multimedialnego + Plik multimediów nie może być odtworzony we wbudowanym odtwarzaczu + Błąd zabezpieczeń podczas próby odtworzenia %1$s + Błąd wprowadzania podczas próby odtworzenia %1$s + Nieoczekiwany błąd podczas próby odtworzenia %1$s + Przycisk przewijania + Przycisk odtwarzania / pauzowania + Przycisk przewijania do przodu Próbuję się zalogować... Brak połączenia sieciowego - Nie znaleziono połączenia sieciowego. Sprawdź połączenie internetowe i spróbuj ponownie. - Połącz mimo wszystko Nie można nawiązać bezpiecznego połączenia. - Aplikacja nie można ustanowić bezpiecznego połączenia z serwerem. Niezabezpieczone połączenie jest dostępne. Możesz kontynuować lub anulować. Połączenie nawiązane Testowanie połączenia… Uszkodzona konfiguracja serwera - Wygląda na to, że twoja instancja ownCloud nie jest poprawnie skonfigurowana. Aby uzyskać więcej informacji, skontaktuj się z administratorem. + Konto tego samego użytkownika i serwera już istnieje na tym urządzeniu + Podany login nie pasuje do użytkowników Wystąpił nieznany błąd! - Wystąpił nieznany błąd. Proszę skontaktować się z autorami i dodać dzienniki z urządzenia. Nie mogę znaleźć hosta - Nie można znaleźć podanego hosta. Sprawdź nazwę hosta oraz dostępność serwera i spróbuj ponownie. - Nie znaleziono instancji ownCloud - Aplikacja nie odnalazła instancji serwera pod podaną ścieżką. Sprawdź ścieżkę i spróbuj ponownie. + Nie znaleziono instancji serwer Serwer zbyt długo nie odpowiadał Zły format adresu URL Inicjowanie SSL nie powiodło się - Tożsamość SSL serwera niezweryfikowana + Nie można zweryfikować tożsamości SSl serwera Nie rozpoznano wersji serwera Nie można ustanowić połączenia Nawiązano bezpieczne połączenie - Szczegóły logowania - Nieprawidłowa nazwa użytkownika / hasło - Wprowadzono nieprawidłową ścieżkę - Wewnętrzny błąd serwera, kod %1$d - Aplikacja została nieoczekiwanie zakończona. Czy chcesz przesłać raport awarii? - Wyślij raport - Nie wysyłaj raportu - Rozszerzenie dostępne! - Twoja instancja serwera wspiera zaawansowane rozszerzenia. Czy chcesz zobaczyć rozszerzenia dostępne dla systemu Android? + Zła nazwa użytkownika lub hasło + Nieudana autoryzacja + Dostęp zabroniony przez serwer autoryzacji + Nieoczekiwany stan; Proszę wpisz adres URL serwera ponownie + Twoja sesja wygasła. Proszę zaloguj się ponownie + Proszę podać obecne hasło + Twoja sesja wygasła. Proszę zaloguj się ponownie + Łączenie z serwerem autoryzacji... + Serwer nie obsługuje tej metody autoryzacji + %1$s nie wspiera multikont + Nie można autoryzować z tym serwerem Automatyczne aktualizuj plik - Udostępnij Zmień nazwę Usuń Czy na pewno chcesz usunąć %1$s ? @@ -180,17 +177,19 @@ Zmiana nazwy nie powiodła się Nie można sprawdzić zdalnego pliku Zawartość pliku została już synchronizowana - Nie można utworzyć katalogu + Folder nie może zostać utworzony + Znaki zabronione: / \\ < > : \" | ? * Poczekaj chwilę Nieoczekiwany problem; spróbuj wybrać plik z innej aplikacji Nie wybrano żadnych plików - Uwaga + Wyślij link do ... + Loguj przez oAuth2 + Łączenie z serwerem oAuth2... Nie można zweryfikować tożsamości strony - Certyfikat serwera jest niezaufany - Certyfikat serwera wygasł - Certyfikat serwera jest wystawiony w przyszłości - URL nie pasuje do nazwy hosta w certyfikacie - Nie można uzyskać certyfikatu serwera Zaakceptować certyfikat mimo wszystko? Nie można zapisać certyfikatu Szczegóły @@ -208,7 +207,14 @@ Do: Sygnatura: Algorytm: - Tekst zastępczy + Nie można wyświetlić certyfikatu. + - Brak informacji o błędzie + Tekst zastępczy + placeholder.txt + Obraz PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Wysyłaj zdjęcia tylko przez WiFi /InstantUpload Konflikt aktualizacji @@ -216,4 +222,25 @@ Zatrzymaj oba Zastąp Nie wysyłaj + Podgląd + Obraz nie może zostać wyświetlony + %1$s nie może zostać skopiowany do lokalnego folderu %2$s + InstantUpload nie powiódł się + Błąd automatycznego przesyłania + Podsumowanie wszystkich nieudanych transferów + zaznacz wszystkie + spróbuj zaznaczyć wszystkie + usuń wszystko z kolejki wysyłania + ponów wysyłanie obrazu: + Wczytaj więcej obrazów + nic nie rób, ponieważ nie jesteś online, nie możesz przesyłać plików + Komunikat błędu: + Proszę sprawdź ustawienia serwera, możliwe że przekroczyłes limit wielkości pliku + Brak możliwości udostępnienia tego pliku lub folderu. Upewnij się, że istnieje. + Wystąpił błąd podczas udostępniania tego pliku lub folderu. + Nie można anulować udostępniania tego pliku lub folderu. Jeśli nie istnieje. + Wystąpił błąd podczas anulowania udostępniania tego pliku lub folderu. + Wyślij + Skopiuj link + Skopiuj do schowka diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml index 8851c6c2..ca772dcb 100644 --- a/res/values-pt-rBR/strings.xml +++ b/res/values-pt-rBR/strings.xml @@ -1,67 +1,51 @@ - ownCloud - Senha: - Nome de usuário: - Login - Bem-vindo - Arquivos - Música - Contatos - Calendário - Favoritos - Ajustes - Configuração de conta - Não existem contas ownCloud no seu dispositivo. Para usar este aplicativo, você precisa criar uma. - Cliente ownCloud para Android\n\nversão: %1$s - Atualizar + %1$s Android App + versão %1$s + Atualização de conta Upload Conteúdo de outros apps Arquivos - Criar pasta - Procurar + Abrir com + Nova pasta Ajustes + Detalhes + Enviar Geral - Rastreamento de dispositivo - Adicionar nova sessão - Criar miniaturas de imagens - Selecione uma conta - Selecione qual das suas contas você deseja utilizar. - Rastreamento de dispositivo - Habilite ownCloud para rastrear a localização do seu dispositivo - Seu ownCloud mantém registro da localização desse dispositivo - Intervalo de atualização - Atualizar a cada %1$s minutos + Mais Contas Gerenciar contas - PIN ownCloud App - Proteja seu cliente ownCloud + PIN App + Proteja seu cliente Habilitar upload instantâneo Instantaneamente faça upload das fotos tiradas pela câmera - URL ownCloud + Habilitar conexão + Isto é usado para registrar os problemas + História de Registro + Isso mostra os registros gravados + Excluir Histórico + Ajuda + Recomendar a um amigo + Feedback + Imprint + Tentar %1$s em seu smartfone! + Verificar Servidor + Endereço do servidor https://... Nome de usuário Senha - Sou novo no ownCloud - URL errada - Nome de sessão errado + Novo para %1$s? Arquivos - Nenhum arquivo selecionado para upload - Nome de usuário - Senha - Endereço Web - Mostrar senha? - Conecte ao seu ownCloud Conectar Upload + Escolher pasta enviar: Nenhuma conta encontrada - Não existem contas ownCloud no seu dispositivo. Por favor, configure uma conta primeiro. + Não existem contas %1$s no seu dispositivo. Por favor, configure uma conta primeiro. Instalação Sair Sem conteúdo para enviar Nenhum foi recebido. Nada para enviar. - ownCloud não é permitido a acessar o conteúdo compartilhado + %1$s não é permitido a acessar o conteúdo compartilhado Enviando - Criar um diretório para upload Não existe arquivos nesta pasta\nNovos arquivos podem ser adicionados com a opção do menu \"Enviar\" Toque em um arquivo para mostrar informações adicionais. Tamanho: @@ -69,10 +53,10 @@ Criado: Modificado: Download - Atualizar - Baixar novamente - Abrir + Atualizar arquivo Arquivo foi renomeado para %1$s durante o upload + Compartilher link + Descompartilhar o link Sim Não OK @@ -80,39 +64,47 @@ Cancelar upload Cancelar Salvar & Sair - Sair do ownCloud Erro + Carregando ... + Erro desconhecido Sobre + Alterar senha Remover conta Criar conta Enviar de … - Nome do diretório + Nome da pasta Enviando … %1$d%% Enviando %2$s Envio bem sucedido %1$s foi enviado com sucesso - %1$d arquivos foram enviados com sucesso Falha no envio Envio de %1$s não pôde ser completado - Envio falhou: %1$d/%2$d arquivos foram enviados Baixando … %1$d%% Baixando %2$s Download bem sucedido %1$s foi baixado com sucesso Download falhou Download de %1$s não pôde ser concluído + Não baixado ainda Escolha a conta - Contatos Sincronização falhou Sincronização de %1$s não pôde ser completada + Senha inválida para %1$s Conflitos encontrados %1$d arquivos \"manter sincronizados\" não puderam ser sincronizados Falha ao manter arquivos sincronizados O conteúdo de %1$d arquivos não puderam ser sincronizados (%2$d conflitos) - Usar Conexão Segura - %1$s não pode rastrear seu dispositivo. Por favor verifique suas configurações de localização + Alguns arquivos locais foram esquecidos + %1$d arquivos de %2$s não puderam ser copiados para pasta + A partir da versão 1.3.16, os arquivos enviados a partir deste dispositivo são copiados para a pasta local %1$s para evitar a perda de dados quando um único arquivo é sincronizado com várias contas.\nDevido a essa mudança, todos os arquivos carregados em versões anteriores deste aplicativo foram copiados para a pasta %2$s. No entanto, um erro impediu a conclusão desta operação durante a sincronização da conta. Você pode tanto deixar o arquivo(s) como é e remover o link para %3$s, ou mover o arquivo(s) para a pasta %1$s e manter o link para %4$s.\nListados abaixo estão o arquivo(s) locais, e o arquivo(s) remotos %5$s em que estavam vinculados. + Pasta %1s não existe mais + Mover todos + Todos os arquivos foram movidos + Alguns arquivos não puderam ser movidos + Local: %1$s + Remoto: %1$s + Não há espaço suficiente para copiar os arquivos selecionados para a pasta %1$s. Ao invés disso, gostaria de movê-los? Por favor, insira o seu PIN de Aplicativo - Por favor, insira seu novo PIN de Aplicativo Insira seu PIN de Aplicativo O PIN (senha) será solicitado toda vez que o aplicativo for iniciado Por favor, reinsira seu PIN de Aplicativo @@ -121,50 +113,55 @@ PIN de Aplicativo incorreto PIN de Aplicativo removido PIN de Aplicativo armazenado - - 15 minutos - 30 minutos - 60 minutos - - - 15 - 30 - 60 - + %1$s reprodutor de música + %1$s (reproduzindo) + %1$s (carregando) + %1$s reprodução finalizada + Nenhum arquivo de mídia encontrado + Nenhuma conta fornecida + Arquivo não está em uma conta válida + Codec de mídia não suportado + Arquivo de mídia não pode ser lido + Arquivo de mídia não corretamente codificado + Expirou o tempo durante a tentativa + Arquivo de mídia não pode ser transmitido + Arquivo de mídia não pode ser reproduzido com o estoque media player + Erro de segurança tentando reproduzir %1$s + Erro de entrada tentando reproduzir %1$s + Erro inesperado tentando reproduzir %1$s + Botão rebobinar + Botão reproduzir parar + Botão de adiantamento rápido Tentando fazer login... Sem conexão de rede - Nenhuma conexão foi detectada, verifique sua conexão com a Internet e tente novamente. - Conectar mesmo assim Conexão segura indisponível. - O aplicativo não pôde estabelecer uma conexão segura. Uma conexão não-segura está disponível. Você pode continuar ou cancelar. Conexão estabelecida Testando conexão... Configuração do servidor mal formada - Parece que a instância do seu servidor não está corretamente configurada. Contate seu administrador para mais detalhes. + Uma conta para o mesmo usuário e servidor já existe no dispositivo + As informações que o usuário digitou não corresponde ao usuário da conta Ocorreu um erro desconhecido! - Ocorreu um erro desconhecido. Por favor contate o suporte e inclua registros do seu dispositivo. Não pôde encontrar host - Couldn\'t find the entered host. Por favor verifique o nome de host, disponibilidade do servidor e tente novamente. Instância de servidor não encontrada - O aplicativo não pôde encontrar uma instância de servidor no caminho indicado. Por favor, verifique-o e tente novamente. O servidor demorou demais a responder URL mal formada Inicialização SSL falhou - Identidade SSL do servidor não verificada - Versão do servidor ownCloud é irreconhecível + Não foi possível verificar a identidade do servidor SSL + Versão do servidor é irreconhecível Não foi possível estabelecer conexão Conexão segura estabelecida - Detalhes de login - Login / senha inválida - Caminho dado está errado - Erro interno do servidor, código %1$d - O aplicativo terminou inesperadamente. Gostaria de enviar um relatório de erros? - Enviar relatório - Não enviar relatório - Extensões disponíveis! - Parece que sua instância ownCloud está suportando extensões avançadas. Gostaria de ver extensões disponíveis para Android? + Nome de usuário e/ou senha está errada! + Autorização sem sucesso + Acesso negado pelo servidor de autorização + Estado inesperado, por favor, digite a URL do servidor novamente + Sua autorização expirou. Por favor, solicite autorizar novamente + Por favor, digite sua senha + Sua sessão expirou. Por favor, conecte-se novamente + Conectando ao servidor de autenticação ... + O servidor não suporta este método de autenticação + %1$s não suporta múltiplas contas + Não foi possível autenticar neste servidor Manter arquivo atualizado - Compartilhar Renomear Remover Você realmente quer remover %1$s ? @@ -180,17 +177,19 @@ Renomeação não pôde ser completada Arquivo remoto não pode ser verificado Conteúdo do arquivo já foi sincronizado - Diretório não pôde ser criado + A pasta não pode ser criada + Caracteres proibidos: / \\ < > : \" | ? * Aguarde um momento Problema inesperado; por favor, tente selecionar o arquivo com outro app Nenhum arquivo foi selecionado - Aviso + Enviar o link para + Login com oAuth2 + Conectando-se a oAuth2 servidor ... A identidade do site não pode ser verificada - O certificado do servidor não é confiável - O certificado do servidor expirou - O certificado do servidor é muito recente - O URL do host não confere com o host do certificado - O certificado do servidor não pode ser obtido Você confia nesse certificado mesmo assim? O certificado não pode ser salvo Detalhes @@ -208,7 +207,14 @@ Para: Assinatura: Algoritmo: - Isso é um marcador de espaço + O certificado não pode ser mostrado. + - Nenhuma informação sobre o erro + Este é um espaço reservado + espaçoreservado.txt + Imagem PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Fazer upload de fotos somente via WiFi /Upload instantâneo Conflito de atualização @@ -216,4 +222,25 @@ Manter ambos Sobrescrever Não fazer upload + Pré-visualização da imagem + Esta imagem não pode ser mostrada + %1$s não pôde ser copiado para pasta local %2$s + Falha no EnvioInstantaneo + Falhas nos envios imediatos + Sumario de todas as falhas nos envios imediatos + selecionar tudo + tentar novamente todos os selecionados + excluir todos os selecionados da lista de envio + tentar novamente envio da imagem: + Carregar mais Imagens + não fazer nada voce não está conectado para envio instantâneo + Mensagem de Falha: + Por favor verifique a configuração do servidor, talvez a sua cota esteja vencida. + Incapaz de compartilhar esse arquivo ou pasta. Por favor, certifique-se que existe + Ocorreu um erro durante a tentativa de compartilhar esse arquivo ou pasta + Incapaz de descompartilhar este arquivo ou pasta. Ela não existe. + Ocorreu um erro ao tentar descompartilhar este arquivo ou pasta + Enviar + Copiar o link + Copiado para área de transferência diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 8b54214f..48a64a3d 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -1,56 +1,40 @@ - ownCloud - Palavra-passe: - Nome de Utilizador: - Login - Bem-vindo à sua ownCloud - Ficheiros - Musica - Contactos - Calendário - Marcadores - Definições - Configurar conta - Não há contas no seu aparelho. Para usar esta Aplicação, precisa de criar uma. - cliente Android %1$s\n\nversão: %1$s - Actualizar + %1$s Aplicação(ões) Android + versão %1$s + Recarregar a conta Enviar Conteúdo das outras apps Ficheiros - Criar pasta - Procurar + Abrir com + Nova Pasta Definições + Detalhes + Enviar Geral - Rastreamento de dispositivo - Acrescentar nova sessão - Criar miniaturas de imagens - Escolha uma conta - Escolha qual das suas contas deve ser usada pela app. - Rastreamento de dispositivo - Permitir que ownCloud siga a localização do seu aparelho. - A sua ownCloud segue a localização deste aparelho. - Intervalo de actualização - Actualizar a cada %1$s minutos + Mais Contas Gerir contas - ownCloud App PIN - Proteja o seu cliente ownCloud + App PIN + Proteja o seu cliente Activar envio instantâneo Enviar automaticamente as fotografias tiradas pela camera - URL ownCloud + Ativar Rastreio + Isto é usado para registar problemas + Historico do rastreio + Isto mostra os registos guardados + Eliminar Histórico + Ajuda + Recomendar a um amigo + Resposta + Imprint + Experimente %1$s no seu smartphone! + Verificar Servidor + Endereço do servidor https://.. Nome de Utilizador Palavra-passe - Sou novo na %1$s - URL dado errado - Nome de sessão errado + Novo em %1$s? Ficheiros - Nenhum ficheiro seleccionado para envio - Nome de utilizador - Palavra-passe - Endereço web - Mostrar palavra-passe? - Ligar à sua %1$s Ligar Enviar Nenhuma conta encontrada @@ -61,7 +45,6 @@ Não foi recebido nenhum conteúdo. Nada para enviar. O %1$s não está autorizado a aceder aos ficheiro partilhados. A enviar - Criar directoria para envio Não existem ficheiros nesta pasta.\nPode adicionar ficheiros com a opção \"Enviar\" no menu. Clique no ficheiro para visualizar informação adicional. Tamanho: @@ -69,10 +52,10 @@ Criado: Modificado: Descarregar - Actualizar - Actualizar - Abrir + Atualizar ficheiro O nome do ficheiro foi alterado para %1$s durante o envio. + Partilhar o link + Deixar de partilhar a ligação Sim Não OK @@ -80,91 +63,101 @@ Cancelar envio Cancelar Guardar & Sair - Sair de %1$s Erro + A carregar... + Erro Desconhecido Sobre + Alterar palavra-chave Apagar conta Criar conta Carregar de... - Nome da directoria + Nome da pasta A carregar... A enviar %1$d%% , %2$s completo. Carregado com sucesso %1$s foi carregado com sucesso - %1$d foram carregados com sucesso Carregamento falhou O envio do ficheiro %1$s não foi concluído. - O envio falhou. %1$d/%2$d ficheiro foram enviados com sucesso. A descarregar... %1$d%% A decarregar %2$s Descarga com sucesso %1$s foi descarregado com sucesso Descarga falhou O descarregamento %1$s não foi possível descarregar + Não transferido Escolha a conta - Contactos Falhou a sincronização Não foi possível sincronizar %1$s + Password inválida para %1$s Foram encontrados conflitos Não foi possível sincronizar o ficheiro %1$d Falhou a operação de manter os ficheiros sincronizados Não foi possível sincronizar o conteúdo de %1$d ficheiros (%2$d conflictos) - Usar Ligação Segura - Nao é possível detectar a sua localização. Por favor verifique se os serviços de localização/GPS estão activados. + Alguns ficheiros locais ficaram esquecidos + A pasta %1$s já não existe + Mover Todos + Todos os ficheiros foram movidos + Não foi possível mover alguns ficheiros + Local: %1$s + Remoto: %1$s + Não existe espaço suficiente para copiar os ficheiros seleccionados para a pasta %1$s . Deseja move-los? Por favor escreva o PIN da Aplicação - Por favor escreva o novo PIN da Aplicação - Escreva o PIN da Aplicação ownCloud + Escreva o PIN da Aplicação O PIN vai ser pedido todas as vezes que iniciar a aplicação. - Volte a inserir o ownCloud App PIN, por favor - Remover o PIN do ownCloud. + Volte a inserir o App PIN, por favor + Remover o PIN do aplicação. Os códigos PIN introduzidos não são iguais. Código PIN Incorrecto. - PIN da aplicação ownCloud removido - PIN da aplicação ownCloud guardado - - 15 Minutos - 30 Minutos - 60 Minutos - - - 15 - 30 - 60 - + PIN da aplicação removido + PIN da aplicação guardado + %1$s leitor de música + A tocar: %1$s + %1$s (A carregar) + %1$s leitura terminada + Não foi encontrado nenhum ficheiro de média + Não foi fornecida conta + O ficheiro não está numa conta válida + Codec de média não suportado + Não foi possível reproduzir o ficheiro + Ficheiro erradamente codificado (codec) + O tempo de espera para jogar expirou + O ficheiro não pode ser reproduzido (streaming) + O ficheiro não pode ser reproduzido com o leitor de média de origem + Erro de segurança a tentar reproduzir o ficheiro %1$s + Erro de input a tentar reproduzir %1$s + Erro inesperado a tentar reproduzir %1$s + Botão de rebobinar + Botão Tocar/Pausa + Botão de avanço rápido A tentar entrar... Sem ligação à rede - Não foi detectada uma ligação de dados, por favor verifique se está se encontra activada. - Ligar de qualquer maneira Ligação segura indisponível - Não foi possível estabelecer uma ligação segura ao servidor, no entanto pode continuar ou cancelar. Ligação estabelecida A testar a ligação... - Configuração do ownCloud incorrecta. - Parece haver um ou mais erros na configuração do ownCloud, por favor contacte o administrador para mais detalhes. + Configuração do servidor incorrecta. + Uma conta para este utilizador e servidor já existe no dispositivo + O utilizador que escreveu não coincide com o nome de utilizador desta conta Ocorreu um erro desconhecido! - Ocorreu um erro desconhecido, por favor contacte os autores e inclua os logs de erro. Não é possível encontrar o servidor - Não foi possível encontrar o host. Por favor verifique se o nome está correcto, e se o servidor está disponível e tente novamente. - Instância ownCloud não encontrada - Não foi possível encontrar o ownCloud no caminho dado. Por favor verifique novamente. + Instância servidor não encontrada O servidor levou demasiado tempo a responder URL errado Inicialização de SSL falhou - Identidade SSL do servidor não verificada. - Versão do servidor ownCloud não reconhecida + Não foi possível verificar a identidade SSL do servidor + Versão do servidor não reconhecida Não consegue estabelecer ligação Ligação segura estabelecida - Detalhes de entrada - Palavra-passe ou nome de utilizador inválidos! - Foi dado um caminho inválido - Erro interno de servidor, código: %1$d - A aplicação terminou de forma inesperada. Deseja enviar o relatório do erro? - Enviar relatório - Não enviar relatório - Extensões disponíveis! - Parece que a sua instalação do ownCloud suporta extensões avançadas. Deseja ver as extensões disponíveis para Android? + Nome de utilizador/password inválida + autorização mal sucedida + Acesso negado pelo servidor + Estado inesperado, por favor, digite a URL do servidor novamente + O prazo da sua autorização expirou. Por favor renove-a + Por favor, introduza a password actual + A sua sessão expirou. Por favor autentique-se de novo + A verificar a sua autenticação no servidor... + O servidor não suporta este método de autenticação + %1$s não suporta contas múltiplas manter ficheiro actualizado - Partilhar Renomear Remover Tem a certeza que deseja remover %1$s ? @@ -180,17 +173,18 @@ Não foi possível renomear Não foi possível verificar o ficheiro remoto O conteúdo do ficheiro já foi sincronizado - Não foi possível criar a pasta + Caracteres não permitidos: / \\ < > : \" | ? * Aguarde um momento Erro inesperado. Por favor tente outra aplicação para seleccionar o ficheiro. Não selecionou nenhum ficheiro - Aviso + Enviar a ligação para ... + Autenticar-se com oAuth2 + A ligar ao servidor oAuth2 Não foi possível verificar a identidade do site. - O certificado do servidor não é de confiança - O certificado do servidor expirou - O certificado do servidor é muito recente O URL não condiz com o nome no certificado. - - Não foi possível obter o certificado do servidor Quer confiar neste certificado de qualquer maneira? O certificado não pôde ser guardado Detalhes @@ -208,7 +202,12 @@ Para: Assinatura: Algoritmo - Isto é uma variável. + Isto é uma variável. + placeholder.txt + Imagem PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Enviar fotografias apenas via WiFi /Upload-Instantâneo Conflito na actualização @@ -216,4 +215,24 @@ Manter os dois Sobrepor Não enviar. + Pré-Visualização da imagem + Esta imagem não pode ser mostrada + O envio rápido falhou + Falharam os Uploads-Instantâneos + Sumário dos Uploads-Instantâneos falhados + Seleccionar Todos + Tentar todos os seleccionados + Eliminar todos os seleccionados da lista de envio + Tentar envio da imagem: + Carregar mais imagens + não fazer nada, nao está online para o Upload-Instantâneo + Mensagem de erro: + Por favor verifique a configuração do servidor, talvez tenha excedido a sua quota + Não é possível partilhar este ficheiro ou pasta. Por favor, verifique se existe + Ocorreu um erro enquanto tentava partilhar este ficheiro ou pasta + Não é possível retirar a partilha deste ficheiro ou pasta. Não existe. + Ocorreu um erro enquanto retirava a partilha deste ficheiro ou pasta + Enviar + Copiar ligação + Copiado para a área de transferência diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index bef6249b..ae1835cd 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -1,133 +1,75 @@ - ownCloud - Parolă: - Nume utilizator: - Autentificare - Bun venit în propriul tău ownCloud - Fișiere - Muzică - Contacte - Calendar - Semne de carte - Setări - Configurează cont - Nu există conturi ownCloud pe dispozitivul tău. Pentru a folosi această aplicație, trebuie să creezi un cont. - Împrospătare Încarcă Conținut de la alte aplicații Fișiere - Creare director - Căutare Setări + Expediază General - Urmărire dispozitiv - Adaugă sesiune nouă - Selectează un cont - Alege contul pe care ar trebui să-l folosească aplicația. - Urmărire dispozitiv - Permite ownCloud să urmărească locația dispozitivului tău - ownCloud urmărește acest dispozitiv - Interval actualizare - Actualizare la fiecare %1$s minute + Mai mult Conturi Administrare conturi Activează încărcarea instant - URL ownCloud + Ajutor Nume utilizator Parolă - Sunt novice în ownCloud - URL specificat greșit - Numele sesiunii este greșit Fișiere - Nici un fișier selectat pentru încărcare - Nume utilizator - Parolă - Adresă web - Afișare parolă? - Conectare la ownCloud Conectare Încărcare Nici un cont găsit - Nu există conturi ownCloud pe dispozitivul tău. Te rugăm să configurezi un cont mai întâi. + Nu există conturi %1$s pe dispozitivul tău. Te rugăm să configurezi un cont mai întâi. Configurare Anulare Încărcare - Creare director pentru încărcare Mărime: Tip: Creat: Modificat: Descarcă - Împrospătare - Descarcă din nou - Deschide Da Nu OK Anulează încărcarea Anulare Salvare & Ieșire - Părăsire ownCloud Eroare + Eroare necunoscută Despre + Schimbă parola Șterge cont Crează cont Încarcă din... - Nume director + Denumire director În curs de încărcare... %1$d%% Se încarcă %2$s Încărcat cu succes %1$s a fost încărcat cu succes - %1$d fișiere au fost încărcate cu succes Încărcarea a eșuat Încărcarea fișierului %1$s nu a putut fi încheiată - Încărcarea a eșuat: %1$d/%2$d fișiere au fost încărcate Se descarcă... %1$d%% Se descarcă %2$s Descărcarea a reușit %1$s a fost descărcat cu succes Descărcarea a eșuat Alege cont - Contacte Sincronizarea a eșuat - Folosește conexiune securizată - ownCloud nu poate urmări dispozitivul tău. Te rugăm să verifici setările pentru locație. Te rugăm să specifici PIN-ul aplicației - Te rugăm să specifici noul PIN pentru aplicație - Elimină PIN-ul aplicației ownCloud - Cele două PIN-uri ale aplicației ownCloud nu sunt similare - PIN-ul aplicației ownCloud este incorect - PIN-ul aplicației ownCloud a fost eliminat - PIN-ul aplicației ownCloud a fost memorat - - 15 minute - 30 minute - 60 minute - - - 15 - 30 - 60 - + Elimină PIN-ul aplicației + Cele două PIN-uri ale aplicației nu sunt similare + PIN-ul aplicației este incorect + PIN-ul aplicației a fost eliminat + PIN-ul aplicației a fost memorat În curs de autentificare... Conexiune securizată indisponibilă Conexiune stabilită Se testează conexiunea... - Configurație ownCloud incorectă - Se pare că instanța ta ownCloud nu este configurată corect. Contactează administratorul pentru mai multe detalii. + Configurație serverului incorectă A apărut o eroare necunoscută! - Instanța ownCloud nu a fost găsită + Instanța serverului nu a fost găsită Inițializarea SSL a eșuat Nu s-a putut stabili conexiunea Conexiune sigură stabilită - Detalii autentificare - Aplicația s-a terminat într-un mod neașteptat. Dorești să trimiți un raport despre asta? - Trimite raport - Nu trimite raport - Extensii disponibile! Păstrează fișierul actualizat - Partajează Redenumește Elimină Sigur dorești să elimini %1$s ? @@ -135,8 +77,7 @@ Elimină de pe server Eliminat cu succes Eliminarea nu a reușit - Directorul nu a putut fi creat Așteaptă un moment Nu a fost selectat nici un fișier - Atenție + Expediază diff --git a/res/values-ru-rRU/strings.xml b/res/values-ru-rRU/strings.xml index 937b45d3..6675918b 100644 --- a/res/values-ru-rRU/strings.xml +++ b/res/values-ru-rRU/strings.xml @@ -1,67 +1,35 @@ - ownCloud - Пароль: - Имя пользователя: - Вход - Добро пожаловать в Ваш ownCloud - Файлы - Музыка - Контакты - Каледарь - Закладки - Настройки - Настройка аккаунта - На Вашем устройстве нет ownCloud-аккаунта. Для того, чтобы использовать это приложение, Вам нужно создать его. - ownCloud Android-клиент\n\nверсия: %1$s Обновить Загрузка Содержимое из других приложений Файлы Создать директорию - Поиск Настройки + Сведения Общие - Отслеживание устройств - Добавить новую сессию - Создание миниатюр - Выбрать аккаунт - Выберите, какой из Ваших аккаунтов должен использоваться приложением. - Отслеживание устройств - Включить ownCloud для отслеживания местоположения устройства - Ваш ownCloud отслеживает данное устройство - Обновить интервал - Обновлять каждые %1$s минут + Подробнее Учетные записи Управление учетными записями - ownCloud App PIN - Защитить Ваш ownCloud-клиент + App PIN + Защитить Ваш клиент Включить немедленную загрузку Мгновенно загрузить фотографии, сделанные камерой - ownCloud URL + Помощь Имя пользователя Пароль - Я новичок в ownCloud - Дан неверный URL - Неверное имя сессии + Я новичок в %1$s Файлы - Не выбран файл для загрузки - Имя пользователя - Пароль - Веб-адрес - Показать пароль? - Подключение к Вашему ownCloud Подключить Загрузка Не найден аккаунт - На Вашем устройстве нет ownCloud-аккаунтов. Пожалуйста, настройте учетную запись. + На Вашем устройстве нет %1$s-аккаунтов. Пожалуйста, настройте учетную запись. Установка Выход Нет содержимого для загрузки Содержимое не было получено. Нечего загружать. - ownCloud не разрешен доступ к общему содержимому + %1$s не разрешен доступ к общему содержимому Загрузка - Создайте каталог для загрузки В этой папке нет файлов.\nНовые файлы могут быть добавлены с помощью опции \"Загрузка\" в меню. Нажмите на файл, чтобы увидеть дополнительную информацию. Размер: @@ -70,8 +38,6 @@ Модифицировано: Загрузка Обновить - Загрузить еще раз - Открыть Файл был переименован в %1$s во время загрузки Да Нет @@ -80,7 +46,6 @@ Отмена загрузки Отмена Сохранить & Выход - Покинуть ownCloud Ошибка О программе Удалить аккаунт @@ -91,10 +56,8 @@ %1$d%% Загрузка %2$s Загрузка выполнена успешно %1$s был успешно загружен - %1$d файлы были успешно загружены Загрузка не удалась Загрузка %1$s не может быть завершена - Загрузка не удалась: %1$d/%2$d файлы были загружены Загрузка … %1$d%% Загрузка %2$s Загрузка выполнена успешно @@ -102,69 +65,38 @@ Загрузка не удалась Загрузка %1$s не может быть завершена Выбрать аккаунт - Контакты Ошибка синхронизации Синхронизация %1$s не может быть завершена Найдены конфликты %1$d файлы, подлежащие синхронизации, не могут быть синхронизированы Синхронизация файлов не удалась Содержимое %1$d файлов не может быть синхронизировано (%2$d конфликты) - Использовать защищенное соединение - ownCloud не может отследить Ваше устройство. Пожалуйста, проверьте настройки местоположения Пожалуйста, введите Ваш PIN-код приложения - Пожалуйста, введите Ваш новый PIN-код приложения - Введите PIN-код приложения ownCloud + Введите PIN-код приложения PIN будет запрашиваться каждый раз при запуске приложения - Повторно введите PIN-код ownCloud-приложения, пожалуйста - Удалите PIN-код ownCloud-приложения - PIN-коды ownCloud-приложения не идентичны - Неверный PIN-код ownCloud-приложения - PIN-код ownCloud-приложения удален - PIN-код ownCloud-приложения сохранен - - 15 минут - 30 минут - 60 минут - - - 15 - 30 - 60 - + Повторно введите PIN-код приложения, пожалуйста + Удалите PIN-код приложения + PIN-коды приложения не идентичны + Неверный PIN-код приложения + PIN-код приложения удален + PIN-код приложения сохранен Попытка входа ... Нет подключения к сети - Подключения к сети не были обнаружены, проверьте подключение к Интернету и повторите попытку. - Подключить все равно Защищенное соединение недоступно. - Приложению не удалось установить безопасное подключение к серверу. Но небезопасное соединение доступно. Вы можете продолжить или отменить. Соединение установлено Тестирование соединения… - Некорректная ownCloud-конфигурация - Похоже, что Ваш ownCloud неправильно настроен. Обратитесь к администратору для получения дополнительной информации. + Некорректная сервер-конфигурация Произошла неизвестная ошибка! - Произошла неизвестная ошибка. Пожалуйста, свяжитесь с разработчиками и отправьте журнал с устройства. Не удалось найти хост - Не удалось найти введенный хост. Пожалуйста, проверьте имя хоста и доступность сервера и попробуйте снова - ownCloud не найден - Приложение не может найти экземпляр ownClound по предложенному пути. Пожалуйста, проверьте ​​путь и попробуйте еще раз. + сервер не найден Ответ сервера занимает слишком много времени Адрес URL неверен SSL-инициализация не удалась - Непроверенная SSL идентификация сервера - Неизвестная версия сервера ownCloud + Неизвестная версия сервера сервер Не удалось установить соединение Защищенное соединение установлено - Учетные данные Неверный логин / пароль - Указан неверный путь - Внутренняя ошибка сервера с кодом %1$d - Приложение неожиданно завершило работу. Хотели бы вы отправить отчет о неполадках? - Отправить отчет - Не отправлять отчет - Расширения доступны! - Похоже, ваш экземпляр ownCloud поддерживает дополнительные расширения. Хотели бы вы видеть расширения, доступные для Android? Обновлять файл - Сделать общим Переименовать Удалить Вы действительно хотите удалить %1$s? @@ -184,13 +116,11 @@ Подождите Неожиданная проблема; пожалуйста, попробуйте другое приложение для выбора файла Файл не был выбран - Предупреждение Идентификация сайта не может быть проверена - Сертификат сервера недостоверен - Сертификат сервера истек - Сертификат сервера еще не действителен - URL не соответствует имени хоста в сертификате - Сертификат сервера не может быть получен Хотите объявить этот сертификат надежным в любом случае? Этот сертификат не может быть сохранен Сведения @@ -208,7 +138,6 @@ Для: Подпись: Алгоритм: - Это заполнитель Загрузать изображения только посредством WiFi /НемедленнаяЗагрузка Конфликт обновления diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 8d58f738..8fca5ff8 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -1,219 +1,241 @@ - ownCloud - Пароль: - Пользователь: - Вход - Добро пожаловать в ваш ownCloud - Файлы - Музыка - Контакты - Календарь - Закладки - Настройки - Настроить аккаунт - На вашем устройстве нет ownCloud аккаунтов. Вам нужно создать аккаунт, чтобы пользоваться этим приложеним. - ownCloud клиент для Android\n\nверсия: %1$s - Обновить - Загрузка - Содержимое от других приложений + %1$s Приложение для Андроида + Версия %1$s + Обновить учетную запись + Загрузить + Содержимое из других приложений Файлы - Создать директорию - Найти + Открыть с помощью + Новый каталог Настройки - Главные - Отслеживание устройства - Добавить новую сессию - Создать картинки для предпросмотра - Выбрать аккаунт - Выберите, какой из ваших аккаунтов использовать приложению - Отслеживание устройства - Разрешить ownCloud отслеживать местонахождение вашего устройства - Ваш ownCloud продолжает отслеживать это устройство - Обновить интервал - Обновлять каждые %1$s минут - Аккаунты - Управление аккаунтами - ownCloud App PIN - Защитить ваш клиент ownCloud - Включить моментальную загрузку - Моментально загружать фотографии, полученные с камеры - Ссылка на ownCloud - Пользователь + Подробно + Отправить + Основные + Больше + Учётные записи + Управление учётными записями + App PIN + Защитить ваш клиент + Включить режим немедленной загрузки + Немедленно загружать фотографии, полученные с камеры + Включить журналирование + Используется для регистрации ошибок + Журнал + Здесь показаны записи в журнал + Удалить историю записей + Помощь + Рекомендовать другу + Обратная связь + Штамп + Попробуйте %1$s на вашем смартфоне! + Проверить сервер + Адрес сервера https://... + Имя пользователя Пароль - Я новичёк в ownCloud - Дана неверная ссылка - Неверное имя сессии + Незнакомы с %1$s? Файлы - Файлы для загрузки не выбраны - Пользователь - Пароль - Веб адрес - Показать пароль? - Соединение с вашим ownCloud Соединить - Загрузка - Аккаунты не найдены - На вашем устройстве нет аккаунтов ownCloud. Настройте сначала аккаунт. + Загрузить + Учётная запись не найдена + На вашем устройстве нет учётных записей %1$s. Сначала нужно настроить учётную запись. Установка Выход Нет содержимого для загрузки - Контент не получен. Нечего загружать. - ownCloud не имеет доступа к опубликованным данным + Содержимое не получено. Нечего загружать. + %1$s не имеет доступа к опубликованным данным Загрузка - Создайте директорию для загрузки - В этой папке нет файлов.\nНовые файлы могут быть добавлены с помощью пункта меню \"Загрузить\". + В этом каталоге нет файлов.\nНовые файлы могут быть добавлены с помощью пункта меню \"Загрузить\". Нажмите на файл для отображения дополнительной информации. Размер: Тип: Создан: Изменён: Скачать - Обновить - Скачать заново - Открыть + Обновить файл Файл был переименован в %1$s во время загрузки + Поделиться ссылкой + Удалить ссылку Да Нет ОК Отменить скачивание - Отмена загрузки + Отменить загрузку Отмена Сохранить & Выйти - Выйти из ownCloud Ошибка + Идёт загрузка... + Неизвестная ошибка О программе - Удалить аккаунт - Создать аккаунт + Сменить пароль + Удалить учётную запись + Создать учётную запись Загрузить из... - Имя директории + Имя папки Загрузка... %1$d%% загрузки %2$s - Загрузка прошла успешно + Загрузка завершена %1$s был успешно загружен - %1$d файлов было успешно загружено Ошибка загрузки Загрузка %1$s не может быть завершена - Загрузка неудалась: %1$d/%2$d файла было загружено Скачивание... %1$d%% скачивания %2$s - Скачивание прошло успешно + Скачивание завершено %1$s успешно скачан - Скачивание не удалась + Скачивание не удалось Скачивание %1$s не может быть завершено - Выберите аккаунт - Контакты + Ещё не скачано + Выберите учётную запись Синхронизация прошла неудачно Синхронизация %1$s не может быть завершена + Неверный пароль для %1$s Обнаружены конфликты - %1$d файлы, которые должны быть синхронизированными не могут синхронизироваться + %1$d файлы не могут быть синхронизированы Не удалось синхронизировать файлы - Содержание %1$d файла(ов) не может быть синхронизировано (%2$d конфликта(ов)) - Использовать защищённое соединение - ownCloud не может отслеживать ваше устройство. Проверьте настройки вашего местоположения + Содержимое %1$d файлов не может быть синхронизировано (конфликтов: %2$d) + Несколько локальных файлов были забыты + Каталог %1$s больше не существует + Переместить всё + Все файлы были перемещены + Некоторые файлы не могут быть перемещены + Локально: %1$s + Удаленно: %1$s + Недостаточно места для копирования выделенных файлов в каталог %1$s. Переместить их в другое место? Вставьте PIN вашего приложения - Введите новый ownCloud App PIN - Введите ownCloud App PIN - ПИН-код будет запрашиваться каждый раз, когда вы запускаете приложение. - Повторите ownCloud App PIN - Удалить ownCloud App PIN - Два ownCloud App PIN не совпадают - Неверный ownCloud App PIN - ownCloud App PIN удалён - ownCloud App PIN сохранён - - 15 Минут - 30 Минут - 60 Минут - - - 15 - 30 - 60 - + Введите App PIN + PIN-код будет запрашиваться при каждом запуске приложения. + Повторите ввод App PIN + Удалить App PIN + Введённые App PIN не совпадают + Неверный App PIN + App PIN удалён + App PIN сохранён + %1$s аудиоплеер + %1$s (проигрывается) + %1$s (загружается) + %1$s воспроизведение завершено + Медиафайлов не найдено + Учётная запись не настроена + Файл в неверной учётной записи + Неподдерживаемый кодек + Медиафайл не может быть прочитан + Медиафайл некорректно закодирован + Время попыток воспроизведения вышло + Невозможно организовать потоковую передачу медиафайла + Медиафайл не может быть проигран стандартным плеером + Ошибка безопасности при воспроизведении %1$s + Ошибка ввода при воспроизведении %1$s + Неожиданная ошибка при воспроизведении %1$s + Перемотка назад + Воспроизведение или пауза + Перемотка вперед Попытка входа... Нет сетевого соединения - Сетевое соединение не обнаружено , проверьте соединение с Интернет и попробуйте ещё раз. - Соединить всё равно Защищённое соединение недоступно. - Приложение не может установить защищённое соединение с сервером. Однако доступно не защищённое соединение. Вы можете продолжить или прервать. Соединение установлено Тестирование соединения... - Неверная конфигурация ownCloud - Похоже что ваш экземпляр ownCloud не был корректно сконфигурирован. Обратитесь за деталями к вашему администратору. + Конфигурация сервера задана неверно + Учётная запись такого пользователя и сервера уже существует на устройстве + Введённый пользователь не соответствует этой учётной записи Произошла неизвестная ошибка! - Произошла неизвестная ошибка. Свяжитесь с авторами и приложите журнал с вашего устройства. Невозможно найти сервер - Невозможно найти введённый сервер. Проверьте имя сервера и его доступность, затем попробуйте ещё раз. - Экземпляр ownCloud не найден - Приложение не может обнаружить ownClound по указанному пути. Проверьте путь и попробуйте ещё раз. + Сервер не найден Сервер слишком долго не отвечает Неверный URL Ошибка инициализации SSL - Непроверенный идентификатор SSL сервера - Неизвестная версия сервера ownCloud + Невозможно проверить SSL-сертификат сервера + Неизвестная версия сервера Невозможно установить соединение Защищённое соединение установлено - Детали входа - Неправильный логин / пароль - Указан неверный путь - Внутренняя ошибка сервера, код %1$d - Приложение завершилось неожиданно. Хотите отправить аварийный отчёт? - Отправить отчёт - Не отправлять отчёт - Доступны расширения! - Похоже, ваш экземпляр ownCloud поддерживает дополнительные расширения. Хотите посмотреть доступные для андроида обновления? + Неверное имя пользователя или пароль + Ошибка авторизации + Сервер авторизации отказал в доступе + Неожиданный ответ; введите адрес сервера ещё раз + Время авторизации истекло. Пожалуйста, авторизуйтесь снова + Пожалуйста, введите пароль + Время сессии истекло. Пожалуйста, подключитесь снова + Подключение к серверу аутентификации... + Сервер не поддерживает выбранный метод аутентификации + %1$s не поддерживает сразу несколько учётных записей + Не могу авторизоваться на этом сервере Обновлять файл - Открыть доступ Переименовать Удалить - Вы в самом деле хотите удалить %1$s ? - Вы действительно хотите удалить %1$s и его содержимое ? + Вы действительно хотите удалить %1$s ? + Вы действительно хотите удалить %1$s и его содержимое? Только локально Только локальные данные Удалить с сервера - И удалённо и локально - Успешное удаление - Удаление не может быть завершено + Удалённо и локально + Удаление завершено + Ошибка удаления Введите новое имя Локальная копия не может быть переименована; попробуйте другое имя Переименование не может быть завершено Удаленный файл не может быть проверен Содержимое файла уже синхронизировано - Директория не может быть создана + Недопустимые символы: / \\ < > : \" | ? * Подождите немного - Неизвестная ошибка; попробуйте другое приложение для выбора файла + Неизвестная ошибка; выберите этот файл из другого приложения Файлы не выбраны - Предупреждение + Отправить ссылку... + Войти через oAuth2 + Подключение к серверу oAuth2... Подлинность сайта не может быть проверена - Сертификат сервера не является доверенным - Срок действия сертификата сервера истёк - - Сертификат сервера слишком новый - - Адрес не совпадает с именем в сертификате - Сертификат сервера не доступен - Все-равно доверять данному сертификату? + - Срок действия сертификата сервера ещё не начался + - URL не совпадает с именем сервера в сертификате + Вы хотите доверять данному сертификату в любом случае? Сертификат не может быть сохранён - Детали - Спрятать - Выдано для: - Выдан: + Подробно + Скрыть + Кому выдано: + Кем выдано: Имя: Организация: Организационное подразделение: Страна: - Статус: + Штат: Местонахождение: Срок действия: - От: - До: + Из: + В: Подпись: Алгоритм: - Это заполнитель - Загружать изображения только через WiFi + Сертификат не может быть показан. + Информации об ошибке нет + Это заполнитель + placeholder.txt + Изображение PNG + 389 КБ + 2012/05/18 12:23 PM + 12:23:45 + Загружать изображения только через Wi-Fi /InstantUpload Конфликт обновления - Удаленный файл %s не синхронизирован с локальным. Завершение приведет к замене содержания файла на сервере. - Оставить оба + Удаленный файл %s не синхронизирован с локальным. Продолжение приведет к замене содержимого файла на сервере. + Сохранить оба Заменить Не загружать + Предпросмотр + Это изображение не может быть показано + Сбой немедленной загрузки + Сбой немедленной загрузки + Сводка по всем сбойным немедленным загрузкам + Выбрать всё + Ещё раз попробовать всё выделенное + Удалить выбранное из очереди загрузки + попробовать ещё раз загрузить изображение: + Загрузить больше картинок + Ничего не делать, если нет подключения к сети + Сообщение об ошибке: + Проверьте настройки сервера, возможно ваш лимит исчерпан + Невозможно предоставить доступ к этому файлу или каталогу. Убедитесь, что он существует + Ошибка предоставления общего доступа к этому файлу или каталогу + Невозможно снять общий доступ с этого файла или каталога. Он не существует. + Ошибка удаления общего доступа к этому файлу или каталогу + Отправить + Копировать ссылку + Скопировано в буфер обмена diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml index ad7863bd..6f06871e 100644 --- a/res/values-si-rLK/strings.xml +++ b/res/values-si-rLK/strings.xml @@ -1,123 +1,65 @@ - ownCloud - මුරපදය - පරිශීලක නාමය - ප්‍රවිශ්ටය - OwnCloud වෙත ඔබව සාදරයෙන් පිළිගනිමු - ගොනු - සංගීතය - සබඳතා - දිනදසුන - පිටු සළකුනු - සිටුවම් - පරිශීලක ගිණුම ස්ථාපිත කරන්න - owncloud ගිණුම් මෙම යන්ත්‍රයේ නොමැත. මෙම යෙදුම භාවිතයට ගිණුමක් තිබිය යුතුය - නැවුම් කරන්න උඩුගත කිරීම ගොනු - ඩිරෙක්ටරිය සාදන්න - සොයන්න සිටුවම් සාමාන්‍යයෙන් - මෙවලම සෙවීම - නව සැසියක් එකතු කරන්න - සිඟිති රූ සාදන්න - ගිණුමක් තෝරන්න - යෙදුම ඔබගේ ගිණුම් අතුරින් කිනම් ගිණුම භාවිතා කළ යුතුදැයි තෝරන්න - මෙවලම සෙවීම - ඔබගේ යන්ත්‍රය ඇති ස්ථානය සොයාගැනීමට අවසර ownCloudà¶§ ලබාදෙන්න - ඔබගේ ownCloud ඔබගේ යන්ත්‍රය ඇති ස්ථානය හඹායයි - යාවත්කාලීන කරන කාල ප්‍රාන්තරය - සෑම මිනිත්තු %1$s වරක් යාවත්කාලීන කරන්න + වැඩි ගිණුම් ගිණුම් කළමනාකරනය - ඔබේ ownCloud සේවාලාභියා සුරකින්න + ඔබේ සේවාලාභියා සුරකින්න ක්‍ෂණික උඩුගත කිරීම් සක්‍රිය කරන්න කැමරාවෙන් ගත් රූප ක්‍ෂණිකව උඩුගත කරන්න - ownCloud යොමුව + උදව් පරිශීලක නම මුර පදය - මම ownCloudà¶§ නවකයෙක්මි - දී ඇති යෙදුම වැරදි - වැරදි සැසිවාර නමක් ගොනු - උඩුගත කිරීමට ගොනුවක් තෝරා නැත - පරිශීලක නම - මුර පදය - අන්තර්ජාල ලිපිනය - මුරපදය පෙන්වන්නද? - ඔබගේ ownCloud හා සම්බන්ධ වන්න සම්බන්ධ වන්න උඩුගත කිරීම ගිණුමක් හමු නොවුණි - owncloud ගිණුම් මෙම යන්ත්‍රයේ නොමැත. කරුණාකර ගිණුමක් සාදන්න + %1$s ගිණුම් මෙම යන්ත්‍රයේ නොමැත. කරුණාකර ගිණුමක් සාදන්න ස්ථාපනය නික්මෙන්න උඩුගතවේ - උඩුගත කිරීම් සඳහා ඩිරෙක්ටරිය තනන්න වැඩි විස්තර සඳහා ගොනුවක් ස්පර්ෂ කරන්න විශාලත්වය: ගණය: සෑදු දිනය: වෙනස් කළ දිනය: භාගත කරන්න - නැවුම් කරන්න - නැවුම් කරන්න - විවෘත කරන්න ඔව් එපා හරි උඩුගත කිරීම අත් හරින්න එපා සුරැක & පිටවන්න - ownCloud අත්හැර යන්න දෝශය පිළිබඳව + මුරපදය වෙනස් කිරීම ගිණුම මකන්න ගිණුම සාදන්න - ඩිරෙක්ටරි නම + ෆොල්ඩරයේ නම උඩුගත කිරීම සාර්ථකයි - %1$d ගොනු සාර්ථකව උඩුගත කෙරුණි උඩුගත කිරීම අසාර්ථකයි - උඩුගත කිරීම අසාර්ථකයි: %1$d/%2$d ගොනු උඩගත විය බාගතවේ... බාගත කිරීම සාර්ථකයි බාගත කිරීම අසාර්ථකයි ගිණුම තෝරන්න - සබඳතා - ආරක්ෂිත සම්බන්ධතාවක් භාවිතා කරන්න - - විනාඩි 15 - විනාඩි 30 - විනාඩි 60 - - - 15 - 30 - 60 - පිවිසීමට උත්සහ කරනවා... ජාල සම්බන්ධතාවක් නොමැත - ජාල සම්බන්ධතාවක් සොයා ගත නොහැක. ඔබගේ අන්තර්ජාල සම්බන්ධතාව පරීක්ෂාකර නැවත උත්සහ කරන්න - කොහොමත් සම්බන්ධවන්න ආරක්ෂිත සම්බන්ධතාවක් නොලැබුණි සම්බන්ධතාවක් සාදන ලදී සම්බන්ධතාව පරීක්ෂා කෙරේ - විකෘතිවු OwnCloud හැඩගැසුමක් + විකෘතිවු හැඩගැසුමක් නොදන්නා දෝෂයක් ඇතිවිය සේවාදායකයා සොයාගත නොහැක සේවාදායකයා පිළිතුරු දීමට දිගු කාලයක් ගත්තේය විකෘතිවු යොමුවක් SSL ආරම්භ කිරීම අසාර්ථකයි - හඳුනාගත නොහැකි ownCloud අනුවාදයක් + හඳුනාගත නොහැකි අනුවාදයක් සම්බන්ධයක් ඇතිකර ගැනීමට නොහැකි විය රක්‍ෂිත සම්බන්ධතාවක් සාදන ලදී - පිවිසුම් සඳහා විස්තර - වාර්තාව යවන්න - වාර්තාව යවන්න එපා ගොනුව යාවත්කාලීනව තබාගන්න - බෙදා හදාගන්න නැවත නම් කරන්න ඉවත් කරන්න %1$s සැබැවින්ම ඉවත්කිරීමට අවශ්‍යද? @@ -127,10 +69,8 @@ සාර්ථක ඉවත්කිරීමක් ඉවත් කිරීම සම්පූර්ණ කළ නොහැක නැවත නම් කිරීම සම්පුර්ණ කළ නොහැකි විය - ඩිරෙක්ටරිය සෑදීමට නොහැකි විය ස්වල්ප මොහොතක් සිටින්න නොසිතු ප්‍රශ්ණයක්; කරුණාකර වෙනත් යෙදුමක් භාවිතා කර ගොනුව තෝරන්න ගොනුවක් තෝරා නැත - අනතුරු ඇඟවිම සඟවන්න diff --git a/res/values-sk-rSK/strings.xml b/res/values-sk-rSK/strings.xml index 1ccdd0b9..6a76220b 100644 --- a/res/values-sk-rSK/strings.xml +++ b/res/values-sk-rSK/strings.xml @@ -1,67 +1,50 @@ - ownCloud - Heslo: - Meno: - Prihlásenie - Vitajte vo svojom ownCloud - Súbory - Hudba - Kontakty - Kalendár - Záložky - Nastavenia - Nastavenie účtu - Vo vaÅ¡om zariadení nie sú žiadne účty. Vytvorte si účet ak chcete používaÅ¥ túto aplikáciu. - %1$s Android App\n\nverzia: %2$s - ObnoviÅ¥ + %1$s Android App + verzia %1$s + ObnoviÅ¥ účet Nahraj súbor Obsah z inej aplikácie Súbory - Vytvor priečinok - Hľadaj + OtvoriÅ¥ v + Nový priečinok Nastavenia + Podrobnosti + OdoslaÅ¥ VÅ¡eobecné - Sledovanie zariadenia - VytvoriÅ¥ nové sedenie - Vytvor náhľad obrázku - Vyber účet - Vyberte účet pre túto aplikáciu. - Sledovanie zariadenia - PovoliÅ¥ sledovanie polohy zariadenia - Váš ownCloud sleduje vaÅ¡u polohu na tomto zariadení - Interval aktualizácie - Aktualizácia každých %1$s minút + Viac Účty Správa účtov - PIN aplikácie ownCloud - ChrániÅ¥ klienta ownCloud + PIN aplikácie + ChrániÅ¥ klienta aplikácie Zapnúť okamžité odosielanie Okamžite odosielaÅ¥ fotky z fotoaparátu - ownCloud URL + PovoliÅ¥ logovanie + Toto je použité pre logovanie problémov + História logovania + Toto zobrazuje zaznamenané logy + ZmazaÅ¥ históriu + Pomoc + DoporučiÅ¥ známemu + Spätná väzba + Podmienky používania + Skúste %1$s na vaÅ¡om telefóne! + SkontrolovaÅ¥ Server + Adresa servera https://... Používateľské meno Heslo - Som nový v %1$s - Zadaná nesprávna URL - Nesprávny názov sedenia + Ste nový v %1$s? Súbory - Nebol označený žiadny súbor pre odoslanie - Meno používateľa - Heslo - Web adresa - ZobraziÅ¥ heslo? - PripojiÅ¥ k ownCloud Pripoj NahraÅ¥ Účet sa nenaÅ¡iel - Na tomto zariadení nie je zadaný žiadny ownCloud účet. Zadajte ho prosím. + Na tomto zariadení nie je zadaný žiadny %1$s účet. Zadajte ho prosím. Nastavenie Koniec Žiadny obsah pre odoslanie Nedodaný žiaden obsah. Nič na odoslanie. %1$s nemá práva pre prístup k zdieľanému obsahu Nahrávanie - VytvoriÅ¥ priečinok pre nahrávanie Priečinok neobsahuje súbory.\nNové súbory môžete pridaÅ¥ cez \"Upload\". Viac informácií získate kliknutím na súbor. VeľkosÅ¥: @@ -69,10 +52,10 @@ Vytvorený: Zmenený: StiahnuÅ¥ - ObnoviÅ¥ - ObnoviÅ¥ - OtvoriÅ¥ + ObnoviÅ¥ súbor Súbor bol premenovaný na %1$s počas nahrávania + ZdieľaÅ¥ linku + ZruÅ¡iÅ¥ zdieľanie odkazu Áno Nie OK @@ -80,9 +63,11 @@ ZruÅ¡iÅ¥ odosielanie ZruÅ¡iÅ¥ UložiÅ¥ a ukončiÅ¥ - OpustiÅ¥ ownCloud Chyba + Načítavam... + Neznáma chyba O + ZmeniÅ¥ heslo ZmazaÅ¥ účet VytvoriÅ¥ účet OdoslaÅ¥ z ... @@ -91,28 +76,32 @@ %1$d%% Odosielam %2$s Odoslanie bolo úspeÅ¡né %1$s úspeÅ¡ne odoslaný - %1$d súborov bolo úspeÅ¡ne odoslaných Odoslanie bolo neúspeÅ¡né Odoslanie %1$s nemohlo byÅ¥ dokončené - Nahrávanie zlyhalo: %1$d/%2$d súborov bolo odoslaných SÅ¥ahujem ... %1$d%% SÅ¥ahovanie %2$s SÅ¥ahovanie bolo úspeÅ¡né %1$s bol úspeÅ¡ne stiahnutý Stiahnutie zlyhalo SÅ¥ahovanie %1$s nebolo dokončené + EÅ¡te nie je stiahnuté ZvoliÅ¥ účet - Kontakty Synchronizácia zlyhala Synchronizáciu %1$s nemožno dokončiÅ¥ + Nesprávne heslo pre %1$s Objavené konflikty %1$d súborov z automatickej synchronizácie nemožno synchronizovaÅ¥ Automatická synchronizácia súborov zlyhala Obsah %1$d súborov nemohol byÅ¥ synchronizovaný (%2$d konfliktov) - PoužiÅ¥ zabezpečené pripojenie - ownCloud nemôže sledovaÅ¥ polohu vášho zariadenia. Skontrolujte nastavenia umiestnenia. + Niektoré lokálne súbory boli zabudnuté + Priečinok %1$s už existuje + PremiestniÅ¥ vÅ¡etko + VÅ¡etky súbory boli premiestnené + Niektoré súbory nebolo možné premiestniÅ¥ + Lokálne: %1$s + Vzdialené: %1$s + Nie je dostatok miesta na kopírovanie vybraných súborov do priečinka %1$s. Želáte si miesto kopírovania zmeniÅ¥? Zadajte PIN aplikácie - Zadajte nový PIN aplikácie Zadajte PIN aplikácie Pri každom spustení aplikácie bude vyžadovaný PIN Zadajte znovu PIN aplikácie @@ -121,50 +110,54 @@ Nesprávny PIN aplikácie PIN aplikácie bol odstránený PIN aplikácie bol uložený - - 15 minút - 30 minút - 60 minút - - - 15 - 30 - 60 - + Prehrávač hudby %1$s + %1$s (prehráva) + %1$s (načítava) + %1$s prehrávanie dokončené + Nenájdený žiaden multimediálny súbor + Účet neposkytnutý + Súbor nie je v platnom účte + Nepodporovaný kodek + Mediálny súbor nemožno čítaÅ¥ + Mediálny súbor nemá správne kódovanie + Skončil počas pokusu o prehratie + Mediálny súbor nemožno streamovaÅ¥ + Mediálny súbor nemožno prehraÅ¥ s východzím prehrávačom + Chyba zabezpečenia pri pokuse o prehranie %1$s + Chyba vstupu pri prehrávaní %1$s + Nečakaná chyba pri prehrávaní %1$s + Tlačidlo pretáčania + Tlačidlo prehrávania / pauzy + Tlačidlo \"rýchlo vpred\" Pokus o pripojenie... Bez sieÅ¥ového pripojenia - Skontrolujte pripojenie k sieti a opakujte pripojenie znovu. - Aj tak pripojiÅ¥ Nie je k dispozícii bezpečné pripojenie - Aplikácia nemôže nadviazaÅ¥ zabezpečené spojenie so serverom. Nezabezpečené pripojenie je k dispozícii. Môžete pokračovaÅ¥ alebo preruÅ¡iÅ¥ spojenie. Pripojenie vytvorené Testovane pripojenia... - Nesprávna konfigurácia ownCloud - Nastavenie ownCloud nie je správne. Kontaktujte administrátora pre získanie viac informácií. + Nesprávna konfigurácia servera + Účet pre tohoto používateľa a tento server už v tomto zariadení existuje + Zadané prihlasovacie údaje používateľa sú nesprávne Nastala neznáma chyba! - Nastala neznáma chyba. Kontaktujte podporu a pripojte záznam z Vášho zariadenia. Nemožno nájsÅ¥ hosta - Nemôžm sa pripojiÅ¥ k serveru. Skontrolujte nastavenia servera, či beží a skúste to znovu. - ownCloud inÅ¡tancia nebola nájdená - Aplikácia na zadanej adrese nenaÅ¡la inÅ¡tanciu ownCloud. Skontrolujte cestu a skúste to znovu. + Servera inÅ¡tancia nebola nájdená Serveru trvá odpoveď príliÅ¡ dlho PoÅ¡kodená URL Inicializácia SSL zlyhala - Neoverená SSL identita servera + Nepodarilo sa overiÅ¥ identitu servera SSL Nerozpoznaná verzia servera Nie je možne vytvoriÅ¥ spojenie Vytvorené zabezpečené spojenie - Podrobnosti prihlásenia - Neplatné prihlasovacie meno / heslo - Je zadaná neplatná cesta - Interná chyba servera, kód %1$d - Aplikácia neočakávane ukončila činnosÅ¥. Chcete poslaÅ¥ správu o chybe? - PoslaÅ¥ výpis - NeposlaÅ¥ výpis - Sú dostupné rozšírenia. - VaÅ¡a inÅ¡tancia ownCloud podporuje rozšírené nastavenia. Chcete zobraziÅ¥ rozšírené nastavenia dosupné pre android? + Neprávne používateľské meno alebo heslo + NeúspeÅ¡ná autorizácia + Prístup odmietnutý autorizačným serverom + Nečakaný stav; prosím, opätovne vložte URL adresu servera + VaÅ¡a autorizácia expirovala. Prosím autorizujte sa znovu prosím + Prosím, zadajte aktuálne heslo + VaÅ¡e pripojenie expirovalo. Pripojte sa znovu prosím + Pripájam sa na autentifikačný server... + Server nepodporuje túto autentifikačnú metódu + %1$s nepodporuje viacero účtov UdržiavaÅ¥ súbor aktuálny. - Zdieľaj Premenuj Odober Naozaj odstrániÅ¥ %1$s ? @@ -180,17 +173,18 @@ Nie je možné premenovaÅ¥ Vzdialený súbor nemohol byÅ¥ prekontrolovaný Obsah súboru je zosynchronizovaný - Priečinok nie je možné vytvoriÅ¥ + Zakázané znaky: / \\ < > : \" | ? * PočkaÅ¥ chvíľu Neočakávaný problém; skúste vybraÅ¥ súbor inou aplikáciou Nebol vybraný súbor - Upozornenie + OdoÅ¡li link do ... + PrihlásiÅ¥ sa z oAuth2 + Pripájam sa na oAuth2 server… Identitu stránky nemožno overiÅ¥ - Certifikát servera nie je overený - PlatnosÅ¥ certifikátu servera vyprÅ¡ala - Certifikát servera je príliÅ¡ mladý - URL nezodpovedá hodnote hostname certifikátu - Nie je možné získaÅ¥ certifikát servera Chcete veriÅ¥ tomuto certifikátu v každom prípade ? Certifikát nemôže byÅ¥ uložený Podrobnosti @@ -208,7 +202,12 @@ Do: Podpis: Algoritmus: - To je zástupný znak + Toto je \"placeholder\" + placeholder.txt + PNG obrázok + 389 KB + 2012/05/18 12:23 PM + 12:23:45 OdoslaÅ¥ fotografie iba cez WiFi /InstantUpload Konflikt pri aktualizácii @@ -216,4 +215,24 @@ PonechaÅ¥ oba PrepísaÅ¥ Nenahrávajte + Ukážka obrazu + Obraz nemôže byÅ¥ zobrazený + Okamžité odoslanie zlyhalo + Zlyhané instantné nahratia + Zhrnutie vÅ¡etkých zlyhaných nahratí + vybraÅ¥ vÅ¡etko + opakovaÅ¥ vÅ¡etky vybrané + zmazaÅ¥ vÅ¡etky vybrané z radu pre nahratie + opakovaÅ¥ nahratie obrazu: + NačítaÅ¥ viac Obrázkov + nevykonaÅ¥ nič, nie ste online pre instantné nahratie + Chybová správa: + Prosím skontrolujte nastavenie vášho servera, možno bola prekročená disková kvóta. + Nie je možné zdieľaÅ¥ tento súbor alebo adresár. Prosím uistite sa že existuje + Pri pokuse o zdieľanie tohto súboru alebo priečinka doÅ¡lo k chybe + Nie je možné zruÅ¡iÅ¥ zdieľanie tohoto súboru alebo priečinka. Súbor alebo priečinok neexistuje. + Pri pokuse zruÅ¡iÅ¥ zdieľanie tohto súboru alebo priečinka doÅ¡lo k chybe + OdoslaÅ¥ + Kopíruj odkaz + Skopírované do \"clipboard\" diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index c757504a..1eb12a48 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -1,2 +1,7 @@ - + + Nastavenia + VÅ¡eobecné + StiahnuÅ¥ + ZruÅ¡iÅ¥ + diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 74135699..63628891 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -1,88 +1,73 @@ - ownCloud - Geslo: - UporabniÅ¡ko ime: - Prijava - DobrodoÅ¡li v oblaku ownCloud - Datoteke - Glasba - Stiki - Koledar - Zaznamki - Nastavitve - Nastavi račun - Na vaÅ¡i napravi ni računa ownCloud. Za uporabo tega programa je treba račun ustvariti. - Odjemalec ownCloud za Android\n\nrazličica: %1$s - Osveži + Program za Android %1$s + različica %1$s + Osveži račun PoÅ¡lji datoteko Vsebina iz drugih programov Datoteke - Ustvari mapo - Poišči + Odpri z + Nova mapa Nastavitve + Podrobnosti + PoÅ¡lji SploÅ¡no - Sledenje napravi - Dodaj novo sejo - Ustvari sličice slik - Izberite račun - Izberite račun, ki ga naj program uporabi. - Sledenje napravi - Omogoči sledenje mestu naprave preko oblaka ownCloud. - Strežnik ownCloud spremlja mesto napravo - Časovni razmik posodobitev - Posodobi vsakih %1$s minut + Več Računi Upravljanje z računi - Koda PIN programa ownCloud - Zaščitite odjemalec ownCloud + Koda PIN programa + Zaščitite odjemalec Omogoči takojÅ¡nje poÅ¡iljanje TakojÅ¡nje poÅ¡iljanje posnetih fotografij - Naslov URL ownCloud + Omogoči zapisovanje dnevnika + Uporablja se za beleženje težav in napak + Zgodovina beleženja dnevnika + Pokaže shranjene dnevnike + IzbriÅ¡i zgodovino + Pomoč + Priporoči prijateljem + Odziv + Natis + Preizkusi %1$s na pametnem telefonu! + Preveri strežnik + Naslov strežnika https://… UporabniÅ¡ko ime Geslo - Sem nov v oblaku ownCloud - Podan je napačen naslov URL - Napačno ime seje + Ali ste novi uporabnik sistema %1$s? Datoteke - Ni izbrane datoteke za poÅ¡iljanje. - UporabniÅ¡ko ime - Geslo - Spletni naslov - Ali naj bo geslo prikazano? - Poveži se z oblakom ownCloud Poveži PoÅ¡lji UporabniÅ¡kega računa ni mogoče najti - Na napravi ni računov ownCloud. Nastaviti je treba vsaj en račun. + Na napravi ni računov %1$s. Nastaviti je treba vsaj en račun. Nastavi Končaj Ni vsebine za poÅ¡iljanje Ni prejete vsebine. Ni datotek za poÅ¡iljanje. - Oblak ownCloud nima dovoljenj za dostop do vsebine v souporabi + Oblak %1$s nima nastavljenih dovoljenj za dostop do vsebine v souporabi PoÅ¡iljanje - Ustvari mapo za poÅ¡iljanje V tej mapi ni datotek.\nNove datoteke je mogoče dodati preko možnosti menija \"PoÅ¡lji\". - Pritisnite n datoteko za prikaz dodatnih podrobnosti. + Pritisnite na datoteko za prikaz dodatnih podrobnosti. Velikost: Vrsta: Ustvarjeno: Spremenjeno: Prejmi - Osveži - Osveži - Odpri + Osveži datoteko Datoteka je bila med nalaganjem preimenovana v %1$s + Povezava za souporabo + Odstrani možnost souporabe Da Ne V redu - Prekliči prenos + Prekliči prejem Prekliči poÅ¡iljanje Prekliči - Shrani & končaj - Zapusti ownCloud + Shrani in končaj Napaka - O oblaku ownCloud + Nalaganje ... + Neznana napaka + O oblaku %1$s + Spremeni geslo IzbriÅ¡i račun Ustvari račun PoÅ¡lji datoteko iz … @@ -91,106 +76,116 @@ %1$d%% PoÅ¡iljanje %2$s PoÅ¡iljanje je uspeÅ¡no končano %1$s je uspeÅ¡no poslan - %1$d datotek je uspeÅ¡no poslanih PoÅ¡iljanje je spodletelo PoÅ¡iljanja %1$s ni mogoče dokončati - PoÅ¡iljanje je spodletelo: Poslanih je %1$d/%2$d datotek. Prejemanje … %1$d%% Prejemanje %2$s Prejemanje je uspeÅ¡no končano %1$s je uspeÅ¡no prejet Prejemanje je spodletelo Prejemanja %1$s ni mogoče dokončati + Prejem Å¡e ni zaključen Izbor računa - Stiki Usklajevanje je spodletelo Usklajevanja %1$s ni mogoče dokončati - Najdeni spori - %1$d vedno-ažurnih datotek ni bilo mogoče uskladiti - Usklajevanje vedno-ažurnih datotek je spodletelo - Vsebino %1$d datotek ni bilo mogoče uskladiti (%2$d sporov) - Uporabi varno povezavo - Z ownCloud ni mogoče slediti napravi. Preverite nastavitve vaÅ¡ega mesta nahajanja. - Vnesite PIN programa - Vnesite nov PIN programa - Vnesite kodo PIN programa ownCloud + Neveljavno geslo za %1$s + Zaznani spori + %1$d stalno usklajenih datotek ni bilo mogoče uskladiti + Posodobitev stalno usklajenih datotek je spodletelo + Vsebine %1$d datotek ni bilo mogoče uskladiti (zaznanih je %2$d sporov) + Nekatere krajevne datoteke so spregledane + Mapa %1$s ne obstaja več + Premakni vse + Vse datoteke so uspeÅ¡no premaknjene na novo mesto + Nekaterih datotek ni mogoče premakniti + Krajevno: %1$s + Oddaljeno: %1$s + Na strežniku ni dovolj prostora za kopiranje izbranih datotek v mapo %1$s. Ali želite datoteke raje premakniti na novo mesto? + Vnesite kodo PIN programa + Vnesite kodo PIN programa Koda PIN bo zahtevana vsakič pred zagonom programa. - Ponovno vnesite kodo PIN programa ownCloud - Odstrani kodo PIN programa ownCloud - Vrednosti kodo PIN programa ownCloud nista enaki - Nepravilen kodo PIN programa ownCloud - Koda PIN programa ownCloud je odstranjena - Koda PIN programa ownCloud je shranjena - - 15 minut - 30 minut - 60 minut - - - 15 - 30 - 60 - + Ponovno vnesite kodo PIN programa + Odstrani kodo PIN programa + Vrednosti kod PIN programa nista enaki + Nepravilna koda PIN programa + Koda PIN programa je odstranjena + Koda PIN programa je shranjena + Predvajalnik glasbe %1$s + %1$s (se predvaja) + %1$s (se nalaga) + Predvajanje %1$s je končano + Predstavnih datotek ni mogoče najti + Ni navedenega računa + Dokument ni shranjen v veljavnem računu. + Nepodprt predstavni kodek + Predstavne datoteke ni mogoče prebrati. + Predstavna datoteka ni pravilno kodirana. + Poskus predvajanja je časovno potekel + Predstavne datoteke ni mogoče prikazati v pretoku + Predstavne datoteke ni mogoče predvajati s sistemsko privzetim predvajalnikom + PriÅ¡lo je do varnostne napake med predvajanjem %1$s + PriÅ¡lo je do napake vhoda med predvajanjem %1$s + PriÅ¡lo je do nepričakovane napake med predvajanjem %1$s + Vrni nazaj + Gumb za predvajanje in premor + Gumb za hitro predvajanje naprej Poskus prijave … Ni omrežne povezave - Omrežna povezava ni zaznana. Preverite internetno povezavo in poskusite znova. - Vseeno poveži Varna povezava ni na voljo. - Programu ni uspelo vzpostaviti varne povezave s strežnikom. Nadaljujete lahko z ne-varno povezavo ali pa prekinete. Povezava je vzpostavljena PreizkuÅ¡anje povezave ... - Nastavitve ownCloud so napačno oblikovane - Zdi se, da vaÅ¡ ownCloud ni pravilno nastavljen. Za dodatna pojasnila se obrnite na skrbnika. - PriÅ¡lo je do neznane napake - PriÅ¡lo je do neznane napake. Na napako opozorite razvijalce in jim posredujete dnevniÅ¡ke zapise z naprave. + Napačno oblikovane nastavitve strežnika + Na napravi račun za istega uporabnika in strežnik že obstaja + Vpisan uporabnik ni lastnik tega računa + PriÅ¡lo je do neznane napake! Gostitelja ni mogoče najti - Vpisanega gostitelja ni bilo mogoče nati. Preverite ime gostitelja in razpoložljivost strežnika ter poskusite znova. - Namestitve ownCloud ni mogoče najti - Na podani poti ni bilo mogoče najti namestitve ownCloud. Preverite vpisano pot in poskusite znova. - Strežnik je potreboval preveč časa za odgovor + Primerka strežnika ni mogoče najti + Odziv s strežnika je časovno pretekel Napačno oblikovan naslov URL Začenjanje SSL je spodletelo - Istovetnost strežnika SSL ni preverjena - Neprepoznana različica strežnika ownCloud - Povezave ni mogoče vzpostaviti + Ni mogoče overiti istovetnosti strežnika SSL + Nepoznana različica strežnika + Ni mogoče vzpostaviti povezave Varna povezava je vzpostavljena - Podrobnost prijave - Neveljavna prijava / geslo - Podana je bila napačna pot - Notranja napaka strežnika, koda %1$d - Program se je nepričakovano zaprl. Ali bi želeli poslati poročilo o sesutju? - PoÅ¡lji poročilo - Ne poÅ¡lji poročila - Na voljo so razÅ¡iritve! - Videti je, da različica oblaka ownCloud podpira napredne razÅ¡iritve. Ali želite pregledati razÅ¡iritve za okolje Android? + Napačno uporabniÅ¡ko ime ali geslo + Overitev ni uspeÅ¡no končana + Dostop je zavrnjen s strani overitvenega strežnika + Nepričakovano stanje; ponovno je treba vpisati naslov URL strežnika + Overitev računa je potekla. Pred nadaljevanjem je treba prijavo ponovno overiti. + Vnesite trenutno geslo + Seja je potekla. Ponovno je treba vzpostaviti povezavo. + Poteka povezovanje z overitvenim strežnikom ... + Strežnik ne podpira tega načina overitve + %1$s ne omogoča podpore več računom + S tem strežnikom overitev ni mogoča Datoteka naj bo posodobljena - Souporaba Preimenuj Odstrani - Ali ste prepričani, da želite odstraniti %1$s ? + Ali res želite odstraniti %1$s ? Ali ste prepričani, da želite odstraniti %1$s in njeno vsebino? - Samo krajevno - Samo lokalne vsebine + Le krajevno + Le krajevno vsebino Odstrani s strežnika Oddaljeno in krajevno Odstranitev je uspeÅ¡no končana - Odstranjevanje ni dokončano + Odstranjevanje je spodletelo Vnesite novo ime Krajevne datoteke ni mogoče preimenovati; poskusite z drugačnim novim imenom Preimenovanja ni možno dokončati - Oddaljene datoteke ni bilo mogoče preveriti - Vsebina datoteke je ažurna - Mape ni mogoče ustvariti + Oddaljene datoteke ni mogoče preveriti + Vsebina datoteke je že usklajena + Nedovoljeni znaki: characters: / \\ < > : \" | ? * Počakajte trenutek ... PriÅ¡lo je do nepričakovane napake. Poskusite datoteko izbrati z drugim programom. Ni izbranih datotek - Opozorilo + PoÅ¡lji povezavo ... + Prijava z oAuth2 + Poteka povezovanje s strežnikom oAuth2 ... Istovetnosti strani ni mogoče preveriti - potrdilo strežnika ni vredno zaupanja - potrdilo strežnika je poteklo - potrdilo strežnika je v uporabi - naslov URL ni skladen z imenom gostitelja potrdila - Potrdila strežnika ni mogoče pridobiti. Ali vseeno želite zaupati potrdilu? Potrdila ni mogoče shraniti. Podrobnosti @@ -198,22 +193,49 @@ Izdano za: Izdajatelj: SploÅ¡no ime: - Organizacija: + Ustanova: Organizacijska enota: Država: - Zvezna država: - Lokacija: + Regija: + Mesto: Veljavnost: Od: Do: Podpis: Algoritem: - To je vsebnik predmetov. - PoÅ¡iljaj slike le preko protokola Wi-Fi - /NeposrednoNalaganje - Posodobi konflikt - Oddaljena datoteka %s ni usklajena z lokalno datoteko. Če nadaljujete, bo datoteka na strežniku zamenjana z vaÅ¡o lokalno. + Potrdila ni mogoče pokazati. + – Ni podatkov o napaki + To je vsebnik predmetov + vsebnik.txt + Slika PNG + 389 KB + 2012/05/18 12:23 PM + 12:23:45 + PoÅ¡iljaj slike le preko povezav Wi-Fi + /TakojÅ¡njePoÅ¡iljanje + Posodobi podatke spora + Oddaljena datoteka %s ni usklajena s krajevno. Z nadaljevanem bo datoteka na strežniku zamenjana s krajevno. Ohrani obe PrepiÅ¡i - Ne naloži + Ne poÅ¡lji + Predogled slike + Te slike ni mogoče prikazati + Spodletelo takojÅ¡nje poÅ¡iljanje + Spodletela takojÅ¡nja poÅ¡iljanja + Povzetek vseh spodletelih takojÅ¡njih poÅ¡iljanj + izberi vse + vse izbrane poskusi znova + izbriÅ¡i izbrane iz vrste za poÅ¡iljanje + poskusi poslati sliko: + Naloži več slik + ne poÅ¡lji takoj, saj je povezava v omrežje ni dejavna + Sporočilo o napaki: + Preverite nastavitve strežnika. Morda je presežena vrednost količinske omejitve. + Ni mogoče omogočiti souporabe te datoteke ali mape. Prepričajte se, da obstaja ... + PriÅ¡lo je do napake med poskusom omogočanja souporabe te datoteke ali mape + Ni mogoče prekiniti souporabe te datoteke ali mape, ker ne obstaja. + PriÅ¡lo je do napake med poskusom odstranjevanja souporabe te datoteke ali mape + PoÅ¡lji + Kopiraj povezavo + Kopirano v odložišče diff --git a/res/values-so/strings.xml b/res/values-so/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/res/values-so/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index c757504a..9082fa69 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -1,2 +1,56 @@ - + + Ngarko + Skedarët + Dosje e\'re + Parametrat + Dërgo + Përgjithshme + Më tepër + Llogarit + Ndihmë + Stampoj + Përdoruesi + Kodi + Skedarët + Lidhu + Ngarko + Nuk u gjend asnjë llogari + Nuk ka %1$s llogari në pajisjen tuaj. Ju lutemi të krijojnë një llogari të parë. + Ndërto + Dil + Ngarko + Trokitje e lehtë në një dokument për të shfaqur informacion shtesë. + Dimensioni: + Tipi: + Krijuar: + Modifikuar: + Shkarko + Po + Jo + Ok + Anulo ngarkimin + Anulo + Veprim i gabuar + Gabim panjohur + Rreth + Ndrysho fjalëkalimin + Fshi llogarin + Krijo llogari + Ngarko nga... + Ngarkim... + %1$d%% Ngarkim %2$s + Ngarkimi me sukses. + %1$s u ngarkua me sukses + Ngarkimi dështoi + Ngarkimi i %1$s nuk mund te behej + Shkarkimi... + %1$d%% Shkarkimi %2$s + Shkarkimi me sukses + %1$s u shkarkua me sukses + Shkarkimi dështoj + Lidhja e Sigurt vendos + Riemërto + Hiq + Dërgo + diff --git a/res/values-sr-rSP/strings.xml b/res/values-sr-rSP/strings.xml index 02e92c8d..c348cbc5 100644 --- a/res/values-sr-rSP/strings.xml +++ b/res/values-sr-rSP/strings.xml @@ -1,17 +1,54 @@ - Fajlovi - Muzika - PodeÅ¡avanja PoÅ¡alji Fajlovi PodeÅ¡avanja + Detaljnije + PoÅ¡alji + OpÅ¡te + Nalozi + Upravljaj nalozima + Pomoć Korisničko ime Lozinka Fajlovi - Korisničko ime - Lozinka PoÅ¡alji + Nalog nije nađen + Å alje se + Veličina: + Tip: Preuzmi + Da + Ne Otkaži + GreÅ¡ka + Izmeni lozinku + Ukloni nalog + Novi nalog + Otpremanje... + UspeÅ¡no otpremljeno + Otpremanje nije uspelo + Preuzimanje... + UspeÅ¡no preuzeto + Preuzimanje nije uspelo + Odaberite nalog + Nema konekcije + Sigurna konekcija nije dostupna. + Konekcija uspostavljena + Preimenij + Ukloni + Da li želite da uklonite %1$s ? + Uklanjanje je uspelo + Uklanjanje nije uspelo + Molim pričekajte + Detaljnije + Sakrij + Organizacija: + Država: + Lokacija: + Od: + Za: + Potpis: + Zadrži oboje + PoÅ¡alji diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 81124e86..47434083 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -1,39 +1,18 @@ - ownCloud - Лозинка: - Корисничко име: - Пријави ме - Датотеке - Музика - Контакти - Календар - Обележивачи - Поставке - Подешавање налога - Освежи Отпреми Садржај са других апликација Датотеке - Направи фасциклу - Претражи Поставке + Пошаљи Опште - Праћење уређаја - Додај нову сесију - Направи умањене приказе слика - Изаберите налог - Ажурирај на %1$s мин. + Више Налози Тренутно отпремај фотографије сликане камером + Помоћ Корисничко име Лозинка Фајлови - Нисте изабрали датотеку за отпремање - Корисничко име - Лозинка - Веб адреса - Приказати лозинку? Повежи ме Отпреми Нема налога @@ -42,7 +21,6 @@ Нема садржаја за отпремање Садржај није примљен. Нема ништа да се отпреми. Отпремање - Направи фасциклу за отпремање Нема датотека у овој фасцикли.\nНове датотеке можете да додате путем опције „Отпреми“. Додирните датотеку ради приказа додатних информација. Величина: @@ -50,86 +28,66 @@ Направљено: Измењено: Преузми - Освежи - Отвори + Освежи датотеку Да Не У реду + Обустави преузимање Прекини слање Откажи Сачувај и изађи Грешка О програму + Измени лозинку Обриши налог Отвори налог Отпреми из… - Име фасцикле Отпремам… %1$d%% Отпремам %2$s Отпремање је успело Отпремање није успело Не могу да довршим отпремање датотеке %1$s - Отпремање није успело. Отпремљено датотека: %1$d/%2$d Преузимам… %1$d%% Преузимам %2$s + Преузимање успешно + %1$s је успешно преузет Преузимање није успело Не могу да довршим преузимање датотеке %1$s + Још увек није преузето Изабери налог - Контакти Синхронизовање није успело Не могу да довршим синхронизацију датотеке %1$s - Безбедна веза + Све датотеке су померене + Неке датотеке нису могле бити померене Унесите PIN апликације - Унесите нови PIN апликације Са сваким покретањем апликације мораћете да унесете PIN - - 15 минута - 30 минута - 60 минута - - - 15 - 30 - 60 - - Покушавам да вас пријавим… Нема мрежне везе - Ипак ме повежи Безбедна веза није доступна. Веза је успостављена - Тестирам везу… Дошло је до непознате грешке. Не могу да пронађем домаћина - Не могу да пронађем наведеног домаћина. Проверите име домаћина и доступност сервера па покушајте поново. Не могу да пронађем примерак сервера Серверу је требало предуго да се одазове Погрешно уобличена адреса Покретање SSL-а није успело - Непроверен идентитет SSL сервера Не могу да успоставим везу Безбедна веза је успостављена - Подаци за пријаву - Пошаљи извештај - Не шаљи извештај - Доступна су проширења. Редовно ажурирај датотеку - Дели Преименуј Уклони Желите ли да уклоните %1$s? Само локално Уклони са сервера Удаљено и локално + Унесите ново име Не могу да довршим преименовање - Не могу да направим фасциклу + Удаљена датотека се не може проверити Сачекајте тренутак Нисте изабрали датотеку - Упозорење Не могу да проверим идентитет сајта – Сертификат сервера није поверљив – Сертификат сервера је истекао – Адреса се не поклапа са именом домаћина у сертификату - Не могу да прибавим сертификат сервера Желите ли ипак да означите сертификат као поверљив? Не могу да сачувам сертификат Подаци @@ -147,7 +105,7 @@ За: Потпис: Алгоритам: - Ово је чувар места Отпремај слике само путем бежичне мреже Ажурирај сукоб + Пошаљи diff --git a/res/values-su/strings.xml b/res/values-su/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-su/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 6aec4a69..56f5d3b9 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -1,59 +1,44 @@ - ownCloud - Lösenord: - Användare: - Logga in - Välkommen till ownCloud - Filer - Musik - Kontakter - Kalender - Bokmärken - Inställningar - Skapa konto - Det finns inget konto för ownCloud pÃ¥ denna enhet. För att kunna använda ownCloud sÃ¥ mÃ¥ste du skapa ett konto. - %1$s Android klient\n\nversion: %2$s - Uppdatera + %1$s Android App + version %1$s + Uppdatera konto Ladda upp InnehÃ¥ll frÃ¥n andra program Filer - Skapa mapp - Sök + Öppna med + Ny mapp Inställningar + Detaljer + Skicka Allmänt - SpÃ¥ra enhet - Lägg till ny session - Skapa miniatyrbilder - Välj ett konto - Välj det konto som du vill använda. - SpÃ¥ra enhet - Aktivera ownCloud att spÃ¥ra din enhets plats - ownCloud spÃ¥rar denna enhet - Uppdateringsintervall - Uppdatera var %1$s minut + Mer Konton Hantera konton - ownCloud PIN - Skydda ownCloud-klienten + applikation PIN + Skydda applikation-klienten Aktivera direktuppladdning Direktuppladdning av kamerabilder - ownCloud URL - Användare + Aktivera loggning + Används för att logga problem + Logghistorik + Visar sparade loggar + Radera historik + Hjälp + Rekommendera till en vän + Återkoppling + Imprint + Försök %1$s pÃ¥ din smarttelefon! + Kontrollera Server + Serveradress https://... + Användarnamn Lösenord - %1$s är nytt för mig - Felaktig URL angavs - Felaktigt sessionsnamn + Ny pÃ¥ %1$s? Filer - Ingen fil vald för uppladdning - Användare - Lösenord - Webbadress - Visa lösenord? - Anslut till %1$s Anslut Ladda upp - Hittar inget konto + Välj mapp för uppladdning: + Hittade inget konto Det finns inga konton för %1$s pÃ¥ denna enhet. Var god skapa ett konto först. Skapa Avsluta @@ -61,7 +46,6 @@ Inget innehÃ¥ll mottaget. Inget att ladda upp. %1$s har inte rättighet till det delade innehÃ¥llet Laddar upp - Skapa mapp för att ladda upp Det finns inga filer i denna mapp.\nNya filer kan läggas till med \"Ladda upp\" i menyn. Peka pÃ¥ en fil för att visa mer information. Storlek: @@ -69,10 +53,10 @@ Skapad: Ändrad: Ladda ner - Uppdatera - Ladda ner igen - Öppna + Ladda om fil Filen bytte namn till %1$s under uppladdningen + Dela länk + Sluta dela länk Ja Nej OK @@ -80,91 +64,104 @@ Avbryt uppladdning Avbryt Spara & Avsluta - Lämna %1$s Fel + Laddar... + Okänt fel Om + Ändra lösenord Radera konto Skapa konto - Ladda upp fil frÃ¥n ... + Ladda upp frÃ¥n ... Mappnamn Laddar upp ... %1$d%% Laddar upp %2$s Uppladdning klar - %1$s var uppladdade - %1$d filer uppladdade + %1$s laddades upp Misslyckad uppladdning Uppladdning av %1$s kunde inte slutföras - Misslyckad uppladdning: %1$d/%2$d filer uppladdade Laddar ner ... %1$d%% Laddar ner %2$s Nedladdning klar %1$s laddades ner Misslyckad nedladdning Nedladdning av %1$s kunde inte slutföras + Ännu inte nedladdade Välj konto - Kontakter Synkroniseringen misslyckades Synkronisering av %1$s kunde inte slutföras + Felaktigt lösenord för %1$s Konflikter uppstod %1$d Flaggad som HÃ¥ll-filen-uppdaterad kan inte synkas Filer flaggade som HÃ¥ll-filen-uppdaterad misslyckades InnehÃ¥llet i %1$d filer kunde inte synkas (%2$d konflikter) - Använd säker anslutning - %1$s kan inte spÃ¥ra denna enhet. Kontrollera dina platsinställningar + Vissa lokala filer glömdes + %1$d filer frÃ¥n %2$s mappar kunde inte kopieras till + FrÃ¥n och med version 1.3.16, kopieras uppladdade filer frÃ¥n den här enheten till den lokala %1$s mappen för att förhindra förlust av data när en enda fil synkroniseras med flera konton.\n\nPÃ¥ grund av denna förändring har alla filer som laddats upp i tidigare versioner av denna app kopierats till %2$s mappen. Men ett fel förhindrade slutförande av denna operation under synkronisering. Du kan antingen lämna fil(er) som det är och ta bort länken till %3$s, eller flytta fil(er) till %1$s mappen och behÃ¥lla länken till %4$s.\n\nNedan listas dom lokala fil(er) och fjärrfil(er) i %5$s dom var länkade till. + Mappen %1$s existerar inte längre + Flytta allt + Alla filer flyttades + Vissa filer kunde inte flyttas + Lokal: %1$s + Fjärr: %1$s + Det finns inte tillräckligt med utrymme för att kopiera valda filer till mappen %1$s. Vill du flytta filerna istället? Ange din PIN - Ange din nya PIN - Ange ownCloud PIN + Ange applikation PIN Din PIN mÃ¥ste anges varje gÃ¥ng du startar programmet. - Ange ownCloud PIN igen - Radera ownCloud PIN - BÃ¥da ownCloud PIN är inte lika - Felaktig ownCloud PIN - ownCloud PIN raderad - ownCloud PIN sparad - - 15 Minuter - 30 Minuter - 60 Minuter - - - 15 - 30 - 60 - + Ange applikation PIN igen + Radera applikation PIN + BÃ¥da applikation PIN är inte lika + Felaktig applikationd PIN + applikation PIN raderad + applikation PIN sparad + %1$ musikspelare + %1$s (spelar) + %1$s (buffrar) + %1$s spelat färdigt + Hittar ingen medie-fil + Inget konto är angivet + Filen finns i ett ogiltigt konto + Saknar stöd för denna mediatyp + Mediafil kunde inte skapas + Mediafilen är felaktigt kodad + Försökt att spela för länge + Meidiafilen kan inte strömmas + Mediefilen kan inte spelas med standardspelaren + Säkerhetsproplem när vi försöker spela %1$s + Inmatningsfel när vi försöker spela %1$s + Oväntat fel när vi försöker spela %1$s + BakÃ¥tspolningsknapp + Spela- / Pausknapp + Snappspolningsknapp Försöker logga in... - Ingen anslutning till nätverket - Ingen nätverksanslutning har upptäckts, kontrollera din Internet-anslutning och försök igen. - Anslut ändÃ¥ + Ingen nätverksanslutning Säker anslutning inte tillgänglig. - Applikationen kunde inte etablera en krypterad anslutning till servern. Okrypterad anslutning kan finnas tillgänglig. Du kan fortsätta eller avbryta. Anslutning etablerad Testar anslutning... Felaktig konfiguration - Det verkar som om din version av ownCloud inte är korrekt konfigurerad. Kontakta din administratör för mer information. - Okänt fel uppstod - Ett okänt fel uppstod. Vänligen kontakta behörig och inkludera loggar frÃ¥n enheten. + En användare med samma namn och server finns redan i denna aparat + Den angivna användaren matchar inte användaren för detta konto + Okänt fel inträffade! Kunde inte hitta server - Kunde inte hitta angiven server. Kontrollera serverns tillgänglighet och försök igen. - Ingen version av ownCloud kan hittas - Applikationen kunde inte hitta nÃ¥gon version av ownCloud i angiven sökväg. Kontrollera sökvägen och försök igen. + Ingen version av server kan hittas Servern svarar inte Felaktig URL Misslyckad initiering av SSL - Obekräftad SSL-identitet - Okänd ownCloud serverversion + Kunde inte identifiera SSL serverns identitet + Okänd server serverversion Kunde inte etablera anslutning Säker anslutning etablerad - Inloggningsuppgifter - Felaktigt användarnamn / lösenord - Felaktig sökväg angavs - Internt serverfel, kod %1$d - Applikationen avslutades oväntat. Vill du skicka en felrapport? - Skicka rapport - Skicka inte rapport - Tillägg finns tillgängliga! - Det verkar som att din version av ownCloud stödjer avancerade tillägg. Vill du se tilläggen tillgängliga för Android? + Felaktigt användarnamn eller lösenord + Behörighet saknas + Nekad Ã¥tkomst av server + Oväntat tillstÃ¥nd; Ange serverns URL igen + Din inloggning har gÃ¥tt ut. Var god logga in igen + Vänligen ange det aktuella lösenordet + Din session har upphört att gälla. Vänligen anslut igen + Ansluter till autentiseringsservern... + Servern har inte stöd för denna autentiseringsmetod + %1$s har inte stöd för multipla konton + Kan inte autentisera mot denna servern HÃ¥ll filen uppdaterad - Dela Byt namn Radera Vill du verkligen radera: %1$s ? @@ -181,16 +178,18 @@ Fjärrfilen kunde inte kontrolleras FilinnehÃ¥ll redan synkroniserat Mapp kunde inte skapas + Förbjudna tecken är: / \\ < > : \" | ? * Var god vänta Oväntat problem; prova annat program för aktuell fil Ingen fil vald - Varning + Sänd länk till ... + Logga in med oAuth2. + Ansluter till oAuth2 servern… Webbplatsens identitet kunde inte verifieras - Servercertifikatet är inte tillförlitligt - Servercertifikatet har gÃ¥tt ut - Servercertifikatet är för nytt - Webbadressen matchar inte värdnamnet i certifikatet - Servercertifikatet kunde inte hämtas Vill du lita pÃ¥ detta certifikat ändÃ¥? Certifikatet kunde inte sparas Detaljer @@ -208,12 +207,40 @@ Till: Signatur: Algoritm: - Detta är en platshÃ¥llare + Certifikatet kunde inte visas. + - Ingen information om felet + Detta är en platshÃ¥llare + placeholder.txt + PNG Bild + 389 KB + 2012/05/18 12:23 PM + 12:23:45 Ladda upp bilder endast via WiFi /DirektUppladdning - Uppdatera konflikt + Uppdateringskonflikt Serverns fil %s är inte synkroniserad med den lokala filen. Fortsätt för att skriva över filen pÃ¥ servern. BehÃ¥ll bÃ¥da Skriv över Ladda inte upp + Förhandsvisa bild + Denna bild kan inte visas + %1$s kunde inte kopieras till %2$s lokal mapp + Fel vid direktuppladdning\" + Misslyckades vid direktuppladdning + Sammanfattning av alla misslyckade uppladdningar + välj alla + försökt igen med alla valda + radera alla valda frÃ¥n uppladdningskön + försök igen att ladda upp bilden: + Ladda fler bilder + Du är inte ansluten, direktuppladdning ej möjligt + Felmeddelande: + Vänligen kontrollera dina serverkonfiguration. Din kvot kan ha överskridits. + Kan inte dela denna fil eller mapp. Se till att den existerar + Ett fel uppstod vid försök att dela denna fil eller mapp + Kan inte ta bort delningen för denna fil eller mapp. Den existerar inte. + Ett fel uppstod vid försök att sluta dela denna fil eller mapp + Skicka + Kopiera länk + Kopierat till urklipp diff --git a/res/values-ta-rLK/strings.xml b/res/values-ta-rLK/strings.xml index c032781b..86e4f293 100644 --- a/res/values-ta-rLK/strings.xml +++ b/res/values-ta-rLK/strings.xml @@ -1,67 +1,32 @@ - OwnCloud - கடவுச்சொல்: - பயனாளர் பெயர் - புகுபதிகை - உங்களுடைய ownCloud இற்கு வரவேற்கின்றோம் - கோப்புகள் - இசை - தொடர்புகள் - நாட்காட்டி - பக்க அடையாளங்கள் - அமைப்புகள் - கணக்கு அமைப்பு - உங்களுடைய சாதனத்தில் ownCloud கணக்குகள் இல்லை. இந்த செயலிகளை பயன்படுத்துவதற்கு நீங்கள் கணக்கொன்றை உருவாக்க வேண்டும். - ownCloud Android சேவைப் பயனர்⏎ ⏎ பதிப்பு: %1$s - மீள் ஏற்றுக பதிவேற்றுக மற்ற செயலிகளிலிருந்து உள்ளடக்கம் கோப்புகள் - அடைவை உருவாக்குக - தேடுதல் அமைப்புகள் + விவரங்கள் பொதுவான - சாதனம் கண்காணிப்பு - புதிய அமர்வை சேர்க்க - படத்தின் சிறிய வடிவத்தை உருவாக்கு - கணக்கொன்றை தெரிவுசெய்க - உங்களுடைய எந்த கணக்கு செயலியை பயன்படுத்தும் என்பதை தெரிவுசெய்க - சாதனம் கண்காணிப்பு - உங்களுடைய சாதனத்தின் இருப்பிடத்தை அறிவதற்கு ownCloud ஐ இயலுமைப்படுத்துக - உங்களுடைய ownCloud ஆனது இந்த சாதனத்தின் கண்காணிப்புகளை வைத்துக்கொள்ளும், - இடைவேலையை இற்றைப்படுத்துக - ஒவ்வொரு %1$s நிமிடங்களையும் இற்றைப்படுத்துக + மேலதிக கணக்குகள் கணக்குகளை நிர்வகிக்க - ownCloud App PIN - உங்களுடைய ownCloud சேவைப் பயனரை பாதுகாக்க + App PIN + உங்களுடைய சேவைப் பயனரை பாதுகாக்க உடனடி பதிவேற்றலை இயலுமைப்படுத்துக கமராவினால் எடுக்கப்பட்ட படங்கள் உடனடியாக பதிவேற்றப்பட்டன - ownCloud URL + உதவி பயனாளர் பெயர் கடவுச்சொல் - ownCloud இற்கு நான் புதிது - பிழையான URL தரப்பட்டுள்ளது - பிழையான அமர்வு பெயர் கோப்புகள் - பதிவேற்றுவதற்கு கோப்புகள் தெரிவுசெய்யப்படவில்லை - பயனாளர் பெயர் - கடவுச்சொல் - வலைய முகவரி - கடவுச்சொல்லை தெரியப்படுத்தவா? - உங்களுடைய ownCloud இற்கு இணைக்க இணைக்க பதிவேற்றுக ஒரு கணக்கும் அறியப்படவில்லை - உங்களுடைய சாதனத்தில் ownCloud கணக்குகள் இல்லை. தயவுசெய்து முதலில் ஒரு கணக்கை அமைக்கவும் + உங்களுடைய சாதனத்தில் %1$s கணக்குகள் இல்லை. தயவுசெய்து முதலில் ஒரு கணக்கை அமைக்கவும் அமைப்பு விலகுக பதிவேற்றுவதற்கு உள்ளடக்கங்கள் இல்லை ஒரு உள்ளடக்கமும் பெறப்படவில்லை. பதிவேற்றுவதற்கு ஒன்றும் இல்லை - பகிரப்பட்ட உள்ளடக்ககங்களை அணுகுவதற்கு ownCloud அனுமதிக்கமாட்டாது + பகிரப்பட்ட உள்ளடக்ககங்களை அணுகுவதற்கு %1$s அனுமதிக்கமாட்டாது பதிவேற்றல் - பதிவேற்றுவதற்கு அடைவை உருவாக்குக இந்த கோப்புறையில் எந்த கோப்பும் இல்லை. \"பதிவேற்றல்\" பட்டி தெரிவு மூலம் புதிய கோப்புகளை பதிவேற்றமுடியும். மேலதிக தகவல்களை காட்சிப்படுத்துவதற்கு கோப்பின் மேல் தட்டுக. அளவு: @@ -69,9 +34,6 @@ உருவாக்கப்பட்டது: மாற்றப்பட்டது: பதிவிறக்குக - மீள் ஏற்றுக - மீள் ஏற்றுக - திறக்க பதிவேற்றும்போது கோப்பின் பெயரானது %1$s ஆக பெயர்மாற்றப்பட்டது ஆம் இல்லை @@ -80,21 +42,19 @@ பதிவேற்றலை இரத்து செய்க இரத்து செய்க சேமிக்க மற்றும் amp; வெளியேறு - ownCloud இலிருந்து விலகு வழு பற்றி + கடவுச்சொல்லை மாற்றுக கணக்கை நீக்குக கணக்கை உருவாக்குக பதிவேற்றல் படிவம் - அடைவு பெயர் + கோப்புறை பெயர் பதிவேற்றல்... %1$d%% பதிவேற்றல்g %2$s வெற்றிகரமாக பதிவேற்றப்பட்டது %1$s வெற்றிகரமாக பதிவேற்றப்பட்டது - %1$d கோப்புகள் வெற்றிகரமாக பதிவேற்றப்பட்டது பதிவேற்றல் தோல்வியுற்றது பதிவேற்றலின் %1$s தை முடிக்கமுடியவில்லை - பதிவேற்றல் தோல்வியுற்றது : %1$d/%2$d கோப்புகள் பதிவேற்றப்பட்டது பதிவிறக்கப்படுகிறது.... %1$d%% பதிவிறக்கல் %2$s வெற்றிகரமான பதிவிறக்கல் @@ -102,64 +62,35 @@ பதிவிறக்கல் தோல்வியுற்றது பதிவிறக்கலின் %1$s தை முடிக்கவில்லை கணக்கை தெரிவுசெய்க - தொடர்புகள் ஒத்திசைவாக்கல் தோல்வியுற்றது ஒத்திசைவாக்கலின் %1$s ஆனதை முடிக்கமுடியவில்லை முரன்பாடுகள் கண்டுப்பிடிக்கப்பட்டன கோப்புகள் %1$d இலுள்ள உள்ளடக்கங்களை ஒத்திசைவாக்கமுடியாது (%2$d முரன்பாடுகள்) - பாதுகாப்பான இணைப்பை பயன்படுத்துக - ownCloud இனால் உங்களுடைய சாதனத்தை கண்காணிக்க முடியவில்லை. தயவுசெய்து உங்களுடைய இருப்பிட அமைப்புக்களை சரிபார்க்கவும் தயவுசெய்து உங்களுடைய App PIN ஐ உள்ளிடுக - தயவுசெய்து உங்களுடைய புதிய App PIN ஐ உள்ளிடுக - ownCloud இன் App PIN ஐ உள்ளிடுக + இன் App PIN ஐ உள்ளிடுக செயலி தொடங்கும் ஒவ்வொரு நேரமும் PIN கேட்கப்படுகின்றது. - தயவுசெய்து மீண்டும் ownCloud App PIN ஐ உள்ளிடுக - உங்களுடைய ownCloud App PIN ஐ அகற்றுக - இரண்டு ownCloud App PIN களும் ஒன்றே அல்ல - தவறான ownCloud App PIN - ownCloud App PIN அகற்றப்பட்டது - ownCloud App PIN சேமிக்கப்பட்டது - - 15 நிமிடங்கள் - 30நிமிடங்கள் - 60 நிமிடங்கள் - - - 15 - 30 - 60 - + தயவுசெய்து மீண்டும் App PIN ஐ உள்ளிடுக + உங்களுடைய App PIN ஐ அகற்றுக + இரண்டு App PIN களும் ஒன்றே அல்ல + தவறான App PIN + App PIN அகற்றப்பட்டது + App PIN சேமிக்கப்பட்டது புகுபதிகைக்கு முயற்சிக்கின்றது... வ​லைய​மைப்பு இணைப்பு இல்லை - எந்தவொரு வலையமைப்பு இணைப்பும் கண்டுப்பிடிக்கப்படவில்லை, இணைய இணைப்பை சரிபார்த்துவிட்டு மீண்டும் முயற்சிக்கவும். - எப்படியாவது இணைக்க பாதுகாப்பான இணைப்பு காணப்படவில்லை. - செயலியினால் சேவையகத்துடன் பாதுகாப்பான இணைப்பை ஏற்படுத்த முடியவில்லை. பாதுகாப்பற்ற இணைப்புகளும் காணப்படுகின்றன. நீங்கள் தொடரலாம் அல்லது இரத்துசெய்யலாம். இணைப்பு நிறுவப்பட்டது இணைப்பு சோதிக்கப்படுகிறது..... - பிறழ்வான ownCloud தகவமைப்பு - உங்களுடைய ownCloud சரியாக தகவமைக்கப்படாததாக தெரிகிறது. மேலதிக தகவல்களுக்கு நிர்வாகியை தொடர்புகொள்ளவும் + பிறழ்வான தகவமைப்பு அறியப்படாத வழு ஏற்பட்டுள்ளது! - அறியப்படாத வழு ஏற்பட்டுள்ளது. தயவுசெய்து உங்களுடைய சாதனத்தினூடாக ஆசிரியரையும் சேர்க்கப்பட்டுள்ள பதிகையையும் தொடர்புகொள்க. ஓம்புனரை கண்டுப்பிடிக்கமுடியவில்லை - நுழைக்கப்பட்ட ஓம்புனரை கண்டுப்பிடிக்கமுடியவில்லை. தயவுசெய்து ஓம்புனர் பெயரையும் சேவையகத்தின் வழங்குதலையும் சரிப்பார்த்து மீண்டும் முயற்சிக்கவும். - ownCloud காணப்படவில்லை - தரப்பட்ட பாதையினூடாக செயலியினால் ownClound ஐ கண்டுப்பிடிக்க முடியவில்லை. தயவுசெய்து உங்களுடைய பாதையை சரிபார்த்து மீண்டும் முயற்சிக்கவும். + காணப்படவில்லை இந்த சேவையகம் பதில் கொடுப்பதற்கு நீண்ட நேரம் எடுக்கின்றது பிறழ்வான URL SSL இன் தொடக்கநிலை தோல்வியுற்றது - உறுதிப்படுத்தப்படாத SSL சேவையகத்தின் அடையாளம் - அங்கீகரிக்கப்படாத ownCloud சேவையகம் பதிப்பு + அங்கீகரிக்கப்படாத சேவையகம் பதிப்பு இணைப்பை நிறுவமுடியாது பாதுகாப்பான இணைப்பு உருவாக்கப்பட்டது. - புகுபதிகை விவரங்கள் - செயலி எதிர்பாராமல் முடிவுற்றது. உங்களுக்கு முறிவு அறிக்கை ஒன்றை சமர்ப்பிப்பதற்கு விருப்பம்மா? - அறிக்கையை அனுப்பவும் - அறிக்கையை அனுப்பவேண்டாம் - நீட்சிகள் காணப்படுகின்றன! - உங்களுடைய ownCloud முன்னேற்றமான நீட்சிக்கு ஆதரவளிப்பதாக தெரிகிறது. android க்கு நீட்சிகள் இருக்கின்றனவா என அறிய ஆவலா? நவீன கோப்பை வைத்திருக்கவும் - பகிர்வு பெயர்மாற்றம் அகற்றுக உங்களுக்கு உண்மையாக %1$s ஐ அகற்றவேண்டுமா? @@ -175,17 +106,14 @@ பெயர்மாற்றத்தை முற்றாக முடிக்கமுடியவில்லை தொலை கோப்பை சரிபார்க்கமுடியவில்லை கோப்பு உள்ளடக்கங்கள் ஏற்கவே ஒத்திசைக்கப்படுள்ளன - அடைவுகளை உருவாக்க முடியவில்லை சிறிது நேரம் காத்திருங்கள் எதிர்பாராத பிரச்சினை ; தயவுசெய்து கோப்பை தெரிவுசெய்ய மற்ற செயலியை பயன்படுத்தவும் ஒரு கோப்பும் தெரிவுசெய்யப்படவில்லை - எச்சரிக்கை தளத்தின் அடையாளத்தை உறுதிப்படுத்தமுடியவில்லை - சேவையகத்தின் சான்றிதழ் நம்ப தகுந்ததாக இல்லை - சேவையகத்தின் சான்றிதழ் காலாவதியாகிவிட்டது - சேவையகத்தின் சான்றிதழ் மிகவும் இளமையானது சான்றிதழில் உள்ள ஓம்புனரின் பெயர் URL உடன் ஒத்திருக்கவில்லை - சேவையக சான்றிதழை பெற முடியவில்லை இந்த சான்றிதழை நம்புகிறீர்களா? சான்றிதழை சேமிக்க முடியவில்லை விவரங்கள் @@ -203,7 +131,7 @@ இற்கு கையொப்பம்: நெறிமுறை - இது ஒரு placeholder + இது ஒரு placeholder WiFi ஊடாக மட்டும் படங்களை பதிவேற்றுக இற்றைப்படுத்தலில் முரண்பாடு இடத்துரி கோப்புடன் தொலைவு கோப்பு %s ஒத்திசைவாக்கப்படவில்லை. தொடர்ந்து மேற்கொண்டால் சேவையகத்தில் உள்ள கோப்பின் உள்ளடக்கம் மாற்றப்படும். diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml new file mode 100644 index 00000000..ab951924 --- /dev/null +++ b/res/values-te/strings.xml @@ -0,0 +1,16 @@ + + + కొత్త సంచయం + అమరికలు + పంపించు + మరిన్ని + సహాయం + వాడుకరి పేరు + సంకేతపదం + అవును + కాదు + రద్దుచేయి + పొరపాటు + సంచయం పేరు + పంపించు + diff --git a/res/values-th-rTH/strings.xml b/res/values-th-rTH/strings.xml index 2b3f7805..5af867cc 100644 --- a/res/values-th-rTH/strings.xml +++ b/res/values-th-rTH/strings.xml @@ -1,67 +1,34 @@ - ownCloud - รหัสผ่าน: - ชื่อผู้ใช้: - เข้าสู่ระบบ - ยินดีต้อนรับเข้าสู่ ownCloud ของคุณ - ไฟล์ - เพลง - ข้อมูลผู้ติดต่อ - ปฏิทิน - รายการโปรด - ตั้งค่า - ตั้งค่าบัญชี - ไม่มีบัญชี ownCloud ของคุณอยู่บนอุปกรณ์ของคุณ หากต้องการใช้งานแอปตัวนี้ คุณจำเป็นต้องสร้างบัญชีใหม่ - โปรแกรมไคลเอนต์ ownCloud สำหรับใช้งานกับแอนดรอยด์\n\nรุ่น: %1$s - รีเฟรช อัพโหลดไฟล์ เนื้อหาจากแอปฯอื่นๆ ไฟล์ - สร้างไดเร็กทอรี่ - ค้นหา + โฟลเดอร์ใหม่ ตั้งค่า + รายละเอียด + ส่ง ทั่วไป - ตรวจหาอุปกรณ์ - เพิ่มเซสชั่นใหม่ - สร้างรูปภาพขนาดย่อ - เลือกบัญชี - เลือกบัญชีที่ต้องการใช้งานกับแอปตัวนี้ - ตรวจหาอุปกรณ์ - เปิดให้ ownCloud ตรวจหาตำแหน่งที่อยู่อุปกรณ์ของคุณ - ownCloud กำลังติดตามอุปกรณ์นี้อยู่ - ช่วงเวลาการอัพเดท - อัพเดททุก %1$s นาที + มาก บัญชี บริหารจัดการบัญชี - ownCloud App PIN - ป้องกันโปรแกรมไคลเอนต์ ownCloud ของคุณ + App PIN + ป้องกันโปรแกรมไคลเอนต์ ของคุณ เปิดใช้งานระบบอัพโหลดได้ทันที อัพโหลดรูปภาพจากกล้องขึ้นไปทันที - ownCloud URL + ช่วยเหลือ ชื่อผู้ใช้ รหัสผ่าน - ข้าพเจ้าเพิ่งใช้งาน ownCloud - ที่อยู่ URL ไม่ถูกต้อง - ชื่อเซสชั่นไม่ถูกต้อง ไฟล์ - ยังไม่ได้เลือกไฟล์สำหรับอัพโหลด - ชื่อผู้ใช้ - รหัสผ่าน - ที่อยู่เว็บ - แสดงรหัสผ่าน? - เชื่อมต่อกับ ownCloud ของคุณ เชื่อมต่อ อัพโหลด ไม่พบบัญชีที่ต้องการ - ไม่มีบัญชี ownCloud บนอุปกรณ์ของคุณ กรุณาตั้งค่าบัญชีของคุณก่อน + ไม่มีบัญชี %1$s บนอุปกรณ์ของคุณ กรุณาตั้งค่าบัญชีของคุณก่อน ตั้งค่า ออก ยังไม่มีเนื้อหาให้ต้องอัพโหลด ยังไม่ได้รับเนื้อหา ไม่มีอะไรให้ต้องอัพโหลด - ownCloud ไม่อนุญาตให้เข้าถึงเนื้อหาที่ถูกแชร์ไว้ + %1$s ไม่อนุญาตให้เข้าถึงเนื้อหาที่ถูกแชร์ไว้ กำลังอัพโหลด - สร้างไดเร็กทอรี่สำหรับอัพโหลด ยังไม่มีไฟล์อยู่ในโฟลเดอร์นี้.\nสามารถเพิ่มไฟล์ใหม่ได้จากตัวเลือกในเมนู \"อัพโหลด\". แตะที่ไฟล์ เพื่อแสดงข้อมูลเพิ่มเติม ขนาด: @@ -69,9 +36,6 @@ สร้างเมื่อ: แก้ไขเมื่อ: ดาวน์โหลด - รีเฟรช - ดาวน์โหลดใหม่อีกครั้ง - เปิด ไฟล์ได้ถูกเปลี่ยนชื่อเป็น %1$s ในระหว่างการอัพโหลด ตกลง ไม่ตกลง @@ -80,21 +44,20 @@ ยกเลิกการอัพโหลด ยกเลิก บันทึก & ออก - ออกจาก ownCloud ข้อผิดพลาด + ข้อผิดพลาดที่ไม่ทราบสาเหตุ เกี่ยวกับเรา + เปลี่ยนรหัสผ่าน ลบบัญชี สร้างบัญชีใหม่ อัพโหลดไฟล์จาก... - ชื่อไดเร็กทอรี่ + ชื่อโฟลเดอร์ กำลังอัพโหลด... %1$d%% กำลังอัพโหลด %2$s อัพโหลดเสร็จสิ้น %1$s ได้ถูกอัพโหลดเรียบร้อยแล้ว - ไฟล์ %1$d ได้ถูกอัพโหลดเรียบร้อยแล้ว อัพโหลดล้มเหลว การอัพโหลด %1$s ไม่สามารถดำเนินการให้เสร็จสมบูรณ์ได้ - การอัพโหลดล้มเหลว: ไฟล์ %1$d/%2$d ถูกอัพโหลดแล้ว กำลังดาวน์โหลด ... %1$d%% กำลังดาวน์โหลด %2$s ดาวน์โหลดเสร็จสิ้น @@ -102,69 +65,44 @@ ดาวน์โหลดล้มเหลว การดาวน์โหลด %1$s ไม่สามารถดำเนินการให้เสร็จสมบูรณ์ได้ เลือกบัญชี - ข้อมูลผู้ิติดต่อ การเชื่อมผสานข้อมูลล้มเหลว การเชื่อมผสานข้อมูลของ %1$s ไม่สามารถดำเนินการให้เสร็จสมบูรณ์ได้ ตรวจพบความขัดแย้ง %1$d ไฟล์ kept-in-sync ไม่สามารถผสานเชื่อมข้อมูลได้ ไฟล์ Kept-in-sync ล้มเหลว เนื้อหาของไฟล์ %1$d ไม่สามารถผสานเชื่อมข้อมูลได้ (ความขัดแย้ง %2$d รายการ) - ใช้การเชื่อมต่อที่มีการรักษาความปลอดภัย - ownCloud ไม่สามารถตรวจหาอุปกรณ์ของคุณได้ กรุณาตรวจสอบการตั้งค่าตำแหน่งที่อยู่ของคุณ + มีบางแฟ้มข้อมูลในเครื่องถูกลืม + ย้ายทั้งหมด + ทุกแฟ้มข้อมูลถูกย้ายเรียบร้อยแล้ว + มีบางแฟ้มข้อมูลไม่สามารถย้ายได้ + ต้นทาง: %1$s + ปลายทาง: %1$s + ไม่มีพื้นที่เหลือเพียงพอสำหรับคัดลอกแฟ้มข้อมูลที่เลือกไว้ไปที่โฟลเดอร์ %1$s คุณต้องการย้ายมันแทนหรือไม่ กรุณาใส่ PIN แอปของคุณ - กรุณาใส่รหัส PIN แอปตัวใหม่ของคุณ - กรอกรหัส PIN ของ ownCloud App + กรอกรหัส PIN ของ App หมายเลข PIN ดังกล่าวจะถูกร้องขอทุกครั้งที่เริ่มใช้งานแอปฯ - กรุณากรอกรหัส PIN ของแอป ownCloud ใหม่อีกครั้ง - ลบรหัส PIN แอป ของ ownCloud ของคุณ - รหัส ownCloud App API ไม่ตรงกัน - รหัส PIN ownCloud App ไม่ถูกต้อง - รหัส PIN แอปสำหรับ ownCloud ถูกลบออกแล้ว - จัดเก็บรหัส PIN แอป ของ ownCloud แล้ว - - 15 นาที - 30 นาที - 60 นาที - - - 15 - 30 - 60 - + กรุณากรอกรหัส PIN ของแอป App ใหม่อีกครั้ง + ลบรหัส PIN แอป ของ App ของคุณ + รหัส App API ไม่ตรงกัน + รหัส PIN App ไม่ถูกต้อง + รหัส PIN แอปสำหรับ ถูกลบออกแล้ว + จัดเก็บรหัส PIN แอป ของ แล้ว กำลังเข้าสู่ระบบ... ไม่มีการเชื่อมต่อเครือข่ายใดๆ - ไม่พบการเชื่อมต่อเครือข่ายใด กรุณาตรวจสอบสถานะการเชื่อมต่ออินเทอร์เน็ตของคุณ แล้วลองใหม่อีกครั้ง - เชื่อมต่ออยู่แล้ว การเชื่อมต่อแบบรักษาความปลอดภัยไม่สามารถใช้งานได้ - แอพพลิเคชั่นยังไม่ได้ติดตั้งการเชื่อมต่อแบบปลอดภัยกับเซิร์ฟเวอร์ ถึงแม้ว่าจะมีการเชื่อมต่อแบบที่ยังไม่ได้กำหนดให้มีความปลอดภัย คุณสามารถดำเนินการต่อไป หรือยกเลิกได้ ติดตั้งการเชื่อมต่อแล้ว กำลังทดสอบการเชื่อมต่อ... - การกำหนดค่า Malformed ownCloud - ดูเหมือนว่าค่าตัวอย่าง ownCloud ของคุณยังไม่ได้ถูกกำหนดค่าอย่างถูกต้อง กรุณาติดต่อผู้ดูแลระบบของคุณ สำหรับรายละเอียดเพิ่มเติม + การกำหนดค่า Malformed เซิร์ฟเวอร์ เกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ! - เกิดข้อผิดพลาดโดยไม่ทราบสาเหตุ กรุณาติดต่อเจ้าของและดูบันทึกข้อมูลการใช้งานจากอุปกรณ์ของคุณ ไม่พบโฮสต์ที่ต้องการ - ไม่พบโฮสต์ตามข้อมูลที่กรอก กรุณาตรวจสอบชื่อโฮสต์และเซิร์ฟเวอร์ที่สามารถใช้งานได้ แล้วลองใหม่อีกครั้ง - ไม่พบค่าตัวอย่าง ownCloud - แอพพลิเคชั่นไม่สามารถค้นพบค่าตัวอย่าง ownCloud ตามตำแหน่งพาธที่ระบุไว้ได้ กรุณาตรวจสอบตำแหน่งของคุณ แล้วลองใหม่อีกครั้ง + ไม่พบค่าตัวอย่างเซิร์ฟเวอร์ เซิร์ฟเวอร์ดังกล่าวใช้เวลาตอบสนองนานเกินไป Malformed URL การเตรียมใช้งาน SSL ล้มเหลว - ข้อมูลการเข้าใช้งานเซิร์ฟเวอร์ SSL ยังไม่ได้ยืนยันความถูกต้อง - รุ่นของเซิร์ฟเวอร์ ownCloud ไม่เป็นที่รู้จัก + รุ่นของเซิร์ฟเวอร์เซิร์ฟเวอร์ ไม่เป็นที่รู้จัก ไม่สามารถเชื่อมต่อได้ ดำเนินการติดตั้งการเชื่อมต่อแบบปลอดภัยเรียบร้อย - รายละเอียดการเข้าสู่ระบบ - ชื่อล็อคอิน/รหัสผ่าน ไม่ถูกต้อง - กรอกตำแหน่งพาธไม่ถูกต้อง - เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์ รหัส %1$d - แอพพลิเคชั่นได้สิ้นสุดการทำงานอย่างไม่คาดคิด คุณต้องการแจ้งข้อมูลความบกพร่องดังกล่าวหรือไม่? - ส่งรายงาน - ไม่ต้องส่งรายงาน - ส่วนเสริมที่สามารถใช้งานได้! - ดูเหมือนว่าค่าตัวอย่าง ownClud ของคุณกำลังสนับสนุนการใช้งานกับส่วนเสริมขั้นสูงอยู่ คุณต้องการดูส่วนเสริมอื่นๆที่สามารถใช้งานกับ android เพิ่มหรือไม่? ปรับปรุงไฟล์ให้ทันสมัยอยู่เสมอ - แชร์ เปลี่ยนชื่อ ลบออก คุณแน่ใจแล้วหรือว่าต้องการลบ %1$s? @@ -180,17 +118,14 @@ ไม่สามารถเปลี่ยนชื่อได้ ไม่สามารถตรวจสอบไฟล์ระยะไกลได้ เนื้อหาของไฟล์ถูกผสานข้อมูลอยู่แล้ว - ไม่สามารถสร้างไดเร็กทอรี่ได้ กรุณารอสักครู่ เกิดปัญหาที่ไม่คาดคิด ; กรุณาลองใช้งานแอปฯอื่นๆ เพื่อเลือกไฟล์ ไม่มีไฟล์ที่ถูกเลือก - คำเตือน ไม่สามารถยืนยันความถูกต้องของตัวตนของเว็บไซต์ได้ - ใบรับรองความปลอดภัยของเซิร์ฟเวอร์ไม่น่าเชื่อถือ - ใบรับรองความปลอดภัยของเซิร์ฟเวอร์หมดอายุแล้ว - ใบรับรองความปลอดภัยของเซิร์ฟเวอร์ยังมีอายุน้อยอยู่ - URL ดังกล่าวไม่ตรงกันกับชื่อโฮสต์ที่อยู่ในใบรับรอง - ไม่สามารถขอรับใบรับรองความปลอดภัยของเซิร์ฟเวอร์ได้ คุณต้องการให้ความไว้วางใจในใบรับรองความปลอดภัยนี้หรือไม่? ไม่สามารถบันทึกใบรับรองความปลอดภัยได้ รายละเอียด @@ -208,7 +143,7 @@ ถึง: ลายเซ็นต์: อัลกอริทึ่ม: - นี่เป็นตัวยึด + นี่เป็นตัวยึด อัพโหลดรูปภาพผ่านทาง WiFi เท่านั้น /อัพโหลดทันที ปรับปรุงปัญหาความขัดแย้ง @@ -216,4 +151,5 @@ เก็บไว้ทั้งสองอย่าง เขียนทับ ไม่ต้องอัพโหลด + ส่ง diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index be221b2b..5f56d502 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -1,76 +1,62 @@ - ownCloud - Şifre: - Kullanıcı Adı: - Giriş - ownCloud\'ınıza hoşgeldiniz - Dosyalar - Müzik - Kişi Rehberi - Takvim - Yer İmleri - Ayarlar - Kurulum Hesabi - Cihazınızda ownCloud hesabı bulunmamaktadır. Bu uygulamayı kullanabilmeniz için ownCloud hesabı oluşturmalısınız. - Eşitleme hesabi - Dosya yükle + %1$s Android Uygulaması + sürüm %1$s + Hesabı yenile + Yükle Diğer uygulamalardan içerik Dosyalar - Klasör yarat - Arama + ile aç + Yeni klasör Ayarlar + Detaylar + Gönder Genel - Cihaz tabiki - Yeni oturum ekle - Küçük resim oluştur - Oturum seç - Uygulamanın hangi hesaplarınızı kullanması gerektiğini seçiniz. - Cihaz takibi - Cihazınınızın konumunu takip etmesi için ownCloud\'u aktif edin - ownCloud\'unuz bu cihazı takip etmektedir - Güncelleme aralığı - Her %1$s dakikada güncelle + Daha fazla Hesaplar Hesapları yönet - ownCloud App PIN + App PIN İstemcinizi koruyun Anında yükleme etkinleştir Anında çekilen fotoğrafları yükle - ownCloud URL + Günlük tutmayı etkinleştir + Bu, sorunları günlük dosyasına kaydetmek için kullanılır + Günlük geçmişi + Bu, kayıtlı günlük dosyalarını görüntüler + Geçmişi Sil + Yardım + Bir arkadaşa öner + Geribildirim + İzlenim + %1$s uygulamasını akıllı telefonunda dene! + Sunucuyu kontrol et + Sunucu Adresi https://… Kullanıcı Adi: Şifre: - ownCloud\'da yeniyim. - Geçersiz URL girildi - Yanlış oturum ismi + %1$s senin için yeni mi? Dosyalar - Yüklemek için dosya seçilmedi - Kullanıcı Adi: - Şifre: - Web adres - Şifre göster? - ownCloud\'nıza bağlanin Bağlan Yükle - Hesap bulunamadi - Cihazınızda ownCloud hesabı bulunmamaktadır. Lütfen öncelikle bir hesap ayarı giriniz. + Yükleme klasörünü seçin: + Hesap bulunamadı + Cihazınızda %1$s hesabı bulunmamaktadır. Lütfen öncelikle bir hesap ayarlayın. Kurulum Çıkış Yüklenecek içerik yok Yüklenecek içerik yok. Yüklenecek dosya yok. - ownCloud, paylaşılan içeriğe erişim izni vermiyor + %1$s, paylaşılan içeriğe erişim izni vermiyor Yükleniyor - Yükleme için dizin oluştur - Klasörde dosya yok. Yeni dosyalar yükle\'ye tıklayarak eklenebilir. - Ek bilgileri görmek için dosyaya tıklayınız. + Bu klasörde dosya yok.\nYeni dosyalar \"Yükle\" menü seçeneği ile eklenebilir. + Ek bilgileri görmek için dosyaya dokunun. Boyut: Tür: - Yaratma: + Oluşturulma: Değiştirme: İndir - Tazele - Tekrar indir - Aç + Dosyayı yenile + Dosya adı, yükleme sırasında %1$s olarak değiştirildi + Paylaşma bağlantısı + Bağlantı paylaşımını kaldır Evet Hayır OK @@ -78,86 +64,104 @@ Yüklemeyi iptal et İptal Kaydet %amp; Kapat - Çıkış Hata + Yükleniyor... + Bilinmeyen hata Hakkında + Parola değiştir Hesabı sil Hesap oluştur ... dan dosya yükle - Dizin ismi + Klasör ismi Yüklüyor ... %1$d%% Yükleniyor %2$s Yükleme başarılı %1$s kadarı başarıyla yüklendi. - %1$d dosya başarılı bir şekilde yüklendi Yükleme başarısız %1$s yüklenmedi tamamlanamadı - Yükleme başarısız : %1$d/%2$d dosya yüklendi İndiriyor ... %1$d%% İndiriliyor %2$s İndirme başarılı %1$s başarıyla indirildi İndirilme başarısız %1$s indirilmesi tamamlanamadı + Henüz indirilemedi Hesap seçiniz - Kontaklar Eşitleme başarısız - %1$s Senkronizasyonu tamamlanamadı + %1$s eşitlemesi tamamlanamadı + %1$s için geçersiz parola Çakışma bulundu - Güvenli bağlanti kullan - ownCloud cihazınızı takip edemiyor. Lütfen konum ayarlarınızı kontrol ediniz + %1$d korumalı eşitleme dosyası, eşitlenemedi + Korunan dosya eşitlemesi başarısız + %1$d dosya eşitlenemedi (%2$d çakışma) + Bazı yerel dosyalar unutuldu + %2$s klasöründeki %1$d dosya şuraya kopyalanamadı + 1.3.16 sürümünden sonra, bu aygıttan yüklenen dosyalar bir dosya birden fazla hesapla eşitlendiğinde veri kaybının önlenebilmesi için %1$s yerel klasörüne kopyalanır.\n\nBu değişiklikten dolayı, bu uygulamanın yüklenmiş tüm önceki sürümündeki dosyalar %2$s klasörüne kopyalandı. Ancak hesap eşitlenmesi sırasında bu işlemin tamamlanmasını engelleyen bir hata oluştu. Dosyayı/dosyaları olduğu gibi bırakabilir ve %3$s bağlantısını kaldırabilirsiniz veya dosyayı/dosyaları %1$s dizinine taşıyıp %4$s bağlantılarını koruyabilirsiniz.\n\nAşağıda listelenenler yerel dosyalar ve bağlı oldukları %5$s içerisindeki uzak dosyalardır. + %1$s klasörü artık mevcut değil. + Tümünü taşı + Tüm dosyalar taşındı + Bazı dosyalar taşınamadı + Yerel: %1$s + Uzak: %1$s + Seçilen dosyaları %1$s dizinine kopyalamak için yeterli alan yok. Bunun yerine dosyayı taşımak ister misiniz? Lütfen uygulama PIN\'ınızı giriniz - Lütfen yeni uygulama PIN\'ınızı giriniz - ownCloud App PIN giriniz + App PIN giriniz PIN uygulama yeniden başladığında tekrar sorulacak - Lütfen, ownCloud App PIN ni tekrar giriniz - ownCloud App PIN\'nizi kaldırınız. - Her iki ownCloud App PIN aynı değil. - Yanlış ownCloud App PIN - ownCloud App PIN kaldırıldı - ownCloud App PIN saklandı - - 15 Dakika - 30 Dakika - 60 Dakika - - - 15 - 30 - 60 - + Lütfen, App PIN ni tekrar giriniz + App PIN\'nizi kaldırınız. + Her iki App PIN aynı değil. + Yanlış App PIN + App PIN kaldırıldı + App PIN saklandı + %1$s müzik çalar + %1$s (oynatılıyor) + %1$s (yükleniyor) + %1$s yeniden oynatım sonlandırıldı + Herhangi bir medya öğesi bulunamadı + Tanımlı hesap yok + Dosya doğru bir hesapta değil + Codec desteklenmiyor + Medya öğesi okunamadı + Medya öğesi doğru bir şekilde kodlanmadı + Oynatmaya çalışırken zaman aşımına uğradı + Medya öğesi aktarılamadı + Ortam dosyası mevcut ortam oynatıcı ile çalınamaz + %1$s oynatılmaya çalışılırken güvenlik hatası oluştu + %1$s oynatılmaya çalışılırken girdi hatası oluştu + %1$s oynatılmaya çalışılırken beklenmeyen bir hata oluştu + Başa sar düğmesi + Oynat veya duraklat düğmesi + Hızlı ileri düğmesi Giriş için deneniyor Ağ bağlantısı yok - Ağ bağlantısı bulunamadı. İnternet bağlantı ayarlarınızı kontrol edin ve tekrar deneyin. - Her halükarda bağlan Günvenli bağlantı mevcut değil. - Uygulama sunucu ile güvenli bağlantı kuramadı. Buna rağmen güvensiz bağlantı mevcut. Devam edebilirsiniz veya iptal edebilirsiniz. Bağlantı kuruldu Bağlantı kontrol ediliyor ... - Hatalı ownCloud ayarı. - Görünüşe bakılırsa ownCloud servisiniz doğru ayarlanmamış. Detaylı bilgi için sistem yöneticinize başvurunuz. + Hatalı sunucu ayarı. + Cihazda aynı kullanıcı adı ve sunucu için bir hesap zaten mevcut + Girilen kullanıcı bu hesabın kullanıcısı ile eşleşmiyor Bilinmeyen hata oluştu. - Bilinmeyen hata oluştu. Lütfen cihazınızdaki hata kayıtlarını ekleyerek programı yazan kişiyle irtibata geçiniz. Anabilgisayar bulunamadı - Girilen anabilgisayar bulunamadı. Anabilgisayaradı ve sunucu kullanılabilirliğini kontrol edin ve yeniden deneyin. - ownCloud servisi bulunamadı. - Uygulama verilen dizin yolunda ownCloud bulamadı. Lütfen dizin yolunu kontrol edin ve tekrar deneyin. + Sunucu örneği bulunamadı. Sunucu çok geç cevap veriyor Hatalı biçimlendirilmiş URL SSL başlatılmasında hata - Doğrulanmamış SSL sunucu kimliğini - Bilinmeyen ownCloud sunucu sürümü + SSL sunucu kimliği doğrulanamadı + Bilinmeyen sunucu sürümü Bağlantı kurulamadı Güvenli bağlantı sağlandı. - Giriş detayları - Geçersiz kullanıcı adı/parola - Uygulama umulmadık bir şekilde sonlandı. Hata raporunu göndermek ister misiniz ? - Rapor gönder - Rapor gönderme - Eklentiler mevcut! - Görünen o ki ownCloud servisiniz gelişmiş eklentileri desteklemektedir. Android için mevcut olan eklentileri görmek ister misiniz ? + Hatalı kullanıcı adı veya parola + Kimlik doğrulama başarısız oldu + Erişim, kimlik doğrulama sunucusu tarafından reddedildi + Beklenmedik durum. Lütfen, sunucu URL\'sini yeniden girin + Kimlik doğrulamanızın süresi doldu. Lütfen tekrar kimlik doğrulayın + Lütfen mevcut parolanızı girin + Oturumuzun süresi doldu. Lütfen tekrar bağlanın + Kimlik doğrulama sunucusuna bağlanılıyor... + Sunucu bu kimlik doğrulama yöntemini desteklemiyor + %1$s çoklu hesapları desteklemiyor + Bu sunucuya karşı kimlik doğrulama yapılamaz Dosyayı güncel tut - Paylaş İsim değiştir. Kaldır %1$s ları gerçekten kaldırmak istiyor musunuz ? @@ -171,22 +175,30 @@ Yeni bir isim girin Yerel kopya adlandırılamaz; farklı bir ad deneyin Yeniden adlandırılma tamamlanmadı - Dizin oluşturulamadı + Dosya teslim edilemedi + Dosya içerikleri zaten eşitlenmiş + Klasör oluşturulamadı + Yasaklı karakterler: / \\ < > : \" | ? * Bir süre bekleyin Beklenmeyen problem ; lütfen, dosya seçmek için diğer uygulamayı deneyin Hiçbir dosya seçilmedi - Uyarı + Bağlantıyı gönder ... + oAuth2 ile oturum aç + oAuth2 sunucusuna bağlanılıyor… Bu sitenin sertifikası doğrulanamadı - Sunucu sertifikası güvenilmez - Sunucu sertifikasının süresi geçmiş + Sunucu sertifikasının geçerli olacağı tarih - URL adresi sunucu isminin sertifikası ile uyuşmuyor - Sunucu sertifikası elde edilemedi Sertifikaya yine de güvenmek istiyor musunuz? Sertifika kaydedilemedi Ayrıntılar Gizle - Organizasyon: - Organizasyon birimi: + Verilen: + Veren: + Ortak ad: + Kurum: + Kuruluş birimi: Ülke: Eyalet: Konum: @@ -195,11 +207,40 @@ Kime: İmza: Algoritma: - Bu bir yer tutucudur + Sertifika gösterilemedi. + - Hata hakkında bilgi yok + Bu bir yer tutucudur + yertutucu.txt + PNG Resmi + 389 KB + 2012/05/18 12:23 ÖS + 12:23:45 Resimleri sadece WiFi bağlantısında yükle /AnındaYükle Çakışmayı güncelle + Uzaktaki %s dosyası, yerel dosya ile eşitlenemedi. İşleme devam etmek sunucudaki dosyanın içeriğini değiştirecektir. İkisini de koru Üzerine yaz Yükleme + Görüntü önizleme + Bu resim gösterilemez + %1$s, %2$s yerel dizine klasörüne + AnındaYükleme başarısız + Anında yüklemeler başarısız + Başarısız olan tüm anında yüklemelerin özeti + hepsini seç + tüm seçili olanları tekrar dene + yükleme kuyruğundaki tüm seçili olanları sil + Resmi yeniden yüklemeyi dene: + Daha fazla Görsel yükle + anında yükleme için çevrimiçi değilsiniz, bir şey yapma + Hata Mesajı: + Sunucu yapılandırmanızı kontrol edin. Kotanızı aşmış olabilirsiniz. + Bu dosya veya klasör paylaşılamıyor. Lütfen mevcut olup olmadığını denetleyin + Bu dosya veya klasörü paylaşmaya çalışılırken bir hata oluştu + Bu dosya veya klasörün paylaşımı kaldırılamadı. Mevcut değil. + Bu dosya veya klasör paylaşımı kaldırılmaya çalışılırken bir hata oluştu + Gönder + Bağlantıyı kopyala + Panoya kopyalandı diff --git a/res/values-ug/strings.xml b/res/values-ug/strings.xml new file mode 100644 index 00000000..3e7ae77d --- /dev/null +++ b/res/values-ug/strings.xml @@ -0,0 +1,41 @@ + + + يۈكلە + ھۆججەتلەر + يېڭى قىسقۇچ + تەڭشەكلەر + يوللا + ئادەتتىكى + تېخىمۇ كۆپ + ھېساباتلار + ياردەم + قايتۇرما ئىنكاس + ئىشلەتكۈچى ئاتى + ئىم + ھۆججەتلەر + باغلان + يۈكلە + ھېسابات تېپىلمىدى + تەڭشەك + چېكىن + يۈكلەۋاتىدۇ + چوڭلۇقى: + تىپى: + قۇرۇلغان ۋاقتى: + ئۆزگەرتكەن ۋاقىت: + چۈشۈر + ھەئە + ياق + جەزملە + يۈكلەشتىن ۋاز كەچ + ۋاز كەچ + ساقلاپ چېكىن + خاتالىق + يوچۇن خاتالىق + ئىم ئۆزگەرت + قىسقۇچ ئاتى + ھېسابات تاللاڭ + ئات ئۆزگەرت + چىقىرىۋەت + يوللا + diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index a835f25e..0e6d5dc0 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -1,67 +1,36 @@ - ownCloud - Пароль: - Ім\'я користувача - Логін - Вітаємо у Вашому ownCloud - Файли - Музика - Контакти - Календар - Закладки - Налаштування - Налаштування облікового запису - На Вашому пристрої відсутні облікові запси ownCloud. Для того, щоб користуватися цим ПО, Вам потрібно створити обліковий запис. - %1$s Android клієнт\n\nверсія: %2$s - Оновити Відвантажити Вміст із інших програм Файли - Створити теку - Пошук + Нова тека Налаштування + Деталі + Надіслати Основне - Стеження за пристроєм - Додати нову сесію - Створити відбитки зображень - Обрати обліковий запис - Оберіть, який із облікових записів повинна використовувати програма. - Стеження за пристроєм - Дозволити ownCloud стежити за місцезнаходженням Вашого пристрою - Ваш ownCloud стежить за цим пристроєм - Інтервал оновлення - Оновлювати кожні %1$s хвилин + Більше Облікові записи Управління обліковими записами - ownCloud програмний PIN - Захист Вашог ownCloud клієнта + App програмний PIN + Захист Вашог App клієнта Включити негайне завантаження Негайно завантажувати фото, зроблені камерою - ownCloud URL + Допомога + Зворотній зв\'язок + Відбиток Ім\'я користувача Пароль - Я новачок в ownCloud - Надано невірний URL - Невірне ім\'я сесії Файли - Не обрано файл для завантаження - Ім\'я користувача - Пароль - Веб адреса - Показувати пароль? - Підключитись до Вашого ownCloud З\'єднати Відвантажити Не знайдено облікового запису - На Вашому пристрої відсутні облікові записи ownCloud. Будь ласка, спочатку створіть запис. + На Вашому пристрої відсутні облікові записи %1$s. Будь ласка, спочатку створіть запис. Налаштування Вийти Відсутні дані для завантаження Не отримано даних. Нічого завантажувати. - ownCloud не може отримати доступ до спільного контенту + %1$s не може отримати доступ до спільного контенту Завантаження - Створити теку для завантаження В цьому каталозі відсутні файли.\nНові файли можна додати через опцію меню \"Завантаження\". Натисніть на файлі для відображення додаткової інформації Розмір: @@ -69,10 +38,8 @@ Створено: Змінено: Завантажити - Оновити - Оновити - Відкрити Файл був переіменований в %1$s протягом вивантаження + Опублікувати посилання Так Ні OK @@ -80,9 +47,10 @@ Перервати завантаження Відмінити Зберегти & Вихід - Вийти з ownCloud Помилка + Невідома помилка Про + Змінити пароль Видалити запис Створити запис Завантажити з … @@ -91,10 +59,8 @@ %1$d%% Завантаження %2$s Успішно завантажено %1$s було успішно завантажено - %1$d файлів було успішно завантажено Помилка завантаження Завантаження %1$s не може завершитись - Помилка завантаження: %1$d/%2$d файли завантажено Зкачування … %1$d%% Зкачування %2$s Успішно зкачано @@ -102,69 +68,44 @@ Завантаження не вдалося Завантаження %1$s не вдається завершити Оберіть обліковий запис - Контакти Помилка синхронізації Синхронізація %1$s не вдалась Конфліктів знайдено %1$d файли, які мають бути синхронізованими не можуть синхронізуватися Синхронізувати файли не вдалося Зміст %1$d файлів не може бути синхронізований (%2$d конфліктів) - Використовувати безпечне з\'єднання - ownCloud не може стежити за Вашим пристроєм. Будь ласка, перевірте свої налаштування місцезнаходження + Деякі локальні файли були забуті + Перемістити все + Всі файли були переміщені + Деякі файли не можуть бути переміщені + Локально: %1$s + Віддалено: %1$s + Недостатньо місця для копіювання обраних файлів у теку %1$s. Чи бажаєте ви перемістити їх замість копіювання? Будь ласка, введіть свій програмний PIN - Будь ласка, введіть новий програмний PIN Введіть програмний PIN PIN необхідно буде вводити щоразу при запуску цієї програми - Повторно введіть ownCloud програмний PIN, будь ласка - Видалити свій ownCloud програмний PIN - Обидва ownCloud програмних PIN не однакові - Не вірний ownCloud програмний PIN - ownCloud програмний PIN видалено - ownCloud програмний PIN збережено - - 15 хвилин - 30 хвилин - 60 хвилин - - - 15 - 30 - 60 - + Повторно введіть App програмний PIN, будь ласка + Видалити свій App програмний PIN + Обидва App програмних PIN не однакові + Не вірний App програмний PIN + App програмний PIN видалено + App програмний PIN збережено Спроба входу… Відсутнє підключення до мережі - Не знайдено підключення до мережі, перевірте своє підключення до Інтернету та спробуйте ще. - Все одно з\'єднатися Безпечне з\'єднання не доступне. - Програма не може встановити безпечне з\'єднання з сервером. Проте, не захищене з\'єднання можливе. Ви можете продовжити або відмінити. З\'єднання встановлено Перевірка з\'єднання… - Не вірні налаштування ownCloud - Здається, Ваш ownCloud примірник не правильно налаштовано. Зверніться до адміністратора для більш докладної інформації. + Не вірні налаштування сервер Виникла невідома помилка! - Виникла невідома помилка. Будь ласка, зв\'яжіться з авторами та надайте лог-файли з Вашого пристрою. Не вдалося знайти хост - Не вдалося знайти визначений хост. Будь ласка, перевірте ім\'я хосту і доступність серверу та спробуйте ще. - Не знайдено примірник ownCloud - Програмі не вдалося знайти примірник ownClound за вказаною адресою. Будь ласка, перевірте адресу та спробуйте ще. + Не знайдено примірник сервер Сервер занадто довго не відповідає Пошкоджений URL Помилка SSL ініціалізації - Неперевірена SSL ідентифікація серверу - Не вдалося визначити версію ownCloud серверу + Не вдалося визначити версію сервер серверу Не вдалося встановити з\'єднання Встановлено захищене з\'єднання - Дані для входу - Невірний Логін / Пароль - Вказано невірний шлях - Внутрішня помилка серверу, код %1$d - Програма несподівано закрилася. Чи бажаєте Ви надіслати доповідь про аварію? - Надіслати доповідь - Не відсилати доповідь - Розширення доступні! - Здається, що Ваш примірник ownCloud підтримує додаткові розширення. Чи бажаєте Ви переглянути розширення, доступні для Андроід? Оновлювати файл - Поділитися Перейменувати Видалити Ви дійсно бажаєте видалити %1$s ? @@ -180,17 +121,14 @@ Перейменування не вдалося Неможливо перевірити віддалений файл Зміст файлу вже синхронізовано - Не вдалося створити теку Зачекайте хвилинку Несподівані проблеми ; будь ласка, спробуйте використати іншу програму для вибору файлу Не обрано файл - Попередження Не вдалося перевірити ідентифікацію сайта - Не довірений сертифікат серверу - Сертифікат серверу втратив чинність - Сертифікат серверу занадто новий - URL не відповідає імені хоста у сертифікаті - Не вдалося отримати сертифікат серверу Ви все одно бажаєте довіряти цьому сертифікату? Не вдалося зберегти сертифікат Деталі @@ -208,7 +146,7 @@ До: Підпис: Алгоритм: - Це заповнювач + Це заповнювач Завантажувати зображення тільки через WiFi /InstantUpload Конфлікт оновлення @@ -216,4 +154,6 @@ Залишити обидва Замінити Не завантажувати + Надіслати + Скопійовано в буфер обміну diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml index 1f54d776..a96fe56d 100644 --- a/res/values-ur-rPK/strings.xml +++ b/res/values-ur-rPK/strings.xml @@ -1,9 +1,11 @@ - سیٹینگز سیٹینگز + مدد یوزر نیم پاسورڈ - یوزر نیم - پاسورڈ + ہاں + نہیں + منسوخ کریں + ایرر diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-ur/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml new file mode 100644 index 00000000..c757504a --- /dev/null +++ b/res/values-uz/strings.xml @@ -0,0 +1,2 @@ + + diff --git a/res/values-v11/versioned_styles.xml b/res/values-v11/versioned_styles.xml new file mode 100644 index 00000000..ab237e84 --- /dev/null +++ b/res/values-v11/versioned_styles.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/res/values-v9/versioned_styles.xml b/res/values-v9/versioned_styles.xml new file mode 100644 index 00000000..57bb396f --- /dev/null +++ b/res/values-v9/versioned_styles.xml @@ -0,0 +1,12 @@ + + + + + + @style/Theme.ownCloud.ButtonStyle + @style/Theme.ownCloud.DropDownStyle + @style/Theme.ownCloud.DropDownStyle + + + + - + + + @@ -101,6 +121,10 @@ true + + #777777 #000000 diff --git a/res/values/urls.xml b/res/values/urls.xml deleted file mode 100644 index 98d2ddd2..00000000 --- a/res/values/urls.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - "https://owncloud.com/mobile/new" - \ No newline at end of file diff --git a/res/values/versioned_styles.xml b/res/values/versioned_styles.xml new file mode 100644 index 00000000..5faa6b2a --- /dev/null +++ b/res/values/versioned_styles.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + diff --git a/res/xml/authenticator.xml b/res/xml/authenticator.xml index 26704234..eb250052 100644 --- a/res/xml/authenticator.xml +++ b/res/xml/authenticator.xml @@ -18,7 +18,7 @@ along with this program. If not, see . --> diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index 78b89bb6..4c6ee40d 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -25,22 +25,32 @@ android:title="@string/prefs_select_oc_account" android:summary="@string/prefs_summary_select_oc_account" / --> - - + - - - + android:summary="@string/prefs_log_summary_history"/ --> + + + + + + + + + diff --git a/res/xml/preferences_new_session.xml b/res/xml/preferences_new_session.xml deleted file mode 100644 index 8327bf09..00000000 --- a/res/xml/preferences_new_session.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - diff --git a/res/xml/syncadapter_files.xml b/res/xml/syncadapter_files.xml index 03fedbf8..8c8d8b36 100644 --- a/res/xml/syncadapter_files.xml +++ b/res/xml/syncadapter_files.xml @@ -22,7 +22,10 @@ diff --git a/setup_env.bat b/setup_env.bat index c0bfeb4f..dc44a120 100644 --- a/setup_env.bat +++ b/setup_env.bat @@ -1,7 +1,8 @@ -git submodule init -git submodule update -android.bat update project -p actionbarsherlock\library --target 1 -android.bat update project -p . --target 1 -cp third_party\android-support-library\android-support-v4.jar actionbarsherlock\library\libs\android-support-v4.jar -cd tests -android.bat update test-project -m .. -p . \ No newline at end of file +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 .. diff --git a/setup_env.sh b/setup_env.sh index 78babf13..e6047528 100755 --- a/setup_env.sh +++ b/setup_env.sh @@ -2,8 +2,9 @@ git submodule init git submodule update -android update project -p actionbarsherlock/library +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 -cd tests -android update test-project -m .. -p . +android update test-project -p tests -m .. diff --git a/src/com/owncloud/android/AccountUtils.java b/src/com/owncloud/android/AccountUtils.java deleted file mode 100644 index f064ce2d..00000000 --- a/src/com/owncloud/android/AccountUtils.java +++ /dev/null @@ -1,150 +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 . - * - */ - -package com.owncloud.android; - -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.utils.OwnCloudVersion; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -public class AccountUtils { - public static final String WEBDAV_PATH_1_2 = "/webdav/owncloud.php"; - public static final String WEBDAV_PATH_2_0 = "/files/webdav.php"; - public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav"; - public static final String CARDDAV_PATH_2_0 = "/apps/contacts/carddav.php"; - public static final String CARDDAV_PATH_4_0 = "/remote/carddav.php"; - public static final String STATUS_PATH = "/status.php"; - - /** - * Can be used to get the currently selected ownCloud account in the - * preferences - * - * @param context The current appContext - * @return The current account or first available, if none is available, - * then null. - */ - public static Account getCurrentOwnCloudAccount(Context context) { - Account[] ocAccounts = AccountManager.get(context).getAccountsByType( - AccountAuthenticator.ACCOUNT_TYPE); - Account defaultAccount = null; - - SharedPreferences appPreferences = PreferenceManager - .getDefaultSharedPreferences(context); - String accountName = appPreferences - .getString("select_oc_account", null); - - if (accountName != null) { - for (Account account : ocAccounts) { - if (account.name.equals(accountName)) { - defaultAccount = account; - break; - } - } - } - - if (defaultAccount == null && ocAccounts.length != 0) { - // we at least need to take first account as fallback - defaultAccount = ocAccounts[0]; - } - - return defaultAccount; - } - - - - /** - * Checks, whether or not there are any ownCloud accounts setup. - * - * @return true, if there is at least one account. - */ - public static boolean accountsAreSetup(Context context) { - AccountManager accMan = AccountManager.get(context); - Account[] accounts = accMan - .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); - return accounts.length > 0; - } - - - public static boolean setCurrentOwnCloudAccount(Context context, String accountName) { - boolean result = false; - if (accountName != null) { - Account[] ocAccounts = AccountManager.get(context).getAccountsByType( - AccountAuthenticator.ACCOUNT_TYPE); - boolean found = false; - for (Account account : ocAccounts) { - found = (account.name.equals(accountName)); - if (found) { - SharedPreferences.Editor appPrefs = PreferenceManager - .getDefaultSharedPreferences(context).edit(); - appPrefs.putString("select_oc_account", accountName); - - appPrefs.commit(); - result = true; - break; - } - } - } - return result; - } - - /** - * - * @param version version of owncloud - * @return webdav path for given OC version, null if OC version unknown - */ - public static String getWebdavPath(OwnCloudVersion version) { - if (version != null) { - if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0) - return WEBDAV_PATH_4_0; - if (version.compareTo(OwnCloudVersion.owncloud_v3) >= 0 - || version.compareTo(OwnCloudVersion.owncloud_v2) >= 0) - return WEBDAV_PATH_2_0; - if (version.compareTo(OwnCloudVersion.owncloud_v1) >= 0) - return WEBDAV_PATH_1_2; - } - return null; - } - - /** - * Constructs full url to host and webdav resource basing on host version - * @param context - * @param account - * @return url or null on failure - */ - public static String constructFullURLForAccount(Context context, Account account) { - try { - AccountManager ama = AccountManager.get(context); - String baseurl = ama.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL); - String strver = ama.getUserData(account, AccountAuthenticator.KEY_OC_VERSION); - OwnCloudVersion ver = new OwnCloudVersion(strver); - String webdavpath = getWebdavPath(ver); - - if (webdavpath == null) return null; - return baseurl + webdavpath; - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - -} diff --git a/src/com/owncloud/android/DisplayUtils.java b/src/com/owncloud/android/DisplayUtils.java deleted file mode 100644 index 70d53283..00000000 --- a/src/com/owncloud/android/DisplayUtils.java +++ /dev/null @@ -1,192 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ - -package com.owncloud.android; - -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -import android.util.Log; - -/** - * A helper class for some string operations. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class DisplayUtils { - - private static String TAG = DisplayUtils.class.getSimpleName(); - - private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; - - private static HashMap mimeType2HUmanReadable; - static { - mimeType2HUmanReadable = new HashMap(); - // images - mimeType2HUmanReadable.put("image/jpeg", "JPEG image"); - mimeType2HUmanReadable.put("image/jpg", "JPEG image"); - mimeType2HUmanReadable.put("image/png", "PNG image"); - mimeType2HUmanReadable.put("image/bmp", "Bitmap image"); - mimeType2HUmanReadable.put("image/gif", "GIF image"); - mimeType2HUmanReadable.put("image/svg+xml", "JPEG image"); - mimeType2HUmanReadable.put("image/tiff", "TIFF image"); - // music - mimeType2HUmanReadable.put("audio/mpeg", "MP3 music file"); - mimeType2HUmanReadable.put("application/ogg", "OGG music file"); - - } - - private static final String TYPE_APPLICATION = "application"; - private static final String TYPE_AUDIO = "audio"; - private static final String TYPE_IMAGE = "image"; - private static final String TYPE_TXT = "text"; - private static final String TYPE_VIDEO = "video"; - - private static final String SUBTYPE_PDF = "pdf"; - private static final String[] SUBTYPES_DOCUMENT = { "msword", "mspowerpoint", "msexcel", - "vnd.oasis.opendocument.presentation", - "vnd.oasis.opendocument.spreadsheet", - "vnd.oasis.opendocument.text" - }; - private static Set SUBTYPES_DOCUMENT_SET = new HashSet(Arrays.asList(SUBTYPES_DOCUMENT)); - private static final String[] SUBTYPES_COMPRESSED = {"x-tar", "x-gzip", "zip"}; - private static final Set SUBTYPES_COMPRESSED_SET = new HashSet(Arrays.asList(SUBTYPES_COMPRESSED)); - - /** - * Converts the file size in bytes to human readable output. - * - * @param bytes Input file size - * @return Like something readable like "12 MB" - */ - public static String bytesToHumanReadable(long bytes) { - double result = bytes; - int attachedsuff = 0; - while (result > 1024 && attachedsuff < sizeSuffixes.length) { - result /= 1024.; - attachedsuff++; - } - result = ((int) (result * 100)) / 100.; - return result + " " + sizeSuffixes[attachedsuff]; - } - - /** - * Removes special HTML entities from a string - * - * @param s Input string - * @return A cleaned version of the string - */ - public static String HtmlDecode(String s) { - /* - * TODO: Perhaps we should use something more proven like: - * http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#unescapeHtml%28java.lang.String%29 - */ - - String ret = ""; - for (int i = 0; i < s.length(); ++i) { - if (s.charAt(i) == '%') { - ret += (char) Integer.parseInt(s.substring(i + 1, i + 3), 16); - i += 2; - } else { - ret += s.charAt(i); - } - } - return ret; - } - - /** - * Converts MIME types like "image/jpg" to more end user friendly output - * like "JPG image". - * - * @param mimetype MIME type to convert - * @return A human friendly version of the MIME type - */ - public static String convertMIMEtoPrettyPrint(String mimetype) { - if (mimeType2HUmanReadable.containsKey(mimetype)) { - return mimeType2HUmanReadable.get(mimetype); - } - if (mimetype.split("/").length >= 2) - return mimetype.split("/")[1].toUpperCase() + " file"; - return "Unknown type"; - } - - - /** - * Returns the resource identifier of an image resource to use as icon associated to a - * known MIME type. - * - * @param mimetype MIME type string. - * @return Resource identifier of an image resource. - */ - public static int getResourceId(String mimetype) { - - if (mimetype == null || "DIR".equals(mimetype)) { - return R.drawable.ic_menu_archive; - - } else { - String [] parts = mimetype.split("/"); - String type = parts[0]; - String subtype = (parts.length > 1) ? parts[1] : ""; - - if(TYPE_TXT.equals(type)) { - return R.drawable.file_doc; - - } else if(TYPE_IMAGE.equals(type)) { - return R.drawable.file_image; - - } else if(TYPE_VIDEO.equals(type)) { - return R.drawable.file_movie; - - } else if(TYPE_AUDIO.equals(type)) { - return R.drawable.file_sound; - - } else if(TYPE_APPLICATION.equals(type)) { - - if (SUBTYPE_PDF.equals(subtype)) { - return R.drawable.file_pdf; - - } else if (SUBTYPES_DOCUMENT_SET.contains(subtype)) { - return R.drawable.file_doc; - - } else if (SUBTYPES_COMPRESSED_SET.contains(subtype)) { - return R.drawable.file_zip; - } - - } - // problems: RAR, RTF, 3GP are send as application/octet-stream from the server ; extension in the filename should be explicitly reviewed - } - - // default icon - return R.drawable.file; - } - - - - /** - * Converts Unix time to human readable format - * @param miliseconds that have passed since 01/01/1970 - * @return The human readable time for the users locale - */ - public static String unixTimeToHumanReadable(long milliseconds) { - Date date = new Date(milliseconds); - return date.toLocaleString(); - } -} diff --git a/src/com/owncloud/android/Log_OC.java b/src/com/owncloud/android/Log_OC.java deleted file mode 100644 index 1e2937cb..00000000 --- a/src/com/owncloud/android/Log_OC.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.owncloud.android; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -import android.util.Log; - - - -public class Log_OC { - - - private static boolean isEnabled = false; - private static File logFile; - private static File folder; - private static BufferedWriter buf; - - public static void i(String TAG, String message){ - // Printing the message to LogCat console - Log.i(TAG, message); - // Write the log message to the file - appendLog(TAG+" : "+message); - } - - public static void d(String TAG, String message){ - Log.d(TAG, message); - appendLog(TAG+" : "+message); - } - public static void d(String TAG, String message, Exception e) { - Log.d(TAG, message, e); - appendLog(TAG+" : "+ message+" Exception : "+e.getStackTrace()); - } - public static void e(String TAG, String message){ - Log.e(TAG, message); - appendLog(TAG+" : "+message); - } - - public static void e(String TAG, String message, Throwable e) { - Log.e(TAG, message, e); - appendLog(TAG+" : "+ message+" Exception : "+e.getStackTrace()); - } - - public static void v(String TAG, String message){ - Log.v(TAG, message); - appendLog(TAG+" : "+message); - } - - public static void w(String TAG, String message) { - Log.w(TAG,message); - appendLog(TAG+" : "+message); - } - - public static void wtf(String TAG, String message) { - Log.wtf(TAG,message); - appendLog(TAG+" : "+message); - } - - public static void startLogging(String logPath) { - folder = new File(logPath); - logFile = new File(folder+File.separator+"log.txt"); - - if (!folder.exists()) { - folder.mkdirs(); - } - if (logFile.exists()) { - logFile.delete(); - } - try { - logFile.createNewFile(); - buf = new BufferedWriter(new FileWriter(logFile, true)); - isEnabled = true; - appendPhoneInfo(); - }catch (IOException e){ - e.printStackTrace(); - } - } - - public static void stopLogging() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss",Locale.getDefault()); - String currentDateandTime = sdf.format(new Date()); - if (logFile != null) { - logFile.renameTo(new File(folder+File.separator+"Owncloud_"+currentDateandTime+".log")); - - isEnabled = false; - try { - buf.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - } - - } - - private static void appendPhoneInfo() { - appendLog("Model : " + android.os.Build.MODEL); - appendLog("Brand : " + android.os.Build.BRAND); - appendLog("Product : " + android.os.Build.PRODUCT); - appendLog("Device : " + android.os.Build.DEVICE); - appendLog("Version-Codename : " + android.os.Build.VERSION.CODENAME); - appendLog("Version-Release : " + android.os.Build.VERSION.RELEASE); - } - - private static void appendLog(String text) { - if (isEnabled) { - try { - buf.append(text); - buf.newLine(); - } catch (IOException e) { - e.printStackTrace(); - } - } -} - - - - - - - - -} diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java new file mode 100644 index 00000000..85718ff6 --- /dev/null +++ b/src/com/owncloud/android/MainApp.java @@ -0,0 +1,81 @@ +/* ownCloud Android client application + * 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 . + * + */ +package com.owncloud.android; + +import android.app.Application; +import android.content.Context; +/** + * Main Application of the project + * + * Contains methods to build the "static" strings. These strings were before constants in different classes + * + * @author masensio + */ +public class MainApp extends Application { + + private static Context mContext; + + public void onCreate(){ + super.onCreate(); + MainApp.mContext = getApplicationContext(); + } + + public static Context getAppContext() { + return MainApp.mContext; + } + + // Methods to obtain Strings referring app_name + // From AccountAuthenticator + // public static final String ACCOUNT_TYPE = "owncloud"; + public static String getAccountType() { + return getAppContext().getResources().getString(R.string.account_type); + } + + // From AccountAuthenticator + // public static final String AUTHORITY = "org.owncloud"; + public static String getAuthority() { + return getAppContext().getResources().getString(R.string.authority); + } + + // From AccountAuthenticator + // public static final String AUTH_TOKEN_TYPE = "org.owncloud"; + public static String getAuthTokenType() { + return getAppContext().getResources().getString(R.string.authority); + } + + // From ProviderMeta + // public static final String DB_FILE = "owncloud.db"; + public static String getDBFile() { + return getAppContext().getResources().getString(R.string.db_file); + } + + // From ProviderMeta + // private final String mDatabaseName = "ownCloud"; + public static String getDBName() { + return getAppContext().getResources().getString(R.string.db_name); + } + + // data_folder + public static String getDataFolder() { + return getAppContext().getResources().getString(R.string.data_folder); + } + + // log_name + public static String getLogName() { + return getAppContext().getResources().getString(R.string.log_name); + } +} diff --git a/src/com/owncloud/android/OwnCloudSession.java b/src/com/owncloud/android/OwnCloudSession.java deleted file mode 100644 index d7bb6094..00000000 --- a/src/com/owncloud/android/OwnCloudSession.java +++ /dev/null @@ -1,56 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ -package com.owncloud.android; - -/** - * Represents a session to an ownCloud instance - * - * @author Bartek Przybylski - * - */ -public class OwnCloudSession { - private String mSessionName; - private String mSessionUrl; - private int mEntryId; - - public OwnCloudSession(String name, String url, int entryId) { - mSessionName = name; - mSessionUrl = url; - mEntryId = entryId; - } - - public void setName(String name) { - mSessionName = name; - } - - public String getName() { - return mSessionName; - } - - public void setUrl(String url) { - mSessionUrl = url; - } - - public String getUrl() { - return mSessionUrl; - } - - public int getEntryId() { - return mEntryId; - } -} diff --git a/src/com/owncloud/android/Uploader.java b/src/com/owncloud/android/Uploader.java deleted file mode 100644 index 7c402c9f..00000000 --- a/src/com/owncloud/android/Uploader.java +++ /dev/null @@ -1,406 +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 . - * - */ -package com.owncloud.android; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Stack; -import java.util.Vector; - -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.datamodel.DataStorageManager; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.network.OwnCloudClientUtils; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.app.ListActivity; -import android.app.ProgressDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcelable; -import android.provider.MediaStore.Images.Media; -import android.util.Log; -import android.view.View; -import android.view.Window; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.Button; -import android.widget.EditText; -import android.widget.SimpleAdapter; -import android.widget.Toast; - -import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavClient; - -/** - * This can be used to upload things to an ownCloud instance. - * - * @author Bartek Przybylski - * - */ -public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener { - private static final String TAG = "ownCloudUploader"; - - private Account mAccount; - private AccountManager mAccountManager; - private Stack mParents; - private ArrayList mStreamsToUpload; - private boolean mCreateDir; - private String mUploadPath; - private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE }; - private DataStorageManager mStorageManager; - private OCFile mFile; - - private final static int DIALOG_NO_ACCOUNT = 0; - private final static int DIALOG_WAITING = 1; - private final static int DIALOG_NO_STREAM = 2; - private final static int DIALOG_MULTIPLE_ACCOUNT = 3; - //private final static int DIALOG_GET_DIRNAME = 4; - - private final static int REQUEST_CODE_SETUP_ACCOUNT = 0; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - mParents = new Stack(); - mParents.add(""); - /*if (getIntent().hasExtra(Intent.EXTRA_STREAM)) { - prepareStreamsToUpload();*/ - if (prepareStreamsToUpload()) { - mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE); - Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); - if (accounts.length == 0) { - Log_OC.i(TAG, "No ownCloud account is available"); - showDialog(DIALOG_NO_ACCOUNT); - } else if (accounts.length > 1) { - Log_OC.i(TAG, "More then one ownCloud is available"); - showDialog(DIALOG_MULTIPLE_ACCOUNT); - } else { - mAccount = accounts[0]; - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - populateDirectoryList(); - } - } else { - showDialog(DIALOG_NO_STREAM); - } - } - - @Override - protected Dialog onCreateDialog(final int id) { - final AlertDialog.Builder builder = new Builder(this); - switch (id) { - case DIALOG_WAITING: - ProgressDialog pDialog = new ProgressDialog(this); - pDialog.setIndeterminate(false); - pDialog.setCancelable(false); - pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading)); - return pDialog; - case DIALOG_NO_ACCOUNT: - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setTitle(R.string.uploader_wrn_no_account_title); - builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name))); - builder.setCancelable(false); - builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) { - // using string value since in API7 this - // constatn is not defined - // in API7 < this constatant is defined in - // Settings.ADD_ACCOUNT_SETTINGS - // and Settings.EXTRA_AUTHORITIES - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra("authorities", new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE }); - startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); - } else { - // since in API7 there is no direct call for - // account setup, so we need to - // show our own AccountSetupAcricity, get - // desired results and setup - // everything for ourself - Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class); - startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); - } - } - }); - builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - return builder.create(); - /*case DIALOG_GET_DIRNAME: - final EditText dirName = new EditText(getBaseContext()); - builder.setView(dirName); - builder.setTitle(R.string.uploader_info_dirname); - String pathToUpload; - if (mParents.empty()) { - pathToUpload = "/"; - } else { - mCursor = managedQuery(Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, mParents.peek()), null, - null, null, null); - mCursor.moveToFirst(); - pathToUpload = mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_PATH)) - + mCursor.getString(mCursor.getColumnIndex(ProviderTableMeta.FILE_NAME)).replace(" ", "%20"); // TODO don't make this ; use WebdavUtils.encode in the right moment - } - a a = new a(pathToUpload, dirName); - builder.setPositiveButton(R.string.common_ok, a); - builder.setNegativeButton(R.string.common_cancel, new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - return builder.create();*/ - case DIALOG_MULTIPLE_ACCOUNT: - CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE).length]; - for (int i = 0; i < ac.length; ++i) { - ac[i] = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[i].name; - } - builder.setTitle(R.string.common_choose_account); - builder.setItems(ac, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mAccount = mAccountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE)[which]; - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - populateDirectoryList(); - } - }); - builder.setCancelable(true); - builder.setOnCancelListener(new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - dialog.cancel(); - finish(); - } - }); - return builder.create(); - case DIALOG_NO_STREAM: - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setTitle(R.string.uploader_wrn_no_content_title); - builder.setMessage(R.string.uploader_wrn_no_content_text); - builder.setCancelable(false); - builder.setNegativeButton(R.string.common_cancel, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - }); - return builder.create(); - default: - throw new IllegalArgumentException("Unknown dialog id: " + id); - } - } - - class a implements OnClickListener { - String mPath; - EditText mDirname; - - public a(String path, EditText dirname) { - mPath = path; - mDirname = dirname; - } - - @Override - public void onClick(DialogInterface dialog, int which) { - Uploader.this.mUploadPath = mPath + mDirname.getText().toString(); - Uploader.this.mCreateDir = true; - uploadFiles(); - } - } - - @Override - public void onBackPressed() { - - if (mParents.size() <= 1) { - super.onBackPressed(); - return; - } else { - mParents.pop(); - populateDirectoryList(); - } - } - - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - // click on folder in the list - Log_OC.d(TAG, "on item click"); - Vector tmpfiles = mStorageManager.getDirectoryContent(mFile); - if (tmpfiles.size() <= 0) return; - // filter on dirtype - Vector files = new Vector(); - for (OCFile f : tmpfiles) - if (f.isDirectory()) - files.add(f); - if (files.size() < position) { - throw new IndexOutOfBoundsException("Incorrect item selected"); - } - mParents.push(files.get(position).getFileName()); - populateDirectoryList(); - } - - @Override - public void onClick(View v) { - // click on button - switch (v.getId()) { - case R.id.uploader_choose_folder: - mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix - for (String p : mParents) - mUploadPath += p + OCFile.PATH_SEPARATOR; - Log_OC.d(TAG, "Uploading file to dir " + mUploadPath); - - uploadFiles(); - - break; - /*case android.R.id.button1: // dynamic action for create aditional dir - showDialog(DIALOG_GET_DIRNAME); - break;*/ - default: - throw new IllegalArgumentException("Wrong element clicked"); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode); - if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) { - dismissDialog(DIALOG_NO_ACCOUNT); - if (resultCode == RESULT_CANCELED) { - finish(); - } - Account[] accounts = mAccountManager.getAccountsByType(AccountAuthenticator.AUTH_TOKEN_TYPE); - if (accounts.length == 0) { - showDialog(DIALOG_NO_ACCOUNT); - } else { - // there is no need for checking for is there more then one - // account at this point - // since account setup can set only one account at time - mAccount = accounts[0]; - populateDirectoryList(); - } - } - } - - private void populateDirectoryList() { - setContentView(R.layout.uploader_layout); - - String full_path = ""; - for (String a : mParents) - full_path += a + "/"; - - Log_OC.d(TAG, "Populating view with content of : " + full_path); - - mFile = mStorageManager.getFileByPath(full_path); - if (mFile != null) { - Vector files = mStorageManager.getDirectoryContent(mFile); - List> data = new LinkedList>(); - for (OCFile f : files) { - HashMap h = new HashMap(); - if (f.isDirectory()) { - h.put("dirname", f.getFileName()); - data.add(h); - } - } - SimpleAdapter sa = new SimpleAdapter(this, - data, - R.layout.uploader_list_item_layout, - new String[] {"dirname"}, - new int[] {R.id.textView1}); - setListAdapter(sa); - Button btn = (Button) findViewById(R.id.uploader_choose_folder); - btn.setOnClickListener(this); - getListView().setOnItemClickListener(this); - } - } - - private boolean prepareStreamsToUpload() { - if (getIntent().getAction().equals(Intent.ACTION_SEND)) { - mStreamsToUpload = new ArrayList(); - mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM)); - } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { - mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); - } - return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null); - } - - public void uploadFiles() { - try { - WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); - - // create last directory in path if necessary - if (mCreateDir) { - wdc.createDirectory(mUploadPath); - } - - String[] local = new String[mStreamsToUpload.size()], remote = new String[mStreamsToUpload.size()]; - - for (int i = 0; i < mStreamsToUpload.size(); ++i) { - Uri uri = (Uri) mStreamsToUpload.get(i); - if (uri.getScheme().equals("content")) { - Cursor c = getContentResolver().query((Uri) mStreamsToUpload.get(i), - CONTENT_PROJECTION, - null, - null, - null); - - if (!c.moveToFirst()) - continue; - - final String display_name = c.getString(c.getColumnIndex(Media.DISPLAY_NAME)), - data = c.getString(c.getColumnIndex(Media.DATA)); - local[i] = data; - remote[i] = mUploadPath + display_name; - } else if (uri.getScheme().equals("file")) { - final File file = new File(Uri.decode(uri.toString()).replace(uri.getScheme() + "://", "")); - local[i] = file.getAbsolutePath(); - remote[i] = mUploadPath + file.getName(); - } - - } - Intent intent = new Intent(getApplicationContext(), FileUploader.class); - intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES); - intent.putExtra(FileUploader.KEY_LOCAL_FILE, local); - intent.putExtra(FileUploader.KEY_REMOTE_FILE, remote); - intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount); - startService(intent); - finish(); - - } catch (SecurityException e) { - String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name)); - Toast.makeText(this, message, Toast.LENGTH_LONG).show(); - } - } - -} diff --git a/src/com/owncloud/android/authentication/AccountAuthenticator.java b/src/com/owncloud/android/authentication/AccountAuthenticator.java new file mode 100644 index 00000000..15d758f5 --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticator.java @@ -0,0 +1,321 @@ +/* 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 . + * + */ + +package com.owncloud.android.authentication; + +import com.owncloud.android.MainApp; +import com.owncloud.android.R; + +import android.accounts.*; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.widget.Toast; + +import com.owncloud.android.lib.common.accounts.AccountTypeUtils; +import com.owncloud.android.utils.Log_OC; + + +/** + * Authenticator for ownCloud accounts. + * + * Controller class accessed from the system AccountManager, providing integration of ownCloud accounts with the Android system. + * + * TODO - better separation in operations for OAuth-capable and regular ownCloud accounts. + * TODO - review completeness + * + * @author David A. Velasco + */ +public class AccountAuthenticator extends AbstractAccountAuthenticator { + + /** + * Is used by android system to assign accounts to authenticators. Should be + * used by application and all extensions. + */ + public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; + public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; + public static final String KEY_LOGIN_OPTIONS = "loginOptions"; + public static final String KEY_ACCOUNT = "account"; + + private static final String TAG = AccountAuthenticator.class.getSimpleName(); + + private Context mContext; + + private Handler mHandler; + + public AccountAuthenticator(Context context) { + super(context); + mContext = context; + mHandler = new Handler(); + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle addAccount(AccountAuthenticatorResponse response, + String accountType, String authTokenType, + String[] requiredFeatures, Bundle options) + throws NetworkErrorException { + Log_OC.i(TAG, "Adding account with type " + accountType + + " and auth token " + authTokenType); + + final Bundle bundle = new Bundle(); + + AccountManager accountManager = AccountManager.get(mContext); + Account[] accounts = accountManager.getAccountsByType(MainApp.getAccountType()); + + if (mContext.getResources().getBoolean(R.bool.multiaccount_support) || accounts.length < 1) { + try { + validateAccountType(accountType); + } catch (AuthenticatorException e) { + Log_OC.e(TAG, "Failed to validate account type " + accountType + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_CREATE); + + setIntentFlags(intent); + + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + + } else { + + // Return an error + bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); + final String message = String.format(mContext.getString(R.string.auth_unsupported_multiaccount), mContext.getString(R.string.app_name)); + bundle.putString(AccountManager.KEY_ERROR_MESSAGE, message); + + mHandler.post(new Runnable() { + + @Override + public void run() { + Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); + } + }); + + } + + return bundle; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse response, + Account account, Bundle options) throws NetworkErrorException { + try { + validateAccountType(account.type); + } catch (AuthenticatorException e) { + Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + + setIntentFlags(intent); + + Bundle resultBundle = new Bundle(); + resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); + return resultBundle; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse response, + String accountType) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + /// validate parameters + try { + validateAccountType(account.type); + validateAuthTokenType(authTokenType); + } catch (AuthenticatorException e) { + Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " + + e.getMessage()); + e.printStackTrace(); + return e.getFailureBundle(); + } + + /// check if required token is stored + final AccountManager am = AccountManager.get(mContext); + String accessToken; + if (authTokenType.equals(AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()))) { + accessToken = am.getPassword(account); + } else { + accessToken = am.peekAuthToken(account, authTokenType); + } + if (accessToken != null) { + final Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType()); + result.putString(AccountManager.KEY_AUTHTOKEN, accessToken); + return result; + } + + /// if not stored, return Intent to access the AuthenticatorActivity and UPDATE the token for the account + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + intent.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account); + intent.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN); + + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public String getAuthTokenLabel(String authTokenType) { + return null; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse response, + Account account, String[] features) throws NetworkErrorException { + final Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + return result; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse response, + Account account, String authTokenType, Bundle options) + throws NetworkErrorException { + final Intent intent = new Intent(mContext, AuthenticatorActivity.class); + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, + response); + intent.putExtra(KEY_ACCOUNT, account); + intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); + intent.putExtra(KEY_LOGIN_OPTIONS, options); + setIntentFlags(intent); + + final Bundle bundle = new Bundle(); + bundle.putParcelable(AccountManager.KEY_INTENT, intent); + return bundle; + } + + @Override + public Bundle getAccountRemovalAllowed( + AccountAuthenticatorResponse response, Account account) + throws NetworkErrorException { + return super.getAccountRemovalAllowed(response, account); + } + + private void setIntentFlags(Intent intent) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + intent.addFlags(Intent.FLAG_FROM_BACKGROUND); + } + + private void validateAccountType(String type) + throws UnsupportedAccountTypeException { + if (!type.equals(MainApp.getAccountType())) { + throw new UnsupportedAccountTypeException(); + } + } + + private void validateAuthTokenType(String authTokenType) + throws UnsupportedAuthTokenTypeException { + if (!authTokenType.equals(MainApp.getAuthTokenType()) && + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType())) && + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType())) && + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypeRefreshToken(MainApp.getAccountType())) && + !authTokenType.equals(AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()))) { + throw new UnsupportedAuthTokenTypeException(); + } + } + + public static class AuthenticatorException extends Exception { + private static final long serialVersionUID = 1L; + private Bundle mFailureBundle; + + public AuthenticatorException(int code, String errorMsg) { + mFailureBundle = new Bundle(); + mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); + mFailureBundle + .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); + } + + public Bundle getFailureBundle() { + return mFailureBundle; + } + } + + public static class UnsupportedAccountTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAccountTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported account type"); + } + } + + public static class UnsupportedAuthTokenTypeException extends + AuthenticatorException { + private static final long serialVersionUID = 1L; + + public UnsupportedAuthTokenTypeException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported auth token type"); + } + } + + public static class UnsupportedFeaturesException extends + AuthenticatorException { + public static final long serialVersionUID = 1L; + + public UnsupportedFeaturesException() { + super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, + "Unsupported features"); + } + } + + public static class AccessDeniedException extends AuthenticatorException { + public AccessDeniedException(int code, String errorMsg) { + super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); + } + + private static final long serialVersionUID = 1L; + + } +} diff --git a/src/com/owncloud/android/authentication/AccountAuthenticatorActivity.java b/src/com/owncloud/android/authentication/AccountAuthenticatorActivity.java new file mode 100644 index 00000000..62c8825f --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticatorActivity.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2009 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 com.owncloud.android.authentication; + +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.os.Bundle; + +import com.actionbarsherlock.app.SherlockFragmentActivity; + + +/* + * Base class for implementing an Activity that is used to help implement an AbstractAccountAuthenticator. + * If the AbstractAccountAuthenticator needs to use an activity to handle the request then it can have the activity extend + * AccountAuthenticatorActivity. The AbstractAccountAuthenticator passes in the response to the intent using the following: + * intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); + * + * The activity then sets the result that is to be handed to the response via setAccountAuthenticatorResult(android.os.Bundle). + * This result will be sent as the result of the request when the activity finishes. If this is never set or if it is set to null + * then error AccountManager.ERROR_CODE_CANCELED will be called on the response. + */ + +public class AccountAuthenticatorActivity extends SherlockFragmentActivity { + + private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null; + private Bundle mResultBundle = null; + + + /** + * Set the result that is to be sent as the result of the request that caused this Activity to be launched. + * If result is null or this method is never called then the request will be canceled. + * + * @param result this is returned as the result of the AbstractAccountAuthenticator request + */ + public final void setAccountAuthenticatorResult(Bundle result) { + mResultBundle = result; + } + + /** + * Retreives the AccountAuthenticatorResponse from either the intent of the icicle, if the + * icicle is non-zero. + * @param icicle the save instance data of this Activity, may be null + */ + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mAccountAuthenticatorResponse = + getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); + + if (mAccountAuthenticatorResponse != null) { + mAccountAuthenticatorResponse.onRequestContinued(); + } + } + + /** + * Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present. + */ + public void finish() { + if (mAccountAuthenticatorResponse != null) { + // send the result bundle back if set, otherwise send an error. + if (mResultBundle != null) { + mAccountAuthenticatorResponse.onResult(mResultBundle); + } else { + mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, + "canceled"); + } + mAccountAuthenticatorResponse = null; + } + super.finish(); + } +} diff --git a/src/com/owncloud/android/authentication/AccountAuthenticatorService.java b/src/com/owncloud/android/authentication/AccountAuthenticatorService.java new file mode 100644 index 00000000..c479cea1 --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountAuthenticatorService.java @@ -0,0 +1,40 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 . + * + */ + +package com.owncloud.android.authentication; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class AccountAuthenticatorService extends Service { + + private AccountAuthenticator mAuthenticator; + + @Override + public void onCreate() { + super.onCreate(); + mAuthenticator = new AccountAuthenticator(this); + } + + @Override + public IBinder onBind(Intent intent) { + return mAuthenticator.getIBinder(); + } + +} diff --git a/src/com/owncloud/android/authentication/AccountUtils.java b/src/com/owncloud/android/authentication/AccountUtils.java new file mode 100644 index 00000000..10c87963 --- /dev/null +++ b/src/com/owncloud/android/authentication/AccountUtils.java @@ -0,0 +1,156 @@ +/* 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 . + * + */ + +package com.owncloud.android.authentication; + +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.accounts.AccountTypeUtils; +import com.owncloud.android.lib.resources.status.OwnCloudVersion; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class AccountUtils { + public static final String WEBDAV_PATH_1_2 = "/webdav/owncloud.php"; + public static final String WEBDAV_PATH_2_0 = "/files/webdav.php"; + public static final String WEBDAV_PATH_4_0 = "/remote.php/webdav"; + private static final String ODAV_PATH = "/remote.php/odav"; + private static final String SAML_SSO_PATH = "/remote.php/webdav"; + public static final String CARDDAV_PATH_2_0 = "/apps/contacts/carddav.php"; + public static final String CARDDAV_PATH_4_0 = "/remote/carddav.php"; + public static final String STATUS_PATH = "/status.php"; + + /** + * Can be used to get the currently selected ownCloud {@link Account} in the + * application preferences. + * + * @param context The current application {@link Context} + * @return The ownCloud {@link Account} currently saved in preferences, or the first + * {@link Account} available, if valid (still registered in the system as ownCloud + * account). If none is available and valid, returns null. + */ + public static Account getCurrentOwnCloudAccount(Context context) { + Account[] ocAccounts = AccountManager.get(context).getAccountsByType( + MainApp.getAccountType()); + Account defaultAccount = null; + + SharedPreferences appPreferences = PreferenceManager + .getDefaultSharedPreferences(context); + String accountName = appPreferences + .getString("select_oc_account", null); + + // account validation: the saved account MUST be in the list of ownCloud Accounts known by the AccountManager + if (accountName != null) { + for (Account account : ocAccounts) { + if (account.name.equals(accountName)) { + defaultAccount = account; + break; + } + } + } + + if (defaultAccount == null && ocAccounts.length != 0) { + // take first account as fallback + defaultAccount = ocAccounts[0]; + } + + return defaultAccount; + } + + + public static boolean exists(Account account, Context context) { + Account[] ocAccounts = AccountManager.get(context).getAccountsByType( + MainApp.getAccountType()); + + if (account != null && account.name != null) { + for (Account ac : ocAccounts) { + if (ac.name.equals(account.name)) { + return true; + } + } + } + return false; + } + + + /** + * Checks, whether or not there are any ownCloud accounts setup. + * + * @return true, if there is at least one account. + */ + public static boolean accountsAreSetup(Context context) { + AccountManager accMan = AccountManager.get(context); + Account[] accounts = accMan + .getAccountsByType(MainApp.getAccountType()); + return accounts.length > 0; + } + + + public static boolean setCurrentOwnCloudAccount(Context context, String accountName) { + boolean result = false; + if (accountName != null) { + Account[] ocAccounts = AccountManager.get(context).getAccountsByType( + MainApp.getAccountType()); + boolean found = false; + for (Account account : ocAccounts) { + found = (account.name.equals(accountName)); + if (found) { + SharedPreferences.Editor appPrefs = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + appPrefs.putString("select_oc_account", accountName); + + appPrefs.commit(); + result = true; + break; + } + } + } + return result; + } + + /** + * Returns the proper URL path to access the WebDAV interface of an ownCloud server, + * according to its version and the authorization method used. + * + * @param version Version of ownCloud server. + * @param authTokenType Authorization token type, matching some of the AUTH_TOKEN_TYPE_* constants in {@link AccountAuthenticator}. + * @return WebDAV path for given OC version and authorization method, null if OC version is unknown. + */ + public static String getWebdavPath(OwnCloudVersion version, String authTokenType) { + if (version != null) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(authTokenType)) { + return ODAV_PATH; + } + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(authTokenType)) { + return SAML_SSO_PATH; + } + if (version.compareTo(OwnCloudVersion.owncloud_v4) >= 0) + return WEBDAV_PATH_4_0; + if (version.compareTo(OwnCloudVersion.owncloud_v3) >= 0 + || version.compareTo(OwnCloudVersion.owncloud_v2) >= 0) + return WEBDAV_PATH_2_0; + if (version.compareTo(OwnCloudVersion.owncloud_v1) >= 0) + return WEBDAV_PATH_1_2; + } + return null; + } + +} diff --git a/src/com/owncloud/android/authentication/AuthenticatorActivity.java b/src/com/owncloud/android/authentication/AuthenticatorActivity.java new file mode 100644 index 00000000..e0a0ba95 --- /dev/null +++ b/src/com/owncloud/android/authentication/AuthenticatorActivity.java @@ -0,0 +1,1788 @@ +/* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * 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 . + * + */ + +package com.owncloud.android.authentication; + +import java.security.cert.X509Certificate; +import java.util.Map; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.net.http.SslError; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.view.View.OnTouchListener; +import android.view.Window; +import android.view.inputmethod.EditorInfo; +import android.webkit.SslErrorHandler; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener; +import com.owncloud.android.lib.common.accounts.AccountTypeUtils; +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; +import com.owncloud.android.operations.DetectAuthenticationMethodOperation.AuthenticationMethod; +import com.owncloud.android.operations.GetServerInfoOperation; +import com.owncloud.android.operations.OAuth2GetAccessToken; + +import com.owncloud.android.lib.common.network.CertificateCombinedException; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +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.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.users.GetRemoteUserNameOperation; + +import com.owncloud.android.services.OperationsService; +import com.owncloud.android.services.OperationsService.OperationsServiceBinder; +import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; +import com.owncloud.android.ui.dialog.SamlWebViewDialog; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener; +import com.owncloud.android.utils.Log_OC; +import com.owncloud.android.lib.resources.status.OwnCloudVersion; + +/** + * This Activity is used to add an ownCloud account to the App + * + * @author Bartek Przybylski + * @author David A. Velasco + * @author masensio + */ +public class AuthenticatorActivity extends AccountAuthenticatorActivity +implements OnRemoteOperationListener, OnFocusChangeListener, OnEditorActionListener, +SsoWebViewClientListener, OnSslUntrustedCertListener { + + private static final String TAG = AuthenticatorActivity.class.getSimpleName(); + + public static final String EXTRA_ACTION = "ACTION"; + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + + private static final String KEY_AUTH_TOKEN_TYPE = "AUTH_TOKEN_TYPE"; + + private static final String KEY_HOST_URL_TEXT = "HOST_URL_TEXT"; + private static final String KEY_OC_VERSION = "OC_VERSION"; + private static final String KEY_SERVER_VALID = "SERVER_VALID"; + private static final String KEY_SERVER_CHECKED = "SERVER_CHECKED"; + private static final String KEY_SERVER_STATUS_TEXT = "SERVER_STATUS_TEXT"; + private static final String KEY_SERVER_STATUS_ICON = "SERVER_STATUS_ICON"; + private static final String KEY_IS_SSL_CONN = "IS_SSL_CONN"; + private static final String KEY_PASSWORD_EXPOSED = "PASSWORD_VISIBLE"; + private static final String KEY_AUTH_STATUS_TEXT = "AUTH_STATUS_TEXT"; + private static final String KEY_AUTH_STATUS_ICON = "AUTH_STATUS_ICON"; + private static final String KEY_SERVER_AUTH_METHOD = "SERVER_AUTH_METHOD"; + private static final String KEY_WAITING_FOR_OP_ID = "DETECT_AUTH_OP_ID"; + private static final String KEY_AUTH_TOKEN = "AUTH_TOKEN"; + + private static final String AUTH_ON = "on"; + private static final String AUTH_OPTIONAL = "optional"; + + public static final byte ACTION_CREATE = 0; + public static final byte ACTION_UPDATE_TOKEN = 1; // requested by the user + public static final byte ACTION_UPDATE_EXPIRED_TOKEN = 2; // detected by the app + + private static final String UNTRUSTED_CERT_DIALOG_TAG = "UNTRUSTED_CERT_DIALOG"; + private static final String SAML_DIALOG_TAG = "SAML_DIALOG"; + private static final String WAIT_DIALOG_TAG = "WAIT_DIALOG"; + + + /// parameters from EXTRAs in starter Intent + private byte mAction; + private Account mAccount; + private String mAuthTokenType; + + + /// activity-level references / state + private final Handler mHandler = new Handler(); + private ServiceConnection mOperationsServiceConnection = null; + private OperationsServiceBinder mOperationsServiceBinder = null; + private AccountManager mAccountMgr; + private Uri mNewCapturedUriFromOAuth2Redirection; + + + /// Server PRE-Fragment elements + private EditText mHostUrlInput; + private View mRefreshButton; + private TextView mServerStatusView; + + private TextWatcher mHostUrlInputWatcher; + private int mServerStatusText = 0, mServerStatusIcon = 0; + + private boolean mServerIsChecked = false; + private boolean mServerIsValid = false; + + private GetServerInfoOperation.ServerInfo mServerInfo = + new GetServerInfoOperation.ServerInfo(); + + + /// Authentication PRE-Fragment elements + private CheckBox mOAuth2Check; + private TextView mOAuthAuthEndpointText; + private TextView mOAuthTokenEndpointText; + private EditText mUsernameInput; + private EditText mPasswordInput; + private View mOkButton; + private TextView mAuthStatusView; + + private int mAuthStatusText = 0, mAuthStatusIcon = 0; + + private String mAuthToken = ""; + + + /// Identifier of operation in progress which result shouldn't be lost + private long mWaitingForOpId = Long.MAX_VALUE; + + + /** + * {@inheritDoc} + * + * IMPORTANT ENTRY POINT 1: activity is shown to the user + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + //Log_OC.wtf(TAG, "onCreate init"); + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + + // bind to Operations Service + mOperationsServiceConnection = new OperationsServiceConnection(); + if (!bindService(new Intent(this, OperationsService.class), + mOperationsServiceConnection, + Context.BIND_AUTO_CREATE)) { + Toast.makeText(this, + R.string.error_cant_bind_to_operations_service, + Toast.LENGTH_LONG) + .show(); + finish(); + } + + /// init activity state + mAccountMgr = AccountManager.get(this); + mNewCapturedUriFromOAuth2Redirection = null; + + /// get input values + mAction = getIntent().getByteExtra(EXTRA_ACTION, ACTION_CREATE); + mAccount = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); + if (savedInstanceState == null) { + initAuthTokenType(); + } else { + mAuthTokenType = savedInstanceState.getString(KEY_AUTH_TOKEN_TYPE); + mWaitingForOpId = savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID); + } + + /// load user interface + setContentView(R.layout.account_setup); + + /// initialize general UI elements + initOverallUi(savedInstanceState); + + /// initialize block to be moved to single Fragment to check server and get info about it + initServerPreFragment(savedInstanceState); + + /// initialize block to be moved to single Fragment to retrieve and validate credentials + initAuthorizationPreFragment(savedInstanceState); + + //Log_OC.wtf(TAG, "onCreate end"); + } + + private void initAuthTokenType() { + mAuthTokenType = + getIntent().getExtras().getString(AccountAuthenticator.KEY_AUTH_TOKEN_TYPE); + if (mAuthTokenType == null) { + if (mAccount != null) { + boolean oAuthRequired = + (mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2) != null); + boolean samlWebSsoRequired = + (mAccountMgr.getUserData(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO) != null); + mAuthTokenType = chooseAuthTokenType(oAuthRequired, samlWebSsoRequired); + + } else { + boolean oAuthSupported = AUTH_ON.equals(getString(R.string.auth_method_oauth2)); + boolean samlWebSsoSupported = AUTH_ON.equals(getString(R.string.auth_method_saml_web_sso)); + mAuthTokenType = chooseAuthTokenType(oAuthSupported, samlWebSsoSupported); + } + } + } + + private String chooseAuthTokenType(boolean oauth, boolean saml) { + if (saml) { + return AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()); + } else if (oauth) { + return AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()); + } else { + return AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()); + } + } + + + /** + * Configures elements in the user interface under direct control of the Activity. + * + * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)} + */ + private void initOverallUi(Bundle savedInstanceState) { + + /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState) + boolean isWelcomeLinkVisible = getResources().getBoolean(R.bool.show_welcome_link); + + String instructionsMessageText = null; + if (mAction == ACTION_UPDATE_EXPIRED_TOKEN) { + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()) + .equals(mAuthTokenType)) { + instructionsMessageText = getString(R.string.auth_expired_oauth_token_toast); + + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()) + .equals(mAuthTokenType)) { + instructionsMessageText = getString(R.string.auth_expired_saml_sso_token_toast); + + } else { + instructionsMessageText = getString(R.string.auth_expired_basic_auth_toast); + } + } + + /// step 2 - set properties of UI elements (text, visibility, enabled...) + Button welcomeLink = (Button) findViewById(R.id.welcome_link); + welcomeLink.setVisibility(isWelcomeLinkVisible ? View.VISIBLE : View.GONE); + welcomeLink.setText( + String.format(getString(R.string.auth_register), getString(R.string.app_name))); + + TextView instructionsView = (TextView) findViewById(R.id.instructions_message); + if (instructionsMessageText != null) { + instructionsView.setVisibility(View.VISIBLE); + instructionsView.setText(instructionsMessageText); + } else { + instructionsView.setVisibility(View.GONE); + } + } + + + /** + * + * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)} + */ + private void initServerPreFragment(Bundle savedInstanceState) { + + /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState) + boolean isUrlInputAllowed = getResources().getBoolean(R.bool.show_server_url_input); + if (savedInstanceState == null) { + if (mAccount != null) { + mServerInfo.mBaseUrl = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_BASE_URL); + mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith("https://"); // TODO do this in a setter for mBaseUrl + String ocVersion = mAccountMgr.getUserData(mAccount, Constants.KEY_OC_VERSION); + if (ocVersion != null) { + mServerInfo.mVersion = new OwnCloudVersion(ocVersion); + } + } else { + mServerInfo.mBaseUrl = getString(R.string.server_url).trim(); + mServerInfo.mIsSslConn = mServerInfo.mBaseUrl.startsWith("https://"); + } + } else { + mServerStatusText = savedInstanceState.getInt(KEY_SERVER_STATUS_TEXT); + mServerStatusIcon = savedInstanceState.getInt(KEY_SERVER_STATUS_ICON); + + mServerIsValid = savedInstanceState.getBoolean(KEY_SERVER_VALID); + mServerIsChecked = savedInstanceState.getBoolean(KEY_SERVER_CHECKED); + + // TODO parcelable + mServerInfo.mIsSslConn = savedInstanceState.getBoolean(KEY_IS_SSL_CONN); + mServerInfo.mBaseUrl = savedInstanceState.getString(KEY_HOST_URL_TEXT); + String ocVersion = savedInstanceState.getString(KEY_OC_VERSION); + if (ocVersion != null) { + mServerInfo.mVersion = new OwnCloudVersion(ocVersion); + } + mServerInfo.mAuthMethod = AuthenticationMethod.valueOf( + savedInstanceState.getString(KEY_SERVER_AUTH_METHOD)); + + } + + /// step 2 - set properties of UI elements (text, visibility, enabled...) + mHostUrlInput = (EditText) findViewById(R.id.hostUrlInput); + mHostUrlInput.setText(mServerInfo.mBaseUrl); + if (mAction != ACTION_CREATE) { + /// lock things that should not change + mHostUrlInput.setEnabled(false); + mHostUrlInput.setFocusable(false); + } + if (isUrlInputAllowed) { + mRefreshButton = findViewById(R.id.embeddedRefreshButton); + } else { + findViewById(R.id.hostUrlFrame).setVisibility(View.GONE); + mRefreshButton = findViewById(R.id.centeredRefreshButton); + } + showRefreshButton(mServerIsChecked && !mServerIsValid && + mWaitingForOpId > Integer.MAX_VALUE); + mServerStatusView = (TextView) findViewById(R.id.server_status_text); + showServerStatus(); + + /// step 3 - bind some listeners and options + mHostUrlInput.setImeOptions(EditorInfo.IME_ACTION_NEXT); + mHostUrlInput.setOnEditorActionListener(this); + + /// step 4 - create listeners that will be bound at onResume + mHostUrlInputWatcher = new TextWatcher() { + + @Override + public void afterTextChanged(Editable s) { + if (mOkButton.isEnabled() && + !mServerInfo.mBaseUrl.equals( + normalizeUrl(s.toString(), mServerInfo.mIsSslConn))) { + mOkButton.setEnabled(false); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (mAuthStatusIcon != 0) { + Log_OC.d(TAG, "onTextChanged: hiding authentication status"); + mAuthStatusIcon = 0; + mAuthStatusText = 0; + showAuthStatus(); + } + } + }; + + + // TODO find out if this is really necessary, or if it can done in a different way + findViewById(R.id.scroll).setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) && + mHostUrlInput.hasFocus()) { + checkOcServer(); + } + } + return false; + } + }); + + + /// step 4 - automatic actions to start + if (savedInstanceState == null) { + if (mAction != ACTION_CREATE || !isUrlInputAllowed) { + checkOcServer(); + } + } + } + + + /** + * + * @param savedInstanceState Saved activity state, as in {{@link #onCreate(Bundle)} + */ + private void initAuthorizationPreFragment(Bundle savedInstanceState) { + + /// step 0 - get UI elements in layout + mOAuth2Check = (CheckBox) findViewById(R.id.oauth_onOff_check); + mOAuthAuthEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_1); + mOAuthTokenEndpointText = (TextView)findViewById(R.id.oAuthEntryPoint_2); + mUsernameInput = (EditText) findViewById(R.id.account_username); + mPasswordInput = (EditText) findViewById(R.id.account_password); + mAuthStatusView = (TextView) findViewById(R.id.auth_status_text); + mOkButton = findViewById(R.id.buttonOK); + + /// step 1 - load and process relevant inputs (resources, intent, savedInstanceState) + String presetUserName = null; + boolean isPasswordExposed = false; + if (savedInstanceState == null) { + if (mAccount != null) { + presetUserName = mAccount.name.substring(0, mAccount.name.lastIndexOf('@')); + } + + } else { + isPasswordExposed = savedInstanceState.getBoolean(KEY_PASSWORD_EXPOSED, false); + mAuthStatusText = savedInstanceState.getInt(KEY_AUTH_STATUS_TEXT); + mAuthStatusIcon = savedInstanceState.getInt(KEY_AUTH_STATUS_ICON); + mAuthToken = savedInstanceState.getString(KEY_AUTH_TOKEN); + } + + /// step 2 - set properties of UI elements (text, visibility, enabled...) + mOAuth2Check.setChecked( + AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()) + .equals(mAuthTokenType)); + if (presetUserName != null) { + mUsernameInput.setText(presetUserName); + } + if (mAction != ACTION_CREATE) { + mUsernameInput.setEnabled(false); + mUsernameInput.setFocusable(false); + } + mPasswordInput.setText(""); // clean password to avoid social hacking + if (isPasswordExposed) { + showPassword(); + } + updateAuthenticationPreFragmentVisibility(); + showAuthStatus(); + mOkButton.setEnabled(mServerIsValid); + + + /// step 3 - bind listeners + // bindings for password input field + mPasswordInput.setOnFocusChangeListener(this); + mPasswordInput.setImeOptions(EditorInfo.IME_ACTION_DONE); + mPasswordInput.setOnEditorActionListener(this); + mPasswordInput.setOnTouchListener(new RightDrawableOnTouchListener() { + @Override + public boolean onDrawableTouch(final MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_UP) { + AuthenticatorActivity.this.onViewPasswordClick(); + } + return true; + } + }); + + } + + + /** + * Changes the visibility of input elements depending on + * the current authorization method. + */ + private void updateAuthenticationPreFragmentVisibility () { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()). + equals(mAuthTokenType)) { + // SAML-based web Single Sign On + mOAuth2Check.setVisibility(View.GONE); + mOAuthAuthEndpointText.setVisibility(View.GONE); + mOAuthTokenEndpointText.setVisibility(View.GONE); + mUsernameInput.setVisibility(View.GONE); + mPasswordInput.setVisibility(View.GONE); + + } else { + if (mAction == ACTION_CREATE && + AUTH_OPTIONAL.equals(getString(R.string.auth_method_oauth2))) { + mOAuth2Check.setVisibility(View.VISIBLE); + } else { + mOAuth2Check.setVisibility(View.GONE); + } + + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()). + equals(mAuthTokenType)) { + // OAuth 2 authorization + + mOAuthAuthEndpointText.setVisibility(View.VISIBLE); + mOAuthTokenEndpointText.setVisibility(View.VISIBLE); + mUsernameInput.setVisibility(View.GONE); + mPasswordInput.setVisibility(View.GONE); + + } else { + // basic HTTP authorization + mOAuthAuthEndpointText.setVisibility(View.GONE); + mOAuthTokenEndpointText.setVisibility(View.GONE); + mUsernameInput.setVisibility(View.VISIBLE); + mPasswordInput.setVisibility(View.VISIBLE); + } + } + } + + + + /** + * Saves relevant state before {@link #onPause()} + * + * Do NOT save {@link #mNewCapturedUriFromOAuth2Redirection}; it keeps a temporal flag, intended to defer the + * processing of the redirection caught in {@link #onNewIntent(Intent)} until {@link #onResume()} + * + * See {@link #loadSavedInstanceState(Bundle)} + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + //Log_OC.wtf(TAG, "onSaveInstanceState init" ); + super.onSaveInstanceState(outState); + + /// global state + outState.putString(KEY_AUTH_TOKEN_TYPE, mAuthTokenType); + outState.putLong(KEY_WAITING_FOR_OP_ID, mWaitingForOpId); + + /// Server PRE-fragment state + outState.putInt(KEY_SERVER_STATUS_TEXT, mServerStatusText); + outState.putInt(KEY_SERVER_STATUS_ICON, mServerStatusIcon); + outState.putBoolean(KEY_SERVER_CHECKED, mServerIsChecked); + outState.putBoolean(KEY_SERVER_VALID, mServerIsValid); + outState.putBoolean(KEY_IS_SSL_CONN, mServerInfo.mIsSslConn); + outState.putString(KEY_HOST_URL_TEXT, mServerInfo.mBaseUrl); + if (mServerInfo.mVersion != null) { + outState.putString(KEY_OC_VERSION, mServerInfo.mVersion.getVersion()); + } + outState.putString(KEY_SERVER_AUTH_METHOD, mServerInfo.mAuthMethod.name()); + + /// Authentication PRE-fragment state + outState.putBoolean(KEY_PASSWORD_EXPOSED, isPasswordVisible()); + outState.putInt(KEY_AUTH_STATUS_ICON, mAuthStatusIcon); + outState.putInt(KEY_AUTH_STATUS_TEXT, mAuthStatusText); + outState.putString(KEY_AUTH_TOKEN, mAuthToken); + + //Log_OC.wtf(TAG, "onSaveInstanceState end" ); + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request + * is caught here. + * + * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the + * AndroidManifest.xml file. + */ + @Override + protected void onNewIntent (Intent intent) { + Log_OC.d(TAG, "onNewIntent()"); + Uri data = intent.getData(); + if (data != null && data.toString().startsWith(getString(R.string.oauth2_redirect_uri))) { + mNewCapturedUriFromOAuth2Redirection = data; + } + } + + + /** + * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and + * deferred in {@link #onNewIntent(Intent)}, is processed here. + */ + @Override + protected void onResume() { + //Log_OC.wtf(TAG, "onResume init" ); + super.onResume(); + + // bound here to avoid spurious changes triggered by Android on device rotations + mHostUrlInput.setOnFocusChangeListener(this); + mHostUrlInput.addTextChangedListener(mHostUrlInputWatcher); + + if (mNewCapturedUriFromOAuth2Redirection != null) { + getOAuth2AccessTokenFromCapturedRedirection(); + } + + if (mOperationsServiceBinder != null) { + doOnResumeAndBound(); + } + + //Log_OC.wtf(TAG, "onResume end" ); + } + + + @Override + protected void onPause() { + //Log_OC.wtf(TAG, "onPause init" ); + if (mOperationsServiceBinder != null) { + //Log_OC.wtf(TAG, "unregistering to listen for operation callbacks" ); + mOperationsServiceBinder.removeOperationListener(this); + } + + mHostUrlInput.removeTextChangedListener(mHostUrlInputWatcher); + mHostUrlInput.setOnFocusChangeListener(null); + + super.onPause(); + //Log_OC.wtf(TAG, "onPause end" ); + } + + @Override + protected void onDestroy() { + + mHostUrlInputWatcher = null; + + if (mOperationsServiceConnection != null) { + unbindService(mOperationsServiceConnection); + mOperationsServiceBinder = null; + } + super.onDestroy(); + } + + + /** + * Parses the redirection with the response to the GET AUTHORIZATION request to the + * oAuth server and requests for the access token (GET ACCESS TOKEN) + */ + private void getOAuth2AccessTokenFromCapturedRedirection() { + /// Parse data from OAuth redirection + String queryParameters = mNewCapturedUriFromOAuth2Redirection.getQuery(); + mNewCapturedUriFromOAuth2Redirection = null; + + /// Showing the dialog with instructions for the user. + IndeterminateProgressDialog dialog = + IndeterminateProgressDialog.newInstance(R.string.auth_getting_authorization, true); + dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); + + /// GET ACCESS TOKEN to the oAuth server + Intent getServerInfoIntent = new Intent(); + getServerInfoIntent.setAction(OperationsService.ACTION_OAUTH2_GET_ACCESS_TOKEN); + + getServerInfoIntent.putExtra( + OperationsService.EXTRA_SERVER_URL, + mOAuthTokenEndpointText.getText().toString().trim()); + + getServerInfoIntent.putExtra( + OperationsService.EXTRA_OAUTH2_QUERY_PARAMETERS, + queryParameters); + + if (mOperationsServiceBinder != null) { + //Log_OC.wtf(TAG, "getting access token..." ); + mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent); + } + } + + + + /** + * Handles the change of focus on the text inputs for the server URL and the password + */ + public void onFocusChange(View view, boolean hasFocus) { + if (view.getId() == R.id.hostUrlInput) { + if (!hasFocus) { + onUrlInputFocusLost((TextView) view); + } + else { + showRefreshButton(false); + } + + } else if (view.getId() == R.id.account_password) { + onPasswordFocusChanged((TextView) view, hasFocus); + } + } + + + /** + * Handles changes in focus on the text input for the server URL. + * + * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to + * other field. The operation to check the existence of the server in the entered URL is + * started. + * + * When hasFocus: user 'comes back' to write again the server URL. + * + * @param hostInput TextView with the URL input field receiving the change of focus. + */ + private void onUrlInputFocusLost(TextView hostInput) { + if (!mServerInfo.mBaseUrl.equals( + normalizeUrl(mHostUrlInput.getText().toString(), mServerInfo.mIsSslConn))) { + // check server again only if the user changed something in the field + checkOcServer(); + } else { + mOkButton.setEnabled(mServerIsValid); + showRefreshButton(!mServerIsValid); + } + } + + + private void checkOcServer() { + String uri = mHostUrlInput.getText().toString().trim(); + mServerIsValid = false; + mServerIsChecked = false; + mOkButton.setEnabled(false); + mServerInfo = new GetServerInfoOperation.ServerInfo(); + showRefreshButton(false); + + if (uri.length() != 0) { + mServerStatusText = R.string.auth_testing_connection; + mServerStatusIcon = R.drawable.progress_small; + showServerStatus(); + + Intent getServerInfoIntent = new Intent(); + getServerInfoIntent.setAction(OperationsService.ACTION_GET_SERVER_INFO); + getServerInfoIntent.putExtra(OperationsService.EXTRA_SERVER_URL, uri); + getServerInfoIntent.putExtra(OperationsService.EXTRA_AUTH_TOKEN_TYPE, mAuthTokenType); + if (mOperationsServiceBinder != null) { + //Log_OC.wtf(TAG, "checking server..." ); + mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent); + } + + } else { + mServerStatusText = 0; + mServerStatusIcon = 0; + showServerStatus(); + } + } + + + /** + * Handles changes in focus on the text input for the password (basic authorization). + * + * When (hasFocus), the button to toggle password visibility is shown. + * + * When (!hasFocus), the button is made invisible and the password is hidden. + * + * @param passwordInput TextView with the password input field receiving the change of focus. + * @param hasFocus 'True' if focus is received, 'false' if is lost + */ + private void onPasswordFocusChanged(TextView passwordInput, boolean hasFocus) { + if (hasFocus) { + showViewPasswordButton(); + } else { + hidePassword(); + hidePasswordButton(); + } + } + + + private void showViewPasswordButton() { + int drawable = R.drawable.ic_view; + if (isPasswordVisible()) { + drawable = R.drawable.ic_hide; + } + mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, drawable, 0); + } + + private boolean isPasswordVisible() { + return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + } + + private void hidePasswordButton() { + mPasswordInput.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + } + + private void showPassword() { + mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + showViewPasswordButton(); + } + + private void hidePassword() { + mPasswordInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + showViewPasswordButton(); + } + + /** + * Checks the credentials of the user in the root of the ownCloud server + * before creating a new local account. + * + * For basic authorization, a check of existence of the root folder is + * performed. + * + * For OAuth, starts the flow to get an access token; the credentials test + * is postponed until it is available. + * + * IMPORTANT ENTRY POINT 4 + * + * @param view OK button + */ + public void onOkClick(View view) { + // this check should be unnecessary + if (mServerInfo.mVersion == null || + !mServerInfo.mVersion.isVersionValid() || + mServerInfo.mBaseUrl == null || + mServerInfo.mBaseUrl.length() == 0) { + mServerStatusIcon = R.drawable.common_error; + mServerStatusText = R.string.auth_wtf_reenter_URL; + showServerStatus(); + mOkButton.setEnabled(false); + //Log_OC.wtf(TAG, "The user was allowed to click 'connect' to an unchecked server!!"); + return; + } + + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { + startOauthorization(); + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + startSamlBasedFederatedSingleSignOnAuthorization(); + } else { + checkBasicAuthorization(); + } + } + + + /** + * Tests the credentials entered by the user performing a check of existence on + * the root folder of the ownCloud server. + */ + private void checkBasicAuthorization() { + /// get the path to the root folder through WebDAV from the version server + String webdav_path = AccountUtils.getWebdavPath(mServerInfo.mVersion, mAuthTokenType); + + /// get basic credentials entered by user + String username = mUsernameInput.getText().toString(); + String password = mPasswordInput.getText().toString(); + + /// be gentle with the user + IndeterminateProgressDialog dialog = + IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true); + dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); + + /// test credentials accessing the root folder + String remotePath =""; + boolean successIfAbsent = false; + boolean followRedirects = true; + startExistenceCheckRemoteOperation(remotePath, this, successIfAbsent, webdav_path, username, password, followRedirects); + + } + + private void startExistenceCheckRemoteOperation(String remotePath, Context context, boolean successIfAbsent, String webdav_path, + String username, String password, boolean followRedirects) { + Intent existenceCheckIntent = new Intent(); + existenceCheckIntent.setAction(OperationsService.ACTION_EXISTENCE_CHECK); + existenceCheckIntent.putExtra(OperationsService.EXTRA_SERVER_URL, mServerInfo.mBaseUrl); + existenceCheckIntent.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath); + existenceCheckIntent.putExtra(OperationsService.EXTRA_SUCCESS_IF_ABSENT, successIfAbsent); + existenceCheckIntent.putExtra(OperationsService.EXTRA_WEBDAV_PATH, webdav_path); + existenceCheckIntent.putExtra(OperationsService.EXTRA_USERNAME, username); + existenceCheckIntent.putExtra(OperationsService.EXTRA_PASSWORD, password); + existenceCheckIntent.putExtra(OperationsService.EXTRA_AUTH_TOKEN, mAuthToken); + existenceCheckIntent.putExtra(OperationsService.EXTRA_FOLLOW_REDIRECTS, followRedirects); + + if (mOperationsServiceBinder != null) { + //Log_OC.wtf(TAG, "starting existenceCheckRemoteOperation..." ); + mWaitingForOpId = mOperationsServiceBinder.newOperation(existenceCheckIntent); + } + } + + /** + * Starts the OAuth 'grant type' flow to get an access token, with + * a GET AUTHORIZATION request to the BUILT-IN authorization server. + */ + private void startOauthorization() { + // be gentle with the user + mAuthStatusIcon = R.drawable.progress_small; + mAuthStatusText = R.string.oauth_login_connection; + showAuthStatus(); + + // GET AUTHORIZATION request + Uri uri = Uri.parse(mOAuthAuthEndpointText.getText().toString().trim()); + Uri.Builder uriBuilder = uri.buildUpon(); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_RESPONSE_TYPE, getString(R.string.oauth2_response_type)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_REDIRECT_URI, getString(R.string.oauth2_redirect_uri)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_CLIENT_ID, getString(R.string.oauth2_client_id)); + uriBuilder.appendQueryParameter(OAuth2Constants.KEY_SCOPE, getString(R.string.oauth2_scope)); + uri = uriBuilder.build(); + Log_OC.d(TAG, "Starting browser to view " + uri.toString()); + Intent i = new Intent(Intent.ACTION_VIEW, uri); + startActivity(i); + } + + + /** + * Starts the Web Single Sign On flow to get access to the root folder + * in the server. + */ + private void startSamlBasedFederatedSingleSignOnAuthorization() { + // be gentle with the user + mAuthStatusIcon = R.drawable.progress_small; + mAuthStatusText = R.string.auth_connecting_auth_server; + showAuthStatus(); + IndeterminateProgressDialog dialog = + IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true); + dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); + + /// get the path to the root folder through WebDAV from the version server + String webdav_path = AccountUtils.getWebdavPath(mServerInfo.mVersion, mAuthTokenType); + + /// test credentials accessing the root folder + String remotePath =""; + boolean successIfAbsent = false; + boolean followRedirections = false; + startExistenceCheckRemoteOperation(remotePath, this, successIfAbsent, webdav_path, "", "", followRedirections); + + } + + /** + * Callback method invoked when a RemoteOperation executed by this Activity finishes. + * + * Dispatches the operation flow to the right method. + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + + if (operation instanceof GetServerInfoOperation) { + if (operation.hashCode() == mWaitingForOpId) { + onGetServerInfoFinish(result); + } // else nothing ; only the last check operation is considered; + // multiple can be started if the user amends a URL quickly + + } else if (operation instanceof OAuth2GetAccessToken) { + onGetOAuthAccessTokenFinish(result); + + } else if (operation instanceof ExistenceCheckRemoteOperation) { + //Log_OC.wtf(TAG, "received detection response through callback" ); + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + onSamlBasedFederatedSingleSignOnAuthorizationStart(result); + + } else { + onAuthorizationCheckFinish(result); + } + } else if (operation instanceof GetRemoteUserNameOperation) { + onGetUserNameFinish(result); + } + + } + + private void onGetUserNameFinish(RemoteOperationResult result) { + mWaitingForOpId = Long.MAX_VALUE; + if (result.isSuccess()) { + boolean success = false; + String username = (String) result.getData().get(0); + + if ( mAction == ACTION_CREATE) { + mUsernameInput.setText(username); + success = createAccount(); + } else { + + if (!mUsernameInput.getText().toString().equals(username)) { + // fail - not a new account, but an existing one; disallow + result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_THE_SAME); + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, result.getLogMessage()); + } else { + updateToken(); + success = true; + } + } + + if (success) + finish(); + } else { + updateStatusIconFailUserName(); + showAuthStatus(); + Log_OC.e(TAG, "Access to user name failed: " + result.getLogMessage()); + } + + } + + private void onSamlBasedFederatedSingleSignOnAuthorizationStart(RemoteOperationResult result) { + mWaitingForOpId = Long.MAX_VALUE; + dismissDialog(WAIT_DIALOG_TAG); + + if (result.isIdPRedirection()) { + String url = result.getRedirectedLocation(); + String targetUrl = mServerInfo.mBaseUrl + + AccountUtils.getWebdavPath(mServerInfo.mVersion, mAuthTokenType); + + // Show dialog + SamlWebViewDialog dialog = SamlWebViewDialog.newInstance(url, targetUrl); + dialog.show(getSupportFragmentManager(), SAML_DIALOG_TAG); + + mAuthStatusIcon = 0; + mAuthStatusText = 0; + + } else { + mAuthStatusIcon = R.drawable.common_error; + mAuthStatusText = R.string.auth_unsupported_auth_method; + + } + showAuthStatus(); + } + + + /** + * Processes the result of the server check performed when the user finishes the enter of the + * server URL. + * + * @param operation Server check performed. + * @param result Result of the check. + */ + private void onGetServerInfoFinish(RemoteOperationResult result) { + /// update activity state + mServerIsChecked = true; + mWaitingForOpId = Long.MAX_VALUE; + + // update server status, but don't show it yet + updateServerStatusIconAndText(result); + + if (result.isSuccess()) { + /// SUCCESS means: + // 1. connection succeeded, and we know if it's SSL or not + // 2. server is installed + // 3. we got the server version + // 4. we got the authentication method required by the server + mServerInfo = (GetServerInfoOperation.ServerInfo) (result.getData().get(0)); + + if (!authSupported(mServerInfo.mAuthMethod)) { + + updateServerStatusIconNoRegularAuth(); // overrides updateServerStatusIconAndText() + mServerIsValid = false; + + } else { + mServerIsValid = true; + } + + } else { + mServerIsValid = false; + } + + // refresh UI + showRefreshButton(!mServerIsValid); + showServerStatus(); + mOkButton.setEnabled(mServerIsValid); + + /// very special case (TODO: move to a common place for all the remote operations) + if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { + showUntrustedCertDialog(result); + } + } + + + private boolean authSupported(AuthenticationMethod authMethod) { + String basic = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()); + String oAuth = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()); + String saml = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()); + + return (( mAuthTokenType.equals(basic) && + authMethod.equals(AuthenticationMethod.BASIC_HTTP_AUTH) ) || + ( mAuthTokenType.equals(oAuth) && + authMethod.equals(AuthenticationMethod.BEARER_TOKEN)) || + ( mAuthTokenType.equals(saml) && + authMethod.equals(AuthenticationMethod.SAML_WEB_SSO)) + ); + } + + + // TODO remove, if possible + private String normalizeUrl(String url, boolean sslWhenUnprefixed) { + if (url != null && url.length() > 0) { + url = url.trim(); + if (!url.toLowerCase().startsWith("http://") && + !url.toLowerCase().startsWith("https://")) { + if (sslWhenUnprefixed) { + url = "https://" + url; + } else { + url = "http://" + url; + } + } + + url = trimUrlWebdav(url); + + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + + } + return (url != null ? url : ""); + } + + + // TODO remove, if possible + private String trimUrlWebdav(String url){ + if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_4_0)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_4_0.length()); + } else if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_2_0)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_2_0.length()); + } else if (url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_1_2)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_1_2.length()); + } + return (url != null ? url : ""); + } + + + /** + * Chooses the right icon and text to show to the user for the received operation result. + * + * @param result Result of a remote operation performed in this activity + */ + private void updateServerStatusIconAndText(RemoteOperationResult result) { + mServerStatusIcon = R.drawable.common_error; // the most common case in the switch below + + switch (result.getCode()) { + case OK_SSL: + mServerStatusIcon = android.R.drawable.ic_secure; + mServerStatusText = R.string.auth_secure_connection; + break; + + case OK_NO_SSL: + case OK: + if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { + mServerStatusText = R.string.auth_connection_established; + mServerStatusIcon = R.drawable.ic_ok; + } else { + mServerStatusText = R.string.auth_nossl_plain_ok_title; + mServerStatusIcon = android.R.drawable.ic_partial_secure; + } + break; + + case NO_NETWORK_CONNECTION: + mServerStatusIcon = R.drawable.no_network; + mServerStatusText = R.string.auth_no_net_conn_title; + break; + + case SSL_RECOVERABLE_PEER_UNVERIFIED: + mServerStatusText = R.string.auth_ssl_unverified_server_title; + break; + case BAD_OC_VERSION: + mServerStatusText = R.string.auth_bad_oc_version_title; + break; + case WRONG_CONNECTION: + mServerStatusText = R.string.auth_wrong_connection_title; + break; + case TIMEOUT: + mServerStatusText = R.string.auth_timeout_title; + break; + case INCORRECT_ADDRESS: + mServerStatusText = R.string.auth_incorrect_address_title; + break; + case SSL_ERROR: + mServerStatusText = R.string.auth_ssl_general_error_title; + break; + case UNAUTHORIZED: + mServerStatusText = R.string.auth_unauthorized; + break; + case HOST_NOT_AVAILABLE: + mServerStatusText = R.string.auth_unknown_host_title; + break; + case INSTANCE_NOT_CONFIGURED: + mServerStatusText = R.string.auth_not_configured_title; + break; + case FILE_NOT_FOUND: + mServerStatusText = R.string.auth_incorrect_path_title; + break; + case OAUTH2_ERROR: + mServerStatusText = R.string.auth_oauth_error; + break; + case OAUTH2_ERROR_ACCESS_DENIED: + mServerStatusText = R.string.auth_oauth_error_access_denied; + break; + case UNHANDLED_HTTP_CODE: + case UNKNOWN_ERROR: + mServerStatusText = R.string.auth_unknown_error_title; + break; + default: + mServerStatusText = 0; + mServerStatusIcon = 0; + } + } + + + /** + * Chooses the right icon and text to show to the user for the received operation result. + * + * @param result Result of a remote operation performed in this activity + */ + private void updateAuthStatusIconAndText(RemoteOperationResult result) { + mAuthStatusIcon = R.drawable.common_error; // the most common case in the switch below + + switch (result.getCode()) { + case OK_SSL: + mAuthStatusIcon = android.R.drawable.ic_secure; + mAuthStatusText = R.string.auth_secure_connection; + break; + + case OK_NO_SSL: + case OK: + if (mHostUrlInput.getText().toString().trim().toLowerCase().startsWith("http://") ) { + mAuthStatusText = R.string.auth_connection_established; + mAuthStatusIcon = R.drawable.ic_ok; + } else { + mAuthStatusText = R.string.auth_nossl_plain_ok_title; + mAuthStatusIcon = android.R.drawable.ic_partial_secure; + } + break; + + case NO_NETWORK_CONNECTION: + mAuthStatusIcon = R.drawable.no_network; + mAuthStatusText = R.string.auth_no_net_conn_title; + break; + + case SSL_RECOVERABLE_PEER_UNVERIFIED: + mAuthStatusText = R.string.auth_ssl_unverified_server_title; + break; + case BAD_OC_VERSION: + mAuthStatusText = R.string.auth_bad_oc_version_title; + break; + case WRONG_CONNECTION: + mAuthStatusText = R.string.auth_wrong_connection_title; + break; + case TIMEOUT: + mAuthStatusText = R.string.auth_timeout_title; + break; + case INCORRECT_ADDRESS: + mAuthStatusText = R.string.auth_incorrect_address_title; + break; + case SSL_ERROR: + mAuthStatusText = R.string.auth_ssl_general_error_title; + break; + case UNAUTHORIZED: + mAuthStatusText = R.string.auth_unauthorized; + break; + case HOST_NOT_AVAILABLE: + mAuthStatusText = R.string.auth_unknown_host_title; + break; + case INSTANCE_NOT_CONFIGURED: + mAuthStatusText = R.string.auth_not_configured_title; + break; + case FILE_NOT_FOUND: + mAuthStatusText = R.string.auth_incorrect_path_title; + break; + case OAUTH2_ERROR: + mAuthStatusText = R.string.auth_oauth_error; + break; + case OAUTH2_ERROR_ACCESS_DENIED: + mAuthStatusText = R.string.auth_oauth_error_access_denied; + break; + case ACCOUNT_NOT_NEW: + mAuthStatusText = R.string.auth_account_not_new; + break; + case ACCOUNT_NOT_THE_SAME: + mAuthStatusText = R.string.auth_account_not_the_same; + break; + case UNHANDLED_HTTP_CODE: + case UNKNOWN_ERROR: + mAuthStatusText = R.string.auth_unknown_error_title; + break; + default: + mAuthStatusText = 0; + mAuthStatusIcon = 0; + } + } + + + private void updateStatusIconFailUserName(){ + mAuthStatusIcon = R.drawable.common_error; + mAuthStatusText = R.string.auth_fail_get_user_name; + } + + private void updateServerStatusIconNoRegularAuth(){ + mServerStatusIcon = R.drawable.common_error; + mServerStatusText = R.string.auth_can_not_auth_against_server; + } + + /** + * Processes the result of the request for and access token send + * to an OAuth authorization server. + * + * @param result Result of the operation. + */ + private void onGetOAuthAccessTokenFinish(RemoteOperationResult result) { + mWaitingForOpId = Long.MAX_VALUE; + dismissDialog(WAIT_DIALOG_TAG); + + String webdav_path = AccountUtils.getWebdavPath(mServerInfo.mVersion, mAuthTokenType); + if (result.isSuccess() && webdav_path != null) { + /// be gentle with the user + IndeterminateProgressDialog dialog = + IndeterminateProgressDialog.newInstance(R.string.auth_trying_to_login, true); + dialog.show(getSupportFragmentManager(), WAIT_DIALOG_TAG); + + /// time to test the retrieved access token on the ownCloud server + @SuppressWarnings("unchecked") + Map tokens = (Map)(result.getData().get(0)); + mAuthToken = tokens.get(OAuth2Constants.KEY_ACCESS_TOKEN); + //mAuthToken = ((OAuth2GetAccessToken)operation).getResultTokenMap().get(OAuth2Constants.KEY_ACCESS_TOKEN); + Log_OC.d(TAG, "Got ACCESS TOKEN: " + mAuthToken); + + String remotePath =""; + boolean successIfAbsent = false; + boolean followRedirects = true; + startExistenceCheckRemoteOperation(remotePath, this, successIfAbsent, webdav_path, "", "", followRedirects); + + } else { + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); + } + } + + + /** + * Processes the result of the access check performed to try the user credentials. + * + * Creates a new account through the AccountManager. + * + * @param operation Access check performed. + * @param result Result of the operation. + */ + private void onAuthorizationCheckFinish(RemoteOperationResult result) { + mWaitingForOpId = Long.MAX_VALUE; + dismissDialog(WAIT_DIALOG_TAG); + + if (result.isSuccess()) { + Log_OC.d(TAG, "Successful access - time to save the account"); + + boolean success = false; + if (mAction == ACTION_CREATE) { + success = createAccount(); + + } else { + updateToken(); + success = true; + } + + if (success) { + finish(); + } + + } else if (result.isServerFail() || result.isException()) { + /// server errors or exceptions in authorization take to requiring a new check of + /// the server + mServerIsChecked = true; + mServerIsValid = false; + mServerInfo = new GetServerInfoOperation.ServerInfo(); + + // update status icon and text + updateServerStatusIconAndText(result); + showServerStatus(); + mAuthStatusIcon = 0; + mAuthStatusText = 0; + showAuthStatus(); + + // update input controls state + showRefreshButton(true); + mOkButton.setEnabled(false); + + // very special case (TODO: move to a common place for all the remote operations) (dangerous here?) + if (result.getCode() == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED) { + showUntrustedCertDialog(result); + } + + } else { // authorization fail due to client side - probably wrong credentials + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, "Access failed: " + result.getLogMessage()); + } + } + + + + + /** + * Sets the proper response to get that the Account Authenticator that started this activity saves + * a new authorization token for mAccount. + */ + private void updateToken() { + Bundle response = new Bundle(); + response.putString(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + response.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccount.type); + + if (AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType)) { + response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); + // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); + + } else if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + + response.putString(AccountManager.KEY_AUTHTOKEN, mAuthToken); + // the next line is necessary; by now, notifications are calling directly to the AuthenticatorActivity to update, without AccountManager intervention + mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); + + } else { + response.putString(AccountManager.KEY_AUTHTOKEN, mPasswordInput.getText().toString()); + mAccountMgr.setPassword(mAccount, mPasswordInput.getText().toString()); + } + setAccountAuthenticatorResult(response); + + } + + + /** + * Creates a new account through the Account Authenticator that started this activity. + * + * This makes the account permanent. + * + * TODO Decide how to name the OAuth accounts + */ + private boolean createAccount() { + /// create and save new ownCloud account + boolean isOAuth = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()).equals(mAuthTokenType); + boolean isSaml = AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType); + + Uri uri = Uri.parse(mServerInfo.mBaseUrl); + String username = mUsernameInput.getText().toString().trim(); + if (isOAuth) { + username = "OAuth_user" + (new java.util.Random(System.currentTimeMillis())).nextLong(); + } + String accountName = username + "@" + uri.getHost(); + if (uri.getPort() >= 0) { + accountName += ":" + uri.getPort(); + } + Account newAccount = new Account(accountName, MainApp.getAccountType()); + if (AccountUtils.exists(newAccount, getApplicationContext())) { + // fail - not a new account, but an existing one; disallow + RemoteOperationResult result = new RemoteOperationResult(ResultCode.ACCOUNT_NOT_NEW); + updateAuthStatusIconAndText(result); + showAuthStatus(); + Log_OC.d(TAG, result.getLogMessage()); + return false; + + } else { + mAccount = newAccount; + + if (isOAuth || isSaml) { + mAccountMgr.addAccountExplicitly(mAccount, "", null); // with external authorizations, the password is never input in the app + } else { + mAccountMgr.addAccountExplicitly(mAccount, mPasswordInput.getText().toString(), null); + } + + /// add the new account as default in preferences, if there is none already + Account defaultAccount = AccountUtils.getCurrentOwnCloudAccount(this); + if (defaultAccount == null) { + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(this).edit(); + editor.putString("select_oc_account", accountName); + editor.commit(); + } + + /// prepare result to return to the Authenticator + // TODO check again what the Authenticator makes with it; probably has the same effect as addAccountExplicitly, but it's not well done + final Intent intent = new Intent(); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, MainApp.getAccountType()); + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mAccount.name); + /*if (!isOAuth) + intent.putExtra(AccountManager.KEY_AUTHTOKEN, MainApp.getAccountType()); */ + intent.putExtra(AccountManager.KEY_USERDATA, username); + if (isOAuth || isSaml) { + mAccountMgr.setAuthToken(mAccount, mAuthTokenType, mAuthToken); + } + /// add user data to the new account; TODO probably can be done in the last parameter addAccountExplicitly, or in KEY_USERDATA + mAccountMgr.setUserData(mAccount, Constants.KEY_OC_VERSION, mServerInfo.mVersion.getVersion()); + mAccountMgr.setUserData(mAccount, Constants.KEY_OC_BASE_URL, mServerInfo.mBaseUrl); + + if (isSaml) { + mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE"); + } else if (isOAuth) { + mAccountMgr.setUserData(mAccount, Constants.KEY_SUPPORTS_OAUTH2, "TRUE"); + } + + setAccountAuthenticatorResult(intent.getExtras()); + setResult(RESULT_OK, intent); + + return true; + } + } + + + /** + * Starts and activity to open the 'new account' page in the ownCloud web site + * + * @param view 'Account register' button + */ + public void onRegisterClick(View view) { + Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.welcome_link_url))); + setResult(RESULT_CANCELED); + startActivity(register); + } + + + /** + * Updates the content and visibility state of the icon and text associated + * to the last check on the ownCloud server. + * + * @param serverStatusText Resource identifier of the text to show. + * @param serverStatusIcon Resource identifier of the icon to show. + */ + private void showServerStatus() { + if (mServerStatusIcon == 0 && mServerStatusText == 0) { + mServerStatusView.setVisibility(View.INVISIBLE); + + } else { + mServerStatusView.setText(mServerStatusText); + mServerStatusView.setCompoundDrawablesWithIntrinsicBounds(mServerStatusIcon, 0, 0, 0); + mServerStatusView.setVisibility(View.VISIBLE); + } + + } + + + /** + * Updates the content and visibility state of the icon and text associated + * to the interactions with the OAuth authorization server. + */ + private void showAuthStatus() { + if (mAuthStatusIcon == 0 && mAuthStatusText == 0) { + mAuthStatusView.setVisibility(View.INVISIBLE); + + } else { + mAuthStatusView.setText(mAuthStatusText); + mAuthStatusView.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0); + mAuthStatusView.setVisibility(View.VISIBLE); + } + } + + + private void showRefreshButton (boolean show) { + if (show) { + mRefreshButton.setVisibility(View.VISIBLE); + } else { + mRefreshButton.setVisibility(View.GONE); + } + } + + /** + * Called when the refresh button in the input field for ownCloud host is clicked. + * + * Performs a new check on the URL in the input field. + * + * @param view Refresh 'button' + */ + public void onRefreshClick(View view) { + checkOcServer(); + } + + + /** + * Called when the eye icon in the password field is clicked. + * + * Toggles the visibility of the password in the field. + */ + public void onViewPasswordClick() { + int selectionStart = mPasswordInput.getSelectionStart(); + int selectionEnd = mPasswordInput.getSelectionEnd(); + if (isPasswordVisible()) { + hidePassword(); + } else { + showPassword(); + } + mPasswordInput.setSelection(selectionStart, selectionEnd); + } + + + /** + * Called when the checkbox for OAuth authorization is clicked. + * + * Hides or shows the input fields for user & password. + * + * @param view 'View password' 'button' + */ + public void onCheckClick(View view) { + CheckBox oAuth2Check = (CheckBox)view; + if (oAuth2Check.isChecked()) { + mAuthTokenType = AccountTypeUtils.getAuthTokenTypeAccessToken(MainApp.getAccountType()); + } else { + mAuthTokenType = AccountTypeUtils.getAuthTokenTypePass(MainApp.getAccountType()); + } + updateAuthenticationPreFragmentVisibility(); + } + + + /** + * Called when the 'action' button in an IME is pressed ('enter' in software keyboard). + * + * Used to trigger the authentication check when the user presses 'enter' after writing the password, + * or to throw the server test when the only field on screen is the URL input field. + */ + @Override + public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null && inputField.equals(mPasswordInput)) { + if (mOkButton.isEnabled()) { + mOkButton.performClick(); + } + + } else if (actionId == EditorInfo.IME_ACTION_NEXT && inputField != null && inputField.equals(mHostUrlInput)) { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType)) { + checkOcServer(); + } + } + return false; // always return false to grant that the software keyboard is hidden anyway + } + + + private abstract static class RightDrawableOnTouchListener implements OnTouchListener { + + private int fuzz = 75; + + /** + * {@inheritDoc} + */ + @Override + public boolean onTouch(View view, MotionEvent event) { + Drawable rightDrawable = null; + if (view instanceof TextView) { + Drawable[] drawables = ((TextView)view).getCompoundDrawables(); + if (drawables.length > 2) { + rightDrawable = drawables[2]; + } + } + if (rightDrawable != null) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + final Rect bounds = rightDrawable.getBounds(); + if (x >= (view.getRight() - bounds.width() - fuzz) && x <= (view.getRight() - view.getPaddingRight() + fuzz) + && y >= (view.getPaddingTop() - fuzz) && y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) { + + return onDrawableTouch(event); + } + } + return false; + } + + public abstract boolean onDrawableTouch(final MotionEvent event); + } + + + private void getRemoteUserNameOperation(String sessionCookie, boolean followRedirects) { + + Intent getUserNameIntent = new Intent(); + getUserNameIntent.setAction(OperationsService.ACTION_GET_USER_NAME); + getUserNameIntent.putExtra(OperationsService.EXTRA_SERVER_URL, mServerInfo.mBaseUrl); + getUserNameIntent.putExtra(OperationsService.EXTRA_COOKIE, sessionCookie); + getUserNameIntent.putExtra(OperationsService.EXTRA_FOLLOW_REDIRECTS, followRedirects); + + if (mOperationsServiceBinder != null) { + //Log_OC.wtf(TAG, "starting getRemoteUserNameOperation..." ); + mWaitingForOpId = mOperationsServiceBinder.newOperation(getUserNameIntent); + } + } + + + @Override + public void onSsoFinished(String sessionCookie) { + if (sessionCookie != null && sessionCookie.length() > 0) { + Log_OC.d(TAG, "Successful SSO - time to save the account"); + mAuthToken = sessionCookie; + getRemoteUserNameOperation(sessionCookie, true); + Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG); + if (fd != null && fd instanceof SherlockDialogFragment) { + Dialog d = ((SherlockDialogFragment)fd).getDialog(); + if (d != null && d.isShowing()) { + d.dismiss(); + } + } + + } else { + // TODO - show fail + Log_OC.d(TAG, "SSO failed"); + } + + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (AccountTypeUtils.getAuthTokenTypeSamlSessionCookie(MainApp.getAccountType()).equals(mAuthTokenType) && + mHostUrlInput.hasFocus() && event.getAction() == MotionEvent.ACTION_DOWN) { + checkOcServer(); + } + return super.onTouchEvent(event); + } + + + /** + * Show untrusted cert dialog + */ + public void showUntrustedCertDialog(X509Certificate x509Certificate, SslError error, SslErrorHandler handler) { + // Show a dialog with the certificate info + SslUntrustedCertDialog dialog = null; + if (x509Certificate == null) { + dialog = SslUntrustedCertDialog.newInstanceForEmptySslError(error, handler); + } else { + dialog = SslUntrustedCertDialog.newInstanceForFullSslError(x509Certificate, error, handler); + } + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + ft.addToBackStack(null); + dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG); + } + + /** + * Show untrusted cert dialog + */ + private void showUntrustedCertDialog(RemoteOperationResult result) { + // Show a dialog with the certificate info + SslUntrustedCertDialog dialog = SslUntrustedCertDialog.newInstanceForFullSslError((CertificateCombinedException)result.getException()); + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + ft.addToBackStack(null); + dialog.show(ft, UNTRUSTED_CERT_DIALOG_TAG); + + } + + /** + * Called from SslValidatorDialog when a new server certificate was correctly saved. + */ + public void onSavedCertificate() { + Fragment fd = getSupportFragmentManager().findFragmentByTag(SAML_DIALOG_TAG); + if (fd == null) { + // if SAML dialog is not shown, the SslDialog was shown due to an SSL error in the server check + checkOcServer(); + } + } + + /** + * Called from SslValidatorDialog when a new server certificate could not be saved + * when the user requested it. + */ + @Override + public void onFailedSavingCertificate() { + dismissDialog(SAML_DIALOG_TAG); + Toast.makeText(this, R.string.ssl_validator_not_saved, Toast.LENGTH_LONG).show(); + } + + @Override + public void onCancelCertificate() { + dismissDialog(SAML_DIALOG_TAG); + } + + + private void doOnResumeAndBound() { + //Log_OC.wtf(TAG, "registering to listen for operation callbacks" ); + mOperationsServiceBinder.addOperationListener(AuthenticatorActivity.this, mHandler); + if (mWaitingForOpId <= Integer.MAX_VALUE) { + mOperationsServiceBinder.dispatchResultIfFinished((int)mWaitingForOpId, this); + } + } + + + private void dismissDialog(String dialogTag){ + Fragment frag = getSupportFragmentManager().findFragmentByTag(dialogTag); + if (frag != null && frag instanceof SherlockDialogFragment) { + SherlockDialogFragment dialog = (SherlockDialogFragment) frag; + dialog.dismiss(); + } + } + + + /** + * Implements callback methods for service binding. + */ + private class OperationsServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + if (component.equals(new ComponentName(AuthenticatorActivity.this, OperationsService.class))) { + //Log_OC.wtf(TAG, "Operations service connected"); + mOperationsServiceBinder = (OperationsServiceBinder) service; + + doOnResumeAndBound(); + + } else { + return; + } + + } + + @Override + public void onServiceDisconnected(ComponentName component) { + if (component.equals(new ComponentName(AuthenticatorActivity.this, OperationsService.class))) { + Log_OC.e(TAG, "Operations service crashed"); + mOperationsServiceBinder = null; + } + } + + } + +} diff --git a/src/com/owncloud/android/authentication/OAuth2Constants.java b/src/com/owncloud/android/authentication/OAuth2Constants.java new file mode 100644 index 00000000..f96b6278 --- /dev/null +++ b/src/com/owncloud/android/authentication/OAuth2Constants.java @@ -0,0 +1,53 @@ +/* ownCloud Android client application + * 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 . + * + */ + +package com.owncloud.android.authentication; + +/** + * Constant values for OAuth 2 protocol. + * + * Includes required and optional parameter NAMES used in the 'authorization code' grant type. + * + * @author David A. Velasco + */ + +public class OAuth2Constants { + + /// Parameters to send to the Authorization Endpoint + public static final String KEY_RESPONSE_TYPE = "response_type"; + public static final String KEY_REDIRECT_URI = "redirect_uri"; + public static final String KEY_CLIENT_ID = "client_id"; + public static final String KEY_SCOPE = "scope"; + public static final String KEY_STATE = "state"; + + /// Additional parameters to send to the Token Endpoint + public static final String KEY_GRANT_TYPE = "grant_type"; + public static final String KEY_CODE = "code"; + + /// Parameters received in an OK response from the Token Endpoint + public static final String KEY_ACCESS_TOKEN = "access_token"; + public static final String KEY_TOKEN_TYPE = "token_type"; + public static final String KEY_EXPIRES_IN = "expires_in"; + public static final String KEY_REFRESH_TOKEN = "refresh_token"; + + /// Parameters in an ERROR response + public static final String KEY_ERROR = "error"; + public static final String KEY_ERROR_DESCRIPTION = "error_description"; + public static final String KEY_ERROR_URI = "error_uri"; + public static final String VALUE_ERROR_ACCESS_DENIED = "access_denied"; + +} diff --git a/src/com/owncloud/android/authentication/OwnCloudAccount.java b/src/com/owncloud/android/authentication/OwnCloudAccount.java new file mode 100644 index 00000000..51a9900b --- /dev/null +++ b/src/com/owncloud/android/authentication/OwnCloudAccount.java @@ -0,0 +1,75 @@ +/* ownCloud Android client application + * Copyright (C) 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 . + * + */ + +package com.owncloud.android.authentication; + +import android.accounts.Account; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Account with extra information specific for ownCloud accounts. + * + * TODO integrate in the main app + * + * @author David A. Velasco + */ +public class OwnCloudAccount extends Account { + + private String mAuthTokenType; + + public OwnCloudAccount(String name, String type, String authTokenType) { + super(name, type); + // TODO validate authTokentype as supported + mAuthTokenType = authTokenType; + } + + /** + * Reconstruct from parcel + * + * @param source The source parcel + */ + public OwnCloudAccount(Parcel source) { + super(source); + mAuthTokenType = source.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mAuthTokenType); + } + + + public String getAuthTokenType() { + return mAuthTokenType; + } + + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public OwnCloudAccount createFromParcel(Parcel source) { + return new OwnCloudAccount(source); + } + + @Override + public OwnCloudAccount [] newArray(int size) { + return new OwnCloudAccount[size]; + } + }; + +} diff --git a/src/com/owncloud/android/authentication/SsoWebViewClient.java b/src/com/owncloud/android/authentication/SsoWebViewClient.java new file mode 100644 index 00000000..27e1f5b8 --- /dev/null +++ b/src/com/owncloud/android/authentication/SsoWebViewClient.java @@ -0,0 +1,233 @@ +/* ownCloud Android client application + * 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 . + * + */ + +package com.owncloud.android.authentication; + +import java.io.ByteArrayInputStream; +import java.lang.ref.WeakReference; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import com.owncloud.android.lib.common.network.NetworkUtils; +import com.owncloud.android.utils.Log_OC; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.http.SslCertificate; +import android.net.http.SslError; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.KeyEvent; +import android.view.View; +import android.webkit.CookieManager; +import android.webkit.HttpAuthHandler; +import android.webkit.SslErrorHandler; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; + + +/** + * Custom {@link WebViewClient} client aimed to catch the end of a single-sign-on process + * running in the {@link WebView} that is attached to. + * + * Assumes that the single-sign-on is kept thanks to a cookie set at the end of the + * authentication process. + * + * @author David A. Velasco + */ +public class SsoWebViewClient extends WebViewClient { + + private static final String TAG = SsoWebViewClient.class.getSimpleName(); + + public interface SsoWebViewClientListener { + public void onSsoFinished(String sessionCookie); + } + + private Context mContext; + private Handler mListenerHandler; + private WeakReference mListenerRef; + private String mTargetUrl; + private String mLastReloadedUrlAtError; + + public SsoWebViewClient (Context context, Handler listenerHandler, SsoWebViewClientListener listener) { + mContext = context; + mListenerHandler = listenerHandler; + mListenerRef = new WeakReference(listener); + mTargetUrl = "fake://url.to.be.set"; + mLastReloadedUrlAtError = null; + } + + public String getTargetUrl() { + return mTargetUrl; + } + + public void setTargetUrl(String targetUrl) { + mTargetUrl = targetUrl; + } + + @Override + public void onPageStarted (WebView view, String url, Bitmap favicon) { + Log_OC.d(TAG, "onPageStarted : " + url); + super.onPageStarted(view, url, favicon); + } + + @Override + public void onFormResubmission (WebView view, Message dontResend, Message resend) { + Log_OC.d(TAG, "onFormResubMission "); + + // necessary to grant reload of last page when device orientation is changed after sending a form + resend.sendToTarget(); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + return false; + } + + @Override + public void onReceivedError (WebView view, int errorCode, String description, String failingUrl) { + Log_OC.e(TAG, "onReceivedError : " + failingUrl + ", code " + errorCode + ", description: " + description); + if (!failingUrl.equals(mLastReloadedUrlAtError)) { + view.reload(); + mLastReloadedUrlAtError = failingUrl; + } else { + mLastReloadedUrlAtError = null; + super.onReceivedError(view, errorCode, description, failingUrl); + } + } + + @Override + public void onPageFinished (WebView view, String url) { + Log_OC.d(TAG, "onPageFinished : " + url); + mLastReloadedUrlAtError = null; + if (url.startsWith(mTargetUrl)) { + view.setVisibility(View.GONE); + CookieManager cookieManager = CookieManager.getInstance(); + final String cookies = cookieManager.getCookie(url); + Log_OC.d(TAG, "Cookies: " + cookies); + if (mListenerHandler != null && mListenerRef != null) { + // this is good idea because onPageFinished is not running in the UI thread + mListenerHandler.post(new Runnable() { + @Override + public void run() { + SsoWebViewClientListener listener = mListenerRef.get(); + if (listener != null) { + // Send Cookies to the listener + listener.onSsoFinished(cookies); + } + } + }); + } + } + } + + + @Override + public void doUpdateVisitedHistory (WebView view, String url, boolean isReload) { + Log_OC.d(TAG, "doUpdateVisitedHistory : " + url); + } + + @Override + public void onReceivedSslError (final WebView view, final SslErrorHandler handler, SslError error) { + Log_OC.d(TAG, "onReceivedSslError : " + error); + // Test 1 + X509Certificate x509Certificate = getX509CertificateFromError(error); + boolean isKnownServer = false; + + if (x509Certificate != null) { + Log_OC.d(TAG, "------>>>>> x509Certificate " + x509Certificate.toString()); + + try { + isKnownServer = NetworkUtils.isCertInKnownServersStore((Certificate) x509Certificate, mContext); + } catch (Exception e) { + Log_OC.e(TAG, "Exception: " + e.getMessage()); + } + } + + if (isKnownServer) { + handler.proceed(); + } else { + ((AuthenticatorActivity)mContext).showUntrustedCertDialog(x509Certificate, error, handler); + } + } + + /** + * Obtain the X509Certificate from SslError + * @param error SslError + * @return X509Certificate from error + */ + public X509Certificate getX509CertificateFromError (SslError error) { + Bundle bundle = SslCertificate.saveState(error.getCertificate()); + X509Certificate x509Certificate; + byte[] bytes = bundle.getByteArray("x509-certificate"); + if (bytes == null) { + x509Certificate = null; + } else { + try { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes)); + x509Certificate = (X509Certificate) cert; + } catch (CertificateException e) { + x509Certificate = null; + } + } + return x509Certificate; + } + + @Override + public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm) { + Log_OC.d(TAG, "onReceivedHttpAuthRequest : " + host); + } + + @Override + public WebResourceResponse shouldInterceptRequest (WebView view, String url) { + Log_OC.d(TAG, "shouldInterceptRequest : " + url); + return null; + } + + @Override + public void onLoadResource (WebView view, String url) { + Log_OC.d(TAG, "onLoadResource : " + url); + } + + @Override + public void onReceivedLoginRequest (WebView view, String realm, String account, String args) { + Log_OC.d(TAG, "onReceivedLoginRequest : " + realm + ", " + account + ", " + args); + } + + @Override + public void onScaleChanged (WebView view, float oldScale, float newScale) { + Log_OC.d(TAG, "onScaleChanged : " + oldScale + " -> " + newScale); + super.onScaleChanged(view, oldScale, newScale); + } + + @Override + public void onUnhandledKeyEvent (WebView view, KeyEvent event) { + Log_OC.d(TAG, "onUnhandledKeyEvent : " + event); + } + + @Override + public boolean shouldOverrideKeyEvent (WebView view, KeyEvent event) { + Log_OC.d(TAG, "shouldOverrideKeyEvent : " + event); + return false; + } + +} diff --git a/src/com/owncloud/android/authenticator/AccountAuthenticator.java b/src/com/owncloud/android/authenticator/AccountAuthenticator.java deleted file mode 100644 index 2cbc9d31..00000000 --- a/src/com/owncloud/android/authenticator/AccountAuthenticator.java +++ /dev/null @@ -1,282 +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 . - * - */ - -package com.owncloud.android.authenticator; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.ui.activity.AuthenticatorActivity; - -import android.accounts.*; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; - -public class AccountAuthenticator extends AbstractAccountAuthenticator { - /** - * Is used by android system to assign accounts to authenticators. Should be - * used by application and all extensions. - */ - public static final String ACCOUNT_TYPE = "owncloud"; - public static final String AUTH_TOKEN_TYPE = "org.owncloud"; - - public static final String KEY_AUTH_TOKEN_TYPE = "authTokenType"; - public static final String KEY_REQUIRED_FEATURES = "requiredFeatures"; - public static final String KEY_LOGIN_OPTIONS = "loginOptions"; - public static final String KEY_ACCOUNT = "account"; - /** - * Value under this key should handle path to webdav php script. Will be - * removed and usage should be replaced by combining - * {@link com.owncloud.android.authenticator.AuthenticatorActivity.KEY_OC_BASE_URL} and - * {@link com.owncloud.android.utils.OwnCloudVersion} - * - * @deprecated - */ - public static final String KEY_OC_URL = "oc_url"; - /** - * Version should be 3 numbers separated by dot so it can be parsed by - * {@link com.owncloud.android.utils.OwnCloudVersion} - */ - public static final String KEY_OC_VERSION = "oc_version"; - /** - * Base url should point to owncloud installation without trailing / ie: - * http://server/path or https://owncloud.server - */ - public static final String KEY_OC_BASE_URL = "oc_base_url"; - - private static final String TAG = "AccountAuthenticator"; - private Context mContext; - - public AccountAuthenticator(Context context) { - super(context); - mContext = context; - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle addAccount(AccountAuthenticatorResponse response, - String accountType, String authTokenType, - String[] requiredFeatures, Bundle options) - throws NetworkErrorException { - Log_OC.i(TAG, "Adding account with type " + accountType - + " and auth token " + authTokenType); - try { - validateAccountType(accountType); - } catch (AuthenticatorException e) { - Log_OC.e(TAG, "Failed to validate account type " + accountType + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_REQUIRED_FEATURES, requiredFeatures); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - - setIntentFlags(intent); - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - /** - * {@inheritDoc} - */ - @Override - public Bundle confirmCredentials(AccountAuthenticatorResponse response, - Account account, Bundle options) throws NetworkErrorException { - try { - validateAccountType(account.type); - } catch (AuthenticatorException e) { - Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_ACCOUNT, account); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - - setIntentFlags(intent); - - Bundle resultBundle = new Bundle(); - resultBundle.putParcelable(AccountManager.KEY_INTENT, intent); - return resultBundle; - } - - @Override - public Bundle editProperties(AccountAuthenticatorResponse response, - String accountType) { - return null; - } - - @Override - public Bundle getAuthToken(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - try { - validateAccountType(account.type); - validateAuthTokenType(authTokenType); - } catch (AuthenticatorException e) { - Log_OC.e(TAG, "Failed to validate account type " + account.type + ": " - + e.getMessage()); - e.printStackTrace(); - return e.getFailureBundle(); - } - final AccountManager am = AccountManager.get(mContext); - final String password = am.getPassword(account); - if (password != null) { - final Bundle result = new Bundle(); - result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); - result.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE); - result.putString(AccountManager.KEY_AUTHTOKEN, password); - return result; - } - - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name); - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - @Override - public String getAuthTokenLabel(String authTokenType) { - return null; - } - - @Override - public Bundle hasFeatures(AccountAuthenticatorResponse response, - Account account, String[] features) throws NetworkErrorException { - final Bundle result = new Bundle(); - result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); - return result; - } - - @Override - public Bundle updateCredentials(AccountAuthenticatorResponse response, - Account account, String authTokenType, Bundle options) - throws NetworkErrorException { - final Intent intent = new Intent(mContext, AuthenticatorActivity.class); - intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, - response); - intent.putExtra(KEY_ACCOUNT, account); - intent.putExtra(KEY_AUTH_TOKEN_TYPE, authTokenType); - intent.putExtra(KEY_LOGIN_OPTIONS, options); - setIntentFlags(intent); - - final Bundle bundle = new Bundle(); - bundle.putParcelable(AccountManager.KEY_INTENT, intent); - return bundle; - } - - @Override - public Bundle getAccountRemovalAllowed( - AccountAuthenticatorResponse response, Account account) - throws NetworkErrorException { - return super.getAccountRemovalAllowed(response, account); - } - - private void setIntentFlags(Intent intent) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - intent.addFlags(Intent.FLAG_FROM_BACKGROUND); - } - - private void validateAccountType(String type) - throws UnsupportedAccountTypeException { - if (!type.equals(ACCOUNT_TYPE)) { - throw new UnsupportedAccountTypeException(); - } - } - - private void validateAuthTokenType(String authTokenType) - throws UnsupportedAuthTokenTypeException { - if (!authTokenType.equals(AUTH_TOKEN_TYPE)) { - throw new UnsupportedAuthTokenTypeException(); - } - } - - public static class AuthenticatorException extends Exception { - private static final long serialVersionUID = 1L; - private Bundle mFailureBundle; - - public AuthenticatorException(int code, String errorMsg) { - mFailureBundle = new Bundle(); - mFailureBundle.putInt(AccountManager.KEY_ERROR_CODE, code); - mFailureBundle - .putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); - } - - public Bundle getFailureBundle() { - return mFailureBundle; - } - } - - public static class UnsupportedAccountTypeException extends - AuthenticatorException { - private static final long serialVersionUID = 1L; - - public UnsupportedAccountTypeException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported account type"); - } - } - - public static class UnsupportedAuthTokenTypeException extends - AuthenticatorException { - private static final long serialVersionUID = 1L; - - public UnsupportedAuthTokenTypeException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported auth token type"); - } - } - - public static class UnsupportedFeaturesException extends - AuthenticatorException { - public static final long serialVersionUID = 1L; - - public UnsupportedFeaturesException() { - super(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, - "Unsupported features"); - } - } - - public static class AccessDeniedException extends AuthenticatorException { - public AccessDeniedException(int code, String errorMsg) { - super(AccountManager.ERROR_CODE_INVALID_RESPONSE, "Access Denied"); - } - - private static final long serialVersionUID = 1L; - - } -} diff --git a/src/com/owncloud/android/authenticator/AccountAuthenticatorService.java b/src/com/owncloud/android/authenticator/AccountAuthenticatorService.java deleted file mode 100644 index 220f7f40..00000000 --- a/src/com/owncloud/android/authenticator/AccountAuthenticatorService.java +++ /dev/null @@ -1,41 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ - -package com.owncloud.android.authenticator; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; - -public class AccountAuthenticatorService extends Service { - - private AccountAuthenticator mAuthenticator; - static final public String ACCOUNT_TYPE = "owncloud"; - - @Override - public void onCreate() { - super.onCreate(); - mAuthenticator = new AccountAuthenticator(this); - } - - @Override - public IBinder onBind(Intent intent) { - return mAuthenticator.getIBinder(); - } - -} diff --git a/src/com/owncloud/android/authenticator/AuthenticationRunnable.java b/src/com/owncloud/android/authenticator/AuthenticationRunnable.java deleted file mode 100644 index 2df44275..00000000 --- a/src/com/owncloud/android/authenticator/AuthenticationRunnable.java +++ /dev/null @@ -1,88 +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 . - * - */ - -package com.owncloud.android.authenticator; - -import java.net.URL; - -import org.apache.commons.httpclient.HttpStatus; - -import com.owncloud.android.R; -import com.owncloud.android.network.OwnCloudClientUtils; - -import eu.alefzero.webdav.WebdavClient; - -import android.content.Context; -import android.net.Uri; -import android.os.Handler; - -public class AuthenticationRunnable implements Runnable { - - private OnAuthenticationResultListener mListener; - private Handler mHandler; - private URL mUrl; - private String mUsername; - private String mPassword; - private Context mContext; - - public AuthenticationRunnable(URL url, String username, String password, Context context) { - mListener = null; - mUrl = url; - mUsername = username; - mPassword = password; - mContext = context; - } - - public void setOnAuthenticationResultListener( - OnAuthenticationResultListener listener, Handler handler) { - mListener = listener; - mHandler = handler; - } - - @Override - public void run() { - Uri uri; - uri = Uri.parse(mUrl.toString()); - WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(uri, mUsername, mPassword, mContext); - int login_result = wdc.tryToLogin(); - switch (login_result) { - case HttpStatus.SC_OK: - postResult(true, uri.toString()); - break; - case HttpStatus.SC_UNAUTHORIZED: - postResult(false, mContext.getString(R.string.auth_unauthorized)); - break; - case HttpStatus.SC_NOT_FOUND: - postResult(false, mContext.getString(R.string.auth_not_found)); - break; - default: - postResult(false, String.format(mContext.getString(R.string.auth_internal), login_result)); - } - } - - private void postResult(final boolean success, final String message) { - if (mHandler != null && mListener != null) { - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onAuthenticationResult(success, message); - } - }); - } - } -} diff --git a/src/com/owncloud/android/authenticator/OnAuthenticationResultListener.java b/src/com/owncloud/android/authenticator/OnAuthenticationResultListener.java deleted file mode 100644 index 66dc018a..00000000 --- a/src/com/owncloud/android/authenticator/OnAuthenticationResultListener.java +++ /dev/null @@ -1,25 +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 . - * - */ - -package com.owncloud.android.authenticator; - -public interface OnAuthenticationResultListener { - - public void onAuthenticationResult(boolean success, String message); - -} diff --git a/src/com/owncloud/android/authenticator/OnConnectCheckListener.java b/src/com/owncloud/android/authenticator/OnConnectCheckListener.java deleted file mode 100644 index 1aa3dc57..00000000 --- a/src/com/owncloud/android/authenticator/OnConnectCheckListener.java +++ /dev/null @@ -1,29 +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 . - * - */ - -package com.owncloud.android.authenticator; - -public interface OnConnectCheckListener { - - enum ResultType { - OK_SSL, OK_NO_SSL, SSL_INIT_ERROR, HOST_NOT_AVAILABLE, TIMEOUT, NO_NETWORK_CONNECTION, INCORRECT_ADDRESS, INSTANCE_NOT_CONFIGURED, FILE_NOT_FOUND, UNKNOWN_ERROR, WRONG_CONNECTION, SSL_UNVERIFIED_SERVER, BAD_OC_VERSION - } - - public void onConnectionCheckResult(ResultType type); - -} diff --git a/src/com/owncloud/android/datamodel/DataStorageManager.java b/src/com/owncloud/android/datamodel/DataStorageManager.java deleted file mode 100644 index 31515a1a..00000000 --- a/src/com/owncloud/android/datamodel/DataStorageManager.java +++ /dev/null @@ -1,49 +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 . - * - */ - -package com.owncloud.android.datamodel; - -import java.util.List; -import java.util.Vector; - -public interface DataStorageManager { - - public static final int ROOT_PARENT_ID = 0; - - public OCFile getFileByPath(String path); - - public OCFile getFileById(long id); - - public boolean fileExists(String path); - - public boolean fileExists(long id); - - public boolean saveFile(OCFile file); - - public void saveFiles(List files); - - public Vector getDirectoryContent(OCFile f); - - public void removeFile(OCFile file, boolean removeLocalCopy); - - public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent); - - public void moveDirectory(OCFile dir, String newPath); - - public Vector getDirectoryImages(OCFile mParentFolder); -} diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java index 434bc472..f132d967 100644 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -20,49 +20,81 @@ package com.owncloud.android.datamodel; import java.io.File; 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.Log_OC; -import com.owncloud.android.db.ProviderMeta; +import com.owncloud.android.MainApp; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.resources.shares.ShareType; +import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + import android.accounts.Account; import android.content.ContentProviderClient; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; +import android.content.ContentUris; import android.content.ContentValues; import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; -import android.util.Log; -public class FileDataStorageManager implements DataStorageManager { +public class FileDataStorageManager { + + public static final int ROOT_PARENT_ID = 0; private ContentResolver mContentResolver; - private ContentProviderClient mContentProvider; + private ContentProviderClient mContentProviderClient; private Account mAccount; - - private static String TAG = "FileDataStorageManager"; + + private static String TAG = FileDataStorageManager.class.getSimpleName(); + public FileDataStorageManager(Account account, ContentResolver cr) { - mContentProvider = null; + mContentProviderClient = null; mContentResolver = cr; mAccount = account; } public FileDataStorageManager(Account account, ContentProviderClient cp) { - mContentProvider = cp; + mContentProviderClient = cp; mContentResolver = null; mAccount = account; } - @Override + + public void setAccount(Account account) { + mAccount = account; + } + + public Account getAccount() { + return mAccount; + } + + public void setContentResolver(ContentResolver cr) { + mContentResolver = cr; + } + + public ContentResolver getContentResolver() { + return mContentResolver; + } + + public void setContentProviderClient(ContentProviderClient cp) { + mContentProviderClient = cp; + } + + public ContentProviderClient getContentProviderClient() { + return mContentProviderClient; + } + + public OCFile getFileByPath(String path) { Cursor c = getCursorForValue(ProviderTableMeta.FILE_PATH, path); OCFile file = null; @@ -70,22 +102,13 @@ public class FileDataStorageManager implements DataStorageManager { file = createFileInstance(c); } c.close(); - if (file == null && OCFile.PATH_SEPARATOR.equals(path)) { + if (file == null && OCFile.ROOT_PATH.equals(path)) { return createRootDir(); // root should always exist } return file; } - - private OCFile createRootDir() { - OCFile file = new OCFile(OCFile.PATH_SEPARATOR); - file.setMimetype("DIR"); - file.setParentId(DataStorageManager.ROOT_PARENT_ID); - saveFile(file); - return file; - } - @Override public OCFile getFileById(long id) { Cursor c = getCursorForValue(ProviderTableMeta._ID, String.valueOf(id)); OCFile file = null; @@ -95,7 +118,7 @@ public class FileDataStorageManager implements DataStorageManager { c.close(); return file; } - + public OCFile getFileByLocalPath(String path) { Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path); OCFile file = null; @@ -106,17 +129,42 @@ public class FileDataStorageManager implements DataStorageManager { return file; } - @Override public boolean fileExists(long id) { return fileExists(ProviderTableMeta._ID, String.valueOf(id)); } - @Override public boolean fileExists(String path) { return fileExists(ProviderTableMeta.FILE_PATH, path); } - @Override + + public Vector getFolderContent(OCFile f) { + if (f != null && f.isFolder() && f.getFileId() != -1) { + return getFolderContent(f.getFileId()); + + } else { + return new Vector(); + } + } + + + public Vector getFolderImages(OCFile folder) { + Vector ret = new Vector(); + if (folder != null) { + // TODO better implementation, filtering in the access to database (if possible) instead of here + Vector tmp = getFolderContent(folder); + OCFile current = null; + for (int i=0; i files) { + /** + * Inserts or updates the list of files contained in a given folder. + * + * CALLER IS THE RESPONSIBLE FOR GRANTING RIGHT UPDATE OF INFORMATION, NOT THIS METHOD. + * HERE ONLY DATA CONSISTENCY SHOULD BE GRANTED + * + * @param folder + * @param files + * @param removeNotUpdated + */ + public void saveFolder(OCFile folder, Collection updatedFiles, Collection filesToRemove) { - Iterator filesIt = files.iterator(); - ArrayList operations = new ArrayList(files.size()); - OCFile file = null; + Log_OC.d(TAG, "Saving folder " + folder.getRemotePath() + " with " + updatedFiles.size() + " children and " + filesToRemove.size() + " files to remove"); - // prepare operations to perform - while (filesIt.hasNext()) { - file = filesIt.next(); + ArrayList operations = new ArrayList(updatedFiles.size()); + + // prepare operations to insert or update files to save in the given folder + for (OCFile file : updatedFiles) { ContentValues cv = new ContentValues(); cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); @@ -208,142 +272,351 @@ public class FileDataStorageManager implements DataStorageManager { cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); - if (file.getParentId() != 0) - cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); + //cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); + cv.put(ProviderTableMeta.FILE_PARENT, folder.getFileId()); cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); - if (!file.isDirectory()) + if (!file.isFolder()) { cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); + } cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag()); + cv.put(ProviderTableMeta.FILE_SHARE_BY_LINK, file.isShareByLink() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, file.getPublicLink()); - if (fileExists(file.getRemotePath())) { - OCFile oldFile = getFileByPath(file.getRemotePath()); - file.setFileId(oldFile.getFileId()); + boolean existsByPath = fileExists(file.getRemotePath()); + if (existsByPath || fileExists(file.getFileId())) { + // updating an existing file operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). withValues(cv). withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }) - .build()); - - } else if (fileExists(file.getFileId())) { - OCFile oldFile = getFileById(file.getFileId()); - if (file.getStoragePath() == null && oldFile.getStoragePath() != null) - file.setStoragePath(oldFile.getStoragePath()); - if (!file.isDirectory()); - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); + new String[] { String.valueOf(file.getFileId()) }) + .build()); - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). - withValues(cv). - withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(file.getFileId()) }) - .build()); - } else { + // adding a new file operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build()); } } + // prepare operations to remove files in the given folder + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + String [] whereArgs = null; + for (OCFile file : filesToRemove) { + if (file.getParentId() == folder.getFileId()) { + whereArgs = new String[]{mAccount.name, file.getRemotePath()}; + //Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, "" + file.getFileId()); + if (file.isFolder()) { + operations.add(ContentProviderOperation + .newDelete(ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, file.getFileId())).withSelection(where, whereArgs) + .build()); + // TODO remove local folder + } else { + operations.add(ContentProviderOperation + .newDelete(ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, file.getFileId())).withSelection(where, whereArgs) + .build()); + if (file.isDown()) { + new File(file.getStoragePath()).delete(); + // TODO move the deletion of local contents after success of deletions + } + } + } + } + + // update metadata of folder + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_MODIFIED, folder.getModificationTimestamp()); + cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, folder.getModificationTimestampAtLastSyncForData()); + cv.put(ProviderTableMeta.FILE_CREATION, folder.getCreationTimestamp()); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, 0); // FileContentProvider calculates the right size + cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, folder.getMimetype()); + cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName()); + cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId()); + cv.put(ProviderTableMeta.FILE_PATH, folder.getRemotePath()); + cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, folder.getLastSyncDateForProperties()); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, folder.getLastSyncDateForData()); + cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, folder.keepInSync() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_ETAG, folder.getEtag()); + cv.put(ProviderTableMeta.FILE_SHARE_BY_LINK, folder.isShareByLink() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, folder.getPublicLink()); + + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(folder.getFileId()) }) + .build()); + // apply operations in batch ContentProviderResult[] results = null; + Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider"); try { if (getContentResolver() != null) { - results = getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations); - + results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); + } else { - results = getContentProvider().applyBatch(operations); + results = getContentProviderClient().applyBatch(operations); } - + } catch (OperationApplicationException e) { - Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage()); - + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); + } catch (RemoteException e) { - Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage()); + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); } - + // update new id in file objects for insertions if (results != null) { long newId; + Iterator filesIt = updatedFiles.iterator(); + OCFile file = null; for (int i=0; i FileDataStorageManager.ROOT_PARENT_ID) { +// Log_OC.d(TAG, "Updating size of " + id); +// if (getContentResolver() != null) { +// getContentResolver().update(ProviderTableMeta.CONTENT_URI_DIR, +// new ContentValues(), // won't be used, but cannot be null; crashes in KLP +// ProviderTableMeta._ID + "=?", +// new String[] { String.valueOf(id) }); +// } else { +// try { +// getContentProviderClient().update(ProviderTableMeta.CONTENT_URI_DIR, +// new ContentValues(), // won't be used, but cannot be null; crashes in KLP +// ProviderTableMeta._ID + "=?", +// new String[] { String.valueOf(id) }); +// +// } catch (RemoteException e) { +// Log_OC.e(TAG, "Exception in update of folder size through compatibility patch " + e.getMessage()); +// } +// } +// } else { +// Log_OC.e(TAG, "not updating size for folder " + id); +// } +// } - public void setAccount(Account account) { - mAccount = account; - } - public Account getAccount() { - return mAccount; - } - - public void setContentResolver(ContentResolver cr) { - mContentResolver = cr; + public void removeFile(OCFile file, boolean removeDBData, boolean removeLocalCopy) { + if (file != null) { + if (file.isFolder()) { + removeFolder(file, removeDBData, removeLocalCopy); + + } else { + if (removeDBData) { + //Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId()); + Uri file_uri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, file.getFileId()); + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + String [] whereArgs = new String[]{mAccount.name, file.getRemotePath()}; + if (getContentProviderClient() != null) { + try { + getContentProviderClient().delete(file_uri, where, whereArgs); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else { + getContentResolver().delete(file_uri, where, whereArgs); + } + //updateFolderSize(file.getParentId()); + } + if (removeLocalCopy && file.isDown() && file.getStoragePath() != null) { + boolean success = new File(file.getStoragePath()).delete(); + if (!removeDBData && success) { + // maybe unnecessary, but should be checked TODO remove if unnecessary + file.setStoragePath(null); + saveFile(file); + } + } + } + } } + - public ContentResolver getContentResolver() { - return mContentResolver; + public void removeFolder(OCFile folder, boolean removeDBData, boolean removeLocalContent) { + if (folder != null && folder.isFolder()) { + if (removeDBData && folder.getFileId() != -1) { + removeFolderInDb(folder); + } + if (removeLocalContent) { + File localFolder = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder)); + removeLocalFolder(localFolder); + } + } } - public void setContentProvider(ContentProviderClient cp) { - mContentProvider = cp; + private void removeFolderInDb(OCFile folder) { + Uri folder_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, ""+ folder.getFileId()); // URI for recursive deletion + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + String [] whereArgs = new String[]{mAccount.name, folder.getRemotePath()}; + if (getContentProviderClient() != null) { + try { + getContentProviderClient().delete(folder_uri, where, whereArgs); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else { + getContentResolver().delete(folder_uri, where, whereArgs); + } + //updateFolderSize(folder.getParentId()); } - public ContentProviderClient getContentProvider() { - return mContentProvider; + private void removeLocalFolder(File folder) { + if (folder.exists()) { + File[] files = folder.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + removeLocalFolder(file); + } else { + file.delete(); + } + } + } + folder.delete(); + } } - @Override - public Vector getDirectoryContent(OCFile f) { - Vector ret = new Vector(); - if (f != null && f.isDirectory() && f.getFileId() != -1) { + /** + * Updates database for a 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 - Uri req_uri = Uri.withAppendedPath( - ProviderTableMeta.CONTENT_URI_DIR, - String.valueOf(f.getFileId())); + 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 (getContentProvider() != null) { + if (getContentProviderClient() != null) { try { - c = getContentProvider().query(req_uri, null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", - new String[] { mAccount.name }, null); + 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()); - return ret; } } else { - c = getContentResolver().query(req_uri, null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", - new String[] { mAccount.name }, null); + 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 operations = new ArrayList(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(); // don't take the constructor out of the loop and clear the object OCFile child = createFileInstance(c); - ret.add(child); + 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(); - - Collections.sort(ret); - + + /// 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); + } + } + } + + + private Vector getFolderContent(long parentId) { + + Vector ret = new Vector(); + + Uri req_uri = Uri.withAppendedPath( + ProviderTableMeta.CONTENT_URI_DIR, + String.valueOf(parentId)); + Cursor c = null; + + if (getContentProviderClient() != null) { + try { + c = getContentProviderClient().query(req_uri, null, + ProviderTableMeta.FILE_PARENT + "=?" , + new String[] { String.valueOf(parentId)}, null); + } catch (RemoteException e) { + Log_OC.e(TAG, e.getMessage()); + return ret; + } + } else { + c = getContentResolver().query(req_uri, null, + ProviderTableMeta.FILE_PARENT + "=?" , + new String[] { String.valueOf(parentId)}, null); + } + + if (c.moveToFirst()) { + do { + OCFile child = createFileInstance(c); + ret.add(child); + } while (c.moveToNext()); + } + + c.close(); + + Collections.sort(ret); + return ret; } + + + private OCFile createRootDir() { + OCFile file = new OCFile(OCFile.ROOT_PATH); + file.setMimetype("DIR"); + file.setParentId(FileDataStorageManager.ROOT_PARENT_ID); + saveFile(file); + return file; + } private boolean fileExists(String cmp_key, String value) { Cursor c; @@ -354,15 +627,15 @@ public class FileDataStorageManager implements DataStorageManager { cmp_key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", - new String[] { value, mAccount.name }, null); + new String[] { value, mAccount.name }, null); } else { try { - c = getContentProvider().query( + c = getContentProviderClient().query( ProviderTableMeta.CONTENT_URI, null, cmp_key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", - new String[] { value, mAccount.name }, null); + new String[] { value, mAccount.name }, null); } catch (RemoteException e) { Log_OC.e(TAG, "Couldn't determine file existance, assuming non existance: " @@ -384,14 +657,40 @@ public class FileDataStorageManager implements DataStorageManager { key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?", - new String[] { value, mAccount.name }, null); + new String[] { value, mAccount.name }, null); } else { try { - c = getContentProvider().query( + c = getContentProviderClient().query( ProviderTableMeta.CONTENT_URI, null, key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER - + "=?", new String[] { value, mAccount.name }, + + "=?", new String[] { value, mAccount.name }, + null); + } catch (RemoteException e) { + Log_OC.e(TAG, "Could not get file details: " + e.getMessage()); + c = null; + } + } + return c; + } + + private Cursor getShareCursorForValue(String key, String value) { + Cursor c = null; + if (getContentResolver() != null) { + c = getContentResolver() + .query(ProviderTableMeta.CONTENT_URI_SHARE, + null, + key + "=? AND " + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + + "=?", + new String[] { value, mAccount.name }, null); + } else { + try { + c = getContentProviderClient().query( + ProviderTableMeta.CONTENT_URI_SHARE, + null, + key + "=? AND " + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + + "=?", new String[] { value, mAccount.name }, null); } catch (RemoteException e) { Log_OC.e(TAG, "Could not get file details: " + e.getMessage()); @@ -411,7 +710,7 @@ public class FileDataStorageManager implements DataStorageManager { .getColumnIndex(ProviderTableMeta.FILE_PARENT))); file.setMimetype(c.getString(c .getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE))); - if (!file.isDirectory()) { + if (!file.isFolder()) { file.setStoragePath(c.getString(c .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH))); if (file.getStoragePath() == null) { @@ -436,152 +735,570 @@ public class FileDataStorageManager implements DataStorageManager { file.setLastSyncDateForData(c.getLong(c. getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA))); file.setKeepInSync(c.getInt( - c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false); + c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false); + file.setEtag(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ETAG))); + file.setShareByLink(c.getInt( + c.getColumnIndex(ProviderTableMeta.FILE_SHARE_BY_LINK)) == 1 ? true : false); + file.setPublicLink(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PUBLIC_LINK))); + } return file; } - @Override - public void removeFile(OCFile file, boolean removeLocalCopy) { - Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId()); - if (getContentProvider() != null) { + /** + * Returns if the file/folder is shared by link or not + * @param path Path of the file/folder + * @return + */ + public boolean isShareByLink(String path) { + Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path); + OCFile file = null; + if (c.moveToFirst()) { + file = createFileInstance(c); + } + c.close(); + return file.isShareByLink(); + } + + /** + * Returns the public link of the file/folder + * @param path Path of the file/folder + * @return + */ + public String getPublicLink(String path) { + Cursor c = getCursorForValue(ProviderTableMeta.FILE_STORAGE_PATH, path); + OCFile file = null; + if (c.moveToFirst()) { + file = createFileInstance(c); + } + c.close(); + return file.getPublicLink(); + } + + + // Methods for Shares + public boolean saveShare(OCShare share) { + boolean overriden = false; + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, share.getFileSource()); + cv.put(ProviderTableMeta.OCSHARES_ITEM_SOURCE, share.getItemSource()); + cv.put(ProviderTableMeta.OCSHARES_SHARE_TYPE, share.getShareType().getValue()); + cv.put(ProviderTableMeta.OCSHARES_SHARE_WITH, share.getShareWith()); + cv.put(ProviderTableMeta.OCSHARES_PATH, share.getPath()); + cv.put(ProviderTableMeta.OCSHARES_PERMISSIONS, share.getPermissions()); + cv.put(ProviderTableMeta.OCSHARES_SHARED_DATE, share.getSharedDate()); + cv.put(ProviderTableMeta.OCSHARES_EXPIRATION_DATE, share.getExpirationDate()); + cv.put(ProviderTableMeta.OCSHARES_TOKEN, share.getToken()); + cv.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, share.getSharedWithDisplayName()); + cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0); + cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId()); + cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared()); + cv.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, mAccount.name); + + if (shareExists(share.getIdRemoteShared())) { // for renamed files; no more delete and create + + overriden = true; + if (getContentResolver() != null) { + getContentResolver().update(ProviderTableMeta.CONTENT_URI_SHARE, cv, + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?", + new String[] { String.valueOf(share.getIdRemoteShared()) }); + } else { + try { + getContentProviderClient().update(ProviderTableMeta.CONTENT_URI_SHARE, + cv, ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?", + new String[] { String.valueOf(share.getIdRemoteShared()) }); + } catch (RemoteException e) { + Log_OC.e(TAG, + "Fail to insert insert file to database " + + e.getMessage()); + } + } + } else { + Uri result_uri = null; + if (getContentResolver() != null) { + result_uri = getContentResolver().insert( + ProviderTableMeta.CONTENT_URI_SHARE, cv); + } else { + try { + result_uri = getContentProviderClient().insert( + ProviderTableMeta.CONTENT_URI_SHARE, cv); + } catch (RemoteException e) { + Log_OC.e(TAG, + "Fail to insert insert file to database " + + e.getMessage()); + } + } + if (result_uri != null) { + long new_id = Long.parseLong(result_uri.getPathSegments() + .get(1)); + share.setId(new_id); + } + } + + return overriden; + } + + private OCShare getShareById(long id) { + Cursor c = getShareCursorForValue(ProviderTableMeta._ID, String.valueOf(id)); + OCShare share = null; + if (c.moveToFirst()) { + share = createShareInstance(c); + } + c.close(); + return share; + } + + private OCShare getShareByRemoteId(long remoteId) { + Cursor c = getShareCursorForValue(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, String.valueOf(remoteId)); + OCShare share = null; + if (c.moveToFirst()) { + share = createShareInstance(c); + } + c.close(); + return share; + } + + public OCShare getFirstShareByPathAndType(String path, ShareType type) { + Cursor c = null; + if (getContentResolver() != null) { + c = getContentResolver().query( + ProviderTableMeta.CONTENT_URI_SHARE, + null, + ProviderTableMeta.OCSHARES_PATH + "=? AND " + + ProviderTableMeta.OCSHARES_SHARE_TYPE + "=? AND " + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?", + new String[] { path, Integer.toString(type.getValue()), mAccount.name }, + null); + } else { try { - getContentProvider().delete(file_uri, - ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", - new String[]{mAccount.name}); + c = getContentProviderClient().query( + ProviderTableMeta.CONTENT_URI_SHARE, + null, + ProviderTableMeta.OCSHARES_PATH + "=? AND " + + ProviderTableMeta.OCSHARES_SHARE_TYPE + "=? AND " + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?", + new String[] { path, Integer.toString(type.getValue()), mAccount.name }, + null); + } catch (RemoteException e) { - e.printStackTrace(); + Log_OC.e(TAG, "Could not get file details: " + e.getMessage()); + c = null; + } + } + OCShare share = null; + if (c.moveToFirst()) { + share = createShareInstance(c); + } + c.close(); + return share; + } + + private OCShare createShareInstance(Cursor c) { + OCShare share = null; + if (c != null) { + share = new OCShare(c.getString(c + .getColumnIndex(ProviderTableMeta.OCSHARES_PATH))); + share.setId(c.getLong(c.getColumnIndex(ProviderTableMeta._ID))); + share.setFileSource(c.getLong(c + .getColumnIndex(ProviderTableMeta.OCSHARES_ITEM_SOURCE))); + share.setShareType(ShareType.fromValue(c.getInt(c + .getColumnIndex(ProviderTableMeta.OCSHARES_SHARE_TYPE)))); + share.setPermissions(c.getInt(c + .getColumnIndex(ProviderTableMeta.OCSHARES_PERMISSIONS))); + share.setSharedDate(c.getLong(c + .getColumnIndex(ProviderTableMeta.OCSHARES_SHARED_DATE))); + share.setExpirationDate(c.getLong(c + .getColumnIndex(ProviderTableMeta.OCSHARES_EXPIRATION_DATE))); + share.setToken(c.getString(c + .getColumnIndex(ProviderTableMeta.OCSHARES_TOKEN))); + share.setSharedWithDisplayName(c.getString(c + .getColumnIndex(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME))); + share.setIsFolder(c.getInt( + c.getColumnIndex(ProviderTableMeta.OCSHARES_IS_DIRECTORY)) == 1 ? true : false); + share.setUserId(c.getLong(c.getColumnIndex(ProviderTableMeta.OCSHARES_USER_ID))); + share.setIdRemoteShared(c.getLong(c.getColumnIndex(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED))); + + } + return share; + } + + private boolean shareExists(String cmp_key, String value) { + Cursor c; + if (getContentResolver() != null) { + c = getContentResolver() + .query(ProviderTableMeta.CONTENT_URI_SHARE, + null, + cmp_key + "=? AND " + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + + "=?", + new String[] { value, mAccount.name }, null); + } else { + try { + c = getContentProviderClient().query( + ProviderTableMeta.CONTENT_URI_SHARE, + null, + cmp_key + "=? AND " + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?", + new String[] { value, mAccount.name }, null); + } catch (RemoteException e) { + Log_OC.e(TAG, + "Couldn't determine file existance, assuming non existance: " + + e.getMessage()); + return false; } + } + boolean retval = c.moveToFirst(); + c.close(); + return retval; + } + + private boolean shareExists(long remoteId) { + return shareExists(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, String.valueOf(remoteId)); + } + + private void cleanSharedFiles() { + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_SHARE_BY_LINK, false); + cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, ""); + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; + String [] whereArgs = new String[]{mAccount.name}; + + if (getContentResolver() != null) { + getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); + } else { - getContentResolver().delete(file_uri, - ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", - new String[]{mAccount.name}); + try { + getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Exception in cleanSharedFiles" + e.getMessage()); + } } - if (file.isDown() && removeLocalCopy) { - new File(file.getStoragePath()).delete(); + } + + private void cleanSharedFilesInFolder(OCFile folder) { + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_SHARE_BY_LINK, false); + cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, ""); + String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PARENT + "=?"; + String [] whereArgs = new String[] { mAccount.name , String.valueOf(folder.getFileId()) }; + + if (getContentResolver() != null) { + getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); + + } else { + try { + getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Exception in cleanSharedFilesInFolder " + e.getMessage()); + } } - if (file.isDirectory() && removeLocalCopy) { - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) { - f.delete(); + } + + private void cleanShares() { + String where = ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; + String [] whereArgs = new String[]{mAccount.name}; + + if (getContentResolver() != null) { + getContentResolver().delete(ProviderTableMeta.CONTENT_URI_SHARE, where, whereArgs); + + } else { + try { + getContentProviderClient().delete(ProviderTableMeta.CONTENT_URI_SHARE, where, whereArgs); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Exception in cleanShares" + e.getMessage()); } } } + + public void saveShares(Collection shares) { + cleanShares(); + if (shares != null) { + ArrayList operations = new ArrayList(shares.size()); - @Override - public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) { - // TODO consider possible failures - if (dir != null && dir.isDirectory() && dir.getFileId() != -1) { - Vector children = getDirectoryContent(dir); - if (children.size() > 0) { - OCFile child = null; - for (int i=0; i 0) { + @SuppressWarnings("unused") + ContentProviderResult[] results = null; + Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider"); + try { + if (getContentResolver() != null) { + results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); + } else { - if (removeDBData) { - removeFile(child, removeLocalContent); - } else if (removeLocalContent) { - if (child.isDown()) { - new File(child.getStoragePath()).delete(); - } - } + results = getContentProviderClient().applyBatch(operations); } + + } catch (OperationApplicationException e) { + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); } } - if (removeDBData) { - removeFile(dir, true); - } } + } - - /** - * Updates database for a folder that was moved to a different location. - * - * TODO explore better (faster) implementations - * TODO throw exceptions up ! - */ - @Override - public void moveDirectory(OCFile dir, String newPath) { - // TODO check newPath + public void updateSharedFiles(Collection sharedFiles) { + cleanSharedFiles(); - if (dir != null && dir.isDirectory() && dir.fileExists() && !dir.getFileName().equals(OCFile.PATH_SEPARATOR)) { - /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir') - Cursor c = null; - if (getContentProvider() != null) { + if (sharedFiles != null) { + ArrayList operations = new ArrayList(sharedFiles.size()); + + // prepare operations to insert or update files to save in the given folder + for (OCFile file : sharedFiles) { + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp()); + cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData()); + cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); + cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); + cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); + cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); + cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); + if (!file.isFolder()) { + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath()); + } + cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties()); + cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData()); + cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag()); + cv.put(ProviderTableMeta.FILE_SHARE_BY_LINK, file.isShareByLink() ? 1 : 0); + cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, file.getPublicLink()); + + boolean existsByPath = fileExists(file.getRemotePath()); + if (existsByPath || fileExists(file.getFileId())) { + // updating an existing file + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(file.getFileId()) }) + .build()); + + } else { + // adding a new file + operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build()); + } + } + + // apply operations in batch + if (operations.size() > 0) { + @SuppressWarnings("unused") + ContentProviderResult[] results = null; + Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider"); try { - c = getContentProvider().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?", - new String[] { mAccount.name, dir.getRemotePath() + "%" }, null); + if (getContentResolver() != null) { + results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); + + } else { + results = getContentProviderClient().applyBatch(operations); + } + + } catch (OperationApplicationException e) { + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); + } catch (RemoteException e) { - Log_OC.e(TAG, e.getMessage()); + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); } - } else { - c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ?", - new String[] { mAccount.name, dir.getRemotePath() + "%" }, null); } + } + + } + + public void removeShare(OCShare share){ + Uri share_uri = ProviderTableMeta.CONTENT_URI_SHARE; + String where = ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?"; + String [] whereArgs = new String[]{mAccount.name, share.getPath()}; + if (getContentProviderClient() != null) { + try { + getContentProviderClient().delete(share_uri, where, whereArgs); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else { + getContentResolver().delete(share_uri, where, whereArgs); + } + } + + public void saveSharesDB(ArrayList shares) { + saveShares(shares); - /// 2. prepare a batch of update operations to change all the descendants - ArrayList operations = new ArrayList(c.getCount()); - int lengthOfOldPath = dir.getRemotePath().length(); - String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name); - int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath; - if (c.moveToFirst()) { - do { - ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object - 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()); + ArrayList sharedFiles = new ArrayList(); + + for (OCShare share : shares) { + // Get the path + String path = share.getPath(); + if (share.isFolder()) { + path = path + FileUtils.PATH_SEPARATOR; + } + + // Update OCFile with data from share: ShareByLink ¿and publicLink? + OCFile file = getFileByPath(path); + if (file != null) { + if (share.getShareType().equals(ShareType.PUBLIC_LINK)) { + file.setShareByLink(true); + sharedFiles.add(file); + } + } + } + + updateSharedFiles(sharedFiles); + } + + + public void saveSharesInFolder(ArrayList shares, OCFile folder) { + cleanSharedFilesInFolder(folder); + ArrayList operations = new ArrayList(); + operations = prepareRemoveSharesInFolder(folder, operations); + + if (shares != null) { + // prepare operations to insert or update files to save in the given folder + for (OCShare share : shares) { + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, share.getFileSource()); + cv.put(ProviderTableMeta.OCSHARES_ITEM_SOURCE, share.getItemSource()); + cv.put(ProviderTableMeta.OCSHARES_SHARE_TYPE, share.getShareType().getValue()); + cv.put(ProviderTableMeta.OCSHARES_SHARE_WITH, share.getShareWith()); + cv.put(ProviderTableMeta.OCSHARES_PATH, share.getPath()); + cv.put(ProviderTableMeta.OCSHARES_PERMISSIONS, share.getPermissions()); + cv.put(ProviderTableMeta.OCSHARES_SHARED_DATE, share.getSharedDate()); + cv.put(ProviderTableMeta.OCSHARES_EXPIRATION_DATE, share.getExpirationDate()); + cv.put(ProviderTableMeta.OCSHARES_TOKEN, share.getToken()); + cv.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, share.getSharedWithDisplayName()); + cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0); + cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId()); + cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared()); + cv.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, mAccount.name); + + /* + if (shareExists(share.getIdRemoteShared())) { + // updating an existing share resource + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI_SHARE). + withValues(cv). + withSelection( ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?", + new String[] { String.valueOf(share.getIdRemoteShared()) }) + .build()); + + } else { + */ + // adding a new share resource + operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI_SHARE).withValues(cv).build()); + //} } - c.close(); + } - /// 3. apply updates in batch + // apply operations in batch + if (operations.size() > 0) { + @SuppressWarnings("unused") + ContentProviderResult[] results = null; + Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider"); try { if (getContentResolver() != null) { - getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations); - + results = getContentResolver().applyBatch(MainApp.getAuthority(), operations); + } else { - getContentProvider().applyBatch(operations); + results = getContentProviderClient().applyBatch(operations); } - + } catch (OperationApplicationException e) { - Log_OC.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e); - + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); + } catch (RemoteException e) { - Log_OC.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e); + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); } - } + //} + } - @Override - public Vector getDirectoryImages(OCFile directory) { - Vector ret = new Vector(); - if (directory != null) { - // TODO better implementation, filtering in the access to database (if possible) instead of here - Vector tmp = getDirectoryContent(directory); - OCFile current = null; - for (int i=0; i prepareRemoveSharesInFolder(OCFile folder, ArrayList preparedOperations) { + if (folder != null) { + String where = ProviderTableMeta.OCSHARES_PATH + "=?" + " AND " + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; + String [] whereArgs = new String[]{ "", mAccount.name }; + + Vector files = getFolderContent(folder); + + for (OCFile file : files) { + whereArgs[0] = file.getRemotePath(); + preparedOperations.add(ContentProviderOperation.newDelete(ProviderTableMeta.CONTENT_URI_SHARE) + .withSelection(where, whereArgs) + .build()); } } - return ret; - } + return preparedOperations; + + /* + if (operations.size() > 0) { + try { + if (getContentResolver() != null) { + getContentResolver().applyBatch(MainApp.getAuthority(), operations); + + } else { + getContentProviderClient().applyBatch(operations); + } + + } catch (OperationApplicationException e) { + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); + } catch (RemoteException e) { + Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage()); + } + } + */ + + /* + if (getContentResolver() != null) { + + getContentResolver().delete(ProviderTableMeta.CONTENT_URI_SHARE, + where, + whereArgs); + } else { + try { + getContentProviderClient().delete( ProviderTableMeta.CONTENT_URI_SHARE, + where, + whereArgs); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Exception deleting shares in a folder " + e.getMessage()); + } + } + */ + //} + } } diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index 6b8e1785..82c341ca 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -20,7 +20,8 @@ package com.owncloud.android.datamodel; import java.io.File; -import com.owncloud.android.Log_OC; +import com.owncloud.android.utils.Log_OC; + import android.os.Parcel; import android.os.Parcelable; @@ -41,6 +42,7 @@ public class OCFile implements Parcelable, Comparable { }; public static final String PATH_SEPARATOR = "/"; + public static final String ROOT_PATH = PATH_SEPARATOR; private static final String TAG = OCFile.class.getSimpleName(); @@ -59,6 +61,10 @@ public class OCFile implements Parcelable, Comparable { private boolean mKeepInSync; private String mEtag; + + private boolean mShareByLink; + private String mPublicLink; + /** * Create new {@link OCFile} with given path. @@ -95,6 +101,9 @@ public class OCFile implements Parcelable, Comparable { mKeepInSync = source.readInt() == 1; mLastSyncDateForProperties = source.readLong(); mLastSyncDateForData = source.readLong(); + mEtag = source.readString(); + mShareByLink = source.readInt() == 1; + mPublicLink = source.readString(); } @Override @@ -112,6 +121,9 @@ public class OCFile implements Parcelable, Comparable { dest.writeInt(mKeepInSync ? 1 : 0); dest.writeLong(mLastSyncDateForProperties); dest.writeLong(mLastSyncDateForData); + dest.writeString(mEtag); + dest.writeInt(mShareByLink ? 1 : 0); + dest.writeString(mPublicLink); } /** @@ -143,11 +155,11 @@ public class OCFile implements Parcelable, Comparable { } /** - * Use this to find out if this file is a Directory + * Use this to find out if this file is a folder. * - * @return true if it is a directory + * @return true if it is a folder */ - public boolean isDirectory() { + public boolean isFolder() { return mMimeType != null && mMimeType.equals("DIR"); } @@ -254,7 +266,7 @@ public class OCFile implements Parcelable, Comparable { */ public String getFileName() { File f = new File(getRemotePath()); - return f.getName().length() == 0 ? PATH_SEPARATOR : f.getName(); + return f.getName().length() == 0 ? ROOT_PATH : f.getName(); } /** @@ -264,11 +276,11 @@ public class OCFile implements Parcelable, Comparable { */ 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(PATH_SEPARATOR)) { + 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; - if (isDirectory()) { + if (isFolder()) { mRemotePath += PATH_SEPARATOR; } Log_OC.d(TAG, "OCFile name changed to " + mRemotePath); @@ -293,7 +305,7 @@ public class OCFile implements Parcelable, Comparable { * not a directory */ public void addFile(OCFile file) throws IllegalStateException { - if (isDirectory()) { + if (isFolder()) { file.mParentId = mId; mNeedsUpdating = true; return; @@ -319,6 +331,9 @@ public class OCFile implements Parcelable, Comparable { mLastSyncDateForData = 0; mKeepInSync = false; mNeedsUpdating = false; + mEtag = null; + mShareByLink = false; + mPublicLink = null; } /** @@ -415,11 +430,11 @@ public class OCFile implements Parcelable, Comparable { @Override public int compareTo(OCFile another) { - if (isDirectory() && another.isDirectory()) { + if (isFolder() && another.isFolder()) { return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); - } else if (isDirectory()) { + } else if (isFolder()) { return -1; - } else if (another.isDirectory()) { + } else if (another.isFolder()) { return 1; } return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); @@ -439,8 +454,8 @@ public class OCFile implements Parcelable, Comparable { @Override public String toString() { - String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s]"; - asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync)); + String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSync=%s etag=%s]"; + asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync), mEtag); return asString; } @@ -448,6 +463,27 @@ public class OCFile implements Parcelable, Comparable { return mEtag; } + public void setEtag(String etag) { + this.mEtag = etag; + } + + + public boolean isShareByLink() { + return mShareByLink; + } + + public void setShareByLink(boolean shareByLink) { + this.mShareByLink = shareByLink; + } + + public String getPublicLink() { + return mPublicLink; + } + + public void setPublicLink(String publicLink) { + this.mPublicLink = publicLink; + } + public long getLocalModificationTimestamp() { if (mLocalPath != null && mLocalPath.length() > 0) { File f = new File(mLocalPath); diff --git a/src/com/owncloud/android/db/DbHandler.java b/src/com/owncloud/android/db/DbHandler.java index e3cfe4fb..89864fea 100644 --- a/src/com/owncloud/android/db/DbHandler.java +++ b/src/com/owncloud/android/db/DbHandler.java @@ -17,14 +17,14 @@ */ package com.owncloud.android.db; -import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; +import com.owncloud.android.utils.Log_OC; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; /** * Custom database helper for ownCloud @@ -35,7 +35,7 @@ import android.util.Log; public class DbHandler { private SQLiteDatabase mDB; private OpenerHelper mHelper; - private final String mDatabaseName = "ownCloud"; + private final String mDatabaseName; private final int mDatabaseVersion = 3; private final String TABLE_INSTANT_UPLOAD = "instant_upload"; @@ -44,6 +44,7 @@ public class DbHandler { public static final int UPLOAD_STATUS_UPLOAD_FAILED = 1; public DbHandler(Context context) { + mDatabaseName = MainApp.getDBName(); mHelper = new OpenerHelper(context); mDB = mHelper.getWritableDatabase(); } diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index b8058b89..2101a688 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -17,6 +17,8 @@ */ package com.owncloud.android.db; +import com.owncloud.android.MainApp; + import android.net.Uri; import android.provider.BaseColumns; @@ -28,26 +30,28 @@ import android.provider.BaseColumns; */ public class ProviderMeta { - public static final String AUTHORITY_FILES = "org.owncloud"; - public static final String DB_FILE = "owncloud.db"; public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 4; + public static final int DB_VERSION = 6; private ProviderMeta() { } static public class ProviderTableMeta implements BaseColumns { - public static final String DB_NAME = "filelist"; + public static final String FILE_TABLE_NAME = "filelist"; + public static final String OCSHARES_TABLE_NAME = "ocshares"; public static final Uri CONTENT_URI = Uri.parse("content://" - + AUTHORITY_FILES + "/"); + + MainApp.getAuthority() + "/"); public static final Uri CONTENT_URI_FILE = Uri.parse("content://" - + AUTHORITY_FILES + "/file"); + + MainApp.getAuthority() + "/file"); public static final Uri CONTENT_URI_DIR = Uri.parse("content://" - + AUTHORITY_FILES + "/dir"); + + MainApp.getAuthority() + "/dir"); + public static final Uri CONTENT_URI_SHARE = Uri.parse("content://" + + MainApp.getAuthority() + "/shares"); public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; + // Columns of filelist table public static final String FILE_PARENT = "parent"; public static final String FILE_NAME = "filename"; public static final String FILE_CREATION = "created"; @@ -61,9 +65,32 @@ public class ProviderMeta { public static final String FILE_LAST_SYNC_DATE = "last_sync_date"; // _for_properties, but let's keep it as it is public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data"; public static final String FILE_KEEP_IN_SYNC = "keep_in_sync"; + public static final String FILE_ETAG = "etag"; + public static final String FILE_SHARE_BY_LINK = "share_by_link"; + public static final String FILE_PUBLIC_LINK = "public_link"; - public static final String DEFAULT_SORT_ORDER = FILE_NAME + public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + + " collate nocase asc"; + + // Columns of ocshares table + public static final String OCSHARES_FILE_SOURCE = "file_source"; + public static final String OCSHARES_ITEM_SOURCE = "item_source"; + public static final String OCSHARES_SHARE_TYPE = "share_type"; + public static final String OCSHARES_SHARE_WITH = "shate_with"; + public static final String OCSHARES_PATH = "path"; + public static final String OCSHARES_PERMISSIONS = "permissions"; + public static final String OCSHARES_SHARED_DATE = "shared_date"; + public static final String OCSHARES_EXPIRATION_DATE = "expiration_date"; + public static final String OCSHARES_TOKEN = "token"; + public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name"; + public static final String OCSHARES_IS_DIRECTORY = "is_directory"; + public static final String OCSHARES_USER_ID = "user_id"; + public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared"; + public static final String OCSHARES_ACCOUNT_OWNER = "owner_share"; + + public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE + " collate nocase asc"; + } } diff --git a/src/com/owncloud/android/extensions/ExtensionsAvailableActivity.java b/src/com/owncloud/android/extensions/ExtensionsAvailableActivity.java deleted file mode 100644 index 7b39931c..00000000 --- a/src/com/owncloud/android/extensions/ExtensionsAvailableActivity.java +++ /dev/null @@ -1,35 +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 . - * - */ - -package com.owncloud.android.extensions; - -import android.os.Bundle; -import android.support.v4.app.FragmentManager; - -import com.actionbarsherlock.app.SherlockFragmentActivity; - -public class ExtensionsAvailableActivity extends SherlockFragmentActivity { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - FragmentManager fm = getSupportFragmentManager(); - ExtensionsAvailableDialog ead = new ExtensionsAvailableDialog(); - ead.show(fm, "extensions_available_dialog"); - } -} diff --git a/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java b/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java deleted file mode 100644 index ebe94501..00000000 --- a/src/com/owncloud/android/extensions/ExtensionsAvailableDialog.java +++ /dev/null @@ -1,69 +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 . - * - */ - -package com.owncloud.android.extensions; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.View.OnClickListener; -import android.widget.Button; - -public class ExtensionsAvailableDialog extends DialogFragment implements - OnClickListener { - - public ExtensionsAvailableDialog() { - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.extensions_available_dialog, - container); - Button btnYes = (Button) view.findViewById(R.id.buttonYes); - Button btnNo = (Button) view.findViewById(R.id.buttonNo); - btnYes.setOnClickListener(this); - btnNo.setOnClickListener(this); - getDialog().setTitle(R.string.extensions_avail_title); - return view; - } - - @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.buttonYes: { - Intent i = new Intent(getActivity(), ExtensionsListActivity.class); - startActivity(i); - getActivity().finish(); - } - break; - case R.id.buttonNo: - getActivity().finish(); - break; - default: - Log_OC.e("EAD", "Button with unknown id clicked " + v.getId()); - } - } - -} diff --git a/src/com/owncloud/android/extensions/ExtensionsListActivity.java b/src/com/owncloud/android/extensions/ExtensionsListActivity.java deleted file mode 100644 index 2a94418b..00000000 --- a/src/com/owncloud/android/extensions/ExtensionsListActivity.java +++ /dev/null @@ -1,156 +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 . - * - */ - -package com.owncloud.android.extensions; - -import java.util.HashMap; -import java.util.LinkedList; - -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.methods.GetMethod; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.utils.OwnCloudVersion; - - -import android.R; -import android.app.ListActivity; -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; -import android.widget.SimpleAdapter; - -public class ExtensionsListActivity extends ListActivity { - - private static final String packages_url = "http://alefzero.eu/a/packages.php"; - - private Thread mGetterThread; - private final Handler mHandler = new Handler(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mGetterThread = new Thread(new JsonGetter()); - mGetterThread.start(); - } - - public void done(JSONArray a) { - LinkedList> ll = new LinkedList>(); - for (int i = 0; i < a.length(); ++i) { - try { - ExtensionApplicationEntry ela = new ExtensionApplicationEntry( - ((JSONObject) a.get(i))); - HashMap ss = new HashMap(); - ss.put("NAME", ela.getName()); - ss.put("DESC", ela.getDescription()); - ll.add(ss); - } catch (JSONException e) { - e.printStackTrace(); - } - } - setListAdapter(new SimpleAdapter(this, ll, R.layout.simple_list_item_2, - new String[] { "NAME", "DESC" }, new int[] { - android.R.id.text1, android.R.id.text2 })); - - } - - private class JsonGetter implements Runnable { - - @Override - public void run() { - HttpClient hc = new HttpClient(); - GetMethod gm = new GetMethod(packages_url); - final JSONArray ar; - try { - hc.executeMethod(gm); - Log_OC.e("ASD", gm.getResponseBodyAsString() + ""); - ar = new JSONObject(gm.getResponseBodyAsString()) - .getJSONArray("apps"); - } catch (Exception e) { - e.printStackTrace(); - return; - } - - mHandler.post(new Runnable() { - @Override - public void run() { - done(ar); - } - }); - - } - - } - - private class ExtensionApplicationEntry { - private static final String APP_NAME = "name"; - private static final String APP_VERSION = "version"; - private static final String APP_DESC = "description"; - private static final String APP_ICON = "icon"; - private static final String APP_URL = "download"; - private static final String APP_PLAYID = "play_id"; - - private String mName, mDescription, mIcon, mDownload, mPlayId; - private OwnCloudVersion mVersion; - - public ExtensionApplicationEntry(JSONObject appentry) { - try { - mName = appentry.getString(APP_NAME); - mDescription = appentry.getString(APP_DESC); - mIcon = appentry.getString(APP_ICON); - mDownload = appentry.getString(APP_URL); - mPlayId = appentry.getString(APP_PLAYID); - mVersion = new OwnCloudVersion(appentry.getString(APP_VERSION)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - public String getName() { - return mName; - } - - public String getDescription() { - return mDescription; - } - - @SuppressWarnings("unused") - public String getIcon() { - return mIcon; - } - - @SuppressWarnings("unused") - public String getDownload() { - return mDownload; - } - - @SuppressWarnings("unused") - public String getPlayId() { - return mPlayId; - } - - @SuppressWarnings("unused") - public OwnCloudVersion getVersion() { - return mVersion; - } - } - -} diff --git a/src/com/owncloud/android/files/BootupBroadcastReceiver.java b/src/com/owncloud/android/files/BootupBroadcastReceiver.java index 97d04042..e90e8d4a 100644 --- a/src/com/owncloud/android/files/BootupBroadcastReceiver.java +++ b/src/com/owncloud/android/files/BootupBroadcastReceiver.java @@ -18,13 +18,12 @@ package com.owncloud.android.files; -import com.owncloud.android.Log_OC; import com.owncloud.android.files.services.FileObserverService; +import com.owncloud.android.utils.Log_OC; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.util.Log; public class BootupBroadcastReceiver extends BroadcastReceiver { diff --git a/src/com/owncloud/android/files/FileOperationsHelper.java b/src/com/owncloud/android/files/FileOperationsHelper.java new file mode 100644 index 00000000..ff571e5d --- /dev/null +++ b/src/com/owncloud/android/files/FileOperationsHelper.java @@ -0,0 +1,187 @@ +/* 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 . + * + */ + +package com.owncloud.android.files; + +import org.apache.http.protocol.HTTP; + +import android.accounts.AccountManager; +import android.content.Intent; +import android.net.Uri; +import android.support.v4.app.DialogFragment; +import android.webkit.MimeTypeMap; +import android.widget.Toast; + +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; + +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; +import com.owncloud.android.lib.common.network.WebdavUtils; +import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import com.owncloud.android.services.OperationsService; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.dialog.ShareLinkToDialog; +import com.owncloud.android.utils.Log_OC; + +/** + * + * @author masensio + * @author David A. Velasco + */ +public class FileOperationsHelper { + + private static final String TAG = FileOperationsHelper.class.getName(); + + private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG"; + + + public void openFile(OCFile file, FileActivity callerActivity) { + if (file != null) { + String storagePath = file.getStoragePath(); + String encodedStoragePath = WebdavUtils.encodePath(storagePath); + + Intent intentForSavedMimeType = new Intent(Intent.ACTION_VIEW); + intentForSavedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); + intentForSavedMimeType.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + Intent intentForGuessedMimeType = null; + if (storagePath.lastIndexOf('.') >= 0) { + String guessedMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); + if (guessedMimeType != null && !guessedMimeType.equals(file.getMimetype())) { + intentForGuessedMimeType = new Intent(Intent.ACTION_VIEW); + intentForGuessedMimeType.setDataAndType(Uri.parse("file://"+ encodedStoragePath), guessedMimeType); + intentForGuessedMimeType.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + } + + Intent chooserIntent = null; + if (intentForGuessedMimeType != null) { + chooserIntent = Intent.createChooser(intentForGuessedMimeType, callerActivity.getString(R.string.actionbar_open_with)); + } else { + chooserIntent = Intent.createChooser(intentForSavedMimeType, callerActivity.getString(R.string.actionbar_open_with)); + } + + callerActivity.startActivity(chooserIntent); + + } else { + Log_OC.wtf(TAG, "Trying to open a NULL OCFile"); + } + } + + + public void shareFileWithLink(OCFile file, FileActivity callerActivity) { + + if (isSharedSupported(callerActivity)) { + if (file != null) { + String link = "https://fake.url"; + Intent intent = createShareWithLinkIntent(link); + String[] packagesToExclude = new String[] { callerActivity.getPackageName() }; + DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intent, packagesToExclude, file); + chooserDialog.show(callerActivity.getSupportFragmentManager(), FTAG_CHOOSER_DIALOG); + + } else { + Log_OC.wtf(TAG, "Trying to share a NULL OCFile"); + } + + } else { + // Show a Message + Toast t = Toast.makeText(callerActivity, callerActivity.getString(R.string.share_link_no_support_share_api), Toast.LENGTH_LONG); + t.show(); + } + } + + + public void shareFileWithLinkToApp(OCFile file, Intent sendIntent, FileActivity callerActivity) { + + if (file != null) { + callerActivity.showLoadingDialog(); + + Intent service = new Intent(callerActivity, OperationsService.class); + service.setAction(OperationsService.ACTION_CREATE_SHARE); + service.putExtra(OperationsService.EXTRA_ACCOUNT, callerActivity.getAccount()); + service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + service.putExtra(OperationsService.EXTRA_SEND_INTENT, sendIntent); + callerActivity.getOperationsServiceBinder().newOperation(service); + + } else { + Log_OC.wtf(TAG, "Trying to open a NULL OCFile"); + } + } + + + private Intent createShareWithLinkIntent(String link) { + Intent intentToShareLink = new Intent(Intent.ACTION_SEND); + intentToShareLink.putExtra(Intent.EXTRA_TEXT, link); + intentToShareLink.setType(HTTP.PLAIN_TEXT_TYPE); + return intentToShareLink; + } + + + /** + * @return 'True' if the server supports the Share API + */ + public boolean isSharedSupported(FileActivity callerActivity) { + if (callerActivity.getAccount() != null) { + AccountManager accountManager = AccountManager.get(callerActivity); + + String version = accountManager.getUserData(callerActivity.getAccount(), Constants.KEY_OC_VERSION); + return (new OwnCloudVersion(version)).isSharedSupported(); + //return Boolean.parseBoolean(accountManager.getUserData(callerActivity.getAccount(), OwnCloudAccount.Constants.KEY_SUPPORTS_SHARE_API)); + } + return false; + } + + + public void unshareFileWithLink(OCFile file, FileActivity callerActivity) { + + if (isSharedSupported(callerActivity)) { + // Unshare the file + Intent service = new Intent(callerActivity, OperationsService.class); + service.setAction(OperationsService.ACTION_UNSHARE); + service.putExtra(OperationsService.EXTRA_ACCOUNT, callerActivity.getAccount()); + service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + callerActivity.getOperationsServiceBinder().newOperation(service); + + callerActivity.showLoadingDialog(); + + } else { + // Show a Message + Toast t = Toast.makeText(callerActivity, callerActivity.getString(R.string.share_link_no_support_share_api), Toast.LENGTH_LONG); + t.show(); + + } + } + + public void sendDownloadedFile(OCFile file, FileActivity callerActivity) { + if (file != null) { + Intent sendIntent = new Intent(android.content.Intent.ACTION_SEND); + // set MimeType + sendIntent.setType(file.getMimetype()); + sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + file.getStoragePath())); + sendIntent.putExtra(Intent.ACTION_SEND, true); // Send Action + + // Show dialog, without the own app + String[] packagesToExclude = new String[] { callerActivity.getPackageName() }; + DialogFragment chooserDialog = ShareLinkToDialog.newInstance(sendIntent, packagesToExclude, file); + chooserDialog.show(callerActivity.getSupportFragmentManager(), FTAG_CHOOSER_DIALOG); + + } else { + Log_OC.wtf(TAG, "Trying to send a NULL OCFile"); + } + } + +} diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index 2c2fa91a..ee004b23 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -1,6 +1,6 @@ /* ownCloud Android client application * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2012-2013 ownCloud Inc. + * 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, @@ -20,11 +20,19 @@ package com.owncloud.android.files; import java.io.File; +import com.owncloud.android.MainApp; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.db.DbHandler; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + + import android.accounts.Account; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; +//import android.content.IntentFilter; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo.State; @@ -32,17 +40,16 @@ import android.preference.PreferenceManager; import android.provider.MediaStore.*; import android.webkit.MimeTypeMap; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.db.DbHandler; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.utils.FileStorageUtils; public class InstantUploadBroadcastReceiver extends BroadcastReceiver { private static String TAG = "InstantUploadBroadcastReceiver"; + // private static final String[] CONTENT_PROJECTION = { Media.DATA, Media.DISPLAY_NAME, Media.MIME_TYPE, Media.SIZE }; + //Unofficial action, works for most devices but not HTC. See: https://github.com/owncloud/android/issues/6 + private static String NEW_PHOTO_ACTION_UNOFFICIAL = "com.android.camera.NEW_PICTURE"; + //Officially supported action since SDK 14: http://developer.android.com/reference/android/hardware/Camera.html#ACTION_NEW_PICTURE private static String NEW_PHOTO_ACTION = "android.hardware.action.NEW_PICTURE"; + // Video action private static String NEW_VIDEO_ACTION = "android.hardware.action.NEW_VIDEO"; @Override @@ -50,9 +57,15 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { Log_OC.d(TAG, "Received: " + intent.getAction()); if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) { handleConnectivityAction(context, intent); + }else if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { + handleNewMediaAction(context, intent); //handleNewPhotoAction(context, intent); + Log_OC.d(TAG, "UNOFFICIAL processed: com.android.camera.NEW_PICTURE"); + } else if (intent.getAction().equals(NEW_PHOTO_ACTION)) { + handleNewMediaAction(context, intent); //handleNewPhotoAction(context, intent); + Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE"); } else if (intent.getAction().equals(NEW_PHOTO_ACTION) || intent.getAction().equals(NEW_VIDEO_ACTION)) { handleNewMediaAction(context, intent); - } else if (intent.getAction().equals(FileUploader.UPLOAD_FINISH_MESSAGE)) { + } else if (intent.getAction().equals(FileUploader.getUploadFinishMessage())) { handleUploadFinished(context, intent); } else { Log_OC.e(TAG, "Incorrect intent sent: " + intent.getAction()); @@ -61,6 +74,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { private void handleUploadFinished(Context context, Intent intent) { // remove successfull uploading, ignore rest for reupload on reconnect + /* if (intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false)) { DbHandler db = new DbHandler(context); String localPath = intent.getStringExtra(FileUploader.EXTRA_OLD_FILE_PATH); @@ -69,6 +83,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { } db.close(); } + */ } private void handleNewMediaAction(Context context, Intent intent) { @@ -78,7 +93,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { String mime_type = null; if (!instantUploadEnabled(context)) { - Log_OC.d(TAG, "Instant upload disabled, abording uploading"); + Log_OC.d(TAG, "Instant upload disabled, aborting uploading"); return; } @@ -136,8 +151,8 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { // we can unregister from entire listenings but thats suck a bit. // On the other hand this might be only for dynamicly registered // broadcast receivers, needs investigation. - IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); - context.getApplicationContext().registerReceiver(this, filter); + /*IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); + context.getApplicationContext().registerReceiver(this, filter);*/ Intent i = new Intent(context, FileUploader.class); i.putExtra(FileUploader.KEY_ACCOUNT, account); @@ -162,14 +177,14 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { DbHandler db = new DbHandler(context); Cursor c = db.getAwaitingFiles(); if (c.moveToFirst()) { - IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); - context.getApplicationContext().registerReceiver(this, filter); + //IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); + //context.getApplicationContext().registerReceiver(this, filter); do { String account_name = c.getString(c.getColumnIndex("account")); String file_path = c.getString(c.getColumnIndex("path")); File f = new File(file_path); if (f.exists()) { - Account account = new Account(account_name, AccountAuthenticator.ACCOUNT_TYPE); + Account account = new Account(account_name, MainApp.getAccountType()); String mimeType = null; try { diff --git a/src/com/owncloud/android/files/OwnCloudFileObserver.java b/src/com/owncloud/android/files/OwnCloudFileObserver.java index 10dbdfc3..d5a42b5e 100644 --- a/src/com/owncloud/android/files/OwnCloudFileObserver.java +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@ -20,65 +20,72 @@ package com.owncloud.android.files; import java.io.File; -import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFileOperation; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ConflictsResolveActivity; - -import eu.alefzero.webdav.WebdavClient; +import com.owncloud.android.utils.Log_OC; import android.accounts.Account; import android.content.Context; import android.content.Intent; import android.os.FileObserver; -import android.util.Log; public class OwnCloudFileObserver extends FileObserver { - public static int CHANGES_ONLY = CLOSE_WRITE; + private static int MASK = (FileObserver.MODIFY | FileObserver.CLOSE_WRITE); private static String TAG = OwnCloudFileObserver.class.getSimpleName(); private String mPath; private int mMask; private Account mOCAccount; - //private OCFile mFile; private Context mContext; + private boolean mModified; - public OwnCloudFileObserver(String path, Account account, Context context, int mask) { - super(path, mask); + public OwnCloudFileObserver(String path, Account account, Context context) { + super(path, MASK); if (path == null) throw new IllegalArgumentException("NULL path argument received"); - /*if (file == null) - throw new IllegalArgumentException("NULL file argument received");*/ if (account == null) throw new IllegalArgumentException("NULL account argument received"); if (context == null) throw new IllegalArgumentException("NULL context argument received"); - /*if (!path.equals(file.getStoragePath()) && !path.equals(FileStorageUtils.getDefaultSavePathFor(account.name, file))) - throw new IllegalArgumentException("File argument is not linked to the local file set in path argument"); */ mPath = path; - //mFile = file; mOCAccount = account; mContext = context; - mMask = mask; + mModified = false; } + @Override public void onEvent(int event, String path) { Log_OC.d(TAG, "Got file modified with event " + event + " and path " + mPath + ((path != null) ? File.separator + path : "")); - if ((event & mMask) == 0) { + if ((event & MASK) == 0) { Log_OC.wtf(TAG, "Incorrect event " + event + " sent for file " + mPath + ((path != null) ? File.separator + path : "") + " with registered for " + mMask + " and original path " + mPath); - return; - } - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mOCAccount, mContext); + } else { + if ((event & FileObserver.MODIFY) != 0) { + // file changed + mModified = true; + } + // not sure if it's possible, but let's assume that both kind of events can be received at the same time + if ((event & FileObserver.CLOSE_WRITE) != 0) { + // file closed + if (mModified) { + mModified = false; + startSyncOperation(); + } + } + } + } + + + private void startSyncOperation() { FileDataStorageManager storageManager = new FileDataStorageManager(mOCAccount, mContext.getContentResolver()); OCFile file = storageManager.getFileByLocalPath(mPath); // a fresh object is needed; many things could have occurred to the file since it was registered to observe // again, assuming that local files are linked to a remote file AT MOST, SOMETHING TO BE DONE; @@ -87,9 +94,8 @@ public class OwnCloudFileObserver extends FileObserver { storageManager, mOCAccount, true, - true, mContext); - RemoteOperationResult result = sfo.execute(wc); + RemoteOperationResult result = sfo.execute(mOCAccount, mContext); if (result.getCode() == ResultCode.SYNC_CONFLICT) { // ISSUE 5: if the user is not running the app (this is a service!), this can be very intrusive; a notification should be preferred Intent i = new Intent(mContext, ConflictsResolveActivity.class); diff --git a/src/com/owncloud/android/files/managers/OCNotificationManager.java b/src/com/owncloud/android/files/managers/OCNotificationManager.java deleted file mode 100644 index 55e98ee8..00000000 --- a/src/com/owncloud/android/files/managers/OCNotificationManager.java +++ /dev/null @@ -1,154 +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 . - * - */ - -package com.owncloud.android.files.managers; - -import java.util.HashMap; -import java.util.Map; - -import android.app.Notification; -import android.app.NotificationManager; -import android.content.Context; -import android.widget.RemoteViews; - -import com.owncloud.android.R; - -public class OCNotificationManager { - - enum NotificationType { - NOTIFICATION_SIMPLE, - NOTIFICATION_PROGRESS - } - - static public class NotificationData { - private String mText, mSubtitle; - private int mPercent; - private boolean mOngoing; - - public NotificationData(String text, String subtitle, boolean ongoing) { - this(text, subtitle, -1, ongoing); - } - - public NotificationData(int percent, boolean ongoing) { - this(null, null, percent, ongoing); - } - - public NotificationData(String text, int percent, boolean ongoing) { - this(text, null, percent, ongoing); - } - - public NotificationData(String text, String subtitle, int percent, boolean ongoing) { - mText = text; - mPercent = percent; - mSubtitle = subtitle; - mOngoing = ongoing; - } - - public String getText() { return mText; } - public int getPercent() { return mPercent; } - public String getSubtitle() { return mSubtitle; } - public boolean getOngoing() { return mOngoing; } - } - - static private OCNotificationManager mInstance = null; - - private class NotificationTypePair { - public Notification mNotificaiton; - public NotificationType mType; - public NotificationTypePair(Notification n, NotificationType type) { - mNotificaiton = n; - mType = type; - } - } - - private Context mContext; - private Map mNotificationMap; - private int mNotificationCounter; - NotificationManager mNM; - - static OCNotificationManager getInstance(Context context) { - if (mInstance == null) - mInstance = new OCNotificationManager(context); - return mInstance; - } - - OCNotificationManager(Context context) { - mContext = context; - mNotificationMap = new HashMap(); - mNM = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); - mNotificationCounter = 0; - } - - public int postNotification(NotificationType type, NotificationData data) { - mNotificationCounter++; - Notification notification = null; - - switch (type) { - case NOTIFICATION_SIMPLE: - notification = new Notification(R.drawable.icon, data.getText(), System.currentTimeMillis()); - break; - case NOTIFICATION_PROGRESS: - notification = new Notification(); - notification.contentView = new RemoteViews(mContext.getPackageName(), R.layout.progressbar_layout); - notification.contentView.setTextViewText(R.id.status_text, - data.getText()); - notification.contentView.setImageViewResource(R.id.status_icon, - R.id.icon); - notification.contentView.setProgressBar(R.id.status_progress, - 100, - data.getPercent(), - false); - break; - default: - return -1; - } - if (data.getOngoing()) { - notification.flags |= notification.flags | Notification.FLAG_ONGOING_EVENT; - } - - mNotificationMap.put(mNotificationCounter, new NotificationTypePair(notification, type)); - return mNotificationCounter; - } - - public boolean updateNotification(int notification_id, NotificationData data) { - if (!mNotificationMap.containsKey(notification_id)) { - return false; - } - NotificationTypePair pair = mNotificationMap.get(notification_id); - switch (pair.mType) { - case NOTIFICATION_PROGRESS: - pair.mNotificaiton.contentView.setProgressBar(R.id.status_text, - 100, - data.getPercent(), - false); - return true; - case NOTIFICATION_SIMPLE: - pair.mNotificaiton = new Notification(R.drawable.icon, - data.getText(), System.currentTimeMillis()); - mNM.notify(notification_id, pair.mNotificaiton); - return true; - default: - return false; - } - } - - public void discardNotification(int notification_id) { - mNM.cancel(notification_id); - mNotificationMap.remove(notification_id); - } -} diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index 83686037..461283ff 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -19,6 +19,7 @@ package com.owncloud.android.files.services; import java.io.File; +import java.io.IOException; import java.util.AbstractList; import java.util.HashMap; import java.util.Iterator; @@ -27,20 +28,27 @@ 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; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import eu.alefzero.webdav.OnDatatransferProgressListener; -import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.operations.DownloadFileOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.ui.activity.FileDetailActivity; -import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.preview.PreviewImageActivity; import com.owncloud.android.ui.preview.PreviewImageFragment; +import com.owncloud.android.utils.Log_OC; +import com.owncloud.android.utils.NotificationBuilderWithProgressBar; import android.accounts.Account; -import android.app.Notification; +import android.accounts.AccountsException; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -52,19 +60,15 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; -import android.widget.RemoteViews; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavClient; +import android.support.v4.app.NotificationCompat; public class FileDownloader extends Service implements OnDatatransferProgressListener { public static final String EXTRA_ACCOUNT = "ACCOUNT"; public static final String EXTRA_FILE = "FILE"; - public static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED"; - public static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH"; + 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"; @@ -75,7 +79,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis private Looper mServiceLooper; private ServiceHandler mServiceHandler; private IBinder mBinder; - private WebdavClient mDownloadClient = null; + private OwnCloudClient mDownloadClient = null; private Account mLastAccount = null; private FileDataStorageManager mStorageManager; @@ -83,10 +87,18 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis private DownloadFileOperation mCurrentDownload = null; private NotificationManager mNotificationManager; - private Notification mNotification; + private NotificationCompat.Builder mNotificationBuilder; private int mLastPercent; + public static String getDownloadAddedMessage() { + return FileDownloader.class.getName().toString() + 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 * @@ -113,7 +125,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis mBinder = new FileDownloaderBinder(); } - /** * Entry point to add one or several files to the queue of downloads. * @@ -227,7 +238,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis if (account == null || file == null) return false; String targetKey = buildRemoteName(account, file); synchronized (mPendingDownloads) { - if (file.isDirectory()) { + if (file.isFolder()) { // this can be slow if there are many downloads :( Iterator it = mPendingDownloads.keySet().iterator(); boolean found = false; @@ -256,7 +267,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis } - /** * Removes a listener interested in the progress of the download for a concrete file. * @@ -272,13 +282,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis } } - - @Override - public void onTransferProgress(long progressRate) { - // old way, should not be in use any more - } - - @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { @@ -321,7 +324,6 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis } } - /** * Core download method: requests a file to download and stores it. @@ -338,21 +340,28 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis notifyDownloadStart(mCurrentDownload); - /// 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()); - mDownloadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); - } - - /// perform the download 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()); + mDownloadClient = OwnCloudClientFactory.createOwnCloudClient(mLastAccount, getApplicationContext()); + } + + /// perform the download 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); + downloadResult = new RemoteOperationResult(e); + } catch (IOException e) { + Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + downloadResult = new RemoteOperationResult(e); + } finally { synchronized(mPendingDownloads) { mPendingDownloads.remove(downloadKey); @@ -372,7 +381,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis * Updates the OC File after a successful download. */ private void saveDownloadedFile() { - OCFile file = mCurrentDownload.getFile(); + OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId()); long syncDate = System.currentTimeMillis(); file.setLastSyncDateForProperties(syncDate); file.setLastSyncDateForData(syncDate); @@ -394,26 +403,35 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis private void notifyDownloadStart(DownloadFileOperation download) { /// create status notification with a progress bar mLastPercent = 0; - mNotification = new Notification(R.drawable.icon, getString(R.string.downloader_download_in_progress_ticker), System.currentTimeMillis()); - mNotification.flags |= Notification.FLAG_ONGOING_EVENT; - mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout); - mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, download.getSize() < 0); - mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.downloader_download_in_progress_content), 0, new File(download.getSavePath()).getName())); - mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon); - + mNotificationBuilder = + NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); + mNotificationBuilder + .setSmallIcon(R.drawable.notification_icon) + .setTicker(getString(R.string.downloader_download_in_progress_ticker)) + .setContentTitle(getString(R.string.downloader_download_in_progress_ticker)) + .setOngoing(true) + .setProgress(100, 0, download.getSize() < 0) + .setContentText( + String.format(getString(R.string.downloader_download_in_progress_content), 0, + new File(download.getSavePath()).getName()) + ); + /// includes a pending intent in the notification showing the details view of the file Intent showDetailsIntent = null; if (PreviewImageFragment.canBePreviewed(download.getFile())) { showDetailsIntent = new Intent(this, PreviewImageActivity.class); } else { - showDetailsIntent = new Intent(this, FileDetailActivity.class); + showDetailsIntent = new Intent(this, FileDisplayActivity.class); } - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile()); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, download.getAccount()); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount()); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0); - mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); + mNotificationBuilder.setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), showDetailsIntent, 0 + )); + + mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build()); } @@ -421,28 +439,20 @@ 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 fileName) { + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) { int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer)); if (percent != mLastPercent) { - mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, totalToTransfer < 0); - String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName); - mNotification.contentView.setTextViewText(R.id.status_text, text); - mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotification); + mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0); + String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1); + String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName); + mNotificationBuilder.setContentText(text); + mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build()); } mLastPercent = percent; } /** - * Callback method to update the progress bar in the status notification (old version) - */ - @Override - public void onTransferProgress(long progressRate) { - // NOTHING TO DO HERE ANYMORE - } - - - /** * Updates the status notification with the result of a download operation. * * @param downloadResult Result of the download operation. @@ -453,26 +463,55 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis if (!downloadResult.isCancelled()) { int tickerId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker; int contentId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_content : R.string.downloader_download_failed_content; - Notification finalNotification = new Notification(R.drawable.icon, getString(tickerId), System.currentTimeMillis()); - finalNotification.flags |= Notification.FLAG_AUTO_CANCEL; - Intent showDetailsIntent = null; - if (downloadResult.isSuccess()) { - if (PreviewImageFragment.canBePreviewed(download.getFile())) { - showDetailsIntent = new Intent(this, PreviewImageActivity.class); - } else { - showDetailsIntent = new Intent(this, FileDetailActivity.class); - } - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, download.getFile()); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, download.getAccount()); - showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + mNotificationBuilder + .setTicker(getString(tickerId)) + .setContentTitle(getString(tickerId)) + .setAutoCancel(true) + .setOngoing(false) + .setProgress(0, 0, false); + boolean needsToUpdateCredentials = (downloadResult.getCode() == ResultCode.UNAUTHORIZED || + // (downloadResult.isTemporalRedirection() && downloadResult.isIdPRedirection() + (downloadResult.isIdPRedirection() + && mDownloadClient.getCredentials() == null)); + //&& MainApp.getAuthTokenTypeSamlSessionCookie().equals(mDownloadClient.getAuthTokenType()))); + if (needsToUpdateCredentials) { + // 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.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND); + mNotificationBuilder + .setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT + )) + .setContentText(String.format(getString(contentId), new File(download.getSavePath()).getName())); + mDownloadClient = null; // grant that future retries on the same account will get the fresh credentials } else { - // TODO put something smart in showDetailsIntent - showDetailsIntent = new Intent(); + Intent showDetailsIntent = null; + if (downloadResult.isSuccess()) { + if (PreviewImageFragment.canBePreviewed(download.getFile())) { + showDetailsIntent = new Intent(this, PreviewImageActivity.class); + } else { + showDetailsIntent = new Intent(this, FileDisplayActivity.class); + } + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount()); + showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + } else { + // TODO put something smart in showDetailsIntent + showDetailsIntent = new Intent(); + } + mNotificationBuilder + .setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), showDetailsIntent, 0 + )) + .setContentText(String.format(getString(contentId), new File(download.getSavePath()).getName())); } - finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), showDetailsIntent, 0); - finalNotification.setLatestEventInfo(getApplicationContext(), getString(tickerId), String.format(getString(contentId), new File(download.getSavePath()).getName()), finalNotification.contentIntent); - mNotificationManager.notify(tickerId, finalNotification); + mNotificationManager.notify(tickerId, mNotificationBuilder.build()); } } @@ -484,7 +523,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis * @param downloadResult Result of the download operation */ private void sendBroadcastDownloadFinished(DownloadFileOperation download, RemoteOperationResult downloadResult) { - Intent end = new Intent(DOWNLOAD_FINISH_MESSAGE); + 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()); @@ -499,7 +538,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis * @param download Added download operation */ private void sendBroadcastNewDownload(DownloadFileOperation download) { - Intent added = new Intent(DOWNLOAD_ADDED_MESSAGE); + 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()); diff --git a/src/com/owncloud/android/files/services/FileObserverService.java b/src/com/owncloud/android/files/services/FileObserverService.java index a01446a9..c6753896 100644 --- a/src/com/owncloud/android/files/services/FileObserverService.java +++ b/src/com/owncloud/android/files/services/FileObserverService.java @@ -22,13 +22,15 @@ import java.io.File; import java.util.HashMap; import java.util.Map; -import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; import com.owncloud.android.files.OwnCloudFileObserver; import com.owncloud.android.operations.SynchronizeFileOperation; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + import android.accounts.Account; import android.accounts.AccountManager; @@ -40,7 +42,6 @@ import android.content.IntentFilter; import android.database.Cursor; import android.os.Binder; import android.os.IBinder; -import android.util.Log; public class FileObserverService extends Service { @@ -57,7 +58,7 @@ public class FileObserverService extends Service { private static Map mObserversMap; private static DownloadCompletedReceiverBis mDownloadReceiver; private IBinder mBinder = new LocalBinder(); - + public class LocalBinder extends Binder { FileObserverService getService() { return FileObserverService.this; @@ -68,9 +69,10 @@ public class FileObserverService extends Service { public void onCreate() { super.onCreate(); mDownloadReceiver = new DownloadCompletedReceiverBis(); + IntentFilter filter = new IntentFilter(); - filter.addAction(FileDownloader.DOWNLOAD_ADDED_MESSAGE); - filter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE); + filter.addAction(FileDownloader.getDownloadAddedMessage()); + filter.addAction(FileDownloader.getDownloadFinishMessage()); registerReceiver(mDownloadReceiver, filter); mObserversMap = new HashMap(); @@ -140,7 +142,7 @@ public class FileObserverService extends Service { null); if (c == null || !c.moveToFirst()) return; AccountManager acm = AccountManager.get(this); - Account[] accounts = acm.getAccounts(); + Account[] accounts = acm.getAccountsByType(MainApp.getAccountType()); do { Account account = null; for (Account a : accounts) @@ -161,8 +163,7 @@ public class FileObserverService extends Service { OwnCloudFileObserver observer = new OwnCloudFileObserver( path, account, - getApplicationContext(), - OwnCloudFileObserver.CHANGES_ONLY); + getApplicationContext()); mObserversMap.put(path, observer); if (new File(path).exists()) { observer.startWatching(); @@ -201,8 +202,7 @@ public class FileObserverService extends Service { /// the local file was never registered to observe before observer = new OwnCloudFileObserver( localPath, account, - getApplicationContext(), - OwnCloudFileObserver.CHANGES_ONLY); + getApplicationContext()); mObserversMap.put(localPath, observer); Log_OC.d(TAG, "Observer added for path " + localPath); @@ -260,12 +260,12 @@ public class FileObserverService extends Service { String downloadPath = intent.getStringExtra(FileDownloader.EXTRA_FILE_PATH); OwnCloudFileObserver observer = mObserversMap.get(downloadPath); if (observer != null) { - if (intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE) && + if (intent.getAction().equals(FileDownloader.getDownloadFinishMessage()) && new File(downloadPath).exists()) { // the download could be successful. not; in both cases, the file could be down, due to a former download or upload observer.startWatching(); Log_OC.d(TAG, "Watching again " + downloadPath); - } else if (intent.getAction().equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) { + } else if (intent.getAction().equals(FileDownloader.getDownloadAddedMessage())) { observer.stopWatching(); Log_OC.d(TAG, "Disabling observance of " + downloadPath); } diff --git a/src/com/owncloud/android/files/services/FileOperation.java b/src/com/owncloud/android/files/services/FileOperation.java deleted file mode 100644 index d48a1850..00000000 --- a/src/com/owncloud/android/files/services/FileOperation.java +++ /dev/null @@ -1,55 +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 . - * - */ -package com.owncloud.android.files.services; - -import java.io.File; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.OwnCloudClientUtils; - -import android.accounts.Account; -import android.content.Context; -import eu.alefzero.webdav.WebdavClient; - -public class FileOperation { - - Context mContext; - - public FileOperation(Context contex){ - this.mContext = contex; - } - - /** - * Deletes a file from ownCloud - locally and remote. - * @param file The file to delete - * @return True on success, otherwise false - */ - public boolean delete(OCFile file){ - - Account account = AccountUtils.getCurrentOwnCloudAccount(mContext); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(account, mContext); - if(client.deleteFile(file.getRemotePath())){ - File localFile = new File(file.getStoragePath()); - return localFile.delete(); - } - - return false; - } - -} diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index a3c76ebc..d302e267 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -19,6 +19,7 @@ package com.owncloud.android.files.services; import java.io.File; +import java.io.IOException; import java.util.AbstractList; import java.util.HashMap; import java.util.Iterator; @@ -27,13 +28,37 @@ import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.apache.http.HttpStatus; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AuthenticatorActivity; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.DbHandler; +import com.owncloud.android.operations.CreateFolderOperation; +import com.owncloud.android.lib.resources.files.RemoteFile; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.operations.UploadFileOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.ui.activity.FailedUploadActivity; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.activity.InstantUploadActivity; +import com.owncloud.android.ui.preview.PreviewImageActivity; +import com.owncloud.android.ui.preview.PreviewImageFragment; +import com.owncloud.android.utils.Log_OC; +import com.owncloud.android.utils.NotificationBuilderWithProgressBar; import android.accounts.Account; import android.accounts.AccountManager; -import android.app.Notification; +import android.accounts.AccountsException; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -45,38 +70,14 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; +import android.support.v4.app.NotificationCompat; import android.webkit.MimeTypeMap; -import android.widget.RemoteViews; -import android.widget.Toast; -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.db.DbHandler; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.ChunkedUploadFileOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.ui.activity.FailedUploadActivity; -import com.owncloud.android.ui.activity.FileDetailActivity; -import com.owncloud.android.ui.activity.InstantUploadActivity; -import com.owncloud.android.ui.fragment.FileDetailFragment; -import com.owncloud.android.ui.preview.PreviewImageActivity; -import com.owncloud.android.ui.preview.PreviewImageFragment; -import com.owncloud.android.utils.FileStorageUtils; -import com.owncloud.android.utils.OwnCloudVersion; -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; public class FileUploader extends Service implements OnDatatransferProgressListener { - public static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; + private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; public static final String EXTRA_UPLOAD_RESULT = "RESULT"; public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH"; @@ -107,7 +108,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe private Looper mServiceLooper; private ServiceHandler mServiceHandler; private IBinder mBinder; - private WebdavClient mUploadClient = null; + private OwnCloudClient mUploadClient = null; private Account mLastAccount = null; private FileDataStorageManager mStorageManager; @@ -115,10 +116,14 @@ public class FileUploader extends Service implements OnDatatransferProgressListe private UploadFileOperation mCurrentUpload = null; private NotificationManager mNotificationManager; - private Notification mNotification; + private NotificationCompat.Builder mNotificationBuilder; private int mLastPercent; - private RemoteViews mDefaultNotificationContentView; + + public static String getUploadFinishMessage() { + return FileUploader.class.getName().toString() + UPLOAD_FINISH_MESSAGE; + } + /** * Builds a key for mPendingUploads from the account and file to upload * @@ -216,16 +221,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false); int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY); - boolean fixed = false; - if (isInstant) { - fixed = checkAndFixInstantUploadDirectory(storageManager); // MUST - // be - // done - // BEFORE - // calling - // obtainNewOCFileToUpload - } - + if (intent.hasExtra(KEY_FILE) && files == null) { Log_OC.e(TAG, "Incorrect array for OCFiles provided in upload intent"); return Service.START_NOT_STICKY; @@ -249,14 +245,16 @@ public class FileUploader extends Service implements OnDatatransferProgressListe files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes != null) ? mimeTypes[i] : (String) null), storageManager); if (files[i] == null) { - // TODO @andomaex add failure Notiification + // TODO @andomaex add failure Notification return Service.START_NOT_STICKY; } } } - OwnCloudVersion ocv = new OwnCloudVersion(AccountManager.get(this).getUserData(account, - AccountAuthenticator.KEY_OC_VERSION)); + AccountManager aMgr = AccountManager.get(this); + String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); + OwnCloudVersion ocv = new OwnCloudVersion(version); + boolean chunked = FileUploader.chunkedUploadIsSupported(ocv); AbstractList requestedUploads = new Vector(); String uploadKey = null; @@ -264,16 +262,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe try { for (int i = 0; i < files.length; i++) { uploadKey = buildRemoteName(account, files[i].getRemotePath()); - if (chunked) { - newUpload = new ChunkedUploadFileOperation(account, files[i], isInstant, forceOverwrite, - localAction); - } else { - newUpload = new UploadFileOperation(account, files[i], isInstant, forceOverwrite, localAction); - } - if (fixed && i == 0) { + newUpload = new UploadFileOperation(account, files[i], chunked, isInstant, forceOverwrite, localAction, + getApplicationContext()); + if (isInstant) { newUpload.setRemoteFolderToBeCreated(); } - mPendingUploads.putIfAbsent(uploadKey, newUpload); + mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time + newUpload.addDatatransferProgressListener(this); newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder); requestedUploads.add(uploadKey); @@ -377,7 +372,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe return false; String targetKey = buildRemoteName(account, file); synchronized (mPendingUploads) { - if (file.isDirectory()) { + if (file.isFolder()) { // this can be slow if there are many uploads :( Iterator it = mPendingUploads.keySet().iterator(); boolean found = false; @@ -424,12 +419,6 @@ public class FileUploader extends Service implements OnDatatransferProgressListe @Override - public void onTransferProgress(long progressRate) { - // old way, should not be in use any more - } - - - @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { String key = buildRemoteName(mCurrentUpload.getAccount(), mCurrentUpload.getFile()); @@ -490,37 +479,55 @@ public class FileUploader extends Service implements OnDatatransferProgressListe notifyUploadStart(mCurrentUpload); - // / prepare client object to send requests to the ownCloud server - if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { - mLastAccount = mCurrentUpload.getAccount(); - mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - mUploadClient = OwnCloudClientUtils.createOwnCloudClient(mLastAccount, getApplicationContext()); - } - - // / create remote folder for instant uploads - if (mCurrentUpload.isRemoteFolderToBeCreated()) { - mUploadClient.createDirectory(FileStorageUtils.getInstantUploadFilePath(this, "")); - // ignoring result fail could just mean that it already exists, - // but local database is not synchronized the upload will be - // tried anyway - } - - // / perform the upload - RemoteOperationResult uploadResult = null; + RemoteOperationResult uploadResult = null, grantResult = null; + try { - uploadResult = mCurrentUpload.execute(mUploadClient); - if (uploadResult.isSuccess()) { - saveUploadedFile(); + /// prepare client object to send requests to the ownCloud server + if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { + mLastAccount = mCurrentUpload.getAccount(); + mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); + mUploadClient = OwnCloudClientFactory.createOwnCloudClient(mLastAccount, getApplicationContext()); } - + + /// check the existence of the parent folder for the file to upload + String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent(); + remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR; + grantResult = grantFolderExistence(remoteParentPath); + + /// perform the upload + if (grantResult.isSuccess()) { + OCFile parent = mStorageManager.getFileByPath(remoteParentPath); + mCurrentUpload.getFile().setParentId(parent.getFileId()); + uploadResult = mCurrentUpload.execute(mUploadClient); + if (uploadResult.isSuccess()) { + saveUploadedFile(); + } + } else { + uploadResult = grantResult; + } + + } catch (AccountsException e) { + Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + uploadResult = new RemoteOperationResult(e); + + } catch (IOException e) { + Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + uploadResult = new RemoteOperationResult(e); + } finally { synchronized (mPendingUploads) { mPendingUploads.remove(uploadKey); Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map."); } + if (uploadResult.isException()) { + // enforce the creation of a new client object for next uploads; this grant that a new socket will + // be created in the future if the current exception is due to an abrupt lose of network connection + mUploadClient = null; + } } - - // notify result + + /// notify result + notifyUploadResult(uploadResult, mCurrentUpload); sendFinalBroadcast(mCurrentUpload, uploadResult); @@ -529,6 +536,58 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } /** + * Checks the existence of the folder where the current file will be uploaded both in the remote server + * and in the local database. + * + * If the upload is set to enforce the creation of the folder, the method tries to create it both remote + * and locally. + * + * @param pathToGrant Full remote path whose existence will be granted. + * @return An {@link OCFile} instance corresponding to the folder where the file will be uploaded. + */ + private RemoteOperationResult grantFolderExistence(String pathToGrant) { + RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, this, false); + RemoteOperationResult result = operation.execute(mUploadClient); + if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mCurrentUpload.isRemoteFolderToBeCreated()) { + operation = new CreateFolderOperation( pathToGrant, + true, + mStorageManager ); + result = operation.execute(mUploadClient); + } + if (result.isSuccess()) { + OCFile parentDir = mStorageManager.getFileByPath(pathToGrant); + if (parentDir == null) { + parentDir = createLocalFolder(pathToGrant); + } + if (parentDir != null) { + result = new RemoteOperationResult(ResultCode.OK); + } else { + result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); + } + } + return result; + } + + + private OCFile createLocalFolder(String remotePath) { + String parentPath = new File(remotePath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + OCFile parent = mStorageManager.getFileByPath(parentPath); + if (parent == null) { + parent = createLocalFolder(parentPath); + } + if (parent != null) { + OCFile createdFolder = new OCFile(remotePath); + createdFolder.setMimetype("DIR"); + createdFolder.setParentId(parent.getFileId()); + mStorageManager.saveFile(createdFolder); + return createdFolder; + } + return null; + } + + + /** * Saves a OC File after a successful upload. * * A PROPFIND is necessary to keep the props in the local database @@ -539,42 +598,21 @@ public class FileUploader extends Service implements OnDatatransferProgressListe */ private void saveUploadedFile() { OCFile file = mCurrentUpload.getFile(); + if (file.fileExists()) { + file = mStorageManager.getFileById(file.getFileId()); + } long syncDate = System.currentTimeMillis(); file.setLastSyncDateForData(syncDate); - // / new PROPFIND to keep data consistent with server in theory, should - // return the same we already have - PropFindMethod propfind = null; - RemoteOperationResult result = null; - try { - propfind = new PropFindMethod(mUploadClient.getBaseUri() - + WebdavUtils.encodePath(mCurrentUpload.getRemotePath())); - int status = mUploadClient.executeMethod(propfind); - boolean isMultiStatus = (status == HttpStatus.SC_MULTI_STATUS); - if (isMultiStatus) { - MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], mUploadClient.getBaseUri().getPath()); - updateOCFile(file, we); - file.setLastSyncDateForProperties(syncDate); - - } else { - mUploadClient.exhaustResponse(propfind.getResponseBodyAsStream()); - } - - result = new RemoteOperationResult(isMultiStatus, status); - Log_OC.i(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " - + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Update: synchronizing properties for uploaded " + mCurrentUpload.getRemotePath() + ": " - + result.getLogMessage(), e); - - } finally { - if (propfind != null) - propfind.releaseConnection(); + // new PROPFIND to keep data consistent with server + // in theory, should return the same we already have + ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mCurrentUpload.getRemotePath()); + RemoteOperationResult result = operation.execute(mUploadClient); + if (result.isSuccess()) { + updateOCFile(file, (RemoteFile) result.getData().get(0)); + file.setLastSyncDateForProperties(syncDate); } - + // / maybe this would be better as part of UploadFileOperation... or // maybe all this method if (mCurrentUpload.wasRenamed()) { @@ -591,35 +629,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe mStorageManager.saveFile(file); } - private void updateOCFile(OCFile file, WebdavEntry we) { - file.setCreationTimestamp(we.createTimestamp()); - file.setFileLength(we.contentLength()); - file.setMimetype(we.contentType()); - file.setModificationTimestamp(we.modifiedTimestamp()); - file.setModificationTimestampAtLastSyncForData(we.modifiedTimestamp()); - // file.setEtag(mCurrentUpload.getEtag()); // TODO Etag, where available - } - - private boolean checkAndFixInstantUploadDirectory(FileDataStorageManager storageManager) { - String instantUploadDirPath = FileStorageUtils.getInstantUploadFilePath(this, ""); - OCFile instantUploadDir = storageManager.getFileByPath(instantUploadDirPath); - if (instantUploadDir == null) { - // first instant upload in the account. never account not - // synchronized after the remote InstantUpload folder was created - OCFile newDir = new OCFile(instantUploadDirPath); - newDir.setMimetype("DIR"); - OCFile path = storageManager.getFileByPath(OCFile.PATH_SEPARATOR); - - if (path != null) { - newDir.setParentId(path.getFileId()); - storageManager.saveFile(newDir); - return true; - } else { // this should not happen anymore - return false; - } - - } - return false; + private void updateOCFile(OCFile file, RemoteFile remoteFile) { + file.setCreationTimestamp(remoteFile.getCreationTimestamp()); + file.setFileLength(remoteFile.getLength()); + file.setMimetype(remoteFile.getMimeType()); + file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); + file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp()); + // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available } private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, @@ -651,12 +667,6 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } newFile.setMimetype(mimeType); - // parent dir - String parentPath = new File(remotePath).getParent(); - parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; - OCFile parentDir = storageManager.getFileByPath(parentPath); - long parentDirId = parentDir.getFileId(); - newFile.setParentId(parentDirId); return newFile; } @@ -665,63 +675,49 @@ public class FileUploader extends Service implements OnDatatransferProgressListe * * @param upload Upload operation starting. */ - @SuppressWarnings("deprecation") private void notifyUploadStart(UploadFileOperation upload) { // / create status notification with a progress bar mLastPercent = 0; - mNotification = new Notification(R.drawable.icon, getString(R.string.uploader_upload_in_progress_ticker), - System.currentTimeMillis()); - mNotification.flags |= Notification.FLAG_ONGOING_EVENT; - mDefaultNotificationContentView = mNotification.contentView; - mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), - R.layout.progressbar_layout); - mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false); - mNotification.contentView.setTextViewText(R.id.status_text, - String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); - mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon); - + mNotificationBuilder = + NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); + mNotificationBuilder + .setOngoing(true) + .setSmallIcon(R.drawable.notification_icon) + .setTicker(getString(R.string.uploader_upload_in_progress_ticker)) + .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker)) + .setProgress(100, 0, false) + .setContentText( + String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); + /// includes a pending intent in the notification showing the details view of the file - Intent showDetailsIntent = null; - if (PreviewImageFragment.canBePreviewed(upload.getFile())) { - showDetailsIntent = new Intent(this, PreviewImageActivity.class); - } else { - showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS); - } - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile()); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount()); + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, upload.getFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int) System.currentTimeMillis(), showDetailsIntent, 0); + mNotificationBuilder.setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), showDetailsIntent, 0 + )); - mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); + mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); } /** * Callback method to update the progress bar in the status notification */ @Override - public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) { int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer)); if (percent != mLastPercent) { - mNotification.contentView.setProgressBar(R.id.status_progress, 100, percent, false); + mNotificationBuilder.setProgress(100, percent, false); + String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1); String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName); - mNotification.contentView.setTextViewText(R.id.status_text, text); - mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); + mNotificationBuilder.setContentText(text); + mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); } mLastPercent = percent; } /** - * Callback method to update the progress bar in the status notification - * (old version) - */ - @Override - public void onTransferProgress(long progressRate) { - // NOTHING TO DO HERE ANYMORE - } - - /** * Updates the status notification with the result of an upload operation. * * @param uploadResult Result of the upload operation. @@ -736,97 +732,123 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } else if (uploadResult.isSuccess()) { // / success -> silent update of progress notification to success // message - mNotification.flags ^= Notification.FLAG_ONGOING_EVENT; // remove - // the - // ongoing - // flag - mNotification.flags |= Notification.FLAG_AUTO_CANCEL; - mNotification.contentView = mDefaultNotificationContentView; + mNotificationBuilder + .setOngoing(false) + .setAutoCancel(true) + .setProgress(0, 0, false); /// includes a pending intent in the notification showing the details view of the file Intent showDetailsIntent = null; if (PreviewImageFragment.canBePreviewed(upload.getFile())) { showDetailsIntent = new Intent(this, PreviewImageActivity.class); } else { - showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS); + showDetailsIntent = new Intent(this, FileDisplayActivity.class); } - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile()); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount()); - showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int) System.currentTimeMillis(), showDetailsIntent, 0); - - mNotification.setLatestEventInfo(getApplicationContext(), - getString(R.string.uploader_upload_succeeded_ticker), - String.format(getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()), - mNotification.contentIntent); - - mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); // NOT - // AN + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, upload.getFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); + showDetailsIntent.putExtra(FileActivity.EXTRA_FROM_NOTIFICATION, true);; + mNotificationBuilder + .setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), showDetailsIntent, 0 + )) + .setTicker(getString(R.string.uploader_upload_succeeded_ticker)) + .setContentTitle(getString(R.string.uploader_upload_succeeded_ticker)) + .setContentText( + String.format(getString(R.string.uploader_upload_succeeded_content_single), + upload.getFileName()) + ); + + mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); // NOT + // AN DbHandler db = new DbHandler(this.getBaseContext()); - db.removeIUPendingFile(mCurrentUpload.getFile().getStoragePath()); + db.removeIUPendingFile(mCurrentUpload.getOriginalStoragePath()); db.close(); } else { // / fail -> explicit failure notification mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); - Notification finalNotification = new Notification(R.drawable.icon, - getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis()); - finalNotification.flags |= Notification.FLAG_AUTO_CANCEL; - + NotificationCompat.Builder errorBuilder = new NotificationCompat.Builder(this); + errorBuilder + .setSmallIcon(R.drawable.notification_icon) + .setTicker(getString(R.string.uploader_upload_failed_ticker)) + .setContentTitle(getString(R.string.uploader_upload_failed_ticker)) + .setAutoCancel(true); String content = null; - if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL - || uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) { - // TODO we need a class to provide error messages for the users - // from a RemoteOperationResult and a RemoteOperation - content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), - getString(R.string.app_name)); - } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { - content = getString(R.string.failed_upload_quota_exceeded_text); + + boolean needsToUpdateCredentials = (uploadResult.getCode() == ResultCode.UNAUTHORIZED || + //(uploadResult.isTemporalRedirection() && uploadResult.isIdPRedirection() && + (uploadResult.isIdPRedirection() && + mUploadClient.getCredentials() == null)); + //MainApp.getAuthTokenTypeSamlSessionCookie().equals(mUploadClient.getAuthTokenType()))); + if (needsToUpdateCredentials) { + // let the user update credentials with one click + Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount()); + 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); + errorBuilder.setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT + )); + content = String.format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); + mUploadClient = null; // grant that future retries on the same account will get the fresh credentials } else { - content = String - .format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); - } + // TODO put something smart in the contentIntent below + + if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL + || uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) { + // TODO we need a class to provide error messages for the users + // from a RemoteOperationResult and a RemoteOperation + content = String.format(getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), + getString(R.string.app_name)); + } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { + content = getString(R.string.failed_upload_quota_exceeded_text); + } else { + content = String + .format(getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); + } - // we add only for instant-uploads the InstantUploadActivity and the - // db entry - Intent detailUploadIntent = null; - if (upload.isInstant() && InstantUploadActivity.IS_ENABLED) { - detailUploadIntent = new Intent(this, InstantUploadActivity.class); - detailUploadIntent.putExtra(FileUploader.KEY_ACCOUNT, upload.getAccount()); - } else { - detailUploadIntent = new Intent(this, FailedUploadActivity.class); - detailUploadIntent.putExtra(FailedUploadActivity.MESSAGE, content); - } - finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), - (int) System.currentTimeMillis(), detailUploadIntent, PendingIntent.FLAG_UPDATE_CURRENT - | PendingIntent.FLAG_ONE_SHOT); - - if (upload.isInstant()) { - DbHandler db = null; - try { - db = new DbHandler(this.getBaseContext()); - String message = uploadResult.getLogMessage() + " errorCode: " + uploadResult.getCode(); - Log_OC.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode()); - if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { - message = getString(R.string.failed_upload_quota_exceeded_text); - } - if (db.updateFileState(upload.getOriginalStoragePath(), DbHandler.UPLOAD_STATUS_UPLOAD_FAILED, - message) == 0) { - db.putFileForLater(upload.getOriginalStoragePath(), upload.getAccount().name, message); - } - } finally { - if (db != null) { - db.close(); + // we add only for instant-uploads the InstantUploadActivity and the + // db entry + Intent detailUploadIntent = null; + if (upload.isInstant() && InstantUploadActivity.IS_ENABLED) { + detailUploadIntent = new Intent(this, InstantUploadActivity.class); + detailUploadIntent.putExtra(FileUploader.KEY_ACCOUNT, upload.getAccount()); + } else { + detailUploadIntent = new Intent(this, FailedUploadActivity.class); + detailUploadIntent.putExtra(FailedUploadActivity.MESSAGE, content); + } + errorBuilder + .setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), detailUploadIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT + )) + .setContentText(content); + + if (upload.isInstant()) { + DbHandler db = null; + try { + db = new DbHandler(this.getBaseContext()); + String message = uploadResult.getLogMessage() + " errorCode: " + uploadResult.getCode(); + Log_OC.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode()); + if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { + message = getString(R.string.failed_upload_quota_exceeded_text); + if (db.updateFileState(upload.getOriginalStoragePath(), DbHandler.UPLOAD_STATUS_UPLOAD_FAILED, + message) == 0) { + db.putFileForLater(upload.getOriginalStoragePath(), upload.getAccount().name, message); + } + } + } finally { + if (db != null) { + db.close(); + } } } } - finalNotification.setLatestEventInfo(getApplicationContext(), - getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent); - - mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification); + + errorBuilder.setContentText(content); + mNotificationManager.notify(R.string.uploader_upload_failed_ticker, errorBuilder.build()); } } @@ -839,7 +861,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe * @param uploadResult Result of the upload operation */ private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) { - Intent end = new Intent(UPLOAD_FINISH_MESSAGE); + Intent end = new Intent(getUploadFinishMessage()); end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote // path, after // possible diff --git a/src/com/owncloud/android/location/LocationServiceLauncherReciever.java b/src/com/owncloud/android/location/LocationServiceLauncherReciever.java deleted file mode 100644 index 6bbb9ee4..00000000 --- a/src/com/owncloud/android/location/LocationServiceLauncherReciever.java +++ /dev/null @@ -1,89 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ -package com.owncloud.android.location; - -import com.owncloud.android.Log_OC; - -import android.app.ActivityManager; -import android.app.ActivityManager.RunningServiceInfo; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import android.util.Log; - -public class LocationServiceLauncherReciever extends BroadcastReceiver { - - private final String TAG = getClass().getSimpleName(); - - @Override - public void onReceive(Context context, Intent intent) { - Intent deviceTrackingIntent = new Intent(); - deviceTrackingIntent - .setAction("com.owncloud.android.location.LocationUpdateService"); - SharedPreferences preferences = PreferenceManager - .getDefaultSharedPreferences(context); - boolean trackDevice = preferences.getBoolean("enable_devicetracking", - true); - - // Used in Preferences activity so that tracking is disabled or - // reenabled - if (intent.hasExtra("TRACKING_SETTING")) { - trackDevice = intent.getBooleanExtra("TRACKING_SETTING", true); - } - - startOrStopDeviceTracking(context, trackDevice); - } - - /** - * Used internally. Starts or stops the device tracking service - * - * @param trackDevice true to start the service, false to stop it - */ - private void startOrStopDeviceTracking(Context context, boolean trackDevice) { - Intent deviceTrackingIntent = new Intent(); - deviceTrackingIntent - .setAction("com.owncloud.android.location.LocationUpdateService"); - if (!isDeviceTrackingServiceRunning(context) && trackDevice) { - Log_OC.d(TAG, "Starting device tracker service"); - context.startService(deviceTrackingIntent); - } else if (isDeviceTrackingServiceRunning(context) && !trackDevice) { - Log_OC.d(TAG, "Stopping device tracker service"); - context.stopService(deviceTrackingIntent); - } - } - - /** - * Checks to see whether or not the LocationUpdateService is running - * - * @return true, if it is. Otherwise false - */ - private boolean isDeviceTrackingServiceRunning(Context context) { - ActivityManager manager = (ActivityManager) context - .getSystemService(Context.ACTIVITY_SERVICE); - for (RunningServiceInfo service : manager - .getRunningServices(Integer.MAX_VALUE)) { - if (getClass().getName().equals(service.service.getClassName())) { - return true; - } - } - return false; - } - -} diff --git a/src/com/owncloud/android/location/LocationUpdateService.java b/src/com/owncloud/android/location/LocationUpdateService.java deleted file mode 100644 index f2eab984..00000000 --- a/src/com/owncloud/android/location/LocationUpdateService.java +++ /dev/null @@ -1,112 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ -package com.owncloud.android.location; - -import android.app.IntentService; -import android.content.Intent; -import android.content.SharedPreferences; -import android.location.Criteria; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.location.LocationProvider; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.util.Log; -import android.widget.Toast; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; - -public class LocationUpdateService extends IntentService implements - LocationListener { - - public static final String TAG = "LocationUpdateService"; - - private LocationManager mLocationManager; - private LocationProvider mLocationProvider; - private SharedPreferences mPreferences; - - public LocationUpdateService() { - super(TAG); - } - - @Override - protected void onHandleIntent(Intent intent) { - mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE); - // Determine, how we can track the device - Criteria criteria = new Criteria(); - criteria.setAccuracy(Criteria.ACCURACY_FINE); - criteria.setPowerRequirement(Criteria.POWER_LOW); - mLocationProvider = mLocationManager.getProvider(mLocationManager - .getBestProvider(criteria, true)); - - // Notify user if there is no way to track the device - if (mLocationProvider == null) { - String message = String.format(getString(R.string.location_no_provider), getString(R.string.app_name)); - Toast.makeText(this, - message, - Toast.LENGTH_LONG).show(); - stopSelf(); - return; - } - - // Get preferences for device tracking - mPreferences = PreferenceManager.getDefaultSharedPreferences(this); - boolean trackDevice = mPreferences.getBoolean("enable_devicetracking", - true); - int updateIntervall = Integer.parseInt(mPreferences.getString( - "devicetracking_update_intervall", "30")) * 60 * 1000; - int distanceBetweenLocationChecks = 50; - - // If we do shall track the device -> Stop - if (!trackDevice) { - Log_OC.d(TAG, "Devicetracking is disabled"); - stopSelf(); - return; - } - - mLocationManager.requestLocationUpdates(mLocationProvider.getName(), - updateIntervall, distanceBetweenLocationChecks, this); - } - - @Override - public void onLocationChanged(Location location) { - Log_OC.d(TAG, "Location changed: " + location); - - } - - @Override - public void onProviderDisabled(String arg0) { - // TODO Auto-generated method stub - - } - - @Override - public void onProviderEnabled(String arg0) { - // TODO Auto-generated method stub - - } - - @Override - public void onStatusChanged(String arg0, int arg1, Bundle arg2) { - // TODO Auto-generated method stub - - } - -} diff --git a/src/com/owncloud/android/media/MediaControlView.java b/src/com/owncloud/android/media/MediaControlView.java index 8047f02e..b257bd37 100644 --- a/src/com/owncloud/android/media/MediaControlView.java +++ b/src/com/owncloud/android/media/MediaControlView.java @@ -42,6 +42,7 @@ import java.util.Locale; import com.owncloud.android.R; + /** * View containing controls for a {@link MediaPlayer}. * diff --git a/src/com/owncloud/android/media/MediaService.java b/src/com/owncloud/android/media/MediaService.java index 9bb0625c..0a746355 100644 --- a/src/com/owncloud/android/media/MediaService.java +++ b/src/com/owncloud/android/media/MediaService.java @@ -37,11 +37,12 @@ import android.widget.Toast; import java.io.IOException; -import com.owncloud.android.Log_OC; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.ui.activity.FileDetailActivity; -import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.utils.Log_OC; + /** * Service that handles media playback, both audio and video. @@ -532,9 +533,9 @@ public class MediaService extends Service implements OnCompletionListener, OnPre @SuppressWarnings("deprecation") private void updateNotification(String content) { // TODO check if updating the Intent is really necessary - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount); + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), @@ -569,9 +570,9 @@ public class MediaService extends Service implements OnCompletionListener, OnPre /// includes a pending intent in the notification showing the details view of the file - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, mFile); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount); + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), (int)System.currentTimeMillis(), diff --git a/src/com/owncloud/android/media/MediaServiceBinder.java b/src/com/owncloud/android/media/MediaServiceBinder.java index 4fab8bdf..d2a42c77 100644 --- a/src/com/owncloud/android/media/MediaServiceBinder.java +++ b/src/com/owncloud/android/media/MediaServiceBinder.java @@ -18,9 +18,9 @@ package com.owncloud.android.media; -import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.media.MediaService.State; +import com.owncloud.android.utils.Log_OC; import android.accounts.Account; import android.content.Intent; @@ -176,6 +176,12 @@ public class MediaServiceBinder extends Binder implements MediaController.MediaP return (currentState == MediaService.State.PLAYING || currentState == MediaService.State.PAUSED); } + + @Override + public int getAudioSessionId() { + return 1; // not really used + } + } diff --git a/src/com/owncloud/android/network/AdvancedSslSocketFactory.java b/src/com/owncloud/android/network/AdvancedSslSocketFactory.java deleted file mode 100644 index 3f1d8ae5..00000000 --- a/src/com/owncloud/android/network/AdvancedSslSocketFactory.java +++ /dev/null @@ -1,242 +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 . - * - */ - -package com.owncloud.android.network; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.UnknownHostException; -import java.security.cert.X509Certificate; - -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; - -import org.apache.commons.httpclient.ConnectTimeoutException; -import org.apache.commons.httpclient.params.HttpConnectionParams; -import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; -import org.apache.http.conn.ssl.X509HostnameVerifier; - -import com.owncloud.android.Log_OC; - -import android.util.Log; - -/** - * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with - * a custom SSLContext and an optional Hostname Verifier. - * - * @author David A. Velasco - */ - -public class AdvancedSslSocketFactory implements ProtocolSocketFactory { - - private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName(); - - private SSLContext mSslContext = null; - private AdvancedX509TrustManager mTrustManager = null; - private X509HostnameVerifier mHostnameVerifier = null; - - public SSLContext getSslContext() { - return mSslContext; - } - - /** - * Constructor for AdvancedSSLProtocolSocketFactory. - */ - public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) { - if (sslContext == null) - throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext"); - if (trustManager == null) - throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager"); - mSslContext = sslContext; - mTrustManager = trustManager; - mHostnameVerifier = hostnameVerifier; - } - - /** - * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int) - */ - public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException { - Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort); - verifyPeerIdentity(host, port, socket); - return socket; - } - - - /** - * Attempts to get a new socket connection to the given host within the - * given time limit. - * - * @param host the host name/IP - * @param port the port on the host - * @param clientHost the local host name/IP to bind the socket to - * @param clientPort the port on the local machine - * @param params {@link HttpConnectionParams Http connection parameters} - * - * @return Socket a new socket - * - * @throws IOException if an I/O error occurs while creating the socket - * @throws UnknownHostException if the IP address of the host cannot be - * determined - */ - public Socket createSocket(final String host, final int port, - final InetAddress localAddress, final int localPort, - final HttpConnectionParams params) throws IOException, - UnknownHostException, ConnectTimeoutException { - Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params); - if (params == null) { - throw new IllegalArgumentException("Parameters may not be null"); - } - int timeout = params.getConnectionTimeout(); - SocketFactory socketfactory = mSslContext.getSocketFactory(); - Log_OC.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout()); - Socket socket = socketfactory.createSocket(); - SocketAddress localaddr = new InetSocketAddress(localAddress, localPort); - SocketAddress remoteaddr = new InetSocketAddress(host, port); - socket.setSoTimeout(params.getSoTimeout()); - socket.bind(localaddr); - socket.connect(remoteaddr, timeout); - verifyPeerIdentity(host, port, socket); - return socket; - } - - /** - * @see ProtocolSocketFactory#createSocket(java.lang.String,int) - */ - public Socket createSocket(String host, int port) throws IOException, - UnknownHostException { - Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port); - Socket socket = mSslContext.getSocketFactory().createSocket(host, port); - verifyPeerIdentity(host, port, socket); - return socket; - } - - public boolean equals(Object obj) { - return ((obj != null) && obj.getClass().equals( - AdvancedSslSocketFactory.class)); - } - - public int hashCode() { - return AdvancedSslSocketFactory.class.hashCode(); - } - - - public X509HostnameVerifier getHostNameVerifier() { - return mHostnameVerifier; - } - - - public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) { - mHostnameVerifier = hostnameVerifier; - } - - /** - * Verifies the identity of the server. - * - * The server certificate is verified first. - * - * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any. - * @param socket - */ - private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException { - try { - CertificateCombinedException failInHandshake = null; - /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager) - try { - SSLSocket sock = (SSLSocket) socket; // a new SSLSession instance is created as a "side effect" - sock.startHandshake(); - - } catch (RuntimeException e) { - - if (e instanceof CertificateCombinedException) { - failInHandshake = (CertificateCombinedException) e; - } else { - Throwable cause = e.getCause(); - Throwable previousCause = null; - while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) { - previousCause = cause; - cause = cause.getCause(); - } - if (cause != null && cause instanceof CertificateCombinedException) { - failInHandshake = (CertificateCombinedException)cause; - } - } - if (failInHandshake == null) { - throw e; - } - failInHandshake.setHostInUrl(host); - - } - - /// 2. VERIFY HOSTNAME - SSLSession newSession = null; - boolean verifiedHostname = true; - if (mHostnameVerifier != null) { - if (failInHandshake != null) { - /// 2.1 : a new SSLSession instance was NOT created in the handshake - X509Certificate serverCert = failInHandshake.getServerCertificate(); - try { - mHostnameVerifier.verify(host, serverCert); - } catch (SSLException e) { - verifiedHostname = false; - } - - } else { - /// 2.2 : a new SSLSession instance was created in the handshake - newSession = ((SSLSocket)socket).getSession(); - if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) { - verifiedHostname = mHostnameVerifier.verify(host, newSession); - } - } - } - - /// 3. Combine the exceptions to throw, if any - if (!verifiedHostname) { - SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL"); - if (failInHandshake == null) { - failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]); - failInHandshake.setHostInUrl(host); - } - failInHandshake.setSslPeerUnverifiedException(pue); - pue.initCause(failInHandshake); - throw pue; - - } else if (failInHandshake != null) { - SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified"); - hse.initCause(failInHandshake); - throw hse; - } - - } catch (IOException io) { - try { - socket.close(); - } catch (Exception x) { - // NOTHING - irrelevant exception for the caller - } - throw io; - } - } - -} diff --git a/src/com/owncloud/android/network/AdvancedX509TrustManager.java b/src/com/owncloud/android/network/AdvancedX509TrustManager.java deleted file mode 100644 index 578da185..00000000 --- a/src/com/owncloud/android/network/AdvancedX509TrustManager.java +++ /dev/null @@ -1,148 +0,0 @@ -/* ownCloud Android client application - * 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 . - * - */ - -package com.owncloud.android.network; - -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertStoreException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; - -import com.owncloud.android.Log_OC; - -import android.util.Log; - -/** - * @author David A. Velasco - */ -public class AdvancedX509TrustManager implements X509TrustManager { - - private static final String TAG = AdvancedX509TrustManager.class.getSimpleName(); - - private X509TrustManager mStandardTrustManager = null; - private KeyStore mKnownServersKeyStore; - - /** - * Constructor for AdvancedX509TrustManager - * - * @param knownServersCertStore Local certificates store with server certificates explicitly trusted by the user. - * @throws CertStoreException When no default X509TrustManager instance was found in the system. - */ - public AdvancedX509TrustManager(KeyStore knownServersKeyStore) - throws NoSuchAlgorithmException, KeyStoreException, CertStoreException { - super(); - TrustManagerFactory factory = TrustManagerFactory - .getInstance(TrustManagerFactory.getDefaultAlgorithm()); - factory.init((KeyStore)null); - mStandardTrustManager = findX509TrustManager(factory); - - mKnownServersKeyStore = knownServersKeyStore; - } - - - /** - * Locates the first X509TrustManager provided by a given TrustManagerFactory - * @param factory TrustManagerFactory to inspect in the search for a X509TrustManager - * @return The first X509TrustManager found in factory. - * @throws CertStoreException When no X509TrustManager instance was found in factory - */ - private X509TrustManager findX509TrustManager(TrustManagerFactory factory) throws CertStoreException { - TrustManager tms[] = factory.getTrustManagers(); - for (int i = 0; i < tms.length; i++) { - if (tms[i] instanceof X509TrustManager) { - return (X509TrustManager) tms[i]; - } - } - return null; - } - - - /** - * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], - * String authType) - */ - public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException { - mStandardTrustManager.checkClientTrusted(certificates, authType); - } - - - /** - * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], - * String authType) - */ - public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException { - if (!isKnownServer(certificates[0])) { - CertificateCombinedException result = new CertificateCombinedException(certificates[0]); - try { - certificates[0].checkValidity(); - } catch (CertificateExpiredException c) { - result.setCertificateExpiredException(c); - - } catch (CertificateNotYetValidException c) { - result.setCertificateNotYetException(c); - } - - try { - mStandardTrustManager.checkServerTrusted(certificates, authType); - } catch (CertificateException c) { - Throwable cause = c.getCause(); - Throwable previousCause = null; - while (cause != null && cause != previousCause && !(cause instanceof CertPathValidatorException)) { // getCause() is not funny - previousCause = cause; - cause = cause.getCause(); - } - if (cause != null && cause instanceof CertPathValidatorException) { - result.setCertPathValidatorException((CertPathValidatorException)cause); - } else { - result.setOtherCertificateException(c); - } - } - - if (result.isException()) - throw result; - - } - } - - - /** - * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() - */ - public X509Certificate[] getAcceptedIssuers() { - return mStandardTrustManager.getAcceptedIssuers(); - } - - - public boolean isKnownServer(X509Certificate cert) { - try { - return (mKnownServersKeyStore.getCertificateAlias(cert) != null); - } catch (KeyStoreException e) { - Log_OC.d(TAG, "Fail while checking certificate in the known-servers store"); - return false; - } - } - -} \ No newline at end of file diff --git a/src/com/owncloud/android/network/CertificateCombinedException.java b/src/com/owncloud/android/network/CertificateCombinedException.java deleted file mode 100644 index e96d9dc6..00000000 --- a/src/com/owncloud/android/network/CertificateCombinedException.java +++ /dev/null @@ -1,130 +0,0 @@ -/* ownCloud Android client application - * 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 . - * - */ - -package com.owncloud.android.network; - -import java.security.cert.CertPathValidatorException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; - -import javax.net.ssl.SSLPeerUnverifiedException; - -/** - * Exception joining all the problems that {@link AdvancedX509TrustManager} can find in - * a certificate chain for a server. - * - * This was initially created as an extension of CertificateException, but some - * implementations of the SSL socket layer in existing devices are REPLACING the CertificateException - * instances thrown by {@link javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], String)} - * with SSLPeerUnverifiedException FORGETTING THE CAUSING EXCEPTION instead of wrapping it. - * - * Due to this, extending RuntimeException is necessary to get that the CertificateCombinedException - * instance reaches {@link AdvancedSslSocketFactory#verifyPeerIdentity}. - * - * BE CAREFUL. As a RuntimeException extensions, Java compilers do not require to handle it - * in client methods. Be sure to use it only when you know exactly where it will go. - * - * @author David A. Velasco - */ -public class CertificateCombinedException extends RuntimeException { - - /** Generated - to refresh every time the class changes */ - private static final long serialVersionUID = -8875782030758554999L; - - private X509Certificate mServerCert = null; - private String mHostInUrl; - - private CertificateExpiredException mCertificateExpiredException = null; - private CertificateNotYetValidException mCertificateNotYetValidException = null; - private CertPathValidatorException mCertPathValidatorException = null; - private CertificateException mOtherCertificateException = null; - private SSLPeerUnverifiedException mSslPeerUnverifiedException = null; - - public CertificateCombinedException(X509Certificate x509Certificate) { - mServerCert = x509Certificate; - } - - public X509Certificate getServerCertificate() { - return mServerCert; - } - - public String getHostInUrl() { - return mHostInUrl; - } - - public void setHostInUrl(String host) { - mHostInUrl = host; - } - - public CertificateExpiredException getCertificateExpiredException() { - return mCertificateExpiredException; - } - - public void setCertificateExpiredException(CertificateExpiredException c) { - mCertificateExpiredException = c; - } - - public CertificateNotYetValidException getCertificateNotYetValidException() { - return mCertificateNotYetValidException; - } - - public void setCertificateNotYetException(CertificateNotYetValidException c) { - mCertificateNotYetValidException = c; - } - - public CertPathValidatorException getCertPathValidatorException() { - return mCertPathValidatorException; - } - - public void setCertPathValidatorException(CertPathValidatorException c) { - mCertPathValidatorException = c; - } - - public CertificateException getOtherCertificateException() { - return mOtherCertificateException; - } - - public void setOtherCertificateException(CertificateException c) { - mOtherCertificateException = c; - } - - public SSLPeerUnverifiedException getSslPeerUnverifiedException() { - return mSslPeerUnverifiedException ; - } - - public void setSslPeerUnverifiedException(SSLPeerUnverifiedException s) { - mSslPeerUnverifiedException = s; - } - - public boolean isException() { - return (mCertificateExpiredException != null || - mCertificateNotYetValidException != null || - mCertPathValidatorException != null || - mOtherCertificateException != null || - mSslPeerUnverifiedException != null); - } - - public boolean isRecoverable() { - return (mCertificateExpiredException != null || - mCertificateNotYetValidException != null || - mCertPathValidatorException != null || - mSslPeerUnverifiedException != null); - } - -} diff --git a/src/com/owncloud/android/network/OwnCloudClientUtils.java b/src/com/owncloud/android/network/OwnCloudClientUtils.java deleted file mode 100644 index 02ff3f03..00000000 --- a/src/com/owncloud/android/network/OwnCloudClientUtils.java +++ /dev/null @@ -1,247 +0,0 @@ -/* ownCloud Android client application - * 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 . - * - */ -package com.owncloud.android.network; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; - -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; -import org.apache.commons.httpclient.protocol.Protocol; -import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; -import org.apache.http.conn.ssl.X509HostnameVerifier; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; - -import eu.alefzero.webdav.WebdavClient; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.Context; -import android.net.Uri; -import android.util.Log; - -public class OwnCloudClientUtils { - - final private static String TAG = "OwnCloudClientFactory"; - - /** Default timeout for waiting data from the server */ - public static final int DEFAULT_DATA_TIMEOUT = 60000; - - /** Default timeout for establishing a connection */ - public static final int DEFAULT_CONNECTION_TIMEOUT = 60000; - - /** Connection manager for all the WebdavClients */ - private static MultiThreadedHttpConnectionManager mConnManager = null; - - private static Protocol mDefaultHttpsProtocol = null; - - private static AdvancedSslSocketFactory mAdvancedSslSocketFactory = null; - - private static X509HostnameVerifier mHostnameVerifier = null; - - - /** - * Creates a WebdavClient setup for an ownCloud account - * - * @param account The ownCloud account - * @param context The application context - * @return A WebdavClient object ready to be used - */ - public static WebdavClient createOwnCloudClient (Account account, Context context) { - Log_OC.d(TAG, "Creating WebdavClient associated to " + account.name); - - Uri uri = Uri.parse(AccountUtils.constructFullURLForAccount(context, account)); - WebdavClient client = createOwnCloudClient(uri, context); - - String username = account.name.substring(0, account.name.lastIndexOf('@')); - String password = AccountManager.get(context).getPassword(account); - //String password = am.blockingGetAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE, true); - - client.setCredentials(username, password); - - return client; - } - - - /** - * Creates a WebdavClient to try a new account before saving it - * - * @param uri URL to the ownCloud server - * @param username User name - * @param password User password - * @param context Android context where the WebdavClient is being created. - * @return A WebdavClient object ready to be used - */ - public static WebdavClient createOwnCloudClient(Uri uri, String username, String password, Context context) { - Log_OC.d(TAG, "Creating WebdavClient for " + username + "@" + uri); - - WebdavClient client = createOwnCloudClient(uri, context); - - client.setCredentials(username, password); - - return client; - } - - - /** - * Creates a WebdavClient to access a URL and sets the desired parameters for ownCloud client connections. - * - * @param uri URL to the ownCloud server - * @param context Android context where the WebdavClient is being created. - * @return A WebdavClient object ready to be used - */ - public static WebdavClient createOwnCloudClient(Uri uri, Context context) { - Log_OC.d(TAG, "Creating WebdavClient for " + uri); - - //allowSelfsignedCertificates(true); - try { - registerAdvancedSslContext(true, context); - } catch (GeneralSecurityException e) { - Log_OC.e(TAG, "Advanced SSL Context could not be loaded. Default SSL management in the system will be used for HTTPS connections", e); - - } catch (IOException e) { - Log_OC.e(TAG, "The local server truststore could not be read. Default SSL management in the system will be used for HTTPS connections", e); - } - - WebdavClient client = new WebdavClient(getMultiThreadedConnManager()); - - client.setDefaultTimeouts(DEFAULT_DATA_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT); - client.setBaseUri(uri); - - return client; - } - - - /** - * Registers or unregisters the proper components for advanced SSL handling. - * @throws IOException - */ - private static void registerAdvancedSslContext(boolean register, Context context) throws GeneralSecurityException, IOException { - Protocol pr = null; - try { - pr = Protocol.getProtocol("https"); - if (pr != null && mDefaultHttpsProtocol == null) { - mDefaultHttpsProtocol = pr; - } - } catch (IllegalStateException e) { - // nothing to do here; really - } - boolean isRegistered = (pr != null && pr.getSocketFactory() instanceof AdvancedSslSocketFactory); - if (register && !isRegistered) { - Protocol.registerProtocol("https", new Protocol("https", getAdvancedSslSocketFactory(context), 443)); - - } else if (!register && isRegistered) { - if (mDefaultHttpsProtocol != null) { - Protocol.registerProtocol("https", mDefaultHttpsProtocol); - } - } - } - - public static AdvancedSslSocketFactory getAdvancedSslSocketFactory(Context context) throws GeneralSecurityException, IOException { - if (mAdvancedSslSocketFactory == null) { - KeyStore trustStore = getKnownServersStore(context); - AdvancedX509TrustManager trustMgr = new AdvancedX509TrustManager(trustStore); - TrustManager[] tms = new TrustManager[] { trustMgr }; - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, tms, null); - - mHostnameVerifier = new BrowserCompatHostnameVerifier(); - mAdvancedSslSocketFactory = new AdvancedSslSocketFactory(sslContext, trustMgr, mHostnameVerifier); - } - return mAdvancedSslSocketFactory; - } - - - private static String LOCAL_TRUSTSTORE_FILENAME = "knownServers.bks"; - - private static String LOCAL_TRUSTSTORE_PASSWORD = "password"; - - private static KeyStore mKnownServersStore = null; - - /** - * Returns the local store of reliable server certificates, explicitly accepted by the user. - * - * Returns a KeyStore instance with empty content if the local store was never created. - * - * Loads the store from the storage environment if needed. - * - * @param context Android context where the operation is being performed. - * @return KeyStore instance with explicitly-accepted server certificates. - * @throws KeyStoreException When the KeyStore instance could not be created. - * @throws IOException When an existing local trust store could not be loaded. - * @throws NoSuchAlgorithmException When the existing local trust store was saved with an unsupported algorithm. - * @throws CertificateException When an exception occurred while loading the certificates from the local trust store. - */ - private static KeyStore getKnownServersStore(Context context) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - if (mKnownServersStore == null) { - //mKnownServersStore = KeyStore.getInstance("BKS"); - mKnownServersStore = KeyStore.getInstance(KeyStore.getDefaultType()); - File localTrustStoreFile = new File(context.getFilesDir(), LOCAL_TRUSTSTORE_FILENAME); - Log_OC.d(TAG, "Searching known-servers store at " + localTrustStoreFile.getAbsolutePath()); - if (localTrustStoreFile.exists()) { - InputStream in = new FileInputStream(localTrustStoreFile); - try { - mKnownServersStore.load(in, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); - } finally { - in.close(); - } - } else { - mKnownServersStore.load(null, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); // necessary to initialize an empty KeyStore instance - } - } - return mKnownServersStore; - } - - - public static void addCertToKnownServersStore(Certificate cert, Context context) throws KeyStoreException, NoSuchAlgorithmException, - CertificateException, IOException { - KeyStore knownServers = getKnownServersStore(context); - knownServers.setCertificateEntry(Integer.toString(cert.hashCode()), cert); - FileOutputStream fos = null; - try { - fos = context.openFileOutput(LOCAL_TRUSTSTORE_FILENAME, Context.MODE_PRIVATE); - knownServers.store(fos, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); - } finally { - fos.close(); - } - } - - - static private MultiThreadedHttpConnectionManager getMultiThreadedConnManager() { - if (mConnManager == null) { - mConnManager = new MultiThreadedHttpConnectionManager(); - mConnManager.getParams().setDefaultMaxConnectionsPerHost(5); - mConnManager.getParams().setMaxTotalConnections(5); - } - return mConnManager; - } - -} diff --git a/src/com/owncloud/android/network/ProgressiveDataTransferer.java b/src/com/owncloud/android/network/ProgressiveDataTransferer.java deleted file mode 100644 index c6fa545b..00000000 --- a/src/com/owncloud/android/network/ProgressiveDataTransferer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* ownCloud Android client application - * 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 . - * - */ - -package com.owncloud.android.network; - -import java.util.Collection; - -import eu.alefzero.webdav.OnDatatransferProgressListener; - -public interface ProgressiveDataTransferer { - - public void addDatatransferProgressListener (OnDatatransferProgressListener listener); - - public void addDatatransferProgressListeners(Collection listeners); - - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener); - -} diff --git a/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java b/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java deleted file mode 100644 index b4f440fd..00000000 --- a/src/com/owncloud/android/operations/ChunkedUploadFileOperation.java +++ /dev/null @@ -1,94 +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 . - * - */ - -package com.owncloud.android.operations; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; -import java.util.Random; - -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.methods.PutMethod; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.ProgressiveDataTransferer; - -import android.accounts.Account; -import android.util.Log; - -import eu.alefzero.webdav.ChunkFromFileChannelRequestEntity; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; - -public class ChunkedUploadFileOperation extends UploadFileOperation { - - private static final long CHUNK_SIZE = 1024000; - private static final String OC_CHUNKED_HEADER = "OC-Chunked"; - private static final String TAG = ChunkedUploadFileOperation.class.getSimpleName(); - - public ChunkedUploadFileOperation( Account account, - OCFile file, - boolean isInstant, - boolean forceOverwrite, - int localBehaviour) { - - super(account, file, isInstant, forceOverwrite, localBehaviour); - } - - @Override - protected int uploadFile(WebdavClient client) throws HttpException, IOException { - int status = -1; - - FileChannel channel = null; - RandomAccessFile raf = null; - try { - File file = new File(getStoragePath()); - raf = new RandomAccessFile(file, "r"); - channel = raf.getChannel(); - mEntity = new ChunkFromFileChannelRequestEntity(channel, getMimeType(), CHUNK_SIZE, file); - ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(getDataTransferListeners()); - long offset = 0; - String uriPrefix = client.getBaseUri() + WebdavUtils.encodePath(getRemotePath()) + "-chunking-" + Math.abs((new Random()).nextInt(9000)+1000) + "-" ; - long chunkCount = (long) Math.ceil((double)file.length() / CHUNK_SIZE); - for (int chunkIndex = 0; chunkIndex < chunkCount ; chunkIndex++, offset += CHUNK_SIZE) { - mPutMethod = new PutMethod(uriPrefix + chunkCount + "-" + chunkIndex); - mPutMethod.addRequestHeader(OC_CHUNKED_HEADER, OC_CHUNKED_HEADER); - ((ChunkFromFileChannelRequestEntity)mEntity).setOffset(offset); - mPutMethod.setRequestEntity(mEntity); - status = client.executeMethod(mPutMethod); - client.exhaustResponse(mPutMethod.getResponseBodyAsStream()); - Log_OC.d(TAG, "Upload of " + getStoragePath() + " to " + getRemotePath() + ", chunk index " + chunkIndex + ", count " + chunkCount + ", HTTP result status " + status); - if (!isSuccess(status)) - break; - } - - } finally { - if (channel != null) - channel.close(); - if (raf != null) - raf.close(); - if (mPutMethod != null) - mPutMethod.releaseConnection(); // let the connection available for other methods - } - return status; - } - -} diff --git a/src/com/owncloud/android/operations/ConnectionCheckOperation.java b/src/com/owncloud/android/operations/ConnectionCheckOperation.java deleted file mode 100644 index 34b64f85..00000000 --- a/src/com/owncloud/android/operations/ConnectionCheckOperation.java +++ /dev/null @@ -1,138 +0,0 @@ -/* ownCloud Android client application - * 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 . - * - */ - -package com.owncloud.android.operations; - -import org.apache.commons.httpclient.HttpStatus; -import org.apache.commons.httpclient.methods.GetMethod; -import org.json.JSONException; -import org.json.JSONObject; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; -import com.owncloud.android.utils.OwnCloudVersion; - -import eu.alefzero.webdav.WebdavClient; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.Uri; -import android.util.Log; - -public class ConnectionCheckOperation extends RemoteOperation { - - /** Maximum time to wait for a response from the server when the connection is being tested, in MILLISECONDs. */ - public static final int TRY_CONNECTION_TIMEOUT = 5000; - - private static final String TAG = ConnectionCheckOperation.class.getSimpleName(); - - private String mUrl; - private RemoteOperationResult mLatestResult; - private Context mContext; - private OwnCloudVersion mOCVersion; - - public ConnectionCheckOperation(String url, Context context) { - mUrl = url; - mContext = context; - mOCVersion = null; - } - - public OwnCloudVersion getDiscoveredVersion() { - return mOCVersion; - } - - private boolean tryConnection(WebdavClient wc, String urlSt) { - boolean retval = false; - GetMethod get = null; - try { - get = new GetMethod(urlSt); - int status = wc.executeMethod(get, TRY_CONNECTION_TIMEOUT, TRY_CONNECTION_TIMEOUT); - String response = get.getResponseBodyAsString(); - if (status == HttpStatus.SC_OK) { - JSONObject json = new JSONObject(response); - if (!json.getBoolean("installed")) { - mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); - } else { - mOCVersion = new OwnCloudVersion(json.getString("version")); - if (!mOCVersion.isVersionValid()) { - mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.BAD_OC_VERSION); - - } else { - mLatestResult = new RemoteOperationResult(urlSt.startsWith("https://") ? - RemoteOperationResult.ResultCode.OK_SSL : - RemoteOperationResult.ResultCode.OK_NO_SSL - ); - - retval = true; - } - } - - } else { - mLatestResult = new RemoteOperationResult(false, status); - } - - } catch (JSONException e) { - mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); - - } catch (Exception e) { - mLatestResult = new RemoteOperationResult(e); - - } finally { - if (get != null) - get.releaseConnection(); - } - - if (mLatestResult.isSuccess()) { - Log_OC.i(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); - - } else if (mLatestResult.getException() != null) { - Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage(), mLatestResult.getException()); - - } else { - Log_OC.e(TAG, "Connection check at " + urlSt + ": " + mLatestResult.getLogMessage()); - } - - return retval; - } - - private boolean isOnline() { - ConnectivityManager cm = (ConnectivityManager) mContext - .getSystemService(Context.CONNECTIVITY_SERVICE); - return cm != null && cm.getActiveNetworkInfo() != null - && cm.getActiveNetworkInfo().isConnectedOrConnecting(); - } - - @Override - protected RemoteOperationResult run(WebdavClient client) { - if (!isOnline()) { - return new RemoteOperationResult(RemoteOperationResult.ResultCode.NO_NETWORK_CONNECTION); - } - if (mUrl.startsWith("http://") || mUrl.startsWith("https://")) { - tryConnection(client, mUrl + AccountUtils.STATUS_PATH); - - } else { - client.setBaseUri(Uri.parse("https://" + mUrl + AccountUtils.STATUS_PATH)); - boolean httpsSuccess = tryConnection(client, "https://" + mUrl + AccountUtils.STATUS_PATH); - if (!httpsSuccess && !mLatestResult.isSslRecoverableException()) { - Log_OC.d(TAG, "establishing secure connection failed, trying non secure connection"); - client.setBaseUri(Uri.parse("http://" + mUrl + AccountUtils.STATUS_PATH)); - tryConnection(client, "http://" + mUrl + AccountUtils.STATUS_PATH); - } - } - return mLatestResult; - } - -} diff --git a/src/com/owncloud/android/operations/CreateFolderOperation.java b/src/com/owncloud/android/operations/CreateFolderOperation.java new file mode 100644 index 00000000..19e9ddc4 --- /dev/null +++ b/src/com/owncloud/android/operations/CreateFolderOperation.java @@ -0,0 +1,107 @@ +/* ownCloud Android client application + * Copyright (C) 2012 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 . + * + */ + +package com.owncloud.android.operations; + +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.files.CreateRemoteFolderOperation; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + + +/** + * Access to remote operation performing the creation of a new folder in the ownCloud server. + * Save the new folder in Database + * + * @author David A. Velasco + * @author masensio + */ +public class CreateFolderOperation extends RemoteOperation implements OnRemoteOperationListener{ + + private static final String TAG = CreateFolderOperation.class.getSimpleName(); + + protected String mRemotePath; + protected boolean mCreateFullPath; + protected FileDataStorageManager mStorageManager; + + /** + * Constructor + * + * @param createFullPath 'True' means that all the ancestor folders should be created if don't exist yet. + * @param storageManager Reference to the local database corresponding to the account where the file is contained. + */ + public CreateFolderOperation(String remotePath, boolean createFullPath, FileDataStorageManager storageManager) { + mRemotePath = remotePath; + mCreateFullPath = createFullPath; + mStorageManager = storageManager; + + } + + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + CreateRemoteFolderOperation operation = new CreateRemoteFolderOperation(mRemotePath, mCreateFullPath); + RemoteOperationResult result = operation.execute(client); + + if (result.isSuccess()) { + saveFolderInDB(); + } else { + Log_OC.e(TAG, mRemotePath + "hasn't been created"); + } + + return result; + } + + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + if (operation instanceof CreateRemoteFolderOperation) { + onCreateRemoteFolderOperationFinish((CreateRemoteFolderOperation)operation, result); + } + } + + + private void onCreateRemoteFolderOperationFinish(CreateRemoteFolderOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + saveFolderInDB(); + } else { + Log_OC.e(TAG, mRemotePath + "hasn't been created"); + } + } + + + /** + * Save new directory in local database + */ + public void saveFolderInDB() { + OCFile newDir = new OCFile(mRemotePath); + newDir.setMimetype("DIR"); + long parentId = mStorageManager.getFileByPath(FileStorageUtils.getParentPath(mRemotePath)).getFileId(); + newDir.setParentId(parentId); + newDir.setModificationTimestamp(System.currentTimeMillis()); + mStorageManager.saveFile(newDir); + + Log_OC.d(TAG, "Create directory " + mRemotePath + " in Database"); + + } + + +} diff --git a/src/com/owncloud/android/operations/CreateShareOperation.java b/src/com/owncloud/android/operations/CreateShareOperation.java new file mode 100644 index 00000000..896ecca7 --- /dev/null +++ b/src/com/owncloud/android/operations/CreateShareOperation.java @@ -0,0 +1,139 @@ +/* ownCloud Android client application + * Copyright (C) 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 . + * + */ + +package com.owncloud.android.operations; + +/** + * Creates a new share from a given file + * + * @author masensio + * + */ + +import android.content.Intent; + +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.resources.shares.GetRemoteSharesForFileOperation; +import com.owncloud.android.lib.resources.shares.ShareType; +import com.owncloud.android.lib.resources.shares.CreateRemoteShareOperation; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.operations.common.SyncOperation; +import com.owncloud.android.utils.Log_OC; + +public class CreateShareOperation extends SyncOperation { + + private static final String TAG = CreateShareOperation.class.getSimpleName(); + + + protected FileDataStorageManager mStorageManager; + + private String mPath; + private ShareType mShareType; + private String mShareWith; + private boolean mPublicUpload; + private String mPassword; + private int mPermissions; + private Intent mSendIntent; + + /** + * Constructor + * @param path Full path of the file/folder being shared. 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 ‘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 – Default for “public” shares + * 2 - Update + * 4 - Create + * 8 - Delete + * 16- Re-share + * 31- All above – Default for “private” shares + * For user or group shares. + * To obtain combinations, add the desired values together. + * For instance, for “Re-Share”, “delete”, “read”, “update”, add 16+8+2+1 = 27. + */ + public CreateShareOperation(String path, ShareType shareType, String shareWith, boolean publicUpload, + String password, int permissions, Intent sendIntent) { + + mPath = path; + mShareType = shareType; + mShareWith = shareWith; + mPublicUpload = publicUpload; + mPassword = password; + mPermissions = permissions; + mSendIntent = sendIntent; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperation operation = null; + + // Check if the share link already exists + operation = new GetRemoteSharesForFileOperation(mPath, false, false); + RemoteOperationResult result = ((GetRemoteSharesForFileOperation)operation).execute(client); + + if (!result.isSuccess() || result.getData().size() <= 0) { + operation = new CreateRemoteShareOperation(mPath, mShareType, mShareWith, mPublicUpload, mPassword, mPermissions); + result = ((CreateRemoteShareOperation)operation).execute(client); + } + + if (result.isSuccess()) { + if (result.getData().size() > 0) { + OCShare share = (OCShare) result.getData().get(0); + updateData(share); + } + } + + return result; + } + + + public Intent getSendIntent() { + return mSendIntent; + } + + private void updateData(OCShare share) { + // Update DB with the response + share.setPath(mPath); + if (mPath.endsWith(FileUtils.PATH_SEPARATOR)) { + share.setIsFolder(true); + } else { + share.setIsFolder(false); + } + share.setPermissions(mPermissions); + + getStorageManager().saveShare(share); + + // Update OCFile with data from share: ShareByLink and publicLink + OCFile file = getStorageManager().getFileByPath(mPath); + if (file!=null) { + mSendIntent.putExtra(Intent.EXTRA_TEXT, share.getShareLink()); + file.setPublicLink(share.getShareLink()); + file.setShareByLink(true); + getStorageManager().saveFile(file); + Log_OC.d(TAG, "Public Link = " + file.getPublicLink()); + + } + } + +} diff --git a/src/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java b/src/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java new file mode 100644 index 00000000..b817e724 --- /dev/null +++ b/src/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java @@ -0,0 +1,154 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2014 ownCloud Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.operations; + +import java.util.ArrayList; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +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.resources.files.ExistenceCheckRemoteOperation; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +/** + * Operation to find out what authentication method requires + * the server to access files. + * + * Basically, tries to access to the root folder without authorization + * and analyzes the response. + * + * When successful, the instance of {@link RemoteOperationResult} passed + * through {@link OnRemoteOperationListener#onRemoteOperationFinish(RemoteOperation, + * RemoteOperationResult)} returns in {@link RemoteOperationResult#getData()} + * a value of {@link AuthenticationMethod}. + * + * @author David A. Velasco + */ +public class DetectAuthenticationMethodOperation extends RemoteOperation { + + private static final String TAG = DetectAuthenticationMethodOperation.class.getSimpleName(); + + public enum AuthenticationMethod { + UNKNOWN, + NONE, + BASIC_HTTP_AUTH, + SAML_WEB_SSO, + BEARER_TOKEN + } + + private Context mContext; + private String mWebDavUrl; + + /** + * Constructor + * + * @param context Android context of the caller. + * @param webdavUrl + */ + public DetectAuthenticationMethodOperation(Context context, String webdavUrl) { + mContext = context; + mWebDavUrl = webdavUrl; + } + + + /** + * Performs the operation. + * + * Triggers a check of existence on the root folder of the server, granting + * that the request is not authenticated. + * + * Analyzes the result of check to find out what authentication method, if + * any, is requested by the server. + */ + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result = null; + AuthenticationMethod authMethod = AuthenticationMethod.UNKNOWN; + + RemoteOperation operation = new ExistenceCheckRemoteOperation("", mContext, false); + client.setWebdavUri(Uri.parse(mWebDavUrl)); + client.setBasicCredentials("", ""); + client.setFollowRedirects(false); + + // try to access the root folder, following redirections but not SAML SSO redirections + result = operation.execute(client); + String redirectedLocation = result.getRedirectedLocation(); + while (redirectedLocation != null && redirectedLocation.length() > 0 && !result.isIdPRedirection()) { + client.setWebdavUri(Uri.parse(result.getRedirectedLocation())); + result = operation.execute(client); + redirectedLocation = result.getRedirectedLocation(); + } + + // analyze response + if (result.getCode() == ResultCode.UNAUTHORIZED) { + String authRequest = ((result.getAuthenticateHeader()).trim()).toLowerCase(); + if (authRequest.startsWith("basic")) { + authMethod = AuthenticationMethod.BASIC_HTTP_AUTH; + + } else if (authRequest.startsWith("bearer")) { + authMethod = AuthenticationMethod.BEARER_TOKEN; + } + // else - fall back to UNKNOWN + + } else if (result.isSuccess()) { + authMethod = AuthenticationMethod.NONE; + + } else if (result.isIdPRedirection()) { + authMethod = AuthenticationMethod.SAML_WEB_SSO; + } + // else - fall back to UNKNOWN + Log.d(TAG, "Authentication method found: " + authenticationMethodToString(authMethod)); + + if (!authMethod.equals(AuthenticationMethod.UNKNOWN)) { + result = new RemoteOperationResult(true, result.getHttpCode(), null); + } + ArrayList data = new ArrayList(); + data.add(authMethod); + result.setData(data); + return result; // same result instance, so that other errors can be handled by the caller transparently + } + + + private String authenticationMethodToString(AuthenticationMethod value) { + switch (value){ + case NONE: + return "NONE"; + case BASIC_HTTP_AUTH: + return "BASIC_HTTP_AUTH"; + case BEARER_TOKEN: + return "BEARER_TOKEN"; + case SAML_WEB_SSO: + return "SAML_WEB_SSO"; + default: + return "UNKNOWN"; + } + } + +} diff --git a/src/com/owncloud/android/operations/DownloadFileOperation.java b/src/com/owncloud/android/operations/DownloadFileOperation.java index cb4355f5..c672d572 100644 --- a/src/com/owncloud/android/operations/DownloadFileOperation.java +++ b/src/com/owncloud/android/operations/DownloadFileOperation.java @@ -17,38 +17,28 @@ package com.owncloud.android.operations; -import java.io.BufferedInputStream; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.commons.httpclient.Header; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.http.HttpStatus; - -import com.owncloud.android.Log_OC; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.DownloadRemoteFileOperation; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; import android.accounts.Account; -import android.util.Log; import android.webkit.MimeTypeMap; /** - * Remote operation performing the download of a file to an ownCloud server + * Remote mDownloadOperation performing the download of a file to an ownCloud server * * @author David A. Velasco + * @author masensio */ public class DownloadFileOperation extends RemoteOperation { @@ -57,8 +47,9 @@ public class DownloadFileOperation extends RemoteOperation { private Account mAccount; private OCFile mFile; private Set mDataTransferListeners = new HashSet(); - private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); private long mModificationTimestamp = 0; + + private DownloadRemoteFileOperation mDownloadOperation; public DownloadFileOperation(Account account, OCFile file) { @@ -69,6 +60,7 @@ public class DownloadFileOperation extends RemoteOperation { mAccount = account; mFile = file; + } @@ -92,6 +84,10 @@ public class DownloadFileOperation extends RemoteOperation { return FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); } + public String getTmpFolder() { + return FileStorageUtils.getTemporalPath(mAccount.name); + } + public String getRemotePath() { return mFile.getRemotePath(); } @@ -120,22 +116,9 @@ public class DownloadFileOperation extends RemoteOperation { public long getModificationTimestamp() { return (mModificationTimestamp > 0) ? mModificationTimestamp : mFile.getModificationTimestamp(); } - - - public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - } - - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - } @Override - protected RemoteOperationResult run(WebdavClient client) { + protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result = null; File newFile = null; boolean moved = true; @@ -143,93 +126,46 @@ public class DownloadFileOperation extends RemoteOperation { /// download will be performed to a temporal file, then moved to the final location File tmpFile = new File(getTmpPath()); + String tmpFolder = getTmpFolder(); + /// perform the download - try { - tmpFile.getParentFile().mkdirs(); - int status = downloadFile(client, tmpFile); - if (isSuccess(status)) { - newFile = new File(getSavePath()); - newFile.getParentFile().mkdirs(); - moved = tmpFile.renameTo(newFile); - } + mDownloadOperation = new DownloadRemoteFileOperation(mFile.getRemotePath(), tmpFolder); + Iterator listener = mDataTransferListeners.iterator(); + while (listener.hasNext()) { + mDownloadOperation.addDatatransferProgressListener(listener.next()); + } + result = mDownloadOperation.execute(client); + + if (result.isSuccess()) { + mModificationTimestamp = mDownloadOperation.getModificationTimestamp(); + newFile = new File(getSavePath()); + newFile.getParentFile().mkdirs(); + moved = tmpFile.renameTo(newFile); + if (!moved) result = new RemoteOperationResult(RemoteOperationResult.ResultCode.LOCAL_STORAGE_NOT_MOVED); - else - result = new RemoteOperationResult(isSuccess(status), status); - Log_OC.i(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage(), e); } + Log_OC.i(TAG, "Download of " + mFile.getRemotePath() + " to " + getSavePath() + ": " + result.getLogMessage()); + return result; } - - public boolean isSuccess(int status) { - return (status == HttpStatus.SC_OK); + public void cancel() { + mDownloadOperation.cancel(); } - - - protected int downloadFile(WebdavClient client, File targetFile) throws HttpException, IOException, OperationCancelledException { - int status = -1; - boolean savedFile = false; - GetMethod get = new GetMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath())); - Iterator it = null; - - FileOutputStream fos = null; - try { - status = client.executeMethod(get); - if (isSuccess(status)) { - targetFile.createNewFile(); - BufferedInputStream bis = new BufferedInputStream(get.getResponseBodyAsStream()); - fos = new FileOutputStream(targetFile); - long transferred = 0; - byte[] bytes = new byte[4096]; - int readResult = 0; - while ((readResult = bis.read(bytes)) != -1) { - synchronized(mCancellationRequested) { - if (mCancellationRequested.get()) { - get.abort(); - throw new OperationCancelledException(); - } - } - fos.write(bytes, 0, readResult); - transferred += readResult; - synchronized (mDataTransferListeners) { - it = mDataTransferListeners.iterator(); - while (it.hasNext()) { - it.next().onTransferProgress(readResult, transferred, mFile.getFileLength(), targetFile.getName()); - } - } - } - savedFile = true; - Header modificationTime = get.getResponseHeader("Last-Modified"); - if (modificationTime != null) { - Date d = WebdavUtils.parseResponseDate((String) modificationTime.getValue()); - mModificationTimestamp = (d != null) ? d.getTime() : 0; - } - - } else { - client.exhaustResponse(get.getResponseBodyAsStream()); - } - - } finally { - if (fos != null) fos.close(); - if (!savedFile && targetFile.exists()) { - targetFile.delete(); - } - get.releaseConnection(); // let the connection available for other methods + + public void addDatatransferProgressListener (OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.add(listener); } - return status; } - - public void cancel() { - mCancellationRequested.set(true); // atomic set; there is no need of synchronizing it + public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { + synchronized (mDataTransferListeners) { + mDataTransferListeners.remove(listener); + } } - - + } diff --git a/src/com/owncloud/android/operations/GetServerInfoOperation.java b/src/com/owncloud/android/operations/GetServerInfoOperation.java new file mode 100644 index 00000000..14f405ea --- /dev/null +++ b/src/com/owncloud/android/operations/GetServerInfoOperation.java @@ -0,0 +1,164 @@ +/* ownCloud Android Library is available under MIT license + * Copyright (C) 2014 ownCloud Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + */ + +package com.owncloud.android.operations; + +import java.util.ArrayList; + +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.lib.common.OwnCloudClient; +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.resources.status.GetRemoteStatusOperation; +import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import com.owncloud.android.operations.DetectAuthenticationMethodOperation.AuthenticationMethod; +import com.owncloud.android.utils.Log_OC; + +import android.content.Context; + +/** + * Get basic information from an ownCloud server given its URL. + * + * Checks the existence of a configured ownCloud server in the URL, gets its version + * and finds out what authentication method is needed to access files in it. + * + * @author David A. Velasco + * @author masensio + */ + +public class GetServerInfoOperation extends RemoteOperation { + + private static final String TAG = GetServerInfoOperation.class.getSimpleName(); + + private String mUrl; + private String mAuthTokenType; + private Context mContext; + + private ServerInfo mResultData; + + /** + * Constructor. + * + * @param url URL to an ownCloud server. + * @param authTokenType Identifies the authorization token supported by the caller; + * TODO ugly dependency, get rid of it. + * @param context Android context; needed to check network state + * TODO ugly dependency, get rid of it. + */ + public GetServerInfoOperation(String url, String authTokenType, Context context) { + mUrl = trimWebdavSuffix(url); + mAuthTokenType = authTokenType; + mContext = context; + + mResultData = new ServerInfo(); + } + + + /** + * Performs the operation + * + * @return Result of the operation. If successful, includes an instance of + * {@link ServerInfo} with the information retrieved from the server. + * Call {@link RemoteOperationResult#getData()}.get(0) to get it. + */ + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + + // first: check the status of the server (including its version) + GetRemoteStatusOperation getStatus = new GetRemoteStatusOperation(mUrl, mContext); + RemoteOperationResult result = getStatus.execute(client); + + if (result.isSuccess()) { + // second: get authentication method required by the server + mResultData.mVersion = (OwnCloudVersion)(result.getData().get(0)); + mResultData.mIsSslConn = (result.getCode() == ResultCode.OK_SSL); + mResultData.mBaseUrl = normalizeProtocolPrefix(mUrl, mResultData.mIsSslConn); + RemoteOperationResult detectAuthResult = detectAuthorizationMethod(client); + + // third: merge results + if (detectAuthResult.isSuccess()) { + mResultData.mAuthMethod = + (AuthenticationMethod)detectAuthResult.getData().get(0); + ArrayList data = new ArrayList(); + data.add(mResultData); + result.setData(data); + } else { + result = detectAuthResult; + } + } + return result; + } + + + private RemoteOperationResult detectAuthorizationMethod(OwnCloudClient client) { + Log_OC.d(TAG, "Trying empty authorization to detect authentication method"); + String webdav_path = AccountUtils.getWebdavPath(mResultData.mVersion, mAuthTokenType); + String webdav_url = mResultData.mBaseUrl + webdav_path; + DetectAuthenticationMethodOperation operation = + new DetectAuthenticationMethodOperation(mContext, webdav_url); + return operation.execute(client); + } + + + private String trimWebdavSuffix(String url) { + if (url == null) { + url = ""; + } else { + if (url.endsWith("/")) { + url = url.substring(0, url.length() - 1); + } + if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_4_0)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_4_0.length()); + } else if(url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_2_0)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_2_0.length()); + } else if (url.toLowerCase().endsWith(AccountUtils.WEBDAV_PATH_1_2)){ + url = url.substring(0, url.length() - AccountUtils.WEBDAV_PATH_1_2.length()); + } + } + return url; + } + + + private String normalizeProtocolPrefix(String url, boolean isSslConn) { + if (!url.toLowerCase().startsWith("http://") && + !url.toLowerCase().startsWith("https://")) { + if (isSslConn) { + return "https://" + url; + } else { + return "http://" + url; + } + } + return url; + } + + + public static class ServerInfo { + public OwnCloudVersion mVersion = null; + public String mBaseUrl = ""; + public AuthenticationMethod mAuthMethod = AuthenticationMethod.UNKNOWN; + public boolean mIsSslConn = false; + } + +} diff --git a/src/com/owncloud/android/operations/GetSharesForFileOperation.java b/src/com/owncloud/android/operations/GetSharesForFileOperation.java new file mode 100644 index 00000000..3ffb0a59 --- /dev/null +++ b/src/com/owncloud/android/operations/GetSharesForFileOperation.java @@ -0,0 +1,79 @@ +/* ownCloud Android client application + * 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 . + * + */ + + +package com.owncloud.android.operations; + +import java.util.ArrayList; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation; +import com.owncloud.android.operations.common.SyncOperation; +import com.owncloud.android.utils.Log_OC; + +/** + * Provide a list shares for a specific file. + * + * @author masensio + * + */ +public class GetSharesForFileOperation extends SyncOperation { + + private static final String TAG = GetSharesForFileOperation.class.getSimpleName(); + + private String mPath; + private boolean mReshares; + private boolean mSubfiles; + + /** + * Constructor + * + * @param path Path to file or folder + * @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; + mReshares = reshares; + mSubfiles = subfiles; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + GetRemoteSharesForFileOperation operation = new GetRemoteSharesForFileOperation(mPath, mReshares, mSubfiles); + RemoteOperationResult result = operation.execute(client); + + if (result.isSuccess()) { + + // Update DB with the response + Log_OC.d(TAG, "File = " + mPath + " Share list size " + result.getData().size()); + ArrayList shares = new ArrayList(); + for(Object obj: result.getData()) { + shares.add((OCShare) obj); + } + + getStorageManager().saveSharesDB(shares); + } + + return result; + } + +} diff --git a/src/com/owncloud/android/operations/GetSharesOperation.java b/src/com/owncloud/android/operations/GetSharesOperation.java new file mode 100644 index 00000000..c9cce406 --- /dev/null +++ b/src/com/owncloud/android/operations/GetSharesOperation.java @@ -0,0 +1,61 @@ +/* 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 . + * + */ + +package com.owncloud.android.operations; + +import java.util.ArrayList; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.resources.shares.GetRemoteSharesOperation; +import com.owncloud.android.operations.common.SyncOperation; +import com.owncloud.android.utils.Log_OC; + +/** + * Access to remote operation to get the share files/folders + * Save the data in Database + * + * @author masensio + * @author David A. Velasco + */ + +public class GetSharesOperation extends SyncOperation { + + private static final String TAG = GetSharesOperation.class.getSimpleName(); + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + GetRemoteSharesOperation operation = new GetRemoteSharesOperation(); + RemoteOperationResult result = operation.execute(client); + + if (result.isSuccess()) { + + // Update DB with the response + Log_OC.d(TAG, "Share list size = " + result.getData().size()); + ArrayList shares = new ArrayList(); + for(Object obj: result.getData()) { + shares.add((OCShare) obj); + } + + getStorageManager().saveSharesDB(shares); + } + + return result; + } + +} diff --git a/src/com/owncloud/android/operations/OAuth2GetAccessToken.java b/src/com/owncloud/android/operations/OAuth2GetAccessToken.java new file mode 100644 index 00000000..5f6a085b --- /dev/null +++ b/src/com/owncloud/android/operations/OAuth2GetAccessToken.java @@ -0,0 +1,177 @@ +package com.owncloud.android.operations; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.NameValuePair; +import org.json.JSONException; +import org.json.JSONObject; + +import com.owncloud.android.authentication.OAuth2Constants; +import com.owncloud.android.lib.common.OwnCloudClient; +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.utils.Log_OC; + + +public class OAuth2GetAccessToken extends RemoteOperation { + + private static final String TAG = OAuth2GetAccessToken.class.getSimpleName(); + + private String mClientId; + private String mRedirectUri; + private String mGrantType; + + private String mOAuth2AuthorizationResponse; + private Map mOAuth2ParsedAuthorizationResponse; + private Map mResultTokenMap; + + + public OAuth2GetAccessToken(String clientId, String redirectUri, String grantType, String oAuth2AuthorizationResponse) { + mClientId = clientId; + mRedirectUri = redirectUri; + mGrantType = grantType; + mOAuth2AuthorizationResponse = oAuth2AuthorizationResponse; + mOAuth2ParsedAuthorizationResponse = new HashMap(); + mResultTokenMap = null; + } + + /* + public Map getResultTokenMap() { + return mResultTokenMap; + } + */ + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result = null; + PostMethod postMethod = null; + + try { + parseAuthorizationResponse(); + if (mOAuth2ParsedAuthorizationResponse.keySet().contains(OAuth2Constants.KEY_ERROR)) { + if (OAuth2Constants.VALUE_ERROR_ACCESS_DENIED.equals(mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_ERROR))) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR_ACCESS_DENIED); + } else { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + } + } + + if (result == null) { + NameValuePair[] nameValuePairs = new NameValuePair[4]; + nameValuePairs[0] = new NameValuePair(OAuth2Constants.KEY_GRANT_TYPE, mGrantType); + nameValuePairs[1] = new NameValuePair(OAuth2Constants.KEY_CODE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_CODE)); + nameValuePairs[2] = new NameValuePair(OAuth2Constants.KEY_REDIRECT_URI, mRedirectUri); + nameValuePairs[3] = new NameValuePair(OAuth2Constants.KEY_CLIENT_ID, mClientId); + //nameValuePairs[4] = new NameValuePair(OAuth2Constants.KEY_SCOPE, mOAuth2ParsedAuthorizationResponse.get(OAuth2Constants.KEY_SCOPE)); + + postMethod = new PostMethod(client.getWebdavUri().toString()); + postMethod.setRequestBody(nameValuePairs); + int status = client.executeMethod(postMethod); + + String response = postMethod.getResponseBodyAsString(); + if (response != null && response.length() > 0) { + JSONObject tokenJson = new JSONObject(response); + parseAccessTokenResult(tokenJson); + if (mResultTokenMap.get(OAuth2Constants.KEY_ERROR) != null || mResultTokenMap.get(OAuth2Constants.KEY_ACCESS_TOKEN) == null) { + result = new RemoteOperationResult(ResultCode.OAUTH2_ERROR); + + } else { + result = new RemoteOperationResult(true, status, postMethod.getResponseHeaders()); + ArrayList data = new ArrayList(); + data.add(mResultTokenMap); + result.setData(data); + } + + } else { + client.exhaustResponse(postMethod.getResponseBodyAsStream()); + result = new RemoteOperationResult(false, status, postMethod.getResponseHeaders()); + } + } + + } catch (Exception e) { + result = new RemoteOperationResult(e); + + } finally { + if (postMethod != null) + postMethod.releaseConnection(); // let the connection available for other methods + + if (result.isSuccess()) { + Log_OC.i(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getWebdavUri() + ": " + result.getLogMessage()); + + } else if (result.getException() != null) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getWebdavUri() + ": " + result.getLogMessage(), result.getException()); + + } else if (result.getCode() == ResultCode.OAUTH2_ERROR) { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getWebdavUri() + ": " + ((mResultTokenMap != null) ? mResultTokenMap.get(OAuth2Constants.KEY_ERROR) : "NULL")); + + } else { + Log_OC.e(TAG, "OAuth2 TOKEN REQUEST with auth code " + mOAuth2ParsedAuthorizationResponse.get("code") + " to " + client.getWebdavUri() + ": " + result.getLogMessage()); + } + } + + return result; + } + + + private void parseAuthorizationResponse() { + String[] pairs = mOAuth2AuthorizationResponse.split("&"); + int i = 0; + String key = ""; + String value = ""; + StringBuilder sb = new StringBuilder(); + while (pairs.length > i) { + int j = 0; + String[] part = pairs[i].split("="); + while (part.length > j) { + String p = part[j]; + if (j == 0) { + key = p; + sb.append(key + " = "); + } else if (j == 1) { + value = p; + mOAuth2ParsedAuthorizationResponse.put(key, value); + sb.append(value + "\n"); + } + + Log_OC.v(TAG, "[" + i + "," + j + "] = " + p); + j++; + } + i++; + } + } + + + private void parseAccessTokenResult (JSONObject tokenJson) throws JSONException { + mResultTokenMap = new HashMap(); + + if (tokenJson.has(OAuth2Constants.KEY_ACCESS_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_ACCESS_TOKEN, tokenJson.getString(OAuth2Constants.KEY_ACCESS_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_TOKEN_TYPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_TOKEN_TYPE, tokenJson.getString(OAuth2Constants.KEY_TOKEN_TYPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_EXPIRES_IN)) { + mResultTokenMap.put(OAuth2Constants.KEY_EXPIRES_IN, tokenJson.getString(OAuth2Constants.KEY_EXPIRES_IN)); + } + if (tokenJson.has(OAuth2Constants.KEY_REFRESH_TOKEN)) { + mResultTokenMap.put(OAuth2Constants.KEY_REFRESH_TOKEN, tokenJson.getString(OAuth2Constants.KEY_REFRESH_TOKEN)); + } + if (tokenJson.has(OAuth2Constants.KEY_SCOPE)) { + mResultTokenMap.put(OAuth2Constants.KEY_SCOPE, tokenJson.getString(OAuth2Constants.KEY_SCOPE)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR, tokenJson.getString(OAuth2Constants.KEY_ERROR)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_DESCRIPTION)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_DESCRIPTION, tokenJson.getString(OAuth2Constants.KEY_ERROR_DESCRIPTION)); + } + if (tokenJson.has(OAuth2Constants.KEY_ERROR_URI)) { + mResultTokenMap.put(OAuth2Constants.KEY_ERROR_URI, tokenJson.getString(OAuth2Constants.KEY_ERROR_URI)); + } + } + +} diff --git a/src/com/owncloud/android/operations/OnRemoteOperationListener.java b/src/com/owncloud/android/operations/OnRemoteOperationListener.java deleted file mode 100644 index e6a58e73..00000000 --- a/src/com/owncloud/android/operations/OnRemoteOperationListener.java +++ /dev/null @@ -1,25 +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 . - * - */ - -package com.owncloud.android.operations; - -public interface OnRemoteOperationListener { - - void onRemoteOperationFinish(RemoteOperation caller, RemoteOperationResult result); - -} diff --git a/src/com/owncloud/android/operations/OperationCancelledException.java b/src/com/owncloud/android/operations/OperationCancelledException.java deleted file mode 100644 index 0b7878ce..00000000 --- a/src/com/owncloud/android/operations/OperationCancelledException.java +++ /dev/null @@ -1,28 +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 . - * - */ - -package com.owncloud.android.operations; - -public class OperationCancelledException extends Exception { - - /** - * Generated serial version - to avoid Java warning - */ - private static final long serialVersionUID = -6350981497740424983L; - -} diff --git a/src/com/owncloud/android/operations/RemoteOperation.java b/src/com/owncloud/android/operations/RemoteOperation.java deleted file mode 100644 index 6d990448..00000000 --- a/src/com/owncloud/android/operations/RemoteOperation.java +++ /dev/null @@ -1,134 +0,0 @@ -/* ownCloud Android client application - * 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 . - * - */ -package com.owncloud.android.operations; - -import android.os.Handler; - -import eu.alefzero.webdav.WebdavClient; - -/** - * Operation which execution involves one or several interactions with an ownCloud server. - * - * Provides methods to execute the operation both synchronously or asynchronously. - * - * @author David A. Velasco - */ -public abstract class RemoteOperation implements Runnable { - - /** Object to interact with the ownCloud server */ - private WebdavClient mClient = null; - - /** Callback object to notify about the execution of the remote operation */ - private OnRemoteOperationListener mListener = null; - - /** Handler to the thread where mListener methods will be called */ - private Handler mListenerHandler = null; - - - /** - * Abstract method to implement the operation in derived classes. - */ - protected abstract RemoteOperationResult run(WebdavClient client); - - - /** - * Synchronously executes the remote operation - * - * @param client Client object to reach an ownCloud server during the execution of the operation. - * @return Result of the operation. - */ - public final RemoteOperationResult execute(WebdavClient client) { - if (client == null) - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); - mClient = client; - return run(client); - } - - - /** - * Asynchronously executes the remote operation - * - * @param client Client object to reach an ownCloud server during the execution of the operation. - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) { - if (client == null) { - throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient"); - } - mClient = client; - - if (listener == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result"); - } - mListener = listener; - - if (listenerHandler == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); - } - mListenerHandler = listenerHandler; - - Thread runnerThread = new Thread(this); - runnerThread.start(); - return runnerThread; - } - - /** - * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)} - * - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public final RemoteOperationResult retry() { - return execute(mClient); - } - - /** - * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)} - * - * @param listener Listener to be notified about the execution of the operation. - * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. - * @return Thread were the remote operation is executed. - */ - public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) { - return execute(mClient, listener, listenerHandler); - } - - - /** - * Asynchronous execution of the operation - * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}, - * and result posting. - */ - @Override - public final void run() { - final RemoteOperationResult result = execute(mClient); - - if (mListenerHandler != null && mListener != null) { - mListenerHandler.post(new Runnable() { - @Override - public void run() { - mListener.onRemoteOperationFinish(RemoteOperation.this, result); - } - }); - } - } - - -} diff --git a/src/com/owncloud/android/operations/RemoteOperationResult.java b/src/com/owncloud/android/operations/RemoteOperationResult.java deleted file mode 100644 index db1771ba..00000000 --- a/src/com/owncloud/android/operations/RemoteOperationResult.java +++ /dev/null @@ -1,245 +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 . - * - */ - -package com.owncloud.android.operations; - -import java.io.IOException; -import java.io.Serializable; -import java.net.MalformedURLException; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; - -import javax.net.ssl.SSLException; - -import org.apache.commons.httpclient.ConnectTimeoutException; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.DavException; - -import android.util.Log; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.network.CertificateCombinedException; - -/** - * The result of a remote operation required to an ownCloud server. - * - * Provides a common classification of remote operation results for all the - * application. - * - * @author David A. Velasco - */ -public class RemoteOperationResult implements Serializable { - - /** Generated - should be refreshed every time the class changes!! */ - private static final long serialVersionUID = -7805531062432602444L; - private static final String TAG = "RemoteOperationResult"; - - public enum ResultCode { - OK, OK_SSL, OK_NO_SSL, UNHANDLED_HTTP_CODE, UNAUTHORIZED, FILE_NOT_FOUND, INSTANCE_NOT_CONFIGURED, UNKNOWN_ERROR, WRONG_CONNECTION, TIMEOUT, INCORRECT_ADDRESS, HOST_NOT_AVAILABLE, NO_NETWORK_CONNECTION, SSL_ERROR, SSL_RECOVERABLE_PEER_UNVERIFIED, BAD_OC_VERSION, CANCELLED, INVALID_LOCAL_FILE_NAME, INVALID_OVERWRITE, CONFLICT, SYNC_CONFLICT, LOCAL_STORAGE_FULL, LOCAL_STORAGE_NOT_MOVED, LOCAL_STORAGE_NOT_COPIED, QUOTA_EXCEEDED - } - - private boolean mSuccess = false; - private int mHttpCode = -1; - private Exception mException = null; - private ResultCode mCode = ResultCode.UNKNOWN_ERROR; - - public RemoteOperationResult(ResultCode code) { - mCode = code; - mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL); - } - - public RemoteOperationResult(boolean success, int httpCode) { - mSuccess = success; - mHttpCode = httpCode; - - if (success) { - mCode = ResultCode.OK; - - } else if (httpCode > 0) { - switch (httpCode) { - case HttpStatus.SC_UNAUTHORIZED: - mCode = ResultCode.UNAUTHORIZED; - break; - case HttpStatus.SC_NOT_FOUND: - mCode = ResultCode.FILE_NOT_FOUND; - break; - case HttpStatus.SC_INTERNAL_SERVER_ERROR: - mCode = ResultCode.INSTANCE_NOT_CONFIGURED; - break; - case HttpStatus.SC_CONFLICT: - mCode = ResultCode.CONFLICT; - break; - case HttpStatus.SC_INSUFFICIENT_STORAGE: - mCode = ResultCode.QUOTA_EXCEEDED; - break; - default: - mCode = ResultCode.UNHANDLED_HTTP_CODE; - Log_OC.d(TAG, "RemoteOperationResult has prcessed UNHANDLED_HTTP_CODE: " + httpCode); - } - } - } - - public RemoteOperationResult(Exception e) { - mException = e; - - if (e instanceof OperationCancelledException) { - mCode = ResultCode.CANCELLED; - - } else if (e instanceof SocketException) { - mCode = ResultCode.WRONG_CONNECTION; - - } else if (e instanceof SocketTimeoutException) { - mCode = ResultCode.TIMEOUT; - - } else if (e instanceof ConnectTimeoutException) { - mCode = ResultCode.TIMEOUT; - - } else if (e instanceof MalformedURLException) { - mCode = ResultCode.INCORRECT_ADDRESS; - - } else if (e instanceof UnknownHostException) { - mCode = ResultCode.HOST_NOT_AVAILABLE; - - } else if (e instanceof SSLException || e instanceof RuntimeException) { - CertificateCombinedException se = getCertificateCombinedException(e); - if (se != null) { - mException = se; - if (se.isRecoverable()) { - mCode = ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED; - } - } else if (e instanceof RuntimeException) { - mCode = ResultCode.HOST_NOT_AVAILABLE; - - } else { - mCode = ResultCode.SSL_ERROR; - } - - } else { - mCode = ResultCode.UNKNOWN_ERROR; - } - - } - - public boolean isSuccess() { - return mSuccess; - } - - public boolean isCancelled() { - return mCode == ResultCode.CANCELLED; - } - - public int getHttpCode() { - return mHttpCode; - } - - public ResultCode getCode() { - return mCode; - } - - public Exception getException() { - return mException; - } - - public boolean isSslRecoverableException() { - return mCode == ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED; - } - - private CertificateCombinedException getCertificateCombinedException(Exception e) { - CertificateCombinedException result = null; - if (e instanceof CertificateCombinedException) { - return (CertificateCombinedException) e; - } - Throwable cause = mException.getCause(); - Throwable previousCause = null; - while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) { - previousCause = cause; - cause = cause.getCause(); - } - if (cause != null && cause instanceof CertificateCombinedException) { - result = (CertificateCombinedException) cause; - } - return result; - } - - public String getLogMessage() { - - if (mException != null) { - if (mException instanceof OperationCancelledException) { - return "Operation cancelled by the caller"; - - } else if (mException instanceof SocketException) { - return "Socket exception"; - - } else if (mException instanceof SocketTimeoutException) { - return "Socket timeout exception"; - - } else if (mException instanceof ConnectTimeoutException) { - return "Connect timeout exception"; - - } else if (mException instanceof MalformedURLException) { - return "Malformed URL exception"; - - } else if (mException instanceof UnknownHostException) { - return "Unknown host exception"; - - } else if (mException instanceof CertificateCombinedException) { - if (((CertificateCombinedException) mException).isRecoverable()) - return "SSL recoverable exception"; - else - return "SSL exception"; - - } else if (mException instanceof SSLException) { - return "SSL exception"; - - } else if (mException instanceof DavException) { - return "Unexpected WebDAV exception"; - - } else if (mException instanceof HttpException) { - return "HTTP violation"; - - } else if (mException instanceof IOException) { - return "Unrecovered transport exception"; - - } else { - return "Unexpected exception"; - } - } - - if (mCode == ResultCode.INSTANCE_NOT_CONFIGURED) { - return "The ownCloud server is not configured!"; - - } else if (mCode == ResultCode.NO_NETWORK_CONNECTION) { - return "No network connection"; - - } else if (mCode == ResultCode.BAD_OC_VERSION) { - return "No valid ownCloud version was found at the server"; - - } else if (mCode == ResultCode.LOCAL_STORAGE_FULL) { - return "Local storage full"; - - } else if (mCode == ResultCode.LOCAL_STORAGE_NOT_MOVED) { - return "Error while moving file to final directory"; - } - - return "Operation finished with HTTP status code " + mHttpCode + " (" + (isSuccess() ? "success" : "fail") + ")"; - - } - -} diff --git a/src/com/owncloud/android/operations/RemoveFileOperation.java b/src/com/owncloud/android/operations/RemoveFileOperation.java index dc699484..ff673cad 100644 --- a/src/com/owncloud/android/operations/RemoveFileOperation.java +++ b/src/com/owncloud/android/operations/RemoveFileOperation.java @@ -17,17 +17,14 @@ package com.owncloud.android.operations; -import org.apache.commons.httpclient.HttpStatus; -import org.apache.jackrabbit.webdav.client.methods.DeleteMethod; - -import android.util.Log; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.lib.common.OwnCloudClient; +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.resources.files.RemoveRemoteFileOperation; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; /** * Remote operation performing the removal of a remote file or folder in the ownCloud server. @@ -36,14 +33,11 @@ import eu.alefzero.webdav.WebdavUtils; */ public class RemoveFileOperation extends RemoteOperation { - private static final String TAG = RemoveFileOperation.class.getSimpleName(); - - private static final int REMOVE_READ_TIMEOUT = 10000; - private static final int REMOVE_CONNECTION_TIMEOUT = 5000; + // private static final String TAG = RemoveFileOperation.class.getSimpleName(); OCFile mFileToRemove; boolean mDeleteLocalCopy; - DataStorageManager mDataStorageManager; + FileDataStorageManager mDataStorageManager; /** @@ -53,7 +47,7 @@ public class RemoveFileOperation extends RemoteOperation { * @param deleteLocalCopy When 'true', and a local copy of the file exists, it is also removed. * @param storageManager Reference to the local database corresponding to the account where the file is contained. */ - public RemoveFileOperation(OCFile fileToRemove, boolean deleteLocalCopy, DataStorageManager storageManager) { + public RemoveFileOperation(OCFile fileToRemove, boolean deleteLocalCopy, FileDataStorageManager storageManager) { mFileToRemove = fileToRemove; mDeleteLocalCopy = deleteLocalCopy; mDataStorageManager = storageManager; @@ -69,38 +63,22 @@ public class RemoveFileOperation extends RemoteOperation { return mFileToRemove; } - /** * Performs the remove operation * * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(WebdavClient client) { + protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result = null; - DeleteMethod delete = null; - try { - delete = new DeleteMethod(client.getBaseUri() + WebdavUtils.encodePath(mFileToRemove.getRemotePath())); - int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT); - if (delete.succeeded() || status == HttpStatus.SC_NOT_FOUND) { - if (mFileToRemove.isDirectory()) { - mDataStorageManager.removeDirectory(mFileToRemove, true, mDeleteLocalCopy); - } else { - mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy); - } - } - delete.getResponseBodyAsString(); // exhaust the response, although not interesting - result = new RemoteOperationResult((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), status); - Log_OC.i(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Remove " + mFileToRemove.getRemotePath() + ": " + result.getLogMessage(), e); - - } finally { - if (delete != null) - delete.releaseConnection(); + + RemoveRemoteFileOperation operation = new RemoveRemoteFileOperation(mFileToRemove.getRemotePath()); + result = operation.execute(client); + + if (result.isSuccess() || result.getCode() == ResultCode.FILE_NOT_FOUND) { + mDataStorageManager.removeFile(mFileToRemove, true, mDeleteLocalCopy); } + return result; } diff --git a/src/com/owncloud/android/operations/RenameFileOperation.java b/src/com/owncloud/android/operations/RenameFileOperation.java index deff1311..a007af2b 100644 --- a/src/com/owncloud/android/operations/RenameFileOperation.java +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@ -20,20 +20,18 @@ package com.owncloud.android.operations; import java.io.File; import java.io.IOException; -import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; -//import org.apache.jackrabbit.webdav.client.methods.MoveMethod; - -import android.accounts.Account; -import android.util.Log; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.OwnCloudClient; +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.resources.files.RenameRemoteFileOperation; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + +import android.accounts.Account; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; /** * Remote operation performing the rename of a remote file (or folder?) in the ownCloud server. @@ -42,17 +40,14 @@ import eu.alefzero.webdav.WebdavUtils; */ public class RenameFileOperation extends RemoteOperation { - private static final String TAG = RemoveFileOperation.class.getSimpleName(); - - private static final int RENAME_READ_TIMEOUT = 10000; - private static final int RENAME_CONNECTION_TIMEOUT = 5000; + private static final String TAG = RenameFileOperation.class.getSimpleName(); private OCFile mFile; private Account mAccount; private String mNewName; private String mNewRemotePath; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; /** @@ -63,7 +58,7 @@ public class RenameFileOperation extends RemoteOperation { * @param newName New name to set as the name of file. * @param storageManager Reference to the local database corresponding to the account where the file is contained. */ - public RenameFileOperation(OCFile file, Account account, String newName, DataStorageManager storageManager) { + public RenameFileOperation(OCFile file, Account account, String newName, FileDataStorageManager storageManager) { mFile = file; mAccount = account; mNewName = newName; @@ -82,78 +77,50 @@ public class RenameFileOperation extends RemoteOperation { * @param client Client object to communicate with the remote ownCloud server. */ @Override - protected RemoteOperationResult run(WebdavClient client) { + protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result = null; - LocalMoveMethod move = null; - mNewRemotePath = null; + // check if the new name is valid in the local file system try { - if (mNewName.equals(mFile.getFileName())) { - return new RemoteOperationResult(ResultCode.OK); + if (!isValidNewName()) { + return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME); } - String parent = (new File(mFile.getRemotePath())).getParent(); parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR; mNewRemotePath = parent + mNewName; - if (mFile.isDirectory()) { + if (mFile.isFolder()) { mNewRemotePath += OCFile.PATH_SEPARATOR; } - - // check if the new name is valid in the local file system - if (!isValidNewName()) { - return new RemoteOperationResult(ResultCode.INVALID_LOCAL_FILE_NAME); - } - - // check if a file with the new name already exists - if (client.existsFile(mNewRemotePath) || // remote check could fail by network failure. by indeterminate behavior of HEAD for folders ... - mStorageManager.getFileByPath(mNewRemotePath) != null) { // ... so local check is convenient + + // ckeck local overwrite + if (mStorageManager.getFileByPath(mNewRemotePath) != null) { return new RemoteOperationResult(ResultCode.INVALID_OVERWRITE); } - move = new LocalMoveMethod( client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath()), - client.getBaseUri() + WebdavUtils.encodePath(mNewRemotePath)); - int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT); - if (move.succeeded()) { + + RenameRemoteFileOperation operation = new RenameRemoteFileOperation(mFile.getFileName(), mFile.getRemotePath(), + mNewName, mFile.isFolder()); + result = operation.execute(client); - if (mFile.isDirectory()) { + if (result.isSuccess()) { + if (mFile.isFolder()) { saveLocalDirectory(); - + } else { saveLocalFile(); - } - - /* - *} else if (mFile.isDirectory() && (status == 207 || status >= 500)) { - * // TODO - * // if server fails in the rename of a folder, some children files could have been moved to a folder with the new name while some others - * // stayed in the old folder; - * // - * // easiest and heaviest solution is synchronizing the parent folder (or the full account); - * // - * // a better solution is synchronizing the folders with the old and new names; - *} - */ - } - move.getResponseBodyAsString(); // exhaust response, although not interesting - result = new RemoteOperationResult(move.succeeded(), status); - Log_OC.i(TAG, "Rename " + mFile.getRemotePath() + " to " + mNewRemotePath + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + result.getLogMessage(), e); - - } finally { - if (move != null) - move.releaseConnection(); + } catch (IOException e) { + Log_OC.e(TAG, "Rename " + mFile.getRemotePath() + " to " + ((mNewRemotePath==null) ? mNewName : mNewRemotePath) + ": " + + ((result!= null) ? result.getLogMessage() : ""), e); } + return result; } private void saveLocalDirectory() { - mStorageManager.moveDirectory(mFile, mNewRemotePath); + mStorageManager.moveFolder(mFile, mNewRemotePath); String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); File localDir = new File(localPath); if (localDir.exists()) { @@ -192,17 +159,23 @@ public class RenameFileOperation extends RemoteOperation { * * TODO move this method, and maybe FileDownload.get***Path(), to a class with utilities specific for the interactions with the file system * - * @return 'True' if a temporal file named with the name to set could be created in the file system where - * local files are stored. + * @return 'True' if a temporal file named with the name to set could be created in the file system where + * local files are stored. + * @throws IOException When the temporal folder can not be created. */ - private boolean isValidNewName() { + private boolean isValidNewName() throws IOException { // check tricky names if (mNewName == null || mNewName.length() <= 0 || mNewName.contains(File.separator) || mNewName.contains("%")) { return false; } // create a test file - String tmpFolder = FileStorageUtils.getTemporalPath(""); - File testFile = new File(tmpFolder + mNewName); + String tmpFolderName = FileStorageUtils.getTemporalPath(""); + File testFile = new File(tmpFolderName + mNewName); + File tmpFolder = testFile.getParentFile(); + tmpFolder.mkdirs(); + if (!tmpFolder.isDirectory()) { + throw new IOException("Unexpected error: temporal directory could not be created"); + } try { testFile.createNewFile(); // return value is ignored; it could be 'false' because the file already existed, that doesn't invalidate the name } catch (IOException e) { @@ -217,26 +190,4 @@ public class RenameFileOperation extends RemoteOperation { return result; } - - // move operation - private class LocalMoveMethod extends DavMethodBase { - - public LocalMoveMethod(String uri, String dest) { - super(uri); - addRequestHeader(new org.apache.commons.httpclient.Header("Destination", dest)); - } - - @Override - public String getName() { - return "MOVE"; - } - - @Override - protected boolean isSuccess(int status) { - return status == 201 || status == 204; - } - - } - - } diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 07093103..7189167d 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -18,38 +18,39 @@ package com.owncloud.android.operations; -import org.apache.http.HttpStatus; -import org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; +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.FileUploader; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.resources.files.RemoteFile; +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.resources.files.ReadRemoteFileOperation; +import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; import android.accounts.Account; import android.content.Context; import android.content.Intent; -import android.util.Log; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileDownloader; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; +/** + * Remote operation performing the read of remote file in the ownCloud server. + * + * @author David A. Velasco + * @author masensio + */ public class SynchronizeFileOperation extends RemoteOperation { private String TAG = SynchronizeFileOperation.class.getSimpleName(); - private static final int SYNC_READ_TIMEOUT = 10000; - private static final int SYNC_CONNECTION_TIMEOUT = 5000; private OCFile mLocalFile; private OCFile mServerFile; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private Account mAccount; private boolean mSyncFileContents; - private boolean mLocalChangeAlreadyKnown; private Context mContext; private boolean mTransferWasRequested = false; @@ -57,10 +58,9 @@ public class SynchronizeFileOperation extends RemoteOperation { public SynchronizeFileOperation( OCFile localFile, OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation - DataStorageManager storageManager, + FileDataStorageManager storageManager, Account account, boolean syncFileContents, - boolean localChangeAlreadyKnown, Context context) { mLocalFile = localFile; @@ -68,106 +68,89 @@ public class SynchronizeFileOperation extends RemoteOperation { mStorageManager = storageManager; mAccount = account; mSyncFileContents = syncFileContents; - mLocalChangeAlreadyKnown = localChangeAlreadyKnown; mContext = context; } @Override - protected RemoteOperationResult run(WebdavClient client) { - - PropFindMethod propfind = null; + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result = null; mTransferWasRequested = false; - try { - if (!mLocalFile.isDown()) { - /// easy decision - requestForDownload(mLocalFile); - result = new RemoteOperationResult(ResultCode.OK); - - } else { - /// local copy in the device -> need to think a bit more before do anything - - if (mServerFile == null) { - /// take the duty of check the server for the current state of the file there - propfind = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mLocalFile.getRemotePath())); - int status = client.executeMethod(propfind, SYNC_READ_TIMEOUT, SYNC_CONNECTION_TIMEOUT); - boolean isMultiStatus = status == HttpStatus.SC_MULTI_STATUS; - if (isMultiStatus) { - MultiStatus resp = propfind.getResponseBodyAsMultiStatus(); - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], - client.getBaseUri().getPath()); - mServerFile = fillOCFile(we); - mServerFile.setLastSyncDateForProperties(System.currentTimeMillis()); - - } else { - client.exhaustResponse(propfind.getResponseBodyAsStream()); - result = new RemoteOperationResult(false, status); - } + if (!mLocalFile.isDown()) { + /// easy decision + requestForDownload(mLocalFile); + result = new RemoteOperationResult(ResultCode.OK); + + } else { + /// local copy in the device -> need to think a bit more before do anything + + if (mServerFile == null) { + String remotePath = mLocalFile.getRemotePath(); + ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath); + result = operation.execute(client); + if (result.isSuccess()){ + mServerFile = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0)); + mServerFile.setLastSyncDateForProperties(System.currentTimeMillis()); } - - if (result == null) { // true if the server was not checked. nothing was wrong with the remote request - - /// check changes in server and local file - boolean serverChanged = false; + } + + if (mServerFile != null) { + + /// check changes in server and local file + 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? + } else { */ + // server without etags + 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 + + /// decide action to perform depending upon changes + //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) { + if (localChanged && serverChanged) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); + + } else if (localChanged) { + if (mSyncFileContents) { + requestForUpload(mLocalFile); + // the local update of file properties will be done by the FileUploader service when the upload finishes } else { - // server without etags - serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData()); + // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid; + // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect + // that an upload is necessary (for instance, in FileObserverService). } - boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()); - // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads - - /// decide action to perform depending upon changes - if (localChanged && serverChanged) { - result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); - - } else if (localChanged) { - if (mSyncFileContents) { - requestForUpload(mLocalFile); - // the local update of file properties will be done by the FileUploader service when the upload finishes - } else { - // NOTHING TO DO HERE: updating the properties of the file in the server without uploading the contents would be stupid; - // So, an instance of SynchronizeFileOperation created with syncFileContents == false is completely useless when we suspect - // that an upload is necessary (for instance, in FileObserverService). - } - result = new RemoteOperationResult(ResultCode.OK); - - } else if (serverChanged) { - if (mSyncFileContents) { - requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync! - // the update of local data will be done later by the FileUploader service when the upload finishes - } else { - // TODO CHECK: is this really useful in some point in the code? - mServerFile.setKeepInSync(mLocalFile.keepInSync()); - mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData()); - mServerFile.setStoragePath(mLocalFile.getStoragePath()); - mServerFile.setParentId(mLocalFile.getParentId()); - mStorageManager.saveFile(mServerFile); - - } - result = new RemoteOperationResult(ResultCode.OK); - + result = new RemoteOperationResult(ResultCode.OK); + + } else if (serverChanged) { + if (mSyncFileContents) { + requestForDownload(mLocalFile); // local, not server; we won't to keep the value of keepInSync! + // the update of local data will be done later by the FileUploader service when the upload finishes } else { - // nothing changed, nothing to do - result = new RemoteOperationResult(ResultCode.OK); + // TODO CHECK: is this really useful in some point in the code? + mServerFile.setKeepInSync(mLocalFile.keepInSync()); + mServerFile.setLastSyncDateForData(mLocalFile.getLastSyncDateForData()); + mServerFile.setStoragePath(mLocalFile.getStoragePath()); + mServerFile.setParentId(mLocalFile.getParentId()); + mStorageManager.saveFile(mServerFile); + } - - } - - } - - Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", file " + (mLocalFile != null ? mLocalFile.getRemotePath() : "NULL") + ": " + result.getLogMessage(), result.getException()); - - } finally { - if (propfind != null) - propfind.releaseConnection(); + result = new RemoteOperationResult(ResultCode.OK); + + } else { + // nothing changed, nothing to do + result = new RemoteOperationResult(ResultCode.OK); + } + + } + } + + Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage()); + return result; } @@ -204,22 +187,6 @@ public class SynchronizeFileOperation extends RemoteOperation { } - /** - * Creates and populates a new {@link OCFile} object with the data read from the server. - * - * @param we WebDAV entry read from the server for a WebDAV resource (remote file or folder). - * @return New OCFile instance representing the remote resource described by we. - */ - private OCFile fillOCFile(WebdavEntry we) { - OCFile file = new OCFile(we.decodedPath()); - file.setCreationTimestamp(we.createTimestamp()); - file.setFileLength(we.contentLength()); - file.setMimetype(we.contentType()); - file.setModificationTimestamp(we.modifiedTimestamp()); - return file; - } - - public boolean transferWasRequested() { return mTransferWasRequested; } diff --git a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java index 843da114..68e78578 100644 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -23,80 +23,121 @@ 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 org.apache.jackrabbit.webdav.MultiStatus; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; - import android.accounts.Account; import android.content.Context; -import android.util.Log; +import android.content.Intent; +//import android.support.v4.content.LocalBroadcastManager; -import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; + +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.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; +import com.owncloud.android.utils.Log_OC; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavEntry; -import eu.alefzero.webdav.WebdavUtils; /** - * Remote operation performing the synchronization a the contents of a remote folder with the local database + * 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 + * @author David A. Velasco */ public class SynchronizeFolderOperation extends RemoteOperation { private static final String TAG = SynchronizeFolderOperation.class.getSimpleName(); - /** Remote folder to synchronize */ - private String mRemotePath; + 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"; - /** Timestamp for the synchronization in progress */ + /** Time stamp for the synchronization process in progress */ private long mCurrentSyncTime; - /** Id of the folder to synchronize in the local database */ - private long mParentId; + /** Remote folder to synchronize */ + private OCFile mLocalFolder; /** Access to the local database */ - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; /** Account where the file to synchronize belongs */ private Account mAccount; - /** Android context; necessary to send requests to the download service; maybe something to refactor */ + /** Android context; necessary to send requests to the download service */ private Context mContext; - /** Files and folders contained in the synchronized folder */ + /** Files and folders contained in the synchronized folder after a successful operation */ private List 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 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 the folder should be refreshed also */ + private boolean mIsShareSupported; + /** 'True' means that the remote folder changed from last synchronization and should be fetched */ + private boolean mRemoteFolderChanged; + - public SynchronizeFolderOperation( String remotePath, + /** + * Creates a new instance of {@link SynchronizeFolderOperation}. + * + * @param remoteFolderPath Remote folder to synchronize. + * @param currentSyncTime Time stamp for the synchronization process in progress. + * @param localFolderId Identifier in the local database of the folder to synchronize. + * @param updateFolderProperties 'True' means that the properties of the folder should be updated also, not just its content. + * @param syncFullAccount 'True' means that this operation is part of a full account synchronization. + * @param dataStorageManager Interface with the local database. + * @param account ownCloud account where the folder is located. + * @param context Application context. + */ + public SynchronizeFolderOperation( OCFile folder, long currentSyncTime, - long parentId, - DataStorageManager dataStorageManager, + boolean syncFullAccount, + boolean isShareSupported, + FileDataStorageManager dataStorageManager, Account account, Context context ) { - mRemotePath = remotePath; + mLocalFolder = folder; mCurrentSyncTime = currentSyncTime; - mParentId = parentId; + mSyncFullAccount = syncFullAccount; + mIsShareSupported = isShareSupported; mStorageManager = dataStorageManager; mAccount = account; mContext = context; mForgottenLocalFiles = new HashMap(); + mRemoteFolderChanged = false; } @@ -115,173 +156,266 @@ public class SynchronizeFolderOperation extends RemoteOperation { /** * 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. + * @return List of files and folders contained in the synchronized folder. */ public List getChildren() { return mChildren; } - + /** + * Performs the synchronization. + * + * {@inheritDoc} + */ @Override - protected RemoteOperationResult run(WebdavClient client) { + protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result = null; mFailsInFavouritesFound = 0; mConflictsFound = 0; mForgottenLocalFiles.clear(); - // code before in FileSyncAdapter.fetchData - PropFindMethod query = null; - try { - Log_OC.d(TAG, "Synchronizing " + mAccount.name + ", fetching files in " + mRemotePath); - - // remote request - query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath)); - int status = client.executeMethod(query); + 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 = false; + 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)); - // check and process response - /// TODO take into account all the possible status per child-resource - if (isMultiStatus(status)) { - MultiStatus resp = query.getResponseBodyAsMultiStatus(); + // 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")); - // synchronize properties of the parent folder, if necessary - if (mParentId == DataStorageManager.ROOT_PARENT_ID) { - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath()); - OCFile parent = fillOCFile(we); - mStorageManager.saveFile(parent); - mParentId = parent.getFileId(); - } - - // read contents in folder - List updatedFiles = new Vector(resp.getResponses().length - 1); - List filesToSyncContents = new Vector(); - for (int i = 1; i < resp.getResponses().length; ++i) { - /// new OCFile instance with the data from the server - WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath()); - OCFile file = fillOCFile(we); - - /// set data about local state, keeping unchanged former data if existing - file.setLastSyncDateForProperties(mCurrentSyncTime); - OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath()); - if (oldFile != null) { - file.setKeepInSync(oldFile.keepInSync()); - file.setLastSyncDateForData(oldFile.getLastSyncDateForData()); - file.setModificationTimestampAtLastSyncForData(oldFile.getModificationTimestampAtLastSyncForData()); // must be kept unchanged when the file contents are not updated - checkAndFixForeignStoragePath(oldFile); - file.setStoragePath(oldFile.getStoragePath()); - } + } 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; + } - /// scan default location if local copy of file is not linked in OCFile instance - if (file.getStoragePath() == null && !file.isDirectory()) { - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists()) { - file.setStoragePath(f.getAbsolutePath()); - file.setLastSyncDateForData(f.lastModified()); - } - } - - /// prepare content synchronization for kept-in-sync files - if (file.keepInSync()) { - SynchronizeFileOperation operation = new SynchronizeFileOperation( oldFile, - file, - mStorageManager, - mAccount, - true, - false, - mContext - ); - filesToSyncContents.add(operation); - } - - updatedFiles.add(file); - } - - // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) - mStorageManager.saveFiles(updatedFiles); - - // request for the synchronization of files AFTER saving last properties - SynchronizeFileOperation op = null; - RemoteOperationResult contentsResult = null; - for (int i=0; i < filesToSyncContents.size(); i++) { - op = filesToSyncContents.get(i); - contentsResult = op.execute(client); // returns without waiting for upload or download finishes - if (!contentsResult.isSuccess()) { - if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { - mConflictsFound++; - } else { - mFailsInFavouritesFound++; - if (contentsResult.getException() != null) { - Log_OC.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); - } else { - Log_OC.d(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); - } - } - } // won't let these fails break the synchronization process - } - - // removal of obsolete files - mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId)); - OCFile file; - String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); - for (int i=0; i < mChildren.size(); ) { - file = mChildren.get(i); - if (file.getLastSyncDateForProperties() != mCurrentSyncTime) { - Log_OC.d(TAG, "removing file: " + file); - mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath))); - mChildren.remove(i); - } else { - i++; - } - } - - } else { - client.exhaustResponse(query.getResponseBodyAsStream()); + 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 different result, 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 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 updatedFiles = new Vector(folderAndFiles.size() - 1); + List filesToSyncContents = new Vector(); + + // get current data about local contents of the folder to synchronize + List localFiles = mStorageManager.getFolderContent(mLocalFolder); + Map localFilesMap = new HashMap(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 0 || mFailsInFavouritesFound > 0) { - result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job - - } else { - result = new RemoteOperationResult(true, status); + /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in the server side) + 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()); + remoteFile.setEtag(localFile.getEtag()); // eTag will not be updated unless contents are synchronized (Synchronize[File|Folder]Operation with remoteFile as parameter) + if (remoteFile.isFolder()) { + remoteFile.setFileLength(localFile.getFileLength()); // TODO move operations about size of folders to FileContentProvider } + remoteFile.setPublicLink(localFile.getPublicLink()); + remoteFile.setShareByLink(localFile.isShareByLink()); } else { - result = new RemoteOperationResult(false, status); + remoteFile.setEtag(""); // remote eTag will not be updated unless contents are synchronized (Synchronize[File|Folder]Operation with remoteFile as parameter) + } + + /// check and fix, if needed, local storage path + checkAndFixForeignStoragePath(remoteFile); // fixing old policy - now local files must be 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, + mStorageManager, + mAccount, + true, + mContext + ); + filesToSyncContents.add(operation); } - Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException()); + updatedFiles.add(remoteFile); + } + + // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) + mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values()); + + // request for the synchronization of file contents AFTER saving current remote properties + startContentSynchronizations(filesToSyncContents, client); + + mChildren = updatedFiles; + } - } finally { - if (query != null) - query.releaseConnection(); // let the connection available for other methods + /** + * 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 filesToSyncContents, OwnCloudClient client) { + RemoteOperationResult contentsResult = null; + for (SynchronizeFileOperation op: filesToSyncContents) { + contentsResult = op.execute(client); // returns without waiting for upload or download finishes + 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 } - - return result; } - + 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 we WebDAV entry read from the server for a WebDAV resource (remote file or folder). + * @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(WebdavEntry we) { - OCFile file = new OCFile(we.decodedPath()); - file.setCreationTimestamp(we.createTimestamp()); - file.setFileLength(we.contentLength()); - file.setMimetype(we.contentType()); - file.setModificationTimestamp(we.modifiedTimestamp()); - file.setParentId(mParentId); + 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()); return file; } @@ -292,7 +426,7 @@ public class SynchronizeFolderOperation extends RemoteOperation { * * 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) { @@ -348,6 +482,68 @@ public class SynchronizeFolderOperation extends RemoteOperation { } } } + + + 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 shares = new ArrayList(); + 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; + } } diff --git a/src/com/owncloud/android/operations/UnshareLinkOperation.java b/src/com/owncloud/android/operations/UnshareLinkOperation.java new file mode 100644 index 00000000..77ca8227 --- /dev/null +++ b/src/com/owncloud/android/operations/UnshareLinkOperation.java @@ -0,0 +1,96 @@ +/* ownCloud Android client application + * Copyright (C) 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 . + * + */ + +package com.owncloud.android.operations; + +import android.content.Context; + +import com.owncloud.android.datamodel.OCFile; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.shares.OCShare; +import com.owncloud.android.lib.resources.shares.RemoveRemoteShareOperation; +import com.owncloud.android.lib.resources.shares.ShareType; + +import com.owncloud.android.operations.common.SyncOperation; +import com.owncloud.android.utils.Log_OC; + +/** + * Unshare file/folder + * Save the data in Database + * + * @author masensio + */ +public class UnshareLinkOperation extends SyncOperation { + + private static final String TAG = UnshareLinkOperation.class.getSimpleName(); + + private String mRemotePath; + private Context mContext; + + + public UnshareLinkOperation(String remotePath, Context context) { + mRemotePath = remotePath; + mContext = context; + } + + @Override + protected RemoteOperationResult run(OwnCloudClient client) { + RemoteOperationResult result = null; + + // Get Share for a file + OCShare share = getStorageManager().getFirstShareByPathAndType(mRemotePath, ShareType.PUBLIC_LINK); + + if (share != null) { + RemoveRemoteShareOperation operation = new RemoveRemoteShareOperation((int) share.getIdRemoteShared()); + result = operation.execute(client); + + if (result.isSuccess() || result.getCode() == ResultCode.SHARE_NOT_FOUND) { + Log_OC.d(TAG, "Share id = " + share.getIdRemoteShared() + " deleted"); + + OCFile file = getStorageManager().getFileByPath(mRemotePath); + file.setShareByLink(false); + file.setPublicLink(""); + getStorageManager().saveFile(file); + getStorageManager().removeShare(share); + + if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { + if (existsFile(client, file.getRemotePath())) { + result = new RemoteOperationResult(ResultCode.OK); + } else { + getStorageManager().removeFile(file, true, true); + } + } + } + + } else { + result = new RemoteOperationResult(ResultCode.SHARE_NOT_FOUND); + } + + return result; + } + + private boolean existsFile(OwnCloudClient client, String remotePath){ + ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, mContext, false); + RemoteOperationResult result = existsOperation.execute(client); + return result.isSuccess(); + } + +} diff --git a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java index f9e4acb3..ddcd52c6 100644 --- a/src/com/owncloud/android/operations/UpdateOCVersionOperation.java +++ b/src/com/owncloud/android/operations/UpdateOCVersionOperation.java @@ -22,18 +22,19 @@ import org.apache.commons.httpclient.methods.GetMethod; import org.json.JSONException; import org.json.JSONObject; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; +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.resources.status.OwnCloudVersion; +import com.owncloud.android.utils.Log_OC; + import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; -import android.util.Log; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.utils.OwnCloudVersion; -import eu.alefzero.webdav.WebdavClient; /** * Remote operation that checks the version of an ownCloud server and stores it locally @@ -46,18 +47,20 @@ public class UpdateOCVersionOperation extends RemoteOperation { private Account mAccount; private Context mContext; + private OwnCloudVersion mOwnCloudVersion; public UpdateOCVersionOperation(Account account, Context context) { mAccount = account; mContext = context; + mOwnCloudVersion = null; } @Override - protected RemoteOperationResult run(WebdavClient client) { + protected RemoteOperationResult run(OwnCloudClient client) { AccountManager accountMngr = AccountManager.get(mContext); - String statUrl = accountMngr.getUserData(mAccount, AccountAuthenticator.KEY_OC_BASE_URL); + String statUrl = accountMngr.getUserData(mAccount, Constants.KEY_OC_BASE_URL); statUrl += AccountUtils.STATUS_PATH; RemoteOperationResult result = null; GetMethod get = null; @@ -66,17 +69,20 @@ public class UpdateOCVersionOperation extends RemoteOperation { int status = client.executeMethod(get); if (status != HttpStatus.SC_OK) { client.exhaustResponse(get.getResponseBodyAsStream()); - result = new RemoteOperationResult(false, status); + result = new RemoteOperationResult(false, status, get.getResponseHeaders()); } else { String response = get.getResponseBodyAsString(); if (response != null) { JSONObject json = new JSONObject(response); if (json != null && json.getString("version") != null) { - OwnCloudVersion ocver = new OwnCloudVersion(json.getString("version")); - if (ocver.isVersionValid()) { - accountMngr.setUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION, ocver.toString()); - Log_OC.d(TAG, "Got new OC version " + ocver.toString()); + + String version = json.getString("version"); + mOwnCloudVersion = new OwnCloudVersion(version); + if (mOwnCloudVersion.isVersionValid()) { + accountMngr.setUserData(mAccount, Constants.KEY_OC_VERSION, mOwnCloudVersion.getVersion()); + Log_OC.d(TAG, "Got new OC version " + mOwnCloudVersion.toString()); + result = new RemoteOperationResult(ResultCode.OK); } else { @@ -89,15 +95,15 @@ public class UpdateOCVersionOperation extends RemoteOperation { result = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); } } - Log_OC.i(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage()); + Log_OC.i(TAG, "Check for update of ownCloud server version at " + client.getWebdavUri() + ": " + result.getLogMessage()); } catch (JSONException e) { result = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED); - Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage(), e); + Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getWebdavUri() + ": " + result.getLogMessage(), e); } catch (Exception e) { result = new RemoteOperationResult(e); - Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getBaseUri() + ": " + result.getLogMessage(), e); + Log_OC.e(TAG, "Check for update of ownCloud server version at " + client.getWebdavUri() + ": " + result.getLogMessage(), e); } finally { if (get != null) @@ -106,4 +112,9 @@ public class UpdateOCVersionOperation extends RemoteOperation { return result; } + + public OwnCloudVersion getOCVersion() { + return mOwnCloudVersion; + } + } diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index 2c891be9..85e84b76 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -24,29 +24,31 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.methods.RequestEntity; -import org.apache.http.HttpStatus; - -import com.owncloud.android.Log_OC; -import android.accounts.Account; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.network.ProgressiveDataTransferer; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.network.ProgressiveDataTransferer; +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.OperationCancelledException; +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.resources.files.ChunkedUploadRemoteFileOperation; +import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + +import android.accounts.Account; +import android.content.Context; -import eu.alefzero.webdav.FileRequestEntity; -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; /** * Remote operation performing the upload of a file to an ownCloud server @@ -61,6 +63,7 @@ public class UploadFileOperation extends RemoteOperation { private OCFile mFile; private OCFile mOldFile; private String mRemotePath = null; + private boolean mChunked = false; private boolean mIsInstant = false; private boolean mRemoteFolderToBeCreated = false; private boolean mForceOverwrite = false; @@ -71,15 +74,20 @@ public class UploadFileOperation extends RemoteOperation { PutMethod mPutMethod = null; private Set mDataTransferListeners = new HashSet(); private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); + private Context mContext; + + private UploadRemoteFileOperation mUploadOperation; protected RequestEntity mEntity = null; public UploadFileOperation( Account account, OCFile file, + boolean chunked, boolean isInstant, boolean forceOverwrite, - int localBehaviour) { + int localBehaviour, + Context context) { if (account == null) throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation"); if (file == null) @@ -94,11 +102,13 @@ public class UploadFileOperation extends RemoteOperation { mAccount = account; mFile = file; mRemotePath = file.getRemotePath(); + mChunked = chunked; mIsInstant = isInstant; mForceOverwrite = forceOverwrite; mLocalBehaviour = localBehaviour; mOriginalStoragePath = mFile.getStoragePath(); mOriginalFileName = mFile.getFileName(); + mContext = context; } public Account getAccount() { @@ -176,7 +186,7 @@ public class UploadFileOperation extends RemoteOperation { } @Override - protected RemoteOperationResult run(WebdavClient client) { + protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result = null; boolean localCopyPassed = false, nameCheckPassed = false; File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null; @@ -198,7 +208,7 @@ public class UploadFileOperation extends RemoteOperation { // !!! expectedFile = new File(expectedPath); - // / check location of local file; if not the expected, copy to a + // check location of local file; if not the expected, copy to a // temporal file before upload (if COPY is the expected behaviour) if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) { @@ -259,19 +269,23 @@ public class UploadFileOperation extends RemoteOperation { } localCopyPassed = true; - // / perform the upload - synchronized (mCancellationRequested) { - if (mCancellationRequested.get()) { - throw new OperationCancelledException(); - } else { - mPutMethod = new PutMethod(client.getBaseUri() + WebdavUtils.encodePath(mFile.getRemotePath())); - } + /// perform the upload + if ( mChunked && (new File(mFile.getStoragePath())).length() > ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) { + mUploadOperation = new ChunkedUploadRemoteFileOperation(mFile.getStoragePath(), mFile.getRemotePath(), + mFile.getMimetype()); + } else { + mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(), mFile.getRemotePath(), + mFile.getMimetype()); + } + Iterator listener = mDataTransferListeners.iterator(); + while (listener.hasNext()) { + mUploadOperation.addDatatransferProgressListener(listener.next()); } - int status = uploadFile(client); + result = mUploadOperation.execute(client); - // / move local temporal file or original file to its corresponding + /// move local temporal file or original file to its corresponding // location in the ownCloud local folder - if (isSuccess(status)) { + if (result.isSuccess()) { if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) { mFile.setStoragePath(null); @@ -304,8 +318,6 @@ public class UploadFileOperation extends RemoteOperation { } } - result = new RemoteOperationResult(isSuccess(status), status); - } catch (Exception e) { // TODO something cleaner with cancellations if (mCancellationRequested.get()) { @@ -357,29 +369,6 @@ public class UploadFileOperation extends RemoteOperation { mFile = newFile; } - public boolean isSuccess(int status) { - return ((status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_NO_CONTENT)); - } - - protected int uploadFile(WebdavClient client) throws HttpException, IOException, OperationCancelledException { - int status = -1; - try { - File f = new File(mFile.getStoragePath()); - mEntity = new FileRequestEntity(f, getMimeType()); - synchronized (mDataTransferListeners) { - ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(mDataTransferListeners); - } - mPutMethod.setRequestEntity(mEntity); - status = client.executeMethod(mPutMethod); - client.exhaustResponse(mPutMethod.getResponseBodyAsStream()); - - } finally { - mPutMethod.releaseConnection(); // let the connection available for - // other methods - } - return status; - } - /** * Checks if remotePath does not exist in the server and returns it, or adds * a suffix to it in order to avoid the server file is overwritten. @@ -387,8 +376,8 @@ public class UploadFileOperation extends RemoteOperation { * @param string * @return */ - private String getAvailableRemotePath(WebdavClient wc, String remotePath) throws Exception { - boolean check = wc.existsFile(remotePath); + private String getAvailableRemotePath(OwnCloudClient wc, String remotePath) throws Exception { + boolean check = existsFile(wc, remotePath); if (!check) { return remotePath; } @@ -403,10 +392,12 @@ public class UploadFileOperation extends RemoteOperation { int count = 2; do { suffix = " (" + count + ")"; - if (pos >= 0) - check = wc.existsFile(remotePath + suffix + "." + extension); - else - check = wc.existsFile(remotePath + suffix); + if (pos >= 0) { + check = existsFile(wc, remotePath + suffix + "." + extension); + } + else { + check = existsFile(wc, remotePath + suffix); + } count++; } while (check); @@ -417,12 +408,14 @@ public class UploadFileOperation extends RemoteOperation { } } + private boolean existsFile(OwnCloudClient client, String remotePath){ + ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, mContext, false); + RemoteOperationResult result = existsOperation.execute(client); + return result.isSuccess(); + } + public void cancel() { - synchronized (mCancellationRequested) { - mCancellationRequested.set(true); - if (mPutMethod != null) - mPutMethod.abort(); - } + mUploadOperation.cancel(); } } diff --git a/src/com/owncloud/android/operations/common/SyncOperation.java b/src/com/owncloud/android/operations/common/SyncOperation.java new file mode 100644 index 00000000..e16530d8 --- /dev/null +++ b/src/com/owncloud/android/operations/common/SyncOperation.java @@ -0,0 +1,129 @@ +/* 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 . + * + */ + +package com.owncloud.android.operations.common; + +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; + +import android.app.Activity; +import android.content.Context; +import android.os.Handler; + + +/** + * Operation which execution involves both interactions with an ownCloud server and + * with local data in the device. + * + * Provides methods to execute the operation both synchronously or asynchronously. + * + * @author David A. Velasco + */ +public abstract class SyncOperation extends RemoteOperation { + + //private static final String TAG = SyncOperation.class.getSimpleName(); + + private FileDataStorageManager mStorageManager; + + public FileDataStorageManager getStorageManager() { + return mStorageManager; + } + + + /** + * Synchronously executes the operation on the received ownCloud account. + * + * Do not call this method from the main thread. + * + * This method should be used whenever an ownCloud account is available, instead of {@link #execute(OwnCloudClient)}. + * + * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. + * @param context Android context for the component calling the method. + * @return Result of the operation. + */ + public RemoteOperationResult execute(FileDataStorageManager storageManager, Context context) { + if (storageManager == null) { + throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager"); + } + if (storageManager.getAccount() == null) { + throw new IllegalArgumentException("Trying to execute a sync operation with a storage manager for a NULL account"); + } + mStorageManager = storageManager; + return super.execute(mStorageManager.getAccount(), context); + } + + + /** + * Synchronously executes the remote operation + * + * Do not call this method from the main thread. + * + * @param client Client object to reach an ownCloud server during the execution of the operation. + * @return Result of the operation. + */ + public RemoteOperationResult execute(OwnCloudClient client, FileDataStorageManager storageManager) { + if (storageManager == null) + throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager"); + mStorageManager = storageManager; + return super.execute(client); + } + + + /** + * Asynchronously executes the remote operation + * + * This method should be used whenever an ownCloud account is available, instead of {@link #execute(OwnCloudClient)}. + * + * @param account ownCloud account in remote ownCloud server to reach during the execution of the operation. + * @param context Android context for the component calling the method. + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public Thread execute(FileDataStorageManager storageManager, Context context, OnRemoteOperationListener listener, Handler listenerHandler, Activity callerActivity) { + if (storageManager == null) { + throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager"); + } + if (storageManager.getAccount() == null) { + throw new IllegalArgumentException("Trying to execute a sync operation with a storage manager for a NULL account"); + } + mStorageManager = storageManager; + return super.execute(storageManager.getAccount(), context, listener, listenerHandler, callerActivity); + } + + + /** + * Asynchronously executes the remote operation + * + * @param client Client object to reach an ownCloud server during the execution of the operation. + * @param listener Listener to be notified about the execution of the operation. + * @param listenerHandler Handler associated to the thread where the methods of the listener objects must be called. + * @return Thread were the remote operation is executed. + */ + public Thread execute(OwnCloudClient client, FileDataStorageManager storageManager, OnRemoteOperationListener listener, Handler listenerHandler) { + if (storageManager == null) { + throw new IllegalArgumentException("Trying to execute a sync operation with a NULL storage manager"); + } + mStorageManager = storageManager; + return super.execute(client, listener, listenerHandler); + } + + +} diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index f9e0a480..54e67a24 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -18,17 +18,24 @@ package com.owncloud.android.providers; +import java.util.ArrayList; import java.util.HashMap; -import com.owncloud.android.Log_OC; +import com.owncloud.android.R; import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.lib.resources.shares.ShareType; +import com.owncloud.android.utils.Log_OC; + import android.content.ContentProvider; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; +import android.content.OperationApplicationException; import android.content.UriMatcher; import android.database.Cursor; import android.database.SQLException; @@ -37,84 +44,195 @@ import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; -import android.util.Log; /** * The ContentProvider for the ownCloud App. * * @author Bartek Przybylski + * @author David A. Velasco * */ public class FileContentProvider extends ContentProvider { private DataBaseHelper mDbHelper; - private static HashMap mProjectionMap; + // Projection for filelist table + private static HashMap mFileProjectionMap; static { - mProjectionMap = new HashMap(); - mProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID); - mProjectionMap.put(ProviderTableMeta.FILE_PARENT, + mFileProjectionMap = new HashMap(); + mFileProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID); + mFileProjectionMap.put(ProviderTableMeta.FILE_PARENT, ProviderTableMeta.FILE_PARENT); - mProjectionMap.put(ProviderTableMeta.FILE_PATH, + mFileProjectionMap.put(ProviderTableMeta.FILE_PATH, ProviderTableMeta.FILE_PATH); - mProjectionMap.put(ProviderTableMeta.FILE_NAME, + mFileProjectionMap.put(ProviderTableMeta.FILE_NAME, ProviderTableMeta.FILE_NAME); - mProjectionMap.put(ProviderTableMeta.FILE_CREATION, + mFileProjectionMap.put(ProviderTableMeta.FILE_CREATION, ProviderTableMeta.FILE_CREATION); - mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED, + mFileProjectionMap.put(ProviderTableMeta.FILE_MODIFIED, ProviderTableMeta.FILE_MODIFIED); - mProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, + mFileProjectionMap.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA); - mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH, + mFileProjectionMap.put(ProviderTableMeta.FILE_CONTENT_LENGTH, ProviderTableMeta.FILE_CONTENT_LENGTH); - mProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE, + mFileProjectionMap.put(ProviderTableMeta.FILE_CONTENT_TYPE, ProviderTableMeta.FILE_CONTENT_TYPE); - mProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH, + mFileProjectionMap.put(ProviderTableMeta.FILE_STORAGE_PATH, ProviderTableMeta.FILE_STORAGE_PATH); - mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, + mFileProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, ProviderTableMeta.FILE_LAST_SYNC_DATE); - mProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, + mFileProjectionMap.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA); - mProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, + mFileProjectionMap.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, ProviderTableMeta.FILE_KEEP_IN_SYNC); - mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, + mFileProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, ProviderTableMeta.FILE_ACCOUNT_OWNER); + mFileProjectionMap.put(ProviderTableMeta.FILE_ETAG, + ProviderTableMeta.FILE_ETAG); + mFileProjectionMap.put(ProviderTableMeta.FILE_SHARE_BY_LINK, + ProviderTableMeta.FILE_SHARE_BY_LINK); + mFileProjectionMap.put(ProviderTableMeta.FILE_PUBLIC_LINK, + ProviderTableMeta.FILE_PUBLIC_LINK); } private static final int SINGLE_FILE = 1; private static final int DIRECTORY = 2; private static final int ROOT_DIRECTORY = 3; - private static final UriMatcher mUriMatcher; + private static final int SHARES = 4; + + // Projection for ocshares table + private static HashMap mOCSharesProjectionMap; static { - mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "/", ROOT_DIRECTORY); - mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/", SINGLE_FILE); - mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/#", SINGLE_FILE); - mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "dir/#", DIRECTORY); + mOCSharesProjectionMap = new HashMap(); + mOCSharesProjectionMap.put(ProviderTableMeta._ID, ProviderTableMeta._ID); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_FILE_SOURCE, + ProviderTableMeta.OCSHARES_FILE_SOURCE); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_ITEM_SOURCE, + ProviderTableMeta.OCSHARES_ITEM_SOURCE); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARE_TYPE, + ProviderTableMeta.OCSHARES_SHARE_TYPE); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARE_WITH, + ProviderTableMeta.OCSHARES_SHARE_WITH); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_PATH, + ProviderTableMeta.OCSHARES_PATH); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_PERMISSIONS, + ProviderTableMeta.OCSHARES_PERMISSIONS); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARED_DATE, + ProviderTableMeta.OCSHARES_SHARED_DATE); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_EXPIRATION_DATE, + ProviderTableMeta.OCSHARES_EXPIRATION_DATE); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_TOKEN, + ProviderTableMeta.OCSHARES_TOKEN); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME, + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, + ProviderTableMeta.OCSHARES_IS_DIRECTORY); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_USER_ID, + ProviderTableMeta.OCSHARES_USER_ID); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED); + mOCSharesProjectionMap.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER); } - + + private UriMatcher mUriMatcher; + @Override public int delete(Uri uri, String where, String[] whereArgs) { + //Log_OC.d(TAG, "Deleting " + uri + " at provider " + this); + int count = 0; SQLiteDatabase db = mDbHelper.getWritableDatabase(); + db.beginTransaction(); + try { + count = delete(db, uri, where, whereArgs); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + getContext().getContentResolver().notifyChange(uri, null); + return count; + } + + private int delete(SQLiteDatabase db, Uri uri, String where, String[] whereArgs) { int count = 0; switch (mUriMatcher.match(uri)) { case SINGLE_FILE: - count = db.delete(ProviderTableMeta.DB_NAME, + /*Cursor c = query(db, uri, null, where, whereArgs, null); + String remotePath = "(unexisting)"; + if (c != null && c.moveToFirst()) { + remotePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)); + } + Log_OC.d(TAG, "Removing FILE " + remotePath); + */ + count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1) + (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), whereArgs); + /* just for log + if (c!=null) { + c.close(); + } + */ + break; + case DIRECTORY: + // deletion of folder is recursive + /* + Uri folderUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, Long.parseLong(uri.getPathSegments().get(1))); + Cursor folder = query(db, folderUri, null, null, null, null); + String folderName = "(unknown)"; + if (folder != null && folder.moveToFirst()) { + folderName = folder.getString(folder.getColumnIndex(ProviderTableMeta.FILE_PATH)); + } + */ + Cursor children = query(uri, null, null, null, null); + if (children != null && children.moveToFirst()) { + long childId; + boolean isDir; + //String remotePath; + while (!children.isAfterLast()) { + childId = children.getLong(children.getColumnIndex(ProviderTableMeta._ID)); + isDir = "DIR".equals(children.getString(children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE))); + //remotePath = children.getString(children.getColumnIndex(ProviderTableMeta.FILE_PATH)); + if (isDir) { + count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId), null, null); + } else { + count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId), null, null); + } + children.moveToNext(); + } + children.close(); + } /*else { + Log_OC.d(TAG, "No child to remove in DIRECTORY " + folderName); + } + Log_OC.d(TAG, "Removing DIRECTORY " + folderName + " (or maybe not) "); + */ + count += db.delete(ProviderTableMeta.FILE_TABLE_NAME, + ProviderTableMeta._ID + + "=" + + uri.getPathSegments().get(1) + + (!TextUtils.isEmpty(where) ? " AND (" + where + + ")" : ""), whereArgs); + /* Just for log + if (folder != null) { + folder.close(); + }*/ break; case ROOT_DIRECTORY: - count = db.delete(ProviderTableMeta.DB_NAME, where, whereArgs); + //Log_OC.d(TAG, "Removing ROOT!"); + count = db.delete(ProviderTableMeta.FILE_TABLE_NAME, where, whereArgs); + break; + case SHARES: + count = db.delete(ProviderTableMeta.OCSHARES_TABLE_NAME, where, whereArgs); break; default: + //Log_OC.e(TAG, "Unknown uri " + uri); throw new IllegalArgumentException("Unknown uri: " + uri.toString()); } - getContext().getContentResolver().notifyChange(uri, null); return count; } + @Override public String getType(Uri uri) { @@ -131,44 +249,137 @@ public class FileContentProvider extends ContentProvider { @Override public Uri insert(Uri uri, ContentValues values) { - if (mUriMatcher.match(uri) != SINGLE_FILE && - mUriMatcher.match(uri) != ROOT_DIRECTORY) { - - throw new IllegalArgumentException("Unknown uri id: " + uri); + //Log_OC.d(TAG, "Inserting " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); + Uri newUri = null; + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + db.beginTransaction(); + try { + newUri = insert(db, uri, values); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); } + getContext().getContentResolver().notifyChange(newUri, null); + return newUri; + } + + private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) { + switch (mUriMatcher.match(uri)){ + case ROOT_DIRECTORY: + case SINGLE_FILE: + String remotePath = values.getAsString(ProviderTableMeta.FILE_PATH); + String accountName = values.getAsString(ProviderTableMeta.FILE_ACCOUNT_OWNER); + String[] projection = new String[] {ProviderTableMeta._ID, ProviderTableMeta.FILE_PATH, ProviderTableMeta.FILE_ACCOUNT_OWNER }; + String where = ProviderTableMeta.FILE_PATH + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; + String[] whereArgs = new String[] {remotePath, accountName}; + Cursor doubleCheck = query(db, uri, projection, where, whereArgs, null); + if (doubleCheck == null || !doubleCheck.moveToFirst()) { // ugly patch; serious refactorization is needed to reduce work in FileDataStorageManager and bring it to FileContentProvider + long rowId = db.insert(ProviderTableMeta.FILE_TABLE_NAME, null, values); + if (rowId > 0) { + Uri insertedFileUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId); + //Log_OC.d(TAG, "Inserted " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); + return insertedFileUri; + } else { + //Log_OC.d(TAG, "Error while inserting " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); + throw new SQLException("ERROR " + uri); + } + } else { + // file is already inserted; race condition, let's avoid a duplicated entry + Uri insertedFileUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, doubleCheck.getLong(doubleCheck.getColumnIndex(ProviderTableMeta._ID))); + doubleCheck.close(); - SQLiteDatabase db = mDbHelper.getWritableDatabase(); - long rowId = db.insert(ProviderTableMeta.DB_NAME, null, values); - if (rowId > 0) { - Uri insertedFileUri = ContentUris.withAppendedId( - ProviderTableMeta.CONTENT_URI_FILE, rowId); - getContext().getContentResolver().notifyChange(insertedFileUri, - null); - return insertedFileUri; + return insertedFileUri; + } + + case SHARES: + String path = values.getAsString(ProviderTableMeta.OCSHARES_PATH); + String accountNameShare= values.getAsString(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER); + String[] projectionShare = new String[] {ProviderTableMeta._ID, ProviderTableMeta.OCSHARES_PATH, ProviderTableMeta.OCSHARES_ACCOUNT_OWNER }; + String whereShare = ProviderTableMeta.OCSHARES_PATH + "=? AND " + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?"; + String[] whereArgsShare = new String[] {path, accountNameShare}; + Uri insertedShareUri = null; + Cursor doubleCheckShare = query(db, uri, projectionShare, whereShare, whereArgsShare, null); + if (doubleCheckShare == null || !doubleCheckShare.moveToFirst()) { // ugly patch; serious refactorization is needed to reduce work in FileDataStorageManager and bring it to FileContentProvider + long rowId = db.insert(ProviderTableMeta.OCSHARES_TABLE_NAME, null, values); + if (rowId >0) { + insertedShareUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, rowId); + } else { + throw new SQLException("ERROR " + uri); + + } + } else { + // file is already inserted; race condition, let's avoid a duplicated entry + insertedShareUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_SHARE, doubleCheckShare.getLong(doubleCheckShare.getColumnIndex(ProviderTableMeta._ID))); + doubleCheckShare.close(); + } + updateFilesTableAccordingToShareInsertion(db, uri, values); + return insertedShareUri; + + + default: + throw new IllegalArgumentException("Unknown uri id: " + uri); } - throw new SQLException("ERROR " + uri); + + } + + private void updateFilesTableAccordingToShareInsertion(SQLiteDatabase db, Uri uri, ContentValues shareValues) { + ContentValues fileValues = new ContentValues(); + fileValues.put(ProviderTableMeta.FILE_SHARE_BY_LINK, + ShareType.PUBLIC_LINK.getValue() == shareValues.getAsInteger(ProviderTableMeta.OCSHARES_SHARE_TYPE)? 1 : 0); + String whereShare = ProviderTableMeta.FILE_PATH + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?"; + String[] whereArgsShare = new String[] { + shareValues.getAsString(ProviderTableMeta.OCSHARES_PATH), + shareValues.getAsString(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER) + }; + db.update(ProviderTableMeta.FILE_TABLE_NAME, fileValues, whereShare, whereArgsShare); } + @Override public boolean onCreate() { mDbHelper = new DataBaseHelper(getContext()); + + String authority = getContext().getResources().getString(R.string.authority); + mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mUriMatcher.addURI(authority, null, ROOT_DIRECTORY); + mUriMatcher.addURI(authority, "file/", SINGLE_FILE); + mUriMatcher.addURI(authority, "file/#", SINGLE_FILE); + mUriMatcher.addURI(authority, "dir/", DIRECTORY); + mUriMatcher.addURI(authority, "dir/#", DIRECTORY); + mUriMatcher.addURI(authority, "shares/", SHARES); + mUriMatcher.addURI(authority, "shares/#", SHARES); + return true; } + @Override - public Cursor query(Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + Cursor result = null; + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + db.beginTransaction(); + try { + result = query(db, uri, projection, selection, selectionArgs, sortOrder); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return result; + } + + private Cursor query(SQLiteDatabase db, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder(); - sqlQuery.setTables(ProviderTableMeta.DB_NAME); - sqlQuery.setProjectionMap(mProjectionMap); + sqlQuery.setTables(ProviderTableMeta.FILE_TABLE_NAME); + sqlQuery.setProjectionMap(mFileProjectionMap); switch (mUriMatcher.match(uri)) { case ROOT_DIRECTORY: break; case DIRECTORY: + String folderId = uri.getPathSegments().get(1); sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "=" - + uri.getPathSegments().get(1)); + + folderId); break; case SINGLE_FILE: if (uri.getPathSegments().size() > 1) { @@ -176,32 +387,141 @@ public class FileContentProvider extends ContentProvider { + uri.getPathSegments().get(1)); } break; + case SHARES: + sqlQuery.setTables(ProviderTableMeta.OCSHARES_TABLE_NAME); + sqlQuery.setProjectionMap(mOCSharesProjectionMap); + if (uri.getPathSegments().size() > 1) { + sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + + uri.getPathSegments().get(1)); + } + break; default: throw new IllegalArgumentException("Unknown uri id: " + uri); } String order; if (TextUtils.isEmpty(sortOrder)) { - order = ProviderTableMeta.DEFAULT_SORT_ORDER; + if (mUriMatcher.match(uri) == SHARES) { + order = ProviderTableMeta.OCSHARES_DEFAULT_SORT_ORDER; + } else { + + order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER; + } } else { order = sortOrder; } - SQLiteDatabase db = mDbHelper.getReadableDatabase(); - Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, - null, null, order); - + // DB case_sensitive + db.execSQL("PRAGMA case_sensitive_like = true"); + Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, null, null, order); c.setNotificationUri(getContext().getContentResolver(), uri); - return c; } @Override - public int update(Uri uri, ContentValues values, String selection, - String[] selectionArgs) { - return mDbHelper.getWritableDatabase().update( - ProviderTableMeta.DB_NAME, values, selection, selectionArgs); + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + + //Log_OC.d(TAG, "Updating " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this); + int count = 0; + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + db.beginTransaction(); + try { + count = update(db, uri, values, selection, selectionArgs); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + getContext().getContentResolver().notifyChange(uri, null); + return count; + } + + + + private int update(SQLiteDatabase db, Uri uri, ContentValues values, String selection, String[] selectionArgs) { + switch (mUriMatcher.match(uri)) { + case DIRECTORY: + return 0; //updateFolderSize(db, selectionArgs[0]); + case SHARES: + return db.update(ProviderTableMeta.OCSHARES_TABLE_NAME, values, selection, selectionArgs); + default: + return db.update(ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs); + } + } + + /* + private int updateFolderSize(SQLiteDatabase db, String folderId) { + int count = 0; + String [] whereArgs = new String[] { folderId }; + + // read current size saved for the folder + long folderSize = 0; + long folderParentId = -1; + Uri selectFolderUri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, folderId); + String[] folderProjection = new String[] { ProviderTableMeta.FILE_CONTENT_LENGTH, ProviderTableMeta.FILE_PARENT}; + String folderWhere = ProviderTableMeta._ID + "=?"; + Cursor folderCursor = query(db, selectFolderUri, folderProjection, folderWhere, whereArgs, null); + if (folderCursor != null && folderCursor.moveToFirst()) { + folderSize = folderCursor.getLong(folderCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH));; + folderParentId = folderCursor.getLong(folderCursor.getColumnIndex(ProviderTableMeta.FILE_PARENT));; + } + folderCursor.close(); + + // read and sum sizes of children + long childrenSize = 0; + Uri selectChildrenUri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, folderId); + String[] childrenProjection = new String[] { ProviderTableMeta.FILE_CONTENT_LENGTH, ProviderTableMeta.FILE_PARENT}; + String childrenWhere = ProviderTableMeta.FILE_PARENT + "=?"; + Cursor childrenCursor = query(db, selectChildrenUri, childrenProjection, childrenWhere, whereArgs, null); + if (childrenCursor != null && childrenCursor.moveToFirst()) { + while (!childrenCursor.isAfterLast()) { + childrenSize += childrenCursor.getLong(childrenCursor.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH)); + childrenCursor.moveToNext(); + } + } + childrenCursor.close(); + + // update if needed + if (folderSize != childrenSize) { + Log_OC.d("FileContentProvider", "Updating " + folderSize + " to " + childrenSize); + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, childrenSize); + count = db.update(ProviderTableMeta.FILE_TABLE_NAME, cv, folderWhere, whereArgs); + + // propagate update until root + if (folderParentId > FileDataStorageManager.ROOT_PARENT_ID) { + Log_OC.d("FileContentProvider", "Propagating update to " + folderParentId); + updateFolderSize(db, String.valueOf(folderParentId)); + } else { + Log_OC.d("FileContentProvider", "NOT propagating to " + folderParentId); + } + } else { + Log_OC.d("FileContentProvider", "NOT updating, sizes are " + folderSize + " and " + childrenSize); + } + return count; } +*/ + + @Override + public ContentProviderResult[] applyBatch (ArrayList operations) throws OperationApplicationException { + Log_OC.d("FileContentProvider", "applying batch in provider " + this + " (temporary: " + isTemporary() + ")" ); + ContentProviderResult[] results = new ContentProviderResult[operations.size()]; + int i=0; + + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + db.beginTransaction(); // it's supposed that transactions can be nested + try { + for (ContentProviderOperation operation : operations) { + results[i] = operation.apply(this, results, i); + i++; + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + Log_OC.d("FileContentProvider", "applied batch in provider " + this); + return results; + } + class DataBaseHelper extends SQLiteOpenHelper { @@ -214,7 +534,7 @@ public class FileContentProvider extends ContentProvider { public void onCreate(SQLiteDatabase db) { // files table Log_OC.i("SQL", "Entering in onCreate"); - db.execSQL("CREATE TABLE " + ProviderTableMeta.DB_NAME + "(" + db.execSQL("CREATE TABLE " + ProviderTableMeta.FILE_TABLE_NAME + "(" + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " + ProviderTableMeta.FILE_NAME + " TEXT, " + ProviderTableMeta.FILE_PATH + " TEXT, " @@ -228,8 +548,29 @@ public class FileContentProvider extends ContentProvider { + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, " - + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );" + + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER, " + + ProviderTableMeta.FILE_ETAG + " TEXT, " + + ProviderTableMeta.FILE_SHARE_BY_LINK + " INTEGER, " + + ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT );" ); + + // Create table ocshares + db.execSQL("CREATE TABLE " + ProviderTableMeta.OCSHARES_TABLE_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " + + ProviderTableMeta.OCSHARES_FILE_SOURCE + " INTEGER, " + + ProviderTableMeta.OCSHARES_ITEM_SOURCE + " INTEGER, " + + ProviderTableMeta.OCSHARES_SHARE_TYPE + " INTEGER, " + + ProviderTableMeta.OCSHARES_SHARE_WITH + " TEXT, " + + ProviderTableMeta.OCSHARES_PATH + " TEXT, " + + ProviderTableMeta.OCSHARES_PERMISSIONS+ " INTEGER, " + + ProviderTableMeta.OCSHARES_SHARED_DATE + " INTEGER, " + + ProviderTableMeta.OCSHARES_EXPIRATION_DATE + " INTEGER, " + + ProviderTableMeta.OCSHARES_TOKEN + " TEXT, " + + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME + " TEXT, " + + ProviderTableMeta.OCSHARES_IS_DIRECTORY + " INTEGER, " // boolean + + ProviderTableMeta.OCSHARES_USER_ID + " INTEGER, " + + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + " INTEGER," + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " TEXT );" ); } @Override @@ -238,7 +579,7 @@ public class FileContentProvider extends ContentProvider { boolean upgraded = false; if (oldVersion == 1 && newVersion >= 2) { Log_OC.i("SQL", "Entering in the #1 ADD in onUpgrade"); - db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + + db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + " ADD COLUMN " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " + " DEFAULT 0"); upgraded = true; @@ -247,12 +588,12 @@ public class FileContentProvider extends ContentProvider { Log_OC.i("SQL", "Entering in the #2 ADD in onUpgrade"); db.beginTransaction(); try { - db.execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + + db.execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + " ADD COLUMN " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER " + " DEFAULT 0"); // assume there are not local changes pending to upload - db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + + db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME + " SET " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " = " + System.currentTimeMillis() + " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); @@ -266,11 +607,11 @@ public class FileContentProvider extends ContentProvider { Log_OC.i("SQL", "Entering in the #3 ADD in onUpgrade"); db.beginTransaction(); try { - db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + + db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + " ADD COLUMN " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER " + " DEFAULT 0"); - db.execSQL("UPDATE " + ProviderTableMeta.DB_NAME + + db.execSQL("UPDATE " + ProviderTableMeta.FILE_TABLE_NAME + " SET " + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " = " + ProviderTableMeta.FILE_MODIFIED + " WHERE " + ProviderTableMeta.FILE_STORAGE_PATH + " IS NOT NULL"); @@ -282,8 +623,63 @@ public class FileContentProvider extends ContentProvider { } if (!upgraded) Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + + if (oldVersion < 5 && newVersion >= 5) { + Log_OC.i("SQL", "Entering in the #4 ADD in onUpgrade"); + db.beginTransaction(); + try { + db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_ETAG + " TEXT " + + " DEFAULT NULL"); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + + if (oldVersion < 6 && newVersion >= 6) { + Log_OC.i("SQL", "Entering in the #5 ADD in onUpgrade"); + db.beginTransaction(); + try { + db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_SHARE_BY_LINK + " INTEGER " + + " DEFAULT 0"); + + db .execSQL("ALTER TABLE " + ProviderTableMeta.FILE_TABLE_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_PUBLIC_LINK + " TEXT " + + " DEFAULT NULL"); + + // Create table ocshares + db.execSQL("CREATE TABLE " + ProviderTableMeta.OCSHARES_TABLE_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " + + ProviderTableMeta.OCSHARES_FILE_SOURCE + " INTEGER, " + + ProviderTableMeta.OCSHARES_ITEM_SOURCE + " INTEGER, " + + ProviderTableMeta.OCSHARES_SHARE_TYPE + " INTEGER, " + + ProviderTableMeta.OCSHARES_SHARE_WITH + " TEXT, " + + ProviderTableMeta.OCSHARES_PATH + " TEXT, " + + ProviderTableMeta.OCSHARES_PERMISSIONS+ " INTEGER, " + + ProviderTableMeta.OCSHARES_SHARED_DATE + " INTEGER, " + + ProviderTableMeta.OCSHARES_EXPIRATION_DATE + " INTEGER, " + + ProviderTableMeta.OCSHARES_TOKEN + " TEXT, " + + ProviderTableMeta.OCSHARES_SHARE_WITH_DISPLAY_NAME + " TEXT, " + + ProviderTableMeta.OCSHARES_IS_DIRECTORY + " INTEGER, " // boolean + + ProviderTableMeta.OCSHARES_USER_ID + " INTEGER, " + + ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + " INTEGER," + + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + " TEXT );" ); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); } - } } diff --git a/src/com/owncloud/android/services/OperationsService.java b/src/com/owncloud/android/services/OperationsService.java new file mode 100644 index 00000000..75363051 --- /dev/null +++ b/src/com/owncloud/android/services/OperationsService.java @@ -0,0 +1,559 @@ +/* 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 . + * + */ + +package com.owncloud.android.services; + +import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; + +import com.owncloud.android.R; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.shares.ShareType; +import com.owncloud.android.lib.resources.users.GetRemoteUserNameOperation; +import com.owncloud.android.operations.common.SyncOperation; +import com.owncloud.android.operations.CreateShareOperation; +import com.owncloud.android.operations.GetServerInfoOperation; +import com.owncloud.android.operations.OAuth2GetAccessToken; +import com.owncloud.android.operations.UnshareLinkOperation; +import com.owncloud.android.utils.Log_OC; + +import android.accounts.Account; +import android.accounts.AccountsException; +import android.app.Service; +import android.content.Intent; +import android.net.Uri; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.util.Pair; + +public class OperationsService extends Service { + + private static final String TAG = OperationsService.class.getSimpleName(); + + public static final String EXTRA_ACCOUNT = "ACCOUNT"; + public static final String EXTRA_SERVER_URL = "SERVER_URL"; + public static final String EXTRA_AUTH_TOKEN_TYPE = "AUTH_TOKEN_TYPE"; + public static final String EXTRA_OAUTH2_QUERY_PARAMETERS = "OAUTH2_QUERY_PARAMETERS"; + public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; + public static final String EXTRA_SEND_INTENT = "SEND_INTENT"; + public static final String EXTRA_RESULT = "RESULT"; + + // TODO review if ALL OF THEM are necessary + public static final String EXTRA_WEBDAV_PATH = "WEBDAV_PATH"; + public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT"; + public static final String EXTRA_USERNAME = "USERNAME"; + public static final String EXTRA_PASSWORD = "PASSWORD"; + public static final String EXTRA_AUTH_TOKEN = "AUTH_TOKEN"; + public static final String EXTRA_FOLLOW_REDIRECTS = "FOLLOW_REDIRECTS"; + public static final String EXTRA_COOKIE = "COOKIE"; + + public static final String ACTION_CREATE_SHARE = "CREATE_SHARE"; + public static final String ACTION_UNSHARE = "UNSHARE"; + public static final String ACTION_GET_SERVER_INFO = "GET_SERVER_INFO"; + public static final String ACTION_OAUTH2_GET_ACCESS_TOKEN = "OAUTH2_GET_ACCESS_TOKEN"; + public static final String ACTION_EXISTENCE_CHECK = "EXISTENCE_CHECK"; + public static final String ACTION_GET_USER_NAME = "GET_USER_NAME"; + + 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> mPendingOperations = + new ConcurrentLinkedQueue>(); + + /* + private ConcurrentMap mOperationResults = + new ConcurrentHashMap(); + */ + + private ConcurrentMap> + mUndispatchedFinishedOperations = + new ConcurrentHashMap>(); + + private static class Target { + public Uri mServerUrl = null; + public Account mAccount = null; + public String mWebDavUrl = null; + public String mUsername = null; + public String mPassword = null; + public String mAuthToken = null; + public boolean mFollowRedirects = true; + public String mCookie = null; + + public Target(Account account, Uri serverUrl, String webdavUrl, String username, String password, String authToken, + boolean followRedirects, String cookie) { + mAccount = account; + mServerUrl = serverUrl; + mWebDavUrl = webdavUrl; + mUsername = username; + mPassword = password; + mAuthToken = authToken; + mFollowRedirects = followRedirects; + mCookie = cookie; + } + } + + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + private OperationsServiceBinder mBinder; + private OwnCloudClient mOwnCloudClient = null; + private Target mLastTarget = null; + private FileDataStorageManager mStorageManager; + private RemoteOperation mCurrentOperation = null; + + + /** + * Service initialization + */ + @Override + public void onCreate() { + super.onCreate(); + HandlerThread thread = new HandlerThread("Operations service thread", Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper, this); + mBinder = new OperationsServiceBinder(); + } + + + /** + * Entry point to add a new operation to the queue of operations. + * + * 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" ); + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + //Log_OC.wtf(TAG, "onDestroy init" ); + super.onDestroy(); + //Log_OC.wtf(TAG, "Clear mUndispatchedFinisiedOperations" ); + mUndispatchedFinishedOperations.clear(); + //Log_OC.wtf(TAG, "onDestroy end" ); + } + + + /** + * Provides a binder object that clients can use to perform actions on the queue of operations, + * except the addition of new operations. + */ + @Override + public IBinder onBind(Intent intent) { + //Log_OC.wtf(TAG, "onBind" ); + return mBinder; + } + + + /** + * Called when ALL the bound clients were unbound. + */ + @Override + public boolean onUnbind(Intent intent) { + ((OperationsServiceBinder)mBinder).clearListeners(); + return false; // not accepting rebinding (default behaviour) + } + + + /** + * Binder to let client components to perform actions on the queue of operations. + * + * It provides by itself the available operations. + */ + public class OperationsServiceBinder extends Binder /* implements OnRemoteOperationListener */ { + + /** + * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder} instance + */ + private ConcurrentMap mBoundListeners = + new ConcurrentHashMap(); + + /** + * Cancels an operation + * + * TODO + */ + public void cancel() { + // TODO + } + + + public void clearListeners() { + + mBoundListeners.clear(); + } + + + /** + * Adds a listener interested in being reported about the end of operations. + * + * @param listener Object to notify about the end of operations. + * @param callbackHandler {@link Handler} to access the listener without breaking Android threading protection. + */ + public void addOperationListener (OnRemoteOperationListener listener, Handler callbackHandler) { + synchronized (mBoundListeners) { + mBoundListeners.put(listener, callbackHandler); + } + } + + + /** + * Removes a listener from the list of objects interested in the being reported about the end of operations. + * + * @param listener Object to notify about progress of transfer. + */ + public void removeOperationListener (OnRemoteOperationListener listener) { + synchronized (mBoundListeners) { + mBoundListeners.remove(listener); + } + } + + + /** + * TODO - IMPORTANT: update implementation when more operations are moved into the service + * + * @return 'True' when an operation that enforces the user to wait for completion is in process. + */ + public boolean isPerformingBlockingOperation() { + return (!mPendingOperations.isEmpty()); + } + + + /** + * Creates and adds to the queue a new operation, as described by operationIntent + * + * @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 webDavPath = operationIntent.getStringExtra(EXTRA_WEBDAV_PATH); + String webDavUrl = serverUrl + webDavPath; + String username = operationIntent.getStringExtra(EXTRA_USERNAME); + String password = operationIntent.getStringExtra(EXTRA_PASSWORD); + String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN); + boolean followRedirects = operationIntent.getBooleanExtra(EXTRA_FOLLOW_REDIRECTS, true); + String cookie = operationIntent.getStringExtra(EXTRA_COOKIE); + target = new Target( + account, + (serverUrl == null) ? null : Uri.parse(serverUrl), + ((webDavPath == null) || (serverUrl == null)) ? null : webDavUrl, + username, + password, + authToken, + followRedirects, + 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 + String authTokenType = + operationIntent.getStringExtra(EXTRA_AUTH_TOKEN_TYPE); + operation = new GetServerInfoOperation( + serverUrl, authTokenType, 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, true); + operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent); + + } else if (action.equals(ACTION_GET_USER_NAME)) { + // Get User Name + operation = new GetRemoteUserNameOperation(); + } + } + + } catch (IllegalArgumentException e) { + Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage()); + operation = null; + } + + if (operation != null) { + mPendingOperations.add(new Pair(target, operation)); + 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(); + + } else { + //Log_OC.wtf(TAG, "New operation failed, returned Long.MAX_VALUE"); + return Long.MAX_VALUE; + } + } + + public void dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) { + Pair undispatched = + mUndispatchedFinishedOperations.remove(operationId); + if (undispatched != null) { + listener.onRemoteOperationFinish(undispatched.first, undispatched.second); + //Log_OC.wtf(TAG, "Sending callback later"); + } else { + //Log_OC.wtf(TAG, "Not finished yet"); + } + } + + } + + + /** + * 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; + public ServiceHandler(Looper looper, OperationsService service) { + super(looper); + if (service == null) { + throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); + } + mService = service; + } + + @Override + public void handleMessage(Message msg) { + mService.nextOperation(); + mService.stopSelf(msg.arg1); + } + } + + + /** + * Performs the next operation in the queue + */ + private void nextOperation() { + + //Log_OC.wtf(TAG, "nextOperation init" ); + + Pair 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) { + mOwnCloudClient = OwnCloudClientFactory.createOwnCloudClient(mLastTarget.mAccount, getApplicationContext()); + mStorageManager = new FileDataStorageManager(mLastTarget.mAccount, getContentResolver()); + } else { + mOwnCloudClient = OwnCloudClientFactory.createOwnCloudClient(mLastTarget.mServerUrl, getApplicationContext(), + mLastTarget.mFollowRedirects); // this is not good enough + if (mLastTarget.mWebDavUrl != null) { + mOwnCloudClient.setWebdavUri(Uri.parse(mLastTarget.mWebDavUrl)); + } + if (mLastTarget.mUsername != null && mLastTarget.mPassword != null) { + mOwnCloudClient.setBasicCredentials(mLastTarget.mUsername, mLastTarget.mPassword); + } else if (mLastTarget.mAuthToken != null) { + mOwnCloudClient.setBearerCredentials(mLastTarget.mAuthToken); + } else if (mLastTarget.mCookie != null) { + mOwnCloudClient.setSsoSessionCookie(mLastTarget.mCookie); + } + mStorageManager = null; + } + } + + /// 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 autorization for a NULL account", e); + } else { + Log_OC.e(TAG, "Error while trying to get autorization 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 autorization for a NULL account", e); + } else { + Log_OC.e(TAG, "Error while trying to get autorization 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); + dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result); + } + } + + + /** + * Sends a broadcast when a new operation is added to the queue. + * + * Local broadcasts are only delivered to activities in the same process, but can't be done sticky :\ + * + * @param target Account or URL pointing to an OC server. + * @param operation Added operation. + */ + private void sendBroadcastNewOperation(Target target, RemoteOperation operation) { + Intent intent = new Intent(ACTION_OPERATION_ADDED); + if (target.mAccount != null) { + intent.putExtra(EXTRA_ACCOUNT, target.mAccount); + } else { + intent.putExtra(EXTRA_SERVER_URL, target.mServerUrl); + } + //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); + //lbm.sendBroadcast(intent); + sendStickyBroadcast(intent); + } + + + // TODO - maybe add a notification for real start of operations + + /** + * Sends a LOCAL broadcast when an operations finishes in order to the interested activities can update their view + * + * Local broadcasts are only delivered to activities in the same process. + * + * @param target Account or URL pointing to an OC server. + * @param operation Finished operation. + * @param result Result of the operation. + */ + private void sendBroadcastOperationFinished(Target target, RemoteOperation operation, RemoteOperationResult result) { + Intent intent = new Intent(ACTION_OPERATION_FINISHED); + intent.putExtra(EXTRA_RESULT, result); + if (target.mAccount != null) { + intent.putExtra(EXTRA_ACCOUNT, target.mAccount); + } else { + intent.putExtra(EXTRA_SERVER_URL, target.mServerUrl); + } + //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); + //lbm.sendBroadcast(intent); + sendStickyBroadcast(intent); + } + + + /** + * 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( + Target target, final RemoteOperation operation, final RemoteOperationResult result) { + int count = 0; + Iterator listeners = mBinder.mBoundListeners.keySet().iterator(); + while (listeners.hasNext()) { + final OnRemoteOperationListener listener = listeners.next(); + final Handler handler = mBinder.mBoundListeners.get(listener); + if (handler != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onRemoteOperationFinish(operation, result); + } + }); + count += 1; + } + } + if (count == 0) { + //mOperationResults.put(operation.hashCode(), result); + Pair undispatched = + new Pair(operation, result); + mUndispatchedFinishedOperations.put(operation.hashCode(), undispatched); + } + Log_OC.d(TAG, "Called " + count + " listeners"); + } + + +} diff --git a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java index fafb0cb8..a9defc0b 100644 --- a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java @@ -19,18 +19,17 @@ package com.owncloud.android.syncadapter; import java.io.IOException; -import java.net.UnknownHostException; -import java.util.Date; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; -import org.apache.http.conn.ConnectionKeepAliveStrategy; -import org.apache.http.protocol.HttpContext; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.datamodel.DataStorageManager; -import com.owncloud.android.network.OwnCloudClientUtils; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.lib.common.accounts.AccountUtils; +import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; +import com.owncloud.android.lib.common.OwnCloudClientFactory; +import com.owncloud.android.lib.common.OwnCloudClient; + import android.accounts.Account; import android.accounts.AccountManager; @@ -39,31 +38,36 @@ import android.accounts.OperationCanceledException; import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.Context; -import eu.alefzero.webdav.WebdavClient; /** - * Base SyncAdapter for OwnCloud Designed to be subclassed for the concrete - * SyncAdapter, like ConcatsSync, CalendarSync, FileSync etc.. + * Base synchronization adapter for ownCloud designed to be subclassed for different + * resource types, like FileSync, ConcatsSync, CalendarSync, etc.. * - * @author sassman + * Implements the standard {@link AbstractThreadedSyncAdapter}. * + * @author sassman + * @author David A. Velasco */ public abstract class AbstractOwnCloudSyncAdapter extends AbstractThreadedSyncAdapter { private AccountManager accountManager; private Account account; - private ContentProviderClient contentProvider; - private Date lastUpdated; - private DataStorageManager mStoreManager; + private ContentProviderClient mContentProviderClient; + private FileDataStorageManager mStoreManager; - private WebdavClient mClient = null; + private OwnCloudClient mClient = null; public AbstractOwnCloudSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); this.setAccountManager(AccountManager.get(context)); } + public AbstractOwnCloudSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { + super(context, autoInitialize, allowParallelSyncs); + this.setAccountManager(AccountManager.get(context)); + } + public AccountManager getAccountManager() { return accountManager; } @@ -80,76 +84,37 @@ public abstract class AbstractOwnCloudSyncAdapter extends this.account = account; } - public ContentProviderClient getContentProvider() { - return contentProvider; - } - - public void setContentProvider(ContentProviderClient contentProvider) { - this.contentProvider = contentProvider; - } - - public Date getLastUpdated() { - return lastUpdated; + public ContentProviderClient getContentProviderClient() { + return mContentProviderClient; } - public void setLastUpdated(Date lastUpdated) { - this.lastUpdated = lastUpdated; + public void setContentProviderClient(ContentProviderClient contentProvider) { + this.mContentProviderClient = contentProvider; } - public void setStorageManager(DataStorageManager storage_manager) { + public void setStorageManager(FileDataStorageManager storage_manager) { mStoreManager = storage_manager; } - public DataStorageManager getStorageManager() { + public FileDataStorageManager getStorageManager() { return mStoreManager; } - protected ConnectionKeepAliveStrategy getKeepAliveStrategy() { - return new ConnectionKeepAliveStrategy() { - public long getKeepAliveDuration(HttpResponse response, - HttpContext context) { - // Change keep alive straategy basing on response: ie - // forbidden/not found/etc - // should have keep alive 0 - // default return: 5s - int statusCode = response.getStatusLine().getStatusCode(); - - // HTTP 400, 500 Errors as well as HTTP 118 - Connection timed - // out - if ((statusCode >= 400 && statusCode <= 418) - || (statusCode >= 421 && statusCode <= 426) - || (statusCode >= 500 && statusCode <= 510) - || statusCode == 118) { - return 0; - } - - return 5 * 1000; - } - }; + protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { + AccountUtils.constructFullURLForAccount(getContext(), account); + mClient = OwnCloudClientFactory.createOwnCloudClient(account, getContext()); } - + + protected OwnCloudClient getClient() { + return mClient; + } + + + /* method called by ContactSyncAdapter, that is never used */ protected HttpResponse fireRawRequest(HttpRequest query) throws ClientProtocolException, OperationCanceledException, AuthenticatorException, IOException { - /* - * BasicHttpContext httpContext = new BasicHttpContext(); BasicScheme - * basicAuth = new BasicScheme(); - * httpContext.setAttribute("preemptive-auth", basicAuth); - * - * HttpResponse response = getClient().execute(mHost, query, - * httpContext); - */ return null; } - protected void initClientForCurrentAccount() throws UnknownHostException { - if (AccountUtils.constructFullURLForAccount(getContext(), account) == null) { - throw new UnknownHostException(); - } - mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext()); - } - - protected WebdavClient getClient() { - return mClient; - } } \ No newline at end of file diff --git a/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java b/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java index 06871467..3ba1676a 100644 --- a/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/ContactSyncAdapter.java @@ -24,8 +24,9 @@ import java.io.IOException; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ByteArrayEntity; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; + import android.accounts.Account; import android.accounts.AccountManager; @@ -52,7 +53,7 @@ public class ContactSyncAdapter extends AbstractOwnCloudSyncAdapter { public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { setAccount(account); - setContentProvider(provider); + setContentProviderClient(provider); Cursor c = getLocalContacts(false); if (c.moveToFirst()) { do { @@ -91,7 +92,7 @@ public class ContactSyncAdapter extends AbstractOwnCloudSyncAdapter { AccountManager am = getAccountManager(); @SuppressWarnings("deprecation") String uri = am.getUserData(getAccount(), - AccountAuthenticator.KEY_OC_URL).replace( + Constants.KEY_OC_URL).replace( AccountUtils.WEBDAV_PATH_2_0, AccountUtils.CARDDAV_PATH_2_0); uri += "/addressbooks/" + getAccount().name.substring(0, diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java index b3a42ea3..407ce1a2 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -19,7 +19,6 @@ package com.owncloud.android.syncadapter; import java.io.IOException; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -27,64 +26,114 @@ import java.util.Map; import org.apache.jackrabbit.webdav.DavException; -import com.owncloud.android.Log_OC; import com.owncloud.android.R; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.SynchronizeFolderOperation; import com.owncloud.android.operations.UpdateOCVersionOperation; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; +import com.owncloud.android.utils.Log_OC; + import android.accounts.Account; -import android.app.Notification; +import android.accounts.AccountsException; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SyncResult; import android.os.Bundle; -import android.util.Log; +import android.support.v4.app.NotificationCompat; /** - * SyncAdapter implementation for syncing sample SyncAdapter contacts to the - * platform ContactOperations provider. + * Implementation of {@link AbstractThreadedSyncAdapter} responsible for synchronizing + * ownCloud files. + * + * Performs a full synchronization of the account recieved in {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)}. * * @author Bartek Przybylski + * @author David A. Velasco */ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { - private final static String TAG = "FileSyncAdapter"; + private final static String TAG = FileSyncAdapter.class.getSimpleName(); - /** - * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation - */ + /** Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation */ private static final int MAX_FAILED_RESULTS = 3; + + public static final String EVENT_FULL_SYNC_START = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_START"; + public static final String EVENT_FULL_SYNC_END = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_END"; + public static final String EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED"; + //public static final String EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED = FileSyncAdapter.class.getName() + ".EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED"; + + public static final String EXTRA_ACCOUNT_NAME = FileSyncAdapter.class.getName() + ".EXTRA_ACCOUNT_NAME"; + public static final String EXTRA_FOLDER_PATH = FileSyncAdapter.class.getName() + ".EXTRA_FOLDER_PATH"; + public static final String EXTRA_RESULT = FileSyncAdapter.class.getName() + ".EXTRA_RESULT"; + + + /** Time stamp for the current synchronization process, used to distinguish fresh data */ private long mCurrentSyncTime; + + /** Flag made 'true' when a request to cancel the synchronization is received */ private boolean mCancellation; + + /** When 'true' the process was requested by the user through the user interface; when 'false', it was requested automatically by the system */ private boolean mIsManualSync; - private int mFailedResultsCounter; + + /** Counter for failed operations in the synchronization process */ + private int mFailedResultsCounter; + + /** Result of the last failed operation */ private RemoteOperationResult mLastFailedResult; - private SyncResult mSyncResult; + + /** 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 mForgottenLocalFiles; + /** {@link SyncResult} instance to return to the system when the synchronization finish */ + private SyncResult mSyncResult; + + /** 'True' means that the server supports the share API */ + private boolean mIsShareSupported; + + /** + * Creates a {@link FileSyncAdapter} + * + * {@inheritDoc} + */ public FileSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } + + /** + * Creates a {@link FileSyncAdapter} + * + * {@inheritDoc} + */ + public FileSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { + super(context, autoInitialize, allowParallelSyncs); + } + + /** * {@inheritDoc} */ @Override public synchronized void onPerformSync(Account account, Bundle extras, - String authority, ContentProviderClient provider, + String authority, ContentProviderClient providerClient, SyncResult syncResult) { mCancellation = false; @@ -96,31 +145,37 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { mForgottenLocalFiles = new HashMap(); mSyncResult = syncResult; mSyncResult.fullSyncRequested = false; - mSyncResult.delayUntil = 60*60*24; // sync after 24h + mSyncResult.delayUntil = 60*60*24; // avoid too many automatic synchronizations this.setAccount(account); - this.setContentProvider(provider); - this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); + this.setContentProviderClient(providerClient); + this.setStorageManager(new FileDataStorageManager(account, providerClient)); + try { this.initClientForCurrentAccount(); - } catch (UnknownHostException e) { - /// the account is unknown for the Synchronization Manager. unreachable for this context; don't try this again + } catch (IOException e) { + /// the account is unknown for the Synchronization Manager, unreachable this context, or can not be authenticated; don't try this again + mSyncResult.tooManyRetries = true; + notifyFailedSynchronization(); + return; + } catch (AccountsException e) { + /// the account is unknown for the Synchronization Manager, unreachable this context, or can not be authenticated; don't try this again mSyncResult.tooManyRetries = true; notifyFailedSynchronization(); return; } Log_OC.d(TAG, "Synchronization of ownCloud account " + account.name + " starting"); - sendStickyBroadcast(true, null, null); // message to signal the start of the synchronization to the UI + sendLocalBroadcast(EVENT_FULL_SYNC_START, null, null); // message to signal the start of the synchronization to the UI try { updateOCVersion(); mCurrentSyncTime = System.currentTimeMillis(); if (!mCancellation) { - fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID); + synchronizeFolder(getStorageManager().getFileByPath(OCFile.ROOT_PATH)); } else { - Log_OC.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested"); + Log_OC.d(TAG, "Leaving synchronization before synchronizing the root folder because cancelation request"); } @@ -134,26 +189,27 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { /// notify the user about the failure of MANUAL synchronization notifyFailedSynchronization(); - } if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { notifyFailsInFavourites(); } if (mForgottenLocalFiles.size() > 0) { notifyForgottenLocalFiles(); - } - sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI + sendLocalBroadcast(EVENT_FULL_SYNC_END, null, mLastFailedResult); // message to signal the end to the UI } } - /** * Called by system SyncManager when a synchronization is required to be cancelled. * - * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder - * fetched will be still saved in the database. See onPerformSync implementation. + * Sets the mCancellation flag to 'true'. THe synchronization will be stopped later, + * before a new folder is fetched. Data of the last folder synchronized will be still + * locally saved. + * + * See {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)} + * and {@link #synchronizeFolder(String, long)}. */ @Override public void onSyncCanceled() { @@ -171,26 +227,43 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { RemoteOperationResult result = update.execute(getClient()); if (!result.isSuccess()) { mLastFailedResult = result; + } else { + mIsShareSupported = update.getOCVersion().isSharedSupported(); } } - /** - * Synchronize the properties of files and folders contained in a remote folder given by remotePath. + * Synchronizes 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. + * + * Enters in the child folders to synchronize their contents also, following a recursive + * depth first strategy. * - * @param remotePath Remote path to the folder to synchronize. - * @param parentId Database Id of the folder to synchronize. + * @param folder Folder to synchronize. */ - private void fetchData(String remotePath, long parentId) { + private void synchronizeFolder(OCFile folder) { if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult)) return; - // perform folder synchronization - SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, + /* + OCFile folder, + long currentSyncTime, + boolean updateFolderProperties, + boolean syncFullAccount, + DataStorageManager dataStorageManager, + Account account, + Context context ) { + } + */ + // folder synchronization + SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( folder, mCurrentSyncTime, - parentId, + true, + mIsShareSupported, getStorageManager(), getAccount(), getContext() @@ -199,8 +272,9 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess - sendStickyBroadcast(true, remotePath, null); + sendLocalBroadcast(EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED, folder.getRemotePath(), result); + // check the result of synchronizing the folder if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) { if (result.getCode() == ResultCode.SYNC_CONFLICT) { @@ -210,12 +284,18 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { if (synchFolderOp.getForgottenLocalFiles().size() > 0) { mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles()); } - // synchronize children folders - List children = synchFolderOp.getChildren(); - fetchChildren(children); // beware of the 'hidden' recursion here! + if (result.isSuccess()) { + // synchronize children folders + List children = synchFolderOp.getChildren(); + fetchChildren(folder, children, synchFolderOp.getRemoteFolderChanged()); // beware of the 'hidden' recursion here! + } } else { - if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED) { + // in failures, the statistics for the global result are updated + if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED || + ( result.isIdPRedirection() && + getClient().getCredentials() == null )) { + //MainApp.getAuthTokenTypeSamlSessionCookie().equals(getClient().getAuthTokenType()))) { mSyncResult.stats.numAuthExceptions++; } else if (result.getException() instanceof DavException) { @@ -249,39 +329,51 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { } /** - * Synchronize data of folders in the list of received files + * Triggers the synchronization of any folder contained in the list of received files. * - * @param files Files to recursively fetch + * @param files Files to recursively synchronize. */ - private void fetchChildren(List files) { + private void fetchChildren(OCFile parent, List files, boolean parentEtagChanged) { int i; + OCFile newFile = null; + //String etag = null; + //boolean syncDown = false; for (i=0; i < files.size() && !mCancellation; i++) { - OCFile newFile = files.get(i); - if (newFile.isDirectory()) { - fetchData(newFile.getRemotePath(), newFile.getFileId()); + newFile = files.get(i); + if (newFile.isFolder()) { + /* + etag = newFile.getEtag(); + syncDown = (parentEtagChanged || etag == null || etag.length() == 0); + if(syncDown) { */ + synchronizeFolder(newFile); + //sendLocalBroadcast(EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED, parent.getRemotePath(), null); + //} } } - if (mCancellation && i 0) { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_fail_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; + NotificationCompat.Builder notificationBuilder = createNotificationBuilder(); + notificationBuilder.setTicker(i18n(R.string.sync_fail_in_favourites_ticker)); + // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_fail_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_fail_in_favourites_content), mFailedResultsCounter + mConflictsFound, mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_fail_in_favourites_ticker, notification); + notificationBuilder + .setContentIntent(PendingIntent.getActivity( + getContext(), (int) System.currentTimeMillis(), new Intent(), 0 + )) + .setContentTitle(i18n(R.string.sync_fail_in_favourites_ticker)) + .setContentText(i18n(R.string.sync_fail_in_favourites_content, mFailedResultsCounter + mConflictsFound, mConflictsFound)); + showNotification(R.string.sync_fail_in_favourites_ticker, notificationBuilder); } else { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_conflicts_in_favourites_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; + NotificationCompat.Builder notificationBuilder = createNotificationBuilder(); + notificationBuilder.setTicker(i18n(R.string.sync_conflicts_in_favourites_ticker)); + // TODO put something smart in the contentIntent below - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), new Intent(), 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_conflicts_in_favourites_ticker), - String.format(getContext().getString(R.string.sync_conflicts_in_favourites_content), mConflictsFound), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_conflicts_in_favourites_ticker, notification); + notificationBuilder + .setContentIntent(PendingIntent.getActivity( + getContext(), (int) System.currentTimeMillis(), new Intent(), 0 + )) + .setContentTitle(i18n(R.string.sync_conflicts_in_favourites_ticker)) + .setContentText(i18n(R.string.sync_conflicts_in_favourites_ticker, mConflictsFound)); + + showNotification(R.string.sync_conflicts_in_favourites_ticker, notificationBuilder); } } - /** * Notifies the user about local copies of files out of the ownCloud local directory that were 'forgotten' because @@ -343,9 +460,9 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { * We won't consider a synchronization as failed when foreign files can not be copied to the ownCloud local directory. */ private void notifyForgottenLocalFiles() { - Notification notification = new Notification(R.drawable.icon, getContext().getString(R.string.sync_foreign_files_forgotten_ticker), System.currentTimeMillis()); - notification.flags |= Notification.FLAG_AUTO_CANCEL; - + NotificationCompat.Builder notificationBuilder = createNotificationBuilder(); + notificationBuilder.setTicker(i18n(R.string.sync_foreign_files_forgotten_ticker)); + /// includes a pending intent in the notification showing a more detailed explanation Intent explanationIntent = new Intent(getContext(), ErrorsWhileCopyingHandlerActivity.class); explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_ACCOUNT, getAccount()); @@ -357,14 +474,45 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { explanationIntent.putExtra(ErrorsWhileCopyingHandlerActivity.EXTRA_REMOTE_PATHS, remotePaths); explanationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - notification.contentIntent = PendingIntent.getActivity(getContext().getApplicationContext(), (int)System.currentTimeMillis(), explanationIntent, 0); - notification.setLatestEventInfo(getContext().getApplicationContext(), - getContext().getString(R.string.sync_foreign_files_forgotten_ticker), - String.format(getContext().getString(R.string.sync_foreign_files_forgotten_content), mForgottenLocalFiles.size(), getContext().getString(R.string.app_name)), - notification.contentIntent); - ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)).notify(R.string.sync_foreign_files_forgotten_ticker, notification); + notificationBuilder + .setContentIntent(PendingIntent.getActivity( + getContext(), (int) System.currentTimeMillis(), explanationIntent, 0 + )) + .setContentTitle(i18n(R.string.sync_foreign_files_forgotten_ticker)) + .setContentText(i18n(R.string.sync_foreign_files_forgotten_content, mForgottenLocalFiles.size(), i18n(R.string.app_name))); + showNotification(R.string.sync_foreign_files_forgotten_ticker, notificationBuilder); } + /** + * Creates a notification builder with some commonly used settings + * + * @return + */ + private NotificationCompat.Builder createNotificationBuilder() { + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getContext()); + notificationBuilder.setSmallIcon(R.drawable.notification_icon).setAutoCancel(true); + return notificationBuilder; + } + /** + * Builds and shows the notification + * + * @param id + * @param builder + */ + private void showNotification(int id, NotificationCompat.Builder builder) { + ((NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE)) + .notify(id, builder.build()); + } + /** + * Shorthand translation + * + * @param key + * @param args + * @return + */ + private String i18n(int key, Object... args) { + return getContext().getString(key, args); + } } diff --git a/src/com/owncloud/android/syncadapter/FileSyncService.java b/src/com/owncloud/android/syncadapter/FileSyncService.java index f6c70dcb..5da8c24c 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncService.java +++ b/src/com/owncloud/android/syncadapter/FileSyncService.java @@ -22,23 +22,30 @@ import android.content.Intent; import android.os.IBinder; /** - * Background service for syncing files to our local Database + * Background service for synchronizing remote files with their local state. * - * @author Bartek Przybylski + * Serves as a connector to an instance of {@link FileSyncAdapter}, as required by standard Android APIs. * + * @author Bartek Przybylski + * @author David A. Velasco */ public class FileSyncService extends Service { - public static final String SYNC_MESSAGE = "ACCOUNT_SYNC"; - public static final String SYNC_FOLDER_REMOTE_PATH = "SYNC_FOLDER_REMOTE_PATH"; - public static final String IN_PROGRESS = "SYNC_IN_PROGRESS"; - public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; - public static final String SYNC_RESULT = "SYNC_RESULT"; - + + // Storage for an instance of the sync adapter + private static FileSyncAdapter sSyncAdapter = null; + // Object to use as a thread-safe lock + private static final Object sSyncAdapterLock = new Object(); + /* * {@inheritDoc} */ @Override public void onCreate() { + synchronized (sSyncAdapterLock) { + if (sSyncAdapter == null) { + sSyncAdapter = new FileSyncAdapter(getApplicationContext(), true); + } + } } /* @@ -46,6 +53,7 @@ public class FileSyncService extends Service { */ @Override public IBinder onBind(Intent intent) { - return new FileSyncAdapter(getApplicationContext(), true).getSyncAdapterBinder(); + return sSyncAdapter.getSyncAdapterBinder(); } + } diff --git a/src/com/owncloud/android/ui/CheckBoxPreferenceWithLongTitle.java b/src/com/owncloud/android/ui/CheckBoxPreferenceWithLongTitle.java new file mode 100644 index 00000000..dac083af --- /dev/null +++ b/src/com/owncloud/android/ui/CheckBoxPreferenceWithLongTitle.java @@ -0,0 +1,47 @@ +/* ownCloud Android client application + * Copyright (C) 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 . + * + */ + +package com.owncloud.android.ui; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; +import android.preference.CheckBoxPreference; + +public class CheckBoxPreferenceWithLongTitle extends CheckBoxPreference{ + + public CheckBoxPreferenceWithLongTitle(Context context) { + super(context); + } + + public CheckBoxPreferenceWithLongTitle(Context context, AttributeSet attrs) { + super(context, attrs); + } + public CheckBoxPreferenceWithLongTitle(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + TextView titleView = (TextView) view.findViewById(android.R.id.title); + titleView.setSingleLine(false); + titleView.setMaxLines(3); + titleView.setEllipsize(null); + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/FragmentListView.java b/src/com/owncloud/android/ui/FragmentListView.java deleted file mode 100644 index 43b2fc65..00000000 --- a/src/com/owncloud/android/ui/FragmentListView.java +++ /dev/null @@ -1,100 +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 . - * - */ - -package com.owncloud.android.ui; - -import com.actionbarsherlock.app.SherlockFragment; -import com.owncloud.android.R; -import com.owncloud.android.ui.fragment.LocalFileListFragment; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.ListAdapter; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; - -public class FragmentListView extends SherlockFragment implements - OnItemClickListener, OnItemLongClickListener { - protected ExtendedListView mList; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - } - - public void setListAdapter(ListAdapter listAdapter) { - mList.setAdapter(listAdapter); - mList.invalidate(); - } - - public ListView getListView() { - return mList; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - //mList = new ExtendedListView(getActivity()); - View v = inflater.inflate(R.layout.list_fragment, null); - mList = (ExtendedListView)(v.findViewById(R.id.list_root)); - mList.setOnItemClickListener(this); - mList.setOnItemLongClickListener(this); - //mList.setEmptyView(v.findViewById(R.id.empty_list_view)); // looks like it's not a cool idea - mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator)); - mList.setDividerHeight(1); - return v; - } - - public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { - } - - @Override - public boolean onItemLongClick(AdapterView arg0, View arg1, int arg2, - long arg3) { - return false; - } - - - /** - * 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. - * - * @return The position in the list of the visible item in the center of the screen. - */ - protected int getReferencePosition() { - return (mList.getFirstVisiblePosition() + mList.getLastVisiblePosition()) / 2; - } - - - /** - * Sets the visible part of the list from the reference position. - * - * @param position Reference position previously returned by {@link LocalFileListFragment#getReferencePosition()} - */ - protected void setReferencePosition(int position) { - mList.setAndCenterSelection(position); - } - - -} diff --git a/src/com/owncloud/android/ui/PreferenceMultiline.java b/src/com/owncloud/android/ui/PreferenceMultiline.java new file mode 100644 index 00000000..28b3621e --- /dev/null +++ b/src/com/owncloud/android/ui/PreferenceMultiline.java @@ -0,0 +1,53 @@ +/* ownCloud Android client application + * Copyright (C) 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 . + * + */ + +package com.owncloud.android.ui; + +import android.content.Context; +import android.preference.Preference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +/** + * Allow multiline titles in preferences + * + * @author masensio + * + */ +public class PreferenceMultiline extends Preference { + + public PreferenceMultiline(Context context) { + super(context); + } + + public PreferenceMultiline(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public PreferenceMultiline(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onBindView(View view) { + super.onBindView(view); + TextView titleView = (TextView) view.findViewById(android.R.id.title); + titleView.setSingleLine(false); + titleView.setMaxLines(3); + } +} diff --git a/src/com/owncloud/android/ui/QuickAction.java b/src/com/owncloud/android/ui/QuickAction.java index 8ae71ec4..86fe3fe3 100644 --- a/src/com/owncloud/android/ui/QuickAction.java +++ b/src/com/owncloud/android/ui/QuickAction.java @@ -38,6 +38,7 @@ import java.util.ArrayList; import com.owncloud.android.R; + /** * Popup window, shows action list as icon and text like the one in Gallery3D * app. diff --git a/src/com/owncloud/android/ui/activity/AccountSelectActivity.java b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java index 7a1cac51..e1c7b47a 100644 --- a/src/com/owncloud/android/ui/activity/AccountSelectActivity.java +++ b/src/com/owncloud/android/ui/activity/AccountSelectActivity.java @@ -27,12 +27,10 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountManagerCallback; import android.accounts.AccountManagerFuture; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; -import android.util.Log; import android.view.ContextMenu; import android.view.View; import android.view.ViewGroup; @@ -48,12 +46,15 @@ import com.actionbarsherlock.app.SherlockListActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; -import com.owncloud.android.authenticator.AccountAuthenticator; - +import com.owncloud.android.authentication.AuthenticatorActivity; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.Log_OC; +import com.owncloud.android.MainApp; import com.owncloud.android.R; + public class AccountSelectActivity extends SherlockListActivity implements AccountManagerCallback { @@ -74,9 +75,10 @@ public class AccountSelectActivity extends SherlockListActivity implements mPreviousAccount = AccountUtils.getCurrentOwnCloudAccount(this); } - ActionBar action_bar = getSupportActionBar(); - action_bar.setDisplayShowTitleEnabled(true); - action_bar.setDisplayHomeAsUpEnabled(false); + ActionBar actionBar = getSupportActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(false); } @Override @@ -94,12 +96,6 @@ public class AccountSelectActivity extends SherlockListActivity implements (mPreviousAccount != null && !mPreviousAccount.equals(current))) { /// the account set as default changed since this activity was created - // trigger synchronization - ContentResolver.cancelSync(null, AccountAuthenticator.AUTH_TOKEN_TYPE); - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync(AccountUtils.getCurrentOwnCloudAccount(this), AccountAuthenticator.AUTH_TOKEN_TYPE, bundle); - // restart the main activity Intent i = new Intent(this, FileDisplayActivity.class); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -110,8 +106,11 @@ public class AccountSelectActivity extends SherlockListActivity implements @Override public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSherlock().getMenuInflater(); - inflater.inflate(R.menu.account_picker, menu); + // Show Create Account if Multiaccount is enabled + if (getResources().getBoolean(R.bool.multiaccount_support)) { + MenuInflater inflater = getSherlock().getMenuInflater(); + inflater.inflate(R.menu.account_picker, menu); + } return true; } @@ -133,21 +132,35 @@ public class AccountSelectActivity extends SherlockListActivity implements @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { if (item.getItemId() == R.id.createAccount) { - Intent intent = new Intent( + /*Intent intent = new Intent( android.provider.Settings.ACTION_ADD_ACCOUNT); intent.putExtra("authorities", - new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE }); - startActivity(intent); + new String[] { MainApp.getAuthTokenType() }); + startActivity(intent);*/ + AccountManager am = AccountManager.get(getApplicationContext()); + am.addAccount(MainApp.getAccountType(), + null, + null, + null, + this, + null, + null); return true; } return false; } + /** + * Called when the user clicked on an item into the context menu created at + * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} for every + * ownCloud {@link Account} , containing 'secondary actions' for them. + * + * {@inheritDoc}} + */ @SuppressWarnings("unchecked") @Override public boolean onContextItemSelected(android.view.MenuItem item) { - AdapterContextMenuInfo info = (AdapterContextMenuInfo) item - .getMenuInfo(); + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); int index = info.position; HashMap map = null; try { @@ -159,11 +172,18 @@ public class AccountSelectActivity extends SherlockListActivity implements String accountName = map.get("NAME"); AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE); - Account accounts[] = am - .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); + Account accounts[] = am.getAccountsByType(MainApp.getAccountType()); for (Account a : accounts) { if (a.name.equals(accountName)) { - am.removeAccount(a, this, mHandler); + if (item.getItemId() == R.id.change_password) { + Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, a); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); + startActivity(updateAccountCredentials); + + } else if (item.getItemId() == R.id.delete_account) { + am.removeAccount(a, this, mHandler); + } } } @@ -173,22 +193,34 @@ public class AccountSelectActivity extends SherlockListActivity implements private void populateAccountList() { AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE); Account accounts[] = am - .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); - LinkedList> ll = new LinkedList>(); - for (Account a : accounts) { - HashMap h = new HashMap(); - h.put("NAME", a.name); - h.put("VER", - "ownCloud version: " - + am.getUserData(a, - AccountAuthenticator.KEY_OC_VERSION)); - ll.add(h); + .getAccountsByType(MainApp.getAccountType()); + if (am.getAccountsByType(MainApp.getAccountType()).length == 0) { + // Show create account screen if there isn't any account + am.addAccount(MainApp.getAccountType(), + null, + null, + null, + this, + null, + null); } + else { + LinkedList> ll = new LinkedList>(); + for (Account a : accounts) { + HashMap h = new HashMap(); + h.put("NAME", a.name); + h.put("VER", + "ownCloud version: " + + am.getUserData(a, + Constants.KEY_OC_VERSION)); + ll.add(h); + } - setListAdapter(new AccountCheckedSimpleAdepter(this, ll, - android.R.layout.simple_list_item_single_choice, - new String[] { "NAME" }, new int[] { android.R.id.text1 })); - registerForContextMenu(getListView()); + setListAdapter(new AccountCheckedSimpleAdepter(this, ll, + android.R.layout.simple_list_item_single_choice, + new String[] { "NAME" }, new int[] { android.R.id.text1 })); + registerForContextMenu(getListView()); + } } @Override @@ -198,7 +230,7 @@ public class AccountSelectActivity extends SherlockListActivity implements String accountName = ""; if (a == null) { Account[] accounts = AccountManager.get(this) - .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); + .getAccountsByType(MainApp.getAccountType()); if (accounts.length != 0) accountName = accounts[0].name; AccountUtils.setCurrentOwnCloudAccount(this, accountName); diff --git a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java b/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java deleted file mode 100644 index 7d75e9db..00000000 --- a/src/com/owncloud/android/ui/activity/AuthenticatorActivity.java +++ /dev/null @@ -1,609 +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 . - * - */ - -package com.owncloud.android.ui.activity; - -import java.net.MalformedURLException; -import java.net.URL; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.authenticator.AuthenticationRunnable; -import com.owncloud.android.authenticator.OnAuthenticationResultListener; -import com.owncloud.android.authenticator.OnConnectCheckListener; -import com.owncloud.android.ui.dialog.SslValidatorDialog; -import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.ConnectionCheckOperation; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; - -import android.accounts.Account; -import android.accounts.AccountAuthenticatorActivity; -import android.accounts.AccountManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.preference.PreferenceManager; -import android.text.InputType; -import android.util.Log; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnFocusChangeListener; -import android.view.Window; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.TextView; -import com.owncloud.android.R; - -import eu.alefzero.webdav.WebdavClient; - -/** - * This Activity is used to add an ownCloud account to the App - * - * @author Bartek Przybylski - * - */ -public class AuthenticatorActivity extends AccountAuthenticatorActivity - implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener, - OnFocusChangeListener, OnClickListener { - - private static final int DIALOG_LOGIN_PROGRESS = 0; - private static final int DIALOG_SSL_VALIDATOR = 1; - private static final int DIALOG_CERT_NOT_SAVED = 2; - - private static final String TAG = "AuthActivity"; - - private Thread mAuthThread; - private AuthenticationRunnable mAuthRunnable; - //private ConnectionCheckerRunnable mConnChkRunnable = null; - private ConnectionCheckOperation mConnChkRunnable; - private final Handler mHandler = new Handler(); - private String mBaseUrl; - - private static final String STATUS_TEXT = "STATUS_TEXT"; - private static final String STATUS_ICON = "STATUS_ICON"; - private static final String STATUS_CORRECT = "STATUS_CORRECT"; - private static final String IS_SSL_CONN = "IS_SSL_CONN"; - private int mStatusText, mStatusIcon; - private boolean mStatusCorrect, mIsSslConn; - private RemoteOperationResult mLastSslUntrustedServerResult; - - public static final String PARAM_USERNAME = "param_Username"; - public static final String PARAM_HOSTNAME = "param_Hostname"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.account_setup); - ImageView iv = (ImageView) findViewById(R.id.refreshButton); - ImageView iv2 = (ImageView) findViewById(R.id.viewPassword); - TextView tv = (TextView) findViewById(R.id.host_URL); - TextView tv2 = (TextView) findViewById(R.id.account_password); - - if (savedInstanceState != null) { - mStatusIcon = savedInstanceState.getInt(STATUS_ICON); - mStatusText = savedInstanceState.getInt(STATUS_TEXT); - mStatusCorrect = savedInstanceState.getBoolean(STATUS_CORRECT); - mIsSslConn = savedInstanceState.getBoolean(IS_SSL_CONN); - setResultIconAndText(mStatusIcon, mStatusText); - findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); - if (!mStatusCorrect) - iv.setVisibility(View.VISIBLE); - else - iv.setVisibility(View.INVISIBLE); - - } else { - mStatusText = mStatusIcon = 0; - mStatusCorrect = false; - mIsSslConn = false; - } - iv.setOnClickListener(this); - iv2.setOnClickListener(this); - tv.setOnFocusChangeListener(this); - tv2.setOnFocusChangeListener(this); - - Button b = (Button) findViewById(R.id.account_register); - if (b != null) { - b.setText(String.format(getString(R.string.auth_register), getString(R.string.app_name))); - } - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - outState.putInt(STATUS_ICON, mStatusIcon); - outState.putInt(STATUS_TEXT, mStatusText); - outState.putBoolean(STATUS_CORRECT, mStatusCorrect); - super.onSaveInstanceState(outState); - } - - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog = null; - switch (id) { - case DIALOG_LOGIN_PROGRESS: { - ProgressDialog working_dialog = new ProgressDialog(this); - working_dialog.setMessage(getResources().getString( - R.string.auth_trying_to_login)); - working_dialog.setIndeterminate(true); - working_dialog.setCancelable(true); - working_dialog - .setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - Log_OC.i(TAG, "Login canceled"); - if (mAuthThread != null) { - mAuthThread.interrupt(); - finish(); - } - } - }); - dialog = working_dialog; - break; - } - case DIALOG_SSL_VALIDATOR: { - dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); - break; - } - case DIALOG_CERT_NOT_SAVED: { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved)); - builder.setCancelable(false); - builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - }; - }); - dialog = builder.create(); - break; - } - default: - Log_OC.e(TAG, "Incorrect dialog called with id = " + id); - } - return dialog; - } - - @Override - protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { - switch (id) { - case DIALOG_LOGIN_PROGRESS: - case DIALOG_CERT_NOT_SAVED: - break; - case DIALOG_SSL_VALIDATOR: { - ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); - break; - } - default: - Log_OC.e(TAG, "Incorrect dialog called with id = " + id); - } - } - - public void onAuthenticationResult(boolean success, String message) { - if (success) { - TextView username_text = (TextView) findViewById(R.id.account_username), password_text = (TextView) findViewById(R.id.account_password); - - URL url; - try { - url = new URL(message); - } catch (MalformedURLException e) { - // should never happen - Log_OC.e(getClass().getName(), "Malformed URL: " + message); - return; - } - - String username = username_text.getText().toString().trim(); - String accountName = username + "@" + url.getHost(); - if (url.getPort() >= 0) { - accountName += ":" + url.getPort(); - } - Account account = new Account(accountName, - AccountAuthenticator.ACCOUNT_TYPE); - AccountManager accManager = AccountManager.get(this); - accManager.addAccountExplicitly(account, password_text.getText() - .toString(), null); - - // Add this account as default in the preferences, if there is none - // already - Account defaultAccount = AccountUtils - .getCurrentOwnCloudAccount(this); - if (defaultAccount == null) { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(this).edit(); - editor.putString("select_oc_account", accountName); - editor.commit(); - } - - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, - AccountAuthenticator.ACCOUNT_TYPE); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name); - intent.putExtra(AccountManager.KEY_AUTHTOKEN, - AccountAuthenticator.ACCOUNT_TYPE); - intent.putExtra(AccountManager.KEY_USERDATA, username); - - accManager.setUserData(account, - AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable - .getDiscoveredVersion().toString()); - - accManager.setUserData(account, - AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl); - - setAccountAuthenticatorResult(intent.getExtras()); - setResult(RESULT_OK, intent); - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - //getContentResolver().startSync(ProviderTableMeta.CONTENT_URI, - // bundle); - ContentResolver.requestSync(account, "org.owncloud", bundle); - - /* - * if - * (mConnChkRunnable.getDiscoveredVersion().compareTo(OwnCloudVersion - * .owncloud_v2) >= 0) { Intent i = new Intent(this, - * ExtensionsAvailableActivity.class); startActivity(i); } - */ - - finish(); - } else { - try { - dismissDialog(DIALOG_LOGIN_PROGRESS); - } catch (IllegalArgumentException e) { - // NOTHING TO DO ; can't find out what situation that leads to the exception in this code, but user logs signal that it happens - } - TextView tv = (TextView) findViewById(R.id.account_username); - tv.setError(message + " "); // the extra spaces are a workaround for an ugly bug: - // 1. insert wrong credentials and connect - // 2. put the focus on the user name field with using hardware controls (don't touch the screen); the error is shown UNDER the field - // 3. touch the user name field; the software keyboard appears; the error popup is moved OVER the field and SHRINKED in width, losing the last word - // Seen, at least, in Android 2.x devices - } - } - public void onCancelClick(View view) { - setResult(RESULT_CANCELED); - finish(); - } - - public void onOkClick(View view) { - String prefix = ""; - String url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim(); - if (mIsSslConn) { - prefix = "https://"; - } else { - prefix = "http://"; - } - if (url.toLowerCase().startsWith("http://") - || url.toLowerCase().startsWith("https://")) { - prefix = ""; - } - continueConnection(prefix); - } - - public void onRegisterClick(View view) { - Intent register = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.url_account_register))); - setResult(RESULT_CANCELED); - startActivity(register); - } - - private void continueConnection(String prefix) { - String url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim(); - String username = ((TextView) findViewById(R.id.account_username)) - .getText().toString(); - String password = ((TextView) findViewById(R.id.account_password)) - .getText().toString(); - if (url.endsWith("/")) - url = url.substring(0, url.length() - 1); - - URL uri = null; - String webdav_path = AccountUtils.getWebdavPath(mConnChkRunnable - .getDiscoveredVersion()); - - if (webdav_path == null) { - onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title)); - return; - } - - try { - mBaseUrl = prefix + url; - String url_str = prefix + url + webdav_path; - uri = new URL(url_str); - } catch (MalformedURLException e) { - // should never happen - onAuthenticationResult(false, getString(R.string.auth_incorrect_address_title)); - return; - } - - showDialog(DIALOG_LOGIN_PROGRESS); - mAuthRunnable = new AuthenticationRunnable(uri, username, password, this); - mAuthRunnable.setOnAuthenticationResultListener(this, mHandler); - mAuthThread = new Thread(mAuthRunnable); - mAuthThread.start(); - } - - @Override - public void onConnectionCheckResult(ResultType type) { - mStatusText = mStatusIcon = 0; - mStatusCorrect = false; - String t_url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim().toLowerCase(); - - switch (type) { - case OK_SSL: - mIsSslConn = true; - mStatusIcon = android.R.drawable.ic_secure; - mStatusText = R.string.auth_secure_connection; - mStatusCorrect = true; - break; - case OK_NO_SSL: - mIsSslConn = false; - mStatusCorrect = true; - if (t_url.startsWith("http://") ) { - mStatusText = R.string.auth_connection_established; - mStatusIcon = R.drawable.ic_ok; - } else { - mStatusText = R.string.auth_nossl_plain_ok_title; - mStatusIcon = android.R.drawable.ic_partial_secure; - } - break; - case BAD_OC_VERSION: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_bad_oc_version_title; - break; - case WRONG_CONNECTION: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_wrong_connection_title; - break; - case TIMEOUT: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_timeout_title; - break; - case INCORRECT_ADDRESS: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_incorrect_address_title; - break; - case SSL_UNVERIFIED_SERVER: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_unverified_server_title; - break; - case SSL_INIT_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_general_error_title; - break; - case HOST_NOT_AVAILABLE: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unknown_host_title; - break; - case NO_NETWORK_CONNECTION: - mStatusIcon = R.drawable.no_network; - mStatusText = R.string.auth_no_net_conn_title; - break; - case INSTANCE_NOT_CONFIGURED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_not_configured_title; - break; - case UNKNOWN_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unknown_error_title; - break; - case FILE_NOT_FOUND: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_incorrect_path_title; - break; - default: - Log_OC.e(TAG, "Incorrect connection checker result type: " + type); - } - setResultIconAndText(mStatusIcon, mStatusText); - if (!mStatusCorrect) - findViewById(R.id.refreshButton).setVisibility(View.VISIBLE); - else - findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE); - findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); - } - - @Override - public void onFocusChange(View view, boolean hasFocus) { - if (view.getId() == R.id.host_URL) { - if (!hasFocus) { - TextView tv = ((TextView) findViewById(R.id.host_URL)); - String uri = tv.getText().toString().trim(); - if (uri.length() != 0) { - setResultIconAndText(R.drawable.progress_small, - R.string.auth_testing_connection); - //mConnChkRunnable = new ConnectionCheckerRunnable(uri, this); - mConnChkRunnable = new ConnectionCheckOperation(uri, this); - //mConnChkRunnable.setListener(this, mHandler); - //mAuthThread = new Thread(mConnChkRunnable); - //mAuthThread.start(); - WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this); - mAuthThread = mConnChkRunnable.execute(client, this, mHandler); - } else { - findViewById(R.id.refreshButton).setVisibility( - View.INVISIBLE); - setResultIconAndText(0, 0); - } - } else { - // avoids that the 'connect' button can be clicked if the test was previously passed - findViewById(R.id.buttonOK).setEnabled(false); - } - } else if (view.getId() == R.id.account_password) { - ImageView iv = (ImageView) findViewById(R.id.viewPassword); - if (hasFocus) { - iv.setVisibility(View.VISIBLE); - } else { - TextView v = (TextView) findViewById(R.id.account_password); - int input_type = InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD; - v.setInputType(input_type); - iv.setVisibility(View.INVISIBLE); - } - } - } - - private void setResultIconAndText(int drawable_id, int text_id) { - ImageView iv = (ImageView) findViewById(R.id.action_indicator); - TextView tv = (TextView) findViewById(R.id.status_text); - - if (drawable_id == 0 && text_id == 0) { - iv.setVisibility(View.INVISIBLE); - tv.setVisibility(View.INVISIBLE); - } else { - iv.setImageResource(drawable_id); - tv.setText(text_id); - iv.setVisibility(View.VISIBLE); - tv.setVisibility(View.VISIBLE); - } - } - - @Override - public void onClick(View v) { - if (v.getId() == R.id.refreshButton) { - onFocusChange(findViewById(R.id.host_URL), false); - } else if (v.getId() == R.id.viewPassword) { - EditText view = (EditText) findViewById(R.id.account_password); - int selectionStart = view.getSelectionStart(); - int selectionEnd = view.getSelectionEnd(); - int input_type = view.getInputType(); - if ((input_type & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { - input_type = InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD; - } else { - input_type = InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; - } - view.setInputType(input_type); - view.setSelection(selectionStart, selectionEnd); - } - } - - @Override - public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { - if (operation.equals(mConnChkRunnable)) { - - mStatusText = mStatusIcon = 0; - mStatusCorrect = false; - String t_url = ((TextView) findViewById(R.id.host_URL)).getText() - .toString().trim().toLowerCase(); - - switch (result.getCode()) { - case OK_SSL: - mIsSslConn = true; - mStatusIcon = android.R.drawable.ic_secure; - mStatusText = R.string.auth_secure_connection; - mStatusCorrect = true; - break; - - case OK_NO_SSL: - case OK: - mIsSslConn = false; - mStatusCorrect = true; - if (t_url.startsWith("http://") ) { - mStatusText = R.string.auth_connection_established; - mStatusIcon = R.drawable.ic_ok; - } else { - mStatusText = R.string.auth_nossl_plain_ok_title; - mStatusIcon = android.R.drawable.ic_partial_secure; - } - break; - - - case BAD_OC_VERSION: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_bad_oc_version_title; - break; - case WRONG_CONNECTION: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_wrong_connection_title; - break; - case TIMEOUT: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_timeout_title; - break; - case INCORRECT_ADDRESS: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_incorrect_address_title; - break; - - case SSL_RECOVERABLE_PEER_UNVERIFIED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_unverified_server_title; - mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); - break; - - case SSL_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_ssl_general_error_title; - break; - - case HOST_NOT_AVAILABLE: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unknown_host_title; - break; - case NO_NETWORK_CONNECTION: - mStatusIcon = R.drawable.no_network; - mStatusText = R.string.auth_no_net_conn_title; - break; - case INSTANCE_NOT_CONFIGURED: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_not_configured_title; - break; - case FILE_NOT_FOUND: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_incorrect_path_title; - break; - case UNHANDLED_HTTP_CODE: - case UNKNOWN_ERROR: - mStatusIcon = R.drawable.common_error; - mStatusText = R.string.auth_unknown_error_title; - break; - default: - Log_OC.e(TAG, "Incorrect connection checker result type: " + result.getHttpCode()); - } - setResultIconAndText(mStatusIcon, mStatusText); - if (!mStatusCorrect) - findViewById(R.id.refreshButton).setVisibility(View.VISIBLE); - else - findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE); - findViewById(R.id.buttonOK).setEnabled(mStatusCorrect); - } - } - - - public void onSavedCertificate() { - mAuthThread = mConnChkRunnable.retry(this, mHandler); - } - - @Override - public void onFailedSavingCertificate() { - showDialog(DIALOG_CERT_NOT_SAVED); - } - -} diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java index 62c5f46b..fbbc5dff 100644 --- a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java +++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -18,15 +18,15 @@ package com.owncloud.android.ui.activity; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.owncloud.android.Log_OC; +import com.actionbarsherlock.app.ActionBar; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.ui.dialog.ConflictsResolveDialog; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.Log_OC; -import android.accounts.Account; import android.content.Intent; import android.os.Bundle; @@ -35,36 +35,21 @@ import android.os.Bundle; * application. * * @author Bartek Przybylski - * + * @author David A. Velasco */ -public class ConflictsResolveActivity extends SherlockFragmentActivity implements OnConflictDecisionMadeListener { - - public static final String EXTRA_FILE = "FILE"; - public static final String EXTRA_ACCOUNT = "ACCOUNT"; +public class ConflictsResolveActivity extends FileActivity implements OnConflictDecisionMadeListener { private String TAG = ConflictsResolveActivity.class.getSimpleName(); - //private String mRemotePath; - - //private String mLocalPath; - - private OCFile mFile; - private Account mOCAccount; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - //mRemotePath = getIntent().getStringExtra("remotepath"); - //mLocalPath = getIntent().getStringExtra("localpath"); - mFile = getIntent().getParcelableExtra(EXTRA_FILE); - mOCAccount = getIntent().getParcelableExtra(EXTRA_ACCOUNT); - ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(mFile.getRemotePath(), this); - d.showDialog(this); + ActionBar actionBar = getSupportActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); } @Override - public void ConflictDecisionMade(Decision decision) { + public void conflictDecisionMade(Decision decision) { Intent i = new Intent(getApplicationContext(), FileUploader.class); switch (decision) { @@ -81,11 +66,39 @@ public class ConflictsResolveActivity extends SherlockFragmentActivity implement Log_OC.wtf(TAG, "Unhandled conflict decision " + decision); return; } - i.putExtra(FileUploader.KEY_ACCOUNT, mOCAccount); - i.putExtra(FileUploader.KEY_FILE, mFile); + i.putExtra(FileUploader.KEY_ACCOUNT, getAccount()); + i.putExtra(FileUploader.KEY_FILE, getFile()); i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); startService(i); finish(); } + + @Override + protected void onAccountSet(boolean stateWasRecovered) { + super.onAccountSet(stateWasRecovered); + if (getAccount() != null) { + OCFile file = getFile(); + if (getFile() == null) { + Log_OC.e(TAG, "No conflictive file received"); + finish(); + } else { + /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account + file = getStorageManager().getFileByPath(file.getRemotePath()); // file = null if not in the current Account + if (file != null) { + setFile(file); + ConflictsResolveDialog d = ConflictsResolveDialog.newInstance(file.getRemotePath(), this); + d.showDialog(this); + + } else { + // account was changed to a different one - just finish + finish(); + } + } + + } else { + finish(); + } + + } } diff --git a/src/com/owncloud/android/ui/activity/CopyToClipboardActivity.java b/src/com/owncloud/android/ui/activity/CopyToClipboardActivity.java new file mode 100644 index 00000000..b503c379 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/CopyToClipboardActivity.java @@ -0,0 +1,65 @@ +/* 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 . + * + */ + +package com.owncloud.android.ui.activity; + +import com.owncloud.android.R; + +import android.app.Activity; +import android.content.ClipData; +import android.content.Intent; +import android.os.Bundle; +import android.text.ClipboardManager; +import android.widget.Toast; + +/** + * Activity copying the text of the received Intent into the system clibpoard. + * + * @author David A. Velasco + */ +@SuppressWarnings("deprecation") +public class CopyToClipboardActivity extends Activity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // get the clipboard system service + ClipboardManager clipboardManager = (ClipboardManager) this.getSystemService(CLIPBOARD_SERVICE); + + // get the text to copy into the clipboard + Intent intent = getIntent(); + CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT); + + // and put the text the clipboard + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { + // API level >= 11 -> modern Clipboard + ClipData clip = ClipData.newPlainText("ownCloud was here", text); + ((android.content.ClipboardManager)clipboardManager).setPrimaryClip(clip); + + } else { + // API level >= 11 -> legacy Clipboard + clipboardManager.setText(text); + } + + // alert the user that the text is in the clipboard and we're done + Toast.makeText(this, R.string.clipboard_text_copied, Toast.LENGTH_SHORT).show(); + + finish(); + } + +} diff --git a/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java b/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java index 5ac6df92..cf69a48c 100644 --- a/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java +++ b/src/com/owncloud/android/ui/activity/ErrorsWhileCopyingHandlerActivity.java @@ -28,7 +28,6 @@ import android.os.Bundle; import android.os.Handler; import android.support.v4.app.DialogFragment; import android.text.method.ScrollingMovementMethod; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; @@ -40,12 +39,14 @@ import android.widget.TextView; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.owncloud.android.Log_OC; import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; + import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + /** @@ -119,6 +120,7 @@ public class ErrorsWhileCopyingHandlerActivity extends SherlockFragmentActivity /// customize buttons Button cancelBtn = (Button) findViewById(R.id.cancel); Button okBtn = (Button) findViewById(R.id.ok); + okBtn.setText(R.string.foreign_files_move); cancelBtn.setOnClickListener(this); okBtn.setOnClickListener(this); diff --git a/src/com/owncloud/android/ui/activity/FailedUploadActivity.java b/src/com/owncloud/android/ui/activity/FailedUploadActivity.java index e468ffb3..33432cfb 100644 --- a/src/com/owncloud/android/ui/activity/FailedUploadActivity.java +++ b/src/com/owncloud/android/ui/activity/FailedUploadActivity.java @@ -1,5 +1,24 @@ +/* ownCloud Android client application + * 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 . + * + */ + package com.owncloud.android.ui.activity; +import com.owncloud.android.R; + import android.app.Activity; import android.os.Bundle; import android.view.View; @@ -7,25 +26,13 @@ import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; -import com.owncloud.android.R; /** * This Activity is used to display a detail message for failed uploads * * The entry-point for this activity is the 'Failed upload Notification" * - * * @author andomaex / Matthias Baumann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License. (at - * your option) any later version. - * - * 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 de/ */ public class FailedUploadActivity extends Activity { @@ -38,8 +45,9 @@ public class FailedUploadActivity extends Activity { String message = getIntent().getStringExtra(MESSAGE); TextView textView = (TextView) findViewById(R.id.faild_upload_message); textView.setText(message); - Button close_button = (Button) findViewById(R.id.failed_uploadactivity_close_button); - close_button.setOnClickListener(new OnClickListener() { + Button closeBtn = (Button) findViewById(R.id.failed_uploadactivity_close_button); + + closeBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { finish(); diff --git a/src/com/owncloud/android/ui/activity/FileActivity.java b/src/com/owncloud/android/ui/activity/FileActivity.java new file mode 100644 index 00000000..be437db8 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/FileActivity.java @@ -0,0 +1,505 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 . + * + */ + +package com.owncloud.android.ui.activity; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.accounts.OperationCanceledException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.FileOperationsHelper; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +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.operations.CreateShareOperation; +import com.owncloud.android.operations.UnshareLinkOperation; + +import com.owncloud.android.services.OperationsService; +import com.owncloud.android.services.OperationsService.OperationsServiceBinder; +import com.owncloud.android.ui.dialog.LoadingDialog; +import com.owncloud.android.utils.Log_OC; + + +/** + * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s . + * + * @author David A. Velasco + */ +public class FileActivity extends SherlockFragmentActivity implements OnRemoteOperationListener { + + public static final String EXTRA_FILE = "com.owncloud.android.ui.activity.FILE"; + public static final String EXTRA_ACCOUNT = "com.owncloud.android.ui.activity.ACCOUNT"; + public static final String EXTRA_WAITING_TO_PREVIEW = "com.owncloud.android.ui.activity.WAITING_TO_PREVIEW"; + public static final String EXTRA_FROM_NOTIFICATION= "com.owncloud.android.ui.activity.FROM_NOTIFICATION"; + + public static final String TAG = FileActivity.class.getSimpleName(); + + private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; + + + /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. */ + private Account mAccount; + + /** Main {@link OCFile} handled by the activity.*/ + private OCFile mFile; + + /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud {@link Account} */ + private boolean mRedirectingToSetupAccount = false; + + /** Flag to signal when the value of mAccount was set */ + private boolean mAccountWasSet; + + /** Flag to signal when the value of mAccount was restored from a saved state */ + private boolean mAccountWasRestored; + + /** Flag to signal if the activity is launched by a notification */ + private boolean mFromNotification; + + /** Messages handler associated to the main thread and the life cycle of the activity */ + private Handler mHandler; + + /** Access point to the cached database for the current ownCloud {@link Account} */ + private FileDataStorageManager mStorageManager = null; + + private FileOperationsHelper mFileOperationsHelper; + + private ServiceConnection mOperationsServiceConnection = null; + + private OperationsServiceBinder mOperationsServiceBinder = null; + + + /** + * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of + * the {@link FileActivity}. + * + * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user + * is requested to create a new one. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mHandler = new Handler(); + mFileOperationsHelper = new FileOperationsHelper(); + Account account; + if(savedInstanceState != null) { + account = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT); + mFile = savedInstanceState.getParcelable(FileActivity.EXTRA_FILE); + mFromNotification = savedInstanceState.getBoolean(FileActivity.EXTRA_FROM_NOTIFICATION); + } else { + account = getIntent().getParcelableExtra(FileActivity.EXTRA_ACCOUNT); + mFile = getIntent().getParcelableExtra(FileActivity.EXTRA_FILE); + mFromNotification = getIntent().getBooleanExtra(FileActivity.EXTRA_FROM_NOTIFICATION, false); + } + + setAccount(account, savedInstanceState != null); + + mOperationsServiceConnection = new OperationsServiceConnection(); + bindService(new Intent(this, OperationsService.class), mOperationsServiceConnection, Context.BIND_AUTO_CREATE); + } + + + /** + * Since ownCloud {@link Account}s can be managed from the system setting menu, + * the existence of the {@link Account} associated to the instance must be checked + * every time it is restarted. + */ + @Override + protected void onRestart() { + super.onRestart(); + boolean validAccount = (mAccount != null && AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), mAccount.name)); + if (!validAccount) { + swapToDefaultAccount(); + } + } + + + @Override + protected void onStart() { + super.onStart(); + + if (mAccountWasSet) { + onAccountSet(mAccountWasRestored); + } + if (mOperationsServiceBinder != null) { + mOperationsServiceBinder.addOperationListener(FileActivity.this, mHandler); + } + } + + + @Override + protected void onStop() { + + if (mOperationsServiceBinder != null) { + mOperationsServiceBinder.removeOperationListener(this); + } + + super.onStop(); + } + + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mOperationsServiceConnection != null) { + unbindService(mOperationsServiceConnection); + mOperationsServiceBinder = null; + } + } + + + /** + * Sets and validates the ownCloud {@link Account} associated to the Activity. + * + * If not valid, tries to swap it for other valid and existing ownCloud {@link Account}. + * + * POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. + * + * @param account New {@link Account} to set. + * @param savedAccount When 'true', account was retrieved from a saved instance state. + */ + private void setAccount(Account account, boolean savedAccount) { + Account oldAccount = mAccount; + boolean validAccount = (account != null && AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), account.name)); + if (validAccount) { + mAccount = account; + mAccountWasSet = true; + mAccountWasRestored = (savedAccount || mAccount.equals(oldAccount)); + + } else { + swapToDefaultAccount(); + } + } + + + /** + * Tries to swap the current ownCloud {@link Account} for other valid and existing. + * + * If no valid ownCloud {@link Account} exists, the the user is requested + * to create a new ownCloud {@link Account}. + * + * POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. + * + * @return 'True' if the checked {@link Account} was valid. + */ + private void swapToDefaultAccount() { + // default to the most recently used account + Account newAccount = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext()); + if (newAccount == null) { + /// no account available: force account creation + createFirstAccount(); + mRedirectingToSetupAccount = true; + mAccountWasSet = false; + mAccountWasRestored = false; + + } else { + mAccountWasSet = true; + mAccountWasRestored = (newAccount.equals(mAccount)); + mAccount = newAccount; + } + } + + + /** + * Launches the account creation activity. To use when no ownCloud account is available + */ + private void createFirstAccount() { + AccountManager am = AccountManager.get(getApplicationContext()); + am.addAccount(MainApp.getAccountType(), + null, + null, + null, + this, + new AccountCreationCallback(), + null); + } + + + /** + * {@inheritDoc} + */ + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(FileActivity.EXTRA_FILE, mFile); + outState.putParcelable(FileActivity.EXTRA_ACCOUNT, mAccount); + outState.putBoolean(FileActivity.EXTRA_FROM_NOTIFICATION, mFromNotification); + } + + + /** + * Getter for the main {@link OCFile} handled by the activity. + * + * @return Main {@link OCFile} handled by the activity. + */ + public OCFile getFile() { + return mFile; + } + + + /** + * Setter for the main {@link OCFile} handled by the activity. + * + * @param file Main {@link OCFile} to be handled by the activity. + */ + public void setFile(OCFile file) { + mFile = file; + } + + + /** + * Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity is located. + * + * @return OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. + */ + public Account getAccount() { + return mAccount; + } + + /** + * @return Value of mFromNotification: True if the Activity is launched by a notification + */ + public boolean fromNotification() { + return mFromNotification; + } + + /** + * @return 'True' when the Activity is finishing to enforce the setup of a new account. + */ + protected boolean isRedirectingToSetupAccount() { + return mRedirectingToSetupAccount; + } + + + public OperationsServiceBinder getOperationsServiceBinder() { + return mOperationsServiceBinder; + } + + + /** + * Helper class handling a callback from the {@link AccountManager} after the creation of + * a new ownCloud {@link Account} finished, successfully or not. + * + * At this moment, only called after the creation of the first account. + * + * @author David A. Velasco + */ + public class AccountCreationCallback implements AccountManagerCallback { + + @Override + public void run(AccountManagerFuture future) { + FileActivity.this.mRedirectingToSetupAccount = false; + boolean accountWasSet = false; + if (future != null) { + try { + Bundle result; + result = future.getResult(); + String name = result.getString(AccountManager.KEY_ACCOUNT_NAME); + String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE); + if (AccountUtils.setCurrentOwnCloudAccount(getApplicationContext(), name)) { + setAccount(new Account(name, type), false); + accountWasSet = true; + } + } catch (OperationCanceledException e) { + Log_OC.d(TAG, "Account creation canceled"); + + } catch (Exception e) { + Log_OC.e(TAG, "Account creation finished in exception: ", e); + } + + } else { + Log_OC.e(TAG, "Account creation callback with null bundle"); + } + if (!accountWasSet) { + moveTaskToBack(true); + } + } + + } + + + /** + * Called when the ownCloud {@link Account} associated to the Activity was just updated. + * + * Child classes must grant that state depending on the {@link Account} is updated. + */ + protected void onAccountSet(boolean stateWasRecovered) { + if (getAccount() != null) { + mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); + + } else { + Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!"); + } + } + + + public FileDataStorageManager getStorageManager() { + return mStorageManager; + } + + + public OnRemoteOperationListener getRemoteOperationListener() { + return this; + } + + + public Handler getHandler() { + return mHandler; + } + + public FileOperationsHelper getFileOperationsHelper() { + return mFileOperationsHelper; + } + + /** + * + * @param operation Removal operation performed. + * @param result Result of the removal. + */ + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + Log_OC.d(TAG, "Received result of operation in FileActivity - common behaviour for all the FileActivities "); + if (operation instanceof CreateShareOperation) { + onCreateShareOperationFinish((CreateShareOperation) operation, result); + + } else if (operation instanceof UnshareLinkOperation) { + onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result); + + } + } + + private void onCreateShareOperationFinish(CreateShareOperation operation, RemoteOperationResult result) { + dismissLoadingDialog(); + if (result.isSuccess()) { + updateFileFromDB(); + + Intent sendIntent = operation.getSendIntent(); + startActivity(sendIntent); + + } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { // Error --> SHARE_NOT_FOUND + Toast t = Toast.makeText(this, getString(R.string.share_link_file_no_exist), Toast.LENGTH_LONG); + t.show(); + } else { // Generic error + // Show a Message, operation finished without success + Toast t = Toast.makeText(this, getString(R.string.share_link_file_error), Toast.LENGTH_LONG); + t.show(); + } + } + + + private void onUnshareLinkOperationFinish(UnshareLinkOperation operation, RemoteOperationResult result) { + dismissLoadingDialog(); + + if (result.isSuccess()){ + updateFileFromDB(); + + } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { // Error --> SHARE_NOT_FOUND + Toast t = Toast.makeText(this, getString(R.string.unshare_link_file_no_exist), Toast.LENGTH_LONG); + t.show(); + } else { // Generic error + // Show a Message, operation finished without success + Toast t = Toast.makeText(this, getString(R.string.unshare_link_file_error), Toast.LENGTH_LONG); + t.show(); + } + + } + + + private void updateFileFromDB(){ + OCFile file = getStorageManager().getFileByPath(getFile().getRemotePath()); + if (file != null) { + setFile(file); + } + } + + /** + * Show loading dialog + */ + public void showLoadingDialog() { + // Construct dialog + LoadingDialog loading = new LoadingDialog(getResources().getString(R.string.wait_a_moment)); + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + loading.show(ft, DIALOG_WAIT_TAG); + + } + + + /** + * Dismiss loading dialog + */ + public void dismissLoadingDialog(){ + Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG); + if (frag != null) { + LoadingDialog loading = (LoadingDialog) frag; + loading.dismiss(); + } + } + + + /** + * Implements callback methods for service binding. Passed as a parameter to { + */ + private class OperationsServiceConnection implements ServiceConnection { + + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + if (component.equals(new ComponentName(FileActivity.this, OperationsService.class))) { + Log_OC.d(TAG, "Operations service connected"); + mOperationsServiceBinder = (OperationsServiceBinder) service; + mOperationsServiceBinder.addOperationListener(FileActivity.this, mHandler); + if (!mOperationsServiceBinder.isPerformingBlockingOperation()) { + dismissLoadingDialog(); + } + + } else { + return; + } + } + + + @Override + public void onServiceDisconnected(ComponentName component) { + if (component.equals(new ComponentName(FileActivity.this, OperationsService.class))) { + Log_OC.d(TAG, "Operations service disconnected"); + mOperationsServiceBinder = null; + // TODO whatever could be waiting for the service is unbound + } + } + }; + +} diff --git a/src/com/owncloud/android/ui/activity/FileDetailActivity.java b/src/com/owncloud/android/ui/activity/FileDetailActivity.java deleted file mode 100644 index 711b07ad..00000000 --- a/src/com/owncloud/android/ui/activity/FileDetailActivity.java +++ /dev/null @@ -1,391 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ -package com.owncloud.android.ui.activity; - -import android.accounts.Account; -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.ServiceConnection; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.IBinder; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; -import android.util.Log; - -import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.MenuItem; -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.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.ui.fragment.FileDetailFragment; -import com.owncloud.android.ui.fragment.FileFragment; -import com.owncloud.android.ui.preview.PreviewMediaFragment; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; - -import com.owncloud.android.R; - -/** - * This activity displays the details of a file like its name, its size and so - * on. - * - * @author Bartek Przybylski - * @author David A. Velasco - */ -public class FileDetailActivity extends SherlockFragmentActivity implements FileFragment.ContainerActivity { - - public static final int DIALOG_SHORT_WAIT = 0; - - public static final String TAG = FileDetailActivity.class.getSimpleName(); - - public static final String EXTRA_MODE = "MODE"; - public static final int MODE_DETAILS = 0; - public static final int MODE_PREVIEW = 1; - - public static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; - - private boolean mConfigurationChangedToLandscape = false; - private FileDownloaderBinder mDownloaderBinder = null; - private ServiceConnection mDownloadConnection, mUploadConnection = null; - private FileUploaderBinder mUploaderBinder = null; - private boolean mWaitingToPreview; - - private OCFile mFile; - private Account mAccount; - - private FileDataStorageManager mStorageManager; - private DownloadFinishReceiver mDownloadFinishReceiver; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - mFile = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); - mAccount = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT); - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - - // check if configuration changed to large-land ; for a tablet being changed from portrait to landscape when in FileDetailActivity - Configuration conf = getResources().getConfiguration(); - mConfigurationChangedToLandscape = (conf.orientation == Configuration.ORIENTATION_LANDSCAPE && - (conf.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE - ); - - if (!mConfigurationChangedToLandscape) { - setContentView(R.layout.file_activity_details); - - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - - if (savedInstanceState == null) { - mWaitingToPreview = false; - createChildFragment(); - } else { - mWaitingToPreview = savedInstanceState.getBoolean(KEY_WAITING_TO_PREVIEW); - } - - mDownloadConnection = new DetailsServiceConnection(); - bindService(new Intent(this, FileDownloader.class), mDownloadConnection, Context.BIND_AUTO_CREATE); - mUploadConnection = new DetailsServiceConnection(); - bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE); - - - } else { - backToDisplayActivity(false); // the 'back' won't be effective until this.onStart() and this.onResume() are completed; - } - - } - - /** - * Creates the proper fragment depending upon the state of the handled {@link OCFile} and - * the requested {@link Intent}. - */ - private void createChildFragment() { - int mode = getIntent().getIntExtra(EXTRA_MODE, MODE_PREVIEW); - - Fragment newFragment = null; - if (PreviewMediaFragment.canBePreviewed(mFile) && mode == MODE_PREVIEW) { - if (mFile.isDown()) { - newFragment = new PreviewMediaFragment(mFile, mAccount); - - } else { - newFragment = new FileDetailFragment(mFile, mAccount); - mWaitingToPreview = true; - } - - } else { - newFragment = new FileDetailFragment(mFile, mAccount); - } - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.fragment, newFragment, FileDetailFragment.FTAG); - ft.commit(); - } - - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEY_WAITING_TO_PREVIEW, mWaitingToPreview); - } - - - @Override - public void onPause() { - super.onPause(); - if (mDownloadFinishReceiver != null) { - unregisterReceiver(mDownloadFinishReceiver); - mDownloadFinishReceiver = null; - } - } - - - @Override - public void onResume() { - super.onResume(); - if (!mConfigurationChangedToLandscape) { - // TODO this is probably unnecessary - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null && fragment instanceof FileDetailFragment) { - ((FileDetailFragment) fragment).updateFileDetails(false, false); - } - } - // Listen for download messages - IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_ADDED_MESSAGE); - downloadIntentFilter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE); - mDownloadFinishReceiver = new DownloadFinishReceiver(); - registerReceiver(mDownloadFinishReceiver, downloadIntentFilter); - } - - - /** Defines callbacks for service binding, passed to bindService() */ - private class DetailsServiceConnection implements ServiceConnection { - - @Override - public void onServiceConnected(ComponentName component, IBinder service) { - - if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) { - Log_OC.d(TAG, "Download service connected"); - mDownloaderBinder = (FileDownloaderBinder) service; - if (mWaitingToPreview) { - requestForDownload(); - } - - } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) { - Log_OC.d(TAG, "Upload service connected"); - mUploaderBinder = (FileUploaderBinder) service; - } else { - return; - } - - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - FileDetailFragment detailsFragment = (fragment instanceof FileDetailFragment) ? (FileDetailFragment) fragment : null; - if (detailsFragment != null) { - detailsFragment.listenForTransferProgress(); - detailsFragment.updateFileDetails(mWaitingToPreview, false); // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais()) - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) { - Log_OC.d(TAG, "Download service disconnected"); - mDownloaderBinder = null; - } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) { - Log_OC.d(TAG, "Upload service disconnected"); - mUploaderBinder = null; - } - } - }; - - - @Override - public void onDestroy() { - super.onDestroy(); - if (mDownloadConnection != null) { - unbindService(mDownloadConnection); - mDownloadConnection = null; - } - if (mUploadConnection != null) { - unbindService(mUploadConnection); - mUploadConnection = null; - } - } - - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - boolean returnValue = false; - - switch(item.getItemId()){ - case android.R.id.home: - backToDisplayActivity(true); - returnValue = true; - break; - default: - returnValue = super.onOptionsItemSelected(item); - } - - return returnValue; - } - - private void backToDisplayActivity(boolean moveToParent) { - Intent intent = new Intent(this, FileDisplayActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - OCFile targetFile = null; - if (mFile != null) { - targetFile = moveToParent ? mStorageManager.getFileById(mFile.getParentId()) : mFile; - } - intent.putExtra(FileDetailFragment.EXTRA_FILE, targetFile); - intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount); - startActivity(intent); - finish(); - } - - - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog = null; - switch (id) { - case DIALOG_SHORT_WAIT: { - ProgressDialog working_dialog = new ProgressDialog(this); - working_dialog.setMessage(getResources().getString( - R.string.wait_a_moment)); - working_dialog.setIndeterminate(true); - working_dialog.setCancelable(false); - dialog = working_dialog; - break; - } - default: - dialog = null; - } - return dialog; - } - - - /** - * {@inheritDoc} - */ - @Override - public void onFileStateChanged() { - // nothing to do here! - } - - - /** - * {@inheritDoc} - */ - @Override - public FileDownloaderBinder getFileDownloaderBinder() { - return mDownloaderBinder; - } - - - @Override - public FileUploaderBinder getFileUploaderBinder() { - return mUploaderBinder; - } - - - @Override - public void showFragmentWithDetails(OCFile file) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragment, new FileDetailFragment(file, mAccount), FileDetailFragment.FTAG); - transaction.commit(); - } - - - private void requestForDownload() { - if (!mDownloaderBinder.isDownloading(mAccount, mFile)) { - Intent i = new Intent(this, FileDownloader.class); - i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount); - i.putExtra(FileDownloader.EXTRA_FILE, mFile); - startService(i); - } - } - - - /** - * Class waiting for broadcast events from the {@link FielDownloader} service. - * - * Updates the UI when a download is started or finished, provided that it is relevant for the - * current file. - */ - private class DownloadFinishReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - boolean sameAccount = isSameAccount(context, intent); - String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - boolean samePath = (mFile != null && mFile.getRemotePath().equals(downloadedRemotePath)); - - if (sameAccount && samePath) { - updateChildFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)); - } - - removeStickyBroadcast(intent); - } - - private boolean isSameAccount(Context context, Intent intent) { - String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); - return (accountName != null && accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name)); - } - } - - - public void updateChildFragment(String downloadEvent, String downloadedRemotePath, boolean success) { - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null && fragment instanceof FileDetailFragment) { - FileDetailFragment detailsFragment = (FileDetailFragment) fragment; - OCFile fileInFragment = detailsFragment.getFile(); - if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) { - // this never should happen; fileInFragment should be always equals to mFile, that was compared to downloadedRemotePath in DownloadReceiver - mWaitingToPreview = false; - - } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) { - // grants that the progress bar is updated - detailsFragment.listenForTransferProgress(); - detailsFragment.updateFileDetails(true, false); - - } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) { - // refresh the details fragment - if (success && mWaitingToPreview) { - mFile = mStorageManager.getFileById(mFile.getFileId()); // update the file from database, for the local storage path - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.fragment, new PreviewMediaFragment(mFile, mAccount), FileDetailFragment.FTAG); - transaction.commit(); - mWaitingToPreview = false; - - } else { - detailsFragment.updateFileDetails(false, (success)); - // TODO error message if !success ¿? - } - } - } // TODO else if (fragment != null && fragment ) - - } - -} diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 88dd728a..f6136ed3 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -22,80 +22,77 @@ import java.io.File; import android.accounts.Account; import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.app.AlertDialog.Builder; import android.app.Dialog; +import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.content.pm.PackageInfo; +import android.content.SyncRequest; import android.content.res.Resources.NotFoundException; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +//import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.ActionBar.OnNavigationListener; -import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; +import com.owncloud.android.MainApp; import com.owncloud.android.R; -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.datamodel.DataStorageManager; -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.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileObserverService; import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.operations.CreateFolderOperation; + +import com.owncloud.android.lib.common.network.CertificateCombinedException; +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.operations.CreateShareOperation; import com.owncloud.android.operations.RemoveFileOperation; import com.owncloud.android.operations.RenameFileOperation; import com.owncloud.android.operations.SynchronizeFileOperation; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.syncadapter.FileSyncService; -import com.owncloud.android.ui.dialog.ChangelogDialog; +import com.owncloud.android.operations.SynchronizeFolderOperation; +import com.owncloud.android.operations.UnshareLinkOperation; +import com.owncloud.android.services.OperationsService; +import com.owncloud.android.syncadapter.FileSyncAdapter; import com.owncloud.android.ui.dialog.EditNameDialog; -import com.owncloud.android.ui.dialog.SslValidatorDialog; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; -import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog.OnSslUntrustedCertListener; import com.owncloud.android.ui.fragment.FileDetailFragment; import com.owncloud.android.ui.fragment.FileFragment; import com.owncloud.android.ui.fragment.OCFileListFragment; import com.owncloud.android.ui.preview.PreviewImageActivity; -import com.owncloud.android.ui.preview.PreviewImageFragment; 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.Log_OC; -import eu.alefzero.webdav.WebdavClient; /** * Displays, what files the user has available in his ownCloud. @@ -104,76 +101,61 @@ import eu.alefzero.webdav.WebdavClient; * @author David A. Velasco */ -public class FileDisplayActivity extends SherlockFragmentActivity implements - OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslValidatorListener, OnRemoteOperationListener, EditNameDialogListener { - +public class FileDisplayActivity extends HookActivity implements +OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNavigationListener, OnSslUntrustedCertListener, EditNameDialogListener { + private ArrayAdapter mDirectories; - private OCFile mCurrentDir = null; - private OCFile mCurrentFile = null; - private DataStorageManager mStorageManager; private SyncBroadcastReceiver mSyncBroadcastReceiver; private UploadFinishReceiver mUploadFinishReceiver; private DownloadFinishReceiver mDownloadFinishReceiver; + //private OperationsServiceReceiver mOperationsServiceReceiver; private FileDownloaderBinder mDownloaderBinder = null; private FileUploaderBinder mUploaderBinder = null; private ServiceConnection mDownloadConnection = null, mUploadConnection = null; private RemoteOperationResult mLastSslUntrustedServerResult = null; - - private OCFileListFragment mFileList; - + private boolean mDualPane; - private boolean mBackFromCreatingFirstAccount; + private View mLeftFragmentContainer; + private View mRightFragmentContainer; + + private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; + private static final String KEY_SYNC_IN_PROGRESS = "SYNC_IN_PROGRESS"; + //private static final String KEY_REFRESH_SHARES_IN_PROGRESS = "SHARES_IN_PROGRESS"; + private static final String KEY_WAITING_TO_SEND = "WAITING_TO_SEND"; + + public static final int DIALOG_SHORT_WAIT = 0; + private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 1; + //private static final int DIALOG_SSL_VALIDATOR = 2; + private static final int DIALOG_CERT_NOT_SAVED = 2; - private static final int DIALOG_SETUP_ACCOUNT = 0; - private static final int DIALOG_CREATE_DIR = 1; - public static final int DIALOG_SHORT_WAIT = 3; - private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 4; - private static final int DIALOG_SSL_VALIDATOR = 5; - private static final int DIALOG_CERT_NOT_SAVED = 6; - private static final String DIALOG_CHANGELOG_TAG = "DIALOG_CHANGELOG"; + public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS"; - private static final int ACTION_SELECT_CONTENT_FROM_APPS = 1; private static final int ACTION_SELECT_MULTIPLE_FILES = 2; - - private static final String TAG = "FileDisplayActivity"; + + private static final String TAG = FileDisplayActivity.class.getSimpleName(); + + private static final String TAG_LIST_OF_FILES = "LIST_OF_FILES"; + private static final String TAG_SECOND_FRAGMENT = "SECOND_FRAGMENT"; private OCFile mWaitingToPreview; - private Handler mHandler; + + private boolean mSyncInProgress = false; + //private boolean mRefreshSharesInProgress = false; + + private String DIALOG_UNTRUSTED_CERT; + + private OCFile mWaitingToSend; @Override - public void onCreate(Bundle savedInstanceState) { - Log_OC.d(getClass().toString(), "onCreate() start"); - super.onCreate(savedInstanceState); - - /// Load of parameters from received intent - Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT); - if (account != null && AccountUtils.setCurrentOwnCloudAccount(this, account.name)) { - mCurrentDir = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); - } - - /// Load of saved instance state: keep this always before initDataFromCurrentAccount() - if(savedInstanceState != null) { - // TODO - test if savedInstanceState should take precedence over file in the intent ALWAYS (now), NEVER. SOME TIMES - mCurrentDir = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE); - mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDetailActivity.KEY_WAITING_TO_PREVIEW); + protected void onCreate(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreate() start"); + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - } else { - mWaitingToPreview = null; - } - - if (!AccountUtils.accountsAreSetup(this)) { - /// no account available: FORCE ACCOUNT CREATION - mStorageManager = null; - createFirstAccount(); - - } else { /// at least an account is available - - initDataFromCurrentAccount(); // it checks mCurrentDir and mCurrentFile with the current account - - } - + super.onCreate(savedInstanceState); // this calls onAccountChanged() when ownCloud Account is valid + + /// bindings to transference services mUploadConnection = new ListServiceConnection(); mDownloadConnection = new ListServiceConnection(); bindService(new Intent(this, FileUploader.class), mUploadConnection, Context.BIND_AUTO_CREATE); @@ -182,244 +164,406 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements // PIN CODE request ; best location is to decide, let's try this first if (getIntent().getAction() != null && getIntent().getAction().equals(Intent.ACTION_MAIN) && savedInstanceState == null) { requestPinCode(); + } else if (getIntent().getAction() == null && savedInstanceState == null) { + requestPinCode(); } - // file observer + /// file observer Intent observer_intent = new Intent(this, FileObserverService.class); observer_intent.putExtra(FileObserverService.KEY_FILE_CMD, FileObserverService.CMD_INIT_OBSERVED_LIST); startService(observer_intent); - - + + /// Load of saved instance state + if(savedInstanceState != null) { + mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW); + mSyncInProgress = savedInstanceState.getBoolean(KEY_SYNC_IN_PROGRESS); + //mRefreshSharesInProgress = savedInstanceState.getBoolean(KEY_REFRESH_SHARES_IN_PROGRESS); + mWaitingToSend = (OCFile) savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_SEND); + + } else { + mWaitingToPreview = null; + mSyncInProgress = false; + //mRefreshSharesInProgress = false; + mWaitingToSend = null; + } + /// USER INTERFACE - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - - // Drop-down navigation - mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); - OCFile currFile = mCurrentDir; - while(mStorageManager != null && currFile != null && currFile.getFileName() != OCFile.PATH_SEPARATOR) { - mDirectories.add(currFile.getFileName()); - currFile = mStorageManager.getFileById(currFile.getParentId()); - } - mDirectories.add(OCFile.PATH_SEPARATOR); // Inflate and set the layout view setContentView(R.layout.files); - mFileList = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList); - mDualPane = (findViewById(R.id.file_details_container) != null); - if (mDualPane) { - if (savedInstanceState == null) initFileDetailsInDualPane(); + mDualPane = getResources().getBoolean(R.bool.large_land_layout); + mLeftFragmentContainer = findViewById(R.id.left_fragment_container); + mRightFragmentContainer = findViewById(R.id.right_fragment_container); + if (savedInstanceState == null) { + createMinFragments(); } else { - // quick patchES to fix problem in turn from landscape to portrait, when a file is selected in the right pane - // TODO serious refactorization in activities and fragments providing file browsing and handling - if (mCurrentFile != null) { - onFileClick(mCurrentFile); - mCurrentFile = null; - } - Fragment rightPanel = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (rightPanel != null) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.remove(rightPanel); - transaction.commit(); + Log_OC.d(TAG, "Init the secondFragment again"); + if (mDualPane) { + initFragmentsWithFile(); } } - + // Action bar setup - ActionBar actionBar = getSupportActionBar(); - actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation - actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null && mCurrentDir.getParentId() != 0); - actionBar.setDisplayShowTitleEnabled(false); - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); - actionBar.setListNavigationCallbacks(mDirectories, this); - setSupportProgressBarIndeterminateVisibility(false); // always AFTER setContentView(...) ; to workaround bug in its implementation - - - // show changelog, if needed - //showChangeLog(); - mBackFromCreatingFirstAccount = false; + mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); + getSupportActionBar().setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation + setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/); // always AFTER setContentView(...) ; to work around bug in its implementation - Log_OC.d(getClass().toString(), "onCreate() end"); + Log_OC.d(TAG, "onCreate() end"); } - + @Override + protected void onStart() { + super.onStart(); + getSupportActionBar().setIcon(DisplayUtils.getSeasonalIconId()); + refeshListOfFilesFragment(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mDownloadConnection != null) + unbindService(mDownloadConnection); + if (mUploadConnection != null) + unbindService(mUploadConnection); + } + /** - * Shows a dialog with the change log of the current version after each app update - * - * TODO make it permanent; by now, only to advice the workaround app for 4.1.x - */ - private void showChangeLog() { - if (android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.JELLY_BEAN) { - final String KEY_VERSION = "version"; - SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - int currentVersionNumber = 0; - int savedVersionNumber = sharedPref.getInt(KEY_VERSION, 0); - try { - PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0); - currentVersionNumber = pi.versionCode; - } catch (Exception e) {} - - if (currentVersionNumber > savedVersionNumber) { - ChangelogDialog.newInstance(true).show(getSupportFragmentManager(), DIALOG_CHANGELOG_TAG); - Editor editor = sharedPref.edit(); - editor.putInt(KEY_VERSION, currentVersionNumber); - editor.commit(); + * Called when the ownCloud {@link Account} associated to the Activity was just updated. + */ + @Override + protected void onAccountSet(boolean stateWasRecovered) { + super.onAccountSet(stateWasRecovered); + if (getAccount() != null) { + /// Check whether the 'main' OCFile handled by the Activity is contained in the current Account + OCFile file = getFile(); + // get parent from path + String parentPath = ""; + if (file != null) { + if (file.isDown() && file.getLastSyncDateForProperties() == 0) { + // upload in progress - right now, files are not inserted in the local cache until the upload is successful + // get parent from path + parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName())); + if (getStorageManager().getFileByPath(parentPath) == null) + file = null; // not able to know the directory where the file is uploading + } else { + file = getStorageManager().getFileByPath(file.getRemotePath()); // currentDir = null if not in the current Account + } + } + if (file == null) { + // fall back to root folder + file = getStorageManager().getFileByPath(OCFile.ROOT_PATH); // never returns null + } + setFile(file); + setNavigationListWithFolder(file); + + if (!stateWasRecovered) { + Log_OC.e(TAG, "Initializing Fragments in onAccountChanged.."); + initFragmentsWithFile(); + if (file.isFolder()) { + startSyncFolderOperation(file); + } + + } else { + updateFragmentsVisibility(!file.isFolder()); + updateNavigationElementsInActionBar(file.isFolder() ? null : file); } } } - - /** - * Launches the account creation activity. To use when no ownCloud account is available - */ - private void createFirstAccount() { - Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT); - intent.putExtra(android.provider.Settings.EXTRA_AUTHORITIES, new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE }); - startActivity(intent); // the new activity won't be created until this.onStart() and this.onResume() are finished; + + private void setNavigationListWithFolder(OCFile file) { + mDirectories.clear(); + OCFile fileIt = file; + String parentPath; + while(fileIt != null && fileIt.getFileName() != OCFile.ROOT_PATH) { + if (fileIt.isFolder()) { + mDirectories.add(fileIt.getFileName()); + } + // get parent from path + parentPath = fileIt.getRemotePath().substring(0, fileIt.getRemotePath().lastIndexOf(fileIt.getFileName())); + fileIt = getStorageManager().getFileByPath(parentPath); + } + mDirectories.add(OCFile.PATH_SEPARATOR); } + + private void createMinFragments() { + OCFileListFragment listOfFiles = new OCFileListFragment(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES); + transaction.commit(); + } + private void initFragmentsWithFile() { + if (getAccount() != null && getFile() != null) { + /// First fragment + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (listOfFiles != null) { + listOfFiles.listDirectory(getCurrentDir()); + } else { + Log.e(TAG, "Still have a chance to lose the initializacion of list fragment >("); + } + + /// Second fragment + OCFile file = getFile(); + Fragment secondFragment = chooseInitialSecondFragment(file); + if (secondFragment != null) { + setSecondFragment(secondFragment); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + + } else { + cleanSecondFragment(); + } + + } else { + Log.wtf(TAG, "initFragments() called with invalid NULLs!"); + if (getAccount() == null) { + Log.wtf(TAG, "\t account is NULL"); + } + if (getFile() == null) { + Log.wtf(TAG, "\t file is NULL"); + } + } + } + + private Fragment chooseInitialSecondFragment(OCFile file) { + Fragment secondFragment = null; + if (file != null && !file.isFolder()) { + if (file.isDown() && PreviewMediaFragment.canBePreviewed(file) + && file.getLastSyncDateForProperties() > 0 // temporal fix + ) { + int startPlaybackPosition = getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0); + boolean autoplay = getIntent().getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, true); + secondFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition, autoplay); + + } else { + secondFragment = new FileDetailFragment(file, getAccount()); + } + } + return secondFragment; + } + + /** - * Load of state dependent of the existence of an ownCloud account + * Replaces the second fragment managed by the activity with the received as + * a parameter. + * + * Assumes never will be more than two fragments managed at the same time. + * + * @param fragment New second Fragment to set. */ - private void initDataFromCurrentAccount() { - /// Storage manager initialization - access to local database - mStorageManager = new FileDataStorageManager( - AccountUtils.getCurrentOwnCloudAccount(this), - getContentResolver()); - - /// Check if mCurrentDir is a directory - if(mCurrentDir != null && !mCurrentDir.isDirectory()) { - mCurrentFile = mCurrentDir; - mCurrentDir = mStorageManager.getFileById(mCurrentDir.getParentId()); + private void setSecondFragment(Fragment fragment) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.right_fragment_container, fragment, TAG_SECOND_FRAGMENT); + transaction.commit(); + } + + + private void updateFragmentsVisibility(boolean existsSecondFragment) { + if (mDualPane) { + if (mLeftFragmentContainer.getVisibility() != View.VISIBLE) { + mLeftFragmentContainer.setVisibility(View.VISIBLE); + } + if (mRightFragmentContainer.getVisibility() != View.VISIBLE) { + mRightFragmentContainer.setVisibility(View.VISIBLE); + } + + } else if (existsSecondFragment) { + if (mLeftFragmentContainer.getVisibility() != View.GONE) { + mLeftFragmentContainer.setVisibility(View.GONE); + } + if (mRightFragmentContainer.getVisibility() != View.VISIBLE) { + mRightFragmentContainer.setVisibility(View.VISIBLE); + } + + } else { + if (mLeftFragmentContainer.getVisibility() != View.VISIBLE) { + mLeftFragmentContainer.setVisibility(View.VISIBLE); + } + if (mRightFragmentContainer.getVisibility() != View.GONE) { + mRightFragmentContainer.setVisibility(View.GONE); + } } - - /// Check if mCurrentDir and mCurrentFile are in the current account, and update them - if (mCurrentDir != null) { - mCurrentDir = mStorageManager.getFileByPath(mCurrentDir.getRemotePath()); // mCurrentDir == null if it is not in the current account + } + + + private OCFileListFragment getListOfFilesFragment() { + Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES); + if (listOfFiles != null) { + return (OCFileListFragment)listOfFiles; + } + Log_OC.wtf(TAG, "Access to unexisting list of files fragment!!"); + return null; + } + + protected FileFragment getSecondFragment() { + Fragment second = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_SECOND_FRAGMENT); + if (second != null) { + return (FileFragment)second; } - if (mCurrentFile != null) { - if (mCurrentFile.fileExists()) { - mCurrentFile = mStorageManager.getFileByPath(mCurrentFile.getRemotePath()); // mCurrentFile == null if it is not in the current account - } // else : keep mCurrentFile with the received value; this is currently the case of an upload in progress, when the user presses the status notification in a landscape tablet + return null; + } + + public void cleanSecondFragment() { + Fragment second = getSecondFragment(); + if (second != null) { + FragmentTransaction tr = getSupportFragmentManager().beginTransaction(); + tr.remove(second); + tr.commit(); } - - /// Default to root if mCurrentDir was not found - if (mCurrentDir == null) { - mCurrentDir = mStorageManager.getFileByPath("/"); // will be NULL if the database was never synchronized + updateFragmentsVisibility(false); + updateNavigationElementsInActionBar(null); + } + + protected void refeshListOfFilesFragment() { + OCFileListFragment fileListFragment = getListOfFilesFragment(); + if (fileListFragment != null) { + fileListFragment.listDirectory(); } } - - - private void initFileDetailsInDualPane() { - if (mDualPane && getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG) == null) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - if (mCurrentFile != null) { - if (PreviewMediaFragment.canBePreviewed(mCurrentFile)) { - if (mCurrentFile.isDown()) { - transaction.replace(R.id.file_details_container, new PreviewMediaFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - } else { - transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - mWaitingToPreview = mCurrentFile; + + protected void refreshSecondFragment(String downloadEvent, String downloadedRemotePath, boolean success) { + FileFragment secondFragment = getSecondFragment(); + boolean waitedPreview = (mWaitingToPreview != null && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath)); + if (secondFragment != null && secondFragment instanceof FileDetailFragment) { + FileDetailFragment detailsFragment = (FileDetailFragment) secondFragment; + OCFile fileInFragment = detailsFragment.getFile(); + if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) { + // the user browsed to other file ; forget the automatic preview + mWaitingToPreview = null; + + } else if (downloadEvent.equals(FileDownloader.getDownloadAddedMessage())) { + // grant that the right panel updates the progress bar + detailsFragment.listenForTransferProgress(); + detailsFragment.updateFileDetails(true, false); + + } else if (downloadEvent.equals(FileDownloader.getDownloadFinishMessage())) { + // update the right panel + boolean detailsFragmentChanged = false; + if (waitedPreview) { + if (success) { + mWaitingToPreview = getStorageManager().getFileById(mWaitingToPreview.getFileId()); // update the file from database, for the local storage path + if (PreviewMediaFragment.canBePreviewed(mWaitingToPreview)) { + startMediaPreview(mWaitingToPreview, 0, true); + detailsFragmentChanged = true; + } else { + getFileOperationsHelper().openFile(mWaitingToPreview, this); + } } - } else { - transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); + mWaitingToPreview = null; + } + if (!detailsFragmentChanged) { + detailsFragment.updateFileDetails(false, (success)); } - mCurrentFile = null; - - } else { - transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment } - transaction.commit(); } } - - - @Override - public void onDestroy() { - super.onDestroy(); - if (mDownloadConnection != null) - unbindService(mDownloadConnection); - if (mUploadConnection != null) - unbindService(mUploadConnection); - } - @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getSherlock().getMenuInflater(); - inflater.inflate(R.menu.main_menu, menu); - - return true; + inflater.inflate(R.menu.main_menu, menu); + return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { boolean retval = true; switch (item.getItemId()) { - case R.id.action_create_dir: { - EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.uploader_info_dirname), "", -1, -1, this); - dialog.show(getSupportFragmentManager(), "createdirdialog"); - break; - } - case R.id.action_sync_account: { - startSynchronization(); - break; - } - case R.id.action_upload: { - showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE); - break; - } - case R.id.action_settings: { - Intent settingsIntent = new Intent(this, Preferences.class); - startActivity(settingsIntent); - break; - } - case android.R.id.home: { - if(mCurrentDir != null && mCurrentDir.getParentId() != 0){ - onBackPressed(); - } - break; + case R.id.action_create_dir: { + EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.uploader_info_dirname), "", -1, -1, this); + dialog.show(getSupportFragmentManager(), "createdirdialog"); + break; + } + case R.id.action_sync_account: { + startSynchronization(); + break; + } + case R.id.action_upload: { + showDialog(DIALOG_CHOOSE_UPLOAD_SOURCE); + break; + } + case R.id.action_settings: { + Intent settingsIntent = new Intent(this, Preferences.class); + startActivity(settingsIntent); + break; + } + case android.R.id.home: { + FileFragment second = getSecondFragment(); + OCFile currentDir = getCurrentDir(); + if((currentDir != null && currentDir.getParentId() != 0) || + (second != null && second.getFile() != null)) { + onBackPressed(); + } - default: - retval = super.onOptionsItemSelected(item); + break; + } + default: + retval = super.onOptionsItemSelected(item); } return retval; } private void startSynchronization() { - ContentResolver.cancelSync(null, AccountAuthenticator.AUTH_TOKEN_TYPE); // cancel the current synchronizations of any ownCloud account - Bundle bundle = new Bundle(); - bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); - ContentResolver.requestSync( - AccountUtils.getCurrentOwnCloudAccount(this), - AccountAuthenticator.AUTH_TOKEN_TYPE, bundle); + Log_OC.e(TAG, "Got to start sync"); + if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.KITKAT) { + Log_OC.e(TAG, "Canceling all syncs for " + MainApp.getAuthority()); + ContentResolver.cancelSync(null, MainApp.getAuthority()); // cancel the current synchronizations of any ownCloud account + Bundle bundle = new Bundle(); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); + bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); + Log_OC.e(TAG, "Requesting sync for " + getAccount().name + " at " + MainApp.getAuthority()); + ContentResolver.requestSync( + getAccount(), + MainApp.getAuthority(), bundle); + } else { + Log_OC.e(TAG, "Requesting sync for " + getAccount().name + " at " + MainApp.getAuthority() + " with new API"); + SyncRequest.Builder builder = new SyncRequest.Builder(); + builder.setSyncAdapter(getAccount(), MainApp.getAuthority()); + builder.setExpedited(true); + builder.setManual(true); + builder.syncOnce(); + SyncRequest request = builder.build(); + ContentResolver.requestSync(request); + } } @Override public boolean onNavigationItemSelected(int itemPosition, long itemId) { - int i = itemPosition; - while (i-- != 0) { - onBackPressed(); + if (itemPosition != 0) { + String targetPath = ""; + for (int i=itemPosition; i < mDirectories.getCount() - 1; i++) { + targetPath = mDirectories.getItem(i) + OCFile.PATH_SEPARATOR + targetPath; + } + targetPath = OCFile.PATH_SEPARATOR + targetPath; + OCFile targetFolder = getStorageManager().getFileByPath(targetPath); + if (targetFolder != null) { + browseTo(targetFolder); + } + + // the next operation triggers a new call to this method, but it's necessary to + // ensure that the name exposed in the action bar is the current directory when the + // user selected it in the navigation list + if (getSupportActionBar().getNavigationMode() == ActionBar.NAVIGATION_MODE_LIST && itemPosition != 0) + getSupportActionBar().setSelectedNavigationItem(0); } - // the next operation triggers a new call to this method, but it's necessary to - // ensure that the name exposed in the action bar is the current directory when the - // user selected it in the navigation list - if (itemPosition != 0) - getSupportActionBar().setSelectedNavigationItem(0); return true; } /** * Called, when the user selected something for uploading */ - public void onActivityResult(int requestCode, int resultCode, Intent data) { - + 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); - + } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) { requestMultipleUpload(data, resultCode); - + } } @@ -438,16 +582,16 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements } Intent i = new Intent(this, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); + i.putExtra(FileUploader.KEY_ACCOUNT, getAccount()); i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths); i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths); i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES); if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); startService(i); - + } else { - Log_OC.d("FileDisplay", "User clicked on 'Update' with no selection"); + Log_OC.d(TAG, "User clicked on 'Update' with no selection"); Toast t = Toast.makeText(this, getString(R.string.filedisplay_no_file_selected), Toast.LENGTH_LONG); t.show(); return; @@ -467,14 +611,14 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements filepath = selectedImagePath; else filepath = filemanagerstring; - + } catch (Exception e) { - Log_OC.e("FileDisplay", "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e); + Log_OC.e(TAG, "Unexpected exception when trying to read the result of Intent.ACTION_GET_CONTENT", e); e.printStackTrace(); - + } finally { if (filepath == null) { - Log_OC.e("FileDisplay", "Couldnt resolve path to file"); + Log_OC.e(TAG, "Couldnt resolve path to file"); Toast t = Toast.makeText(this, getString(R.string.filedisplay_unexpected_bad_get_content), Toast.LENGTH_LONG); t.show(); return; @@ -483,7 +627,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements Intent i = new Intent(this, FileUploader.class); i.putExtra(FileUploader.KEY_ACCOUNT, - AccountUtils.getCurrentOwnCloudAccount(this)); + getAccount()); String remotepath = new String(); for (int j = mDirectories.getCount() - 2; j >= 0; --j) { remotepath += OCFile.PATH_SEPARATOR + mDirectories.getItem(j); @@ -500,104 +644,89 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements startService(i); } - @Override public void onBackPressed() { - if (mDirectories.getCount() <= 1) { - finish(); - return; - } - popDirname(); - mFileList.onNavigateUp(); - mCurrentDir = mFileList.getCurrentFile(); - - if (mDualPane) { - // Resets the FileDetailsFragment on Tablets so that it always displays - Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fileFragment != null && (fileFragment instanceof PreviewMediaFragment || !((FileDetailFragment) fileFragment).isEmpty())) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment - transaction.commit(); + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (mDualPane || getSecondFragment() == null) { + if (listOfFiles != null) { // should never be null, indeed + if (mDirectories.getCount() <= 1) { + finish(); + return; + } + int levelsUp = listOfFiles.onBrowseUp(); + for (int i=0; i < levelsUp && mDirectories.getCount() > 1 ; i++) { + popDirname(); + } } } - - if(mCurrentDir.getParentId() == 0){ - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(false); - } + if (listOfFiles != null) { // should never be null, indeed + setFile(listOfFiles.getCurrentFile()); + } + cleanSecondFragment(); + } @Override protected void onSaveInstanceState(Bundle outState) { // responsibility of restore is preferred in onCreate() before than in onRestoreInstanceState when there are Fragments involved - Log_OC.d(getClass().toString(), "onSaveInstanceState() start"); + Log_OC.e(TAG, "onSaveInstanceState() start"); super.onSaveInstanceState(outState); - outState.putParcelable(FileDetailFragment.EXTRA_FILE, mCurrentDir); - if (mDualPane) { - FileFragment fragment = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null) { - OCFile file = fragment.getFile(); - if (file != null) { - outState.putParcelable(FileDetailFragment.EXTRA_FILE, file); - } - } - } - outState.putParcelable(FileDetailActivity.KEY_WAITING_TO_PREVIEW, mWaitingToPreview); - Log_OC.d(getClass().toString(), "onSaveInstanceState() end"); - } + outState.putParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW, mWaitingToPreview); + outState.putBoolean(FileDisplayActivity.KEY_SYNC_IN_PROGRESS, mSyncInProgress); + //outState.putBoolean(FileDisplayActivity.KEY_REFRESH_SHARES_IN_PROGRESS, mRefreshSharesInProgress); + outState.putParcelable(FileDisplayActivity.KEY_WAITING_TO_SEND, mWaitingToSend); + Log_OC.d(TAG, "onSaveInstanceState() end"); + } + + @Override protected void onResume() { - Log_OC.d(getClass().toString(), "onResume() start"); super.onResume(); - - if (AccountUtils.accountsAreSetup(this)) { - - if (mStorageManager == null) { - // this is necessary for handling the come back to FileDisplayActivity when the first ownCloud account is created - initDataFromCurrentAccount(); - if (mDualPane) { - initFileDetailsInDualPane(); - } - mBackFromCreatingFirstAccount = true; - } - - // Listen for sync messages - IntentFilter syncIntentFilter = new IntentFilter(FileSyncService.SYNC_MESSAGE); - mSyncBroadcastReceiver = new SyncBroadcastReceiver(); - registerReceiver(mSyncBroadcastReceiver, syncIntentFilter); - - // Listen for upload messages - IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); - mUploadFinishReceiver = new UploadFinishReceiver(); - registerReceiver(mUploadFinishReceiver, uploadIntentFilter); - - // Listen for download messages - IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.DOWNLOAD_ADDED_MESSAGE); - downloadIntentFilter.addAction(FileDownloader.DOWNLOAD_FINISH_MESSAGE); - mDownloadFinishReceiver = new DownloadFinishReceiver(); - registerReceiver(mDownloadFinishReceiver, downloadIntentFilter); + Log_OC.e(TAG, "onResume() start"); + + // Listen for sync messages + IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START); + syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END); + //syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_SIZE_SYNCED); + 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); + mSyncBroadcastReceiver = new SyncBroadcastReceiver(); + registerReceiver(mSyncBroadcastReceiver, syncIntentFilter); + //LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver, syncIntentFilter); + + // Listen for upload messages + IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.getUploadFinishMessage()); + mUploadFinishReceiver = new UploadFinishReceiver(); + registerReceiver(mUploadFinishReceiver, uploadIntentFilter); + + // Listen for download messages + IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.getDownloadAddedMessage()); + downloadIntentFilter.addAction(FileDownloader.getDownloadFinishMessage()); + mDownloadFinishReceiver = new DownloadFinishReceiver(); + registerReceiver(mDownloadFinishReceiver, downloadIntentFilter); - // List current directory - mFileList.listDirectory(mCurrentDir); // TODO we should find the way to avoid the need of this (maybe it's not necessary yet; to check) - - } else { - - mStorageManager = null; // an invalid object will be there if all the ownCloud accounts are removed - showDialog(DIALOG_SETUP_ACCOUNT); - - } - Log_OC.d(getClass().toString(), "onResume() end"); + // Listen for messages from the OperationsService + /* + IntentFilter operationsIntentFilter = new IntentFilter(OperationsService.ACTION_OPERATION_ADDED); + operationsIntentFilter.addAction(OperationsService.ACTION_OPERATION_FINISHED); + mOperationsServiceReceiver = new OperationsServiceReceiver(); + LocalBroadcastManager.getInstance(this).registerReceiver(mOperationsServiceReceiver, operationsIntentFilter); + */ + + Log_OC.d(TAG, "onResume() end"); } - + @Override protected void onPause() { - Log_OC.d(getClass().toString(), "onPause() start"); super.onPause(); + Log_OC.e(TAG, "onPause() start"); if (mSyncBroadcastReceiver != null) { unregisterReceiver(mSyncBroadcastReceiver); + //LocalBroadcastManager.getInstance(this).unregisterReceiver(mSyncBroadcastReceiver); mSyncBroadcastReceiver = null; } if (mUploadFinishReceiver != null) { @@ -608,96 +737,21 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements unregisterReceiver(mDownloadFinishReceiver); mDownloadFinishReceiver = null; } - if (!AccountUtils.accountsAreSetup(this)) { - dismissDialog(DIALOG_SETUP_ACCOUNT); + /* + if (mOperationsServiceReceiver != null) { + LocalBroadcastManager.getInstance(this).unregisterReceiver(mOperationsServiceReceiver); + mOperationsServiceReceiver = null; } - - Log_OC.d(getClass().toString(), "onPause() end"); + */ + Log_OC.d(TAG, "onPause() end"); } - - @Override - protected void onPrepareDialog(int id, Dialog dialog, Bundle args) { - if (id == DIALOG_SSL_VALIDATOR && mLastSslUntrustedServerResult != null) { - ((SslValidatorDialog)dialog).updateResult(mLastSslUntrustedServerResult); - } - } - @Override protected Dialog onCreateDialog(int id) { Dialog dialog = null; AlertDialog.Builder builder; switch (id) { - case DIALOG_SETUP_ACCOUNT: { - builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.main_tit_accsetup); - builder.setMessage(R.string.main_wrn_accsetup); - builder.setCancelable(false); - builder.setPositiveButton(android.R.string.ok, new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - createFirstAccount(); - dialog.dismiss(); - } - }); - String message = String.format(getString(R.string.common_exit), getString(R.string.app_name)); - builder.setNegativeButton(message, new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - finish(); - } - }); - //builder.setNegativeButton(android.R.string.cancel, this); - dialog = builder.create(); - break; - } - case DIALOG_CREATE_DIR: { - builder = new Builder(this); - final EditText dirNameInput = new EditText(getBaseContext()); - builder.setView(dirNameInput); - builder.setTitle(R.string.uploader_info_dirname); - int typed_color = getResources().getColor(R.color.setup_text_typed); - dirNameInput.setTextColor(typed_color); - builder.setPositiveButton(android.R.string.ok, - new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String directoryName = dirNameInput.getText().toString(); - if (directoryName.trim().length() == 0) { - dialog.cancel(); - return; - } - - // Figure out the path where the dir needs to be created - String path; - if (mCurrentDir == null) { - // this is just a patch; we should ensure that mCurrentDir never is null - if (!mStorageManager.fileExists(OCFile.PATH_SEPARATOR)) { - OCFile file = new OCFile(OCFile.PATH_SEPARATOR); - mStorageManager.saveFile(file); - } - mCurrentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); - } - path = FileDisplayActivity.this.mCurrentDir.getRemotePath(); - - // Create directory - path += directoryName + OCFile.PATH_SEPARATOR; - Thread thread = new Thread(new DirectoryCreator(path, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), new Handler())); - thread.start(); - - dialog.dismiss(); - - showDialog(DIALOG_SHORT_WAIT); - } - }); - builder.setNegativeButton(R.string.common_cancel, - new OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }); - dialog = builder.create(); - break; - } case DIALOG_SHORT_WAIT: { ProgressDialog working_dialog = new ProgressDialog(this); working_dialog.setMessage(getResources().getString( @@ -708,44 +762,42 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements break; } case DIALOG_CHOOSE_UPLOAD_SOURCE: { - + String[] items = null; - + String[] allTheItems = { getString(R.string.actionbar_upload_files), - getString(R.string.actionbar_upload_from_apps), - getString(R.string.actionbar_failed_instant_upload) }; - + getString(R.string.actionbar_upload_from_apps), + getString(R.string.actionbar_failed_instant_upload) }; + String[] commonItems = { getString(R.string.actionbar_upload_files), - getString(R.string.actionbar_upload_from_apps) }; - + getString(R.string.actionbar_upload_from_apps) }; + if (InstantUploadActivity.IS_ENABLED) items = allTheItems; else items = commonItems; - + builder = new AlertDialog.Builder(this); builder.setTitle(R.string.actionbar_upload); builder.setItems(items, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { if (item == 0) { // if (!mDualPane) { - Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class); - action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, - AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this)); - startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES); - // } else { - // TODO create and handle new fragment - // LocalFileListFragment - // } + Intent action = new Intent(FileDisplayActivity.this, UploadFilesActivity.class); + action.putExtra(UploadFilesActivity.EXTRA_ACCOUNT, FileDisplayActivity.this.getAccount()); + startActivityForResult(action, ACTION_SELECT_MULTIPLE_FILES); + // } else { + // TODO create and handle new fragment + // LocalFileListFragment + // } } else if (item == 1) { Intent action = new Intent(Intent.ACTION_GET_CONTENT); action = action.setType("*/*").addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(Intent.createChooser(action, getString(R.string.upload_chooser_title)), ACTION_SELECT_CONTENT_FROM_APPS); } else if (item == 2 && InstantUploadActivity.IS_ENABLED) { - Account account = AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this); Intent action = new Intent(FileDisplayActivity.this, InstantUploadActivity.class); - action.putExtra(FileUploader.KEY_ACCOUNT, account); + action.putExtra(FileUploader.KEY_ACCOUNT, FileDisplayActivity.this.getAccount()); startActivity(action); } } @@ -753,31 +805,27 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements dialog = builder.create(); break; } - case DIALOG_SSL_VALIDATOR: { - dialog = SslValidatorDialog.newInstance(this, mLastSslUntrustedServerResult, this); - break; - } case DIALOG_CERT_NOT_SAVED: { builder = new AlertDialog.Builder(this); builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved)); builder.setCancelable(false); builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - }; - }); + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + }; + }); dialog = builder.create(); break; } default: dialog = null; } - + return dialog; } - + /** * Translates a content URI of an image to a physical path * on the disk @@ -795,18 +843,18 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements } return null; } - + /** * Pushes a directory to the drop down list * @param directory to push - * @throws IllegalArgumentException If the {@link OCFile#isDirectory()} returns false. + * @throws IllegalArgumentException If the {@link OCFile#isFolder()} returns false. */ public void pushDirname(OCFile directory) { - if(!directory.isDirectory()){ + if(!directory.isFolder()){ throw new IllegalArgumentException("Only directories may be pushed!"); } mDirectories.insert(directory.getFileName(), 0); - mCurrentDir = directory; + setFile(directory); } /** @@ -818,135 +866,120 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements return !mDirectories.isEmpty(); } - private class DirectoryCreator implements Runnable { - private String mTargetPath; - private Account mAccount; - private Handler mHandler; - - public DirectoryCreator(String targetPath, Account account, Handler handler) { - mTargetPath = targetPath; - mAccount = account; - mHandler = handler; - } - - @Override - public void run() { - WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); - boolean created = wdc.createDirectory(mTargetPath); - if (created) { - mHandler.post(new Runnable() { - @Override - public void run() { - dismissDialog(DIALOG_SHORT_WAIT); - - // Save new directory in local database - OCFile newDir = new OCFile(mTargetPath); - newDir.setMimetype("DIR"); - newDir.setParentId(mCurrentDir.getFileId()); - mStorageManager.saveFile(newDir); - - // Display the new folder right away - mFileList.listDirectory(); - } - }); - - } else { - mHandler.post(new Runnable() { - @Override - public void run() { - dismissDialog(DIALOG_SHORT_WAIT); - try { - Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); - msg.show(); - - } catch (NotFoundException e) { - Log_OC.e(TAG, "Error while trying to show fail message ", e); - } - } - }); - } - } - - } - // Custom array adapter to override text colors private class CustomArrayAdapter extends ArrayAdapter { - + public CustomArrayAdapter(FileDisplayActivity ctx, int view) { super(ctx, view); } - + public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); - + ((TextView) v).setTextColor(getResources().getColorStateList( android.R.color.white)); + + fixRoot((TextView) v ); return v; } - + public View getDropDownView(int position, View convertView, ViewGroup parent) { View v = super.getDropDownView(position, convertView, parent); - + ((TextView) v).setTextColor(getResources().getColorStateList( android.R.color.white)); - + + fixRoot((TextView) v ); return v; } - + + private void fixRoot(TextView v) { + if (v.getText().equals(OCFile.PATH_SEPARATOR)) { + v.setText(R.string.default_display_name_for_root_folder); + } + } + } private class SyncBroadcastReceiver extends BroadcastReceiver { /** * {@link BroadcastReceiver} to enable syncing feedback in UI - */ - @Override - public void onReceive(Context context, Intent intent) { - boolean inProgress = intent.getBooleanExtra(FileSyncService.IN_PROGRESS, false); - String accountName = intent.getStringExtra(FileSyncService.ACCOUNT_NAME); - - Log_OC.d("FileDisplay", "sync of account " + accountName + " is in_progress: " + inProgress); - - if (accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name)) { - - String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); - - boolean fillBlankRoot = false; - if (mCurrentDir == null) { - mCurrentDir = mStorageManager.getFileByPath("/"); - fillBlankRoot = (mCurrentDir != null); - } - - if ((synchFolderRemotePath != null && mCurrentDir != null && (mCurrentDir.getRemotePath().equals(synchFolderRemotePath))) - || fillBlankRoot ) { - if (!fillBlankRoot) - mCurrentDir = getStorageManager().getFileByPath(synchFolderRemotePath); - OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager() - .findFragmentById(R.id.fileList); - if (fileListFragment != null) { - fileListFragment.listDirectory(mCurrentDir); - } + */ + @Override + public void onReceive(Context context, Intent intent) { + try { + String event = intent.getAction(); + Log_OC.d(TAG, "Received broadcast " + event); + String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME); + String synchFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH); + RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncAdapter.EXTRA_RESULT); + boolean sameAccount = (getAccount() != null && accountName.equals(getAccount().name) && getStorageManager() != null); + + if (sameAccount) { + + if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) { + mSyncInProgress = true; + + } else { + OCFile currentFile = (getFile() == null) ? null : getStorageManager().getFileByPath(getFile().getRemotePath()); + OCFile currentDir = (getCurrentDir() == null) ? null : getStorageManager().getFileByPath(getCurrentDir().getRemotePath()); + + if (currentDir == null) { + // current folder was removed from the server + Toast.makeText( FileDisplayActivity.this, + String.format(getString(R.string.sync_current_folder_was_removed), mDirectories.getItem(0)), + Toast.LENGTH_LONG) + .show(); + browseToRoot(); + + } else { + if (currentFile == null && !getFile().isFolder()) { + // currently selected file was removed in the server, and now we know it + cleanSecondFragment(); + currentFile = currentDir; + } + + if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) { + OCFileListFragment fileListFragment = getListOfFilesFragment(); + if (fileListFragment != null) { + fileListFragment.listDirectory(currentDir); + } + } + setFile(currentFile); + } + + mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event)); + + /* + if (synchResult != null && + synchResult.isSuccess() && + (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SYNCED.equals(event) || + FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED.equals(event) + ) && + !mRefreshSharesInProgress && + getFileOperationsHelper().isSharedSupported(FileDisplayActivity.this) + ) { + startGetShares(); + } + */ + + } + removeStickyBroadcast(intent); + Log_OC.d(TAG, "Setting progress visibility to " + mSyncInProgress); + setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/); } - setSupportProgressBarIndeterminateVisibility(inProgress); - if (mBackFromCreatingFirstAccount) { - // awful patch to fix problem with visibility of progress circle with the first refresh of the first account - // TODO - kill this Activity when the first account has to be created instead of stack the account creation on it - getSupportActionBar().hide(); - getSupportActionBar().show(); - mBackFromCreatingFirstAccount = false; + if (synchResult != null) { + if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) { + mLastSslUntrustedServerResult = synchResult; + } } + } catch (RuntimeException e) { + // avoid app crashes after changing the serial id of RemoteOperationResult + // in owncloud library with broadcast notifications pending to process removeStickyBroadcast(intent); - - } - - RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT); - if (synchResult != null) { - if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) { - mLastSslUntrustedServerResult = synchResult; - showDialog(DIALOG_SSL_VALIDATOR); - } } } } @@ -962,19 +995,17 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements public void onReceive(Context context, Intent intent) { String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME); - boolean sameAccount = accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name); - boolean isDescendant = (mCurrentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(mCurrentDir.getRemotePath())); + boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name); + OCFile currentDir = getCurrentDir(); + boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(currentDir.getRemotePath())); if (sameAccount && isDescendant) { - OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList); - if (fileListFragment != null) { - fileListFragment.listDirectory(); - } + refeshListOfFilesFragment(); } } - + } - - + + /** * Class waiting for broadcast events from the {@link FielDownloader} service. * @@ -987,11 +1018,16 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements boolean sameAccount = isSameAccount(context, intent); String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); boolean isDescendant = isDescendant(downloadedRemotePath); - + if (sameAccount && isDescendant) { - updateLeftPanel(); - if (mDualPane) { - updateRightPanel(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)); + refeshListOfFilesFragment(); + refreshSecondFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)); + } + + if (mWaitingToSend != null) { + mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath()); // Update the file to send + if (mWaitingToSend.isDown()) { + sendDownloadedFile(); } } @@ -999,183 +1035,202 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements } private boolean isDescendant(String downloadedRemotePath) { - return (mCurrentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(mCurrentDir.getRemotePath())); + OCFile currentDir = getCurrentDir(); + return (currentDir != null && downloadedRemotePath != null && downloadedRemotePath.startsWith(currentDir.getRemotePath())); } private boolean isSameAccount(Context context, Intent intent) { String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); - return (accountName != null && accountName.equals(AccountUtils.getCurrentOwnCloudAccount(context).name)); + return (accountName != null && getAccount() != null && accountName.equals(getAccount().name)); } } - protected void updateLeftPanel() { - OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList); - if (fileListFragment != null) { - fileListFragment.listDirectory(); - } - } + /** + * Class waiting for broadcast events from the {@link OperationsService}. + * + * Updates the list of files when a get for shares is finished; at this moment the refresh of shares is the only + * operation performed in {@link OperationsService}. + * + * In the future will handle the progress or finalization of all the operations performed in {@link OperationsService}, + * probably all the operations associated to the app model. + */ + private class OperationsServiceReceiver extends BroadcastReceiver { - protected void updateRightPanel(String downloadEvent, String downloadedRemotePath, boolean success) { - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - boolean waitedPreview = (mWaitingToPreview != null && mWaitingToPreview.getRemotePath().equals(downloadedRemotePath)); - if (fragment != null && fragment instanceof FileDetailFragment) { - FileDetailFragment detailsFragment = (FileDetailFragment) fragment; - OCFile fileInFragment = detailsFragment.getFile(); - if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) { - // the user browsed to other file ; forget the automatic preview - mWaitingToPreview = null; + @Override + public void onReceive(Context context, Intent intent) { + if (OperationsService.ACTION_OPERATION_ADDED.equals(intent.getAction())) { - } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_ADDED_MESSAGE)) { - // grant that the right panel updates the progress bar - detailsFragment.listenForTransferProgress(); - detailsFragment.updateFileDetails(true, false); + } else if (OperationsService.ACTION_OPERATION_FINISHED.equals(intent.getAction())) { + //mRefreshSharesInProgress = false; - } else if (downloadEvent.equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) { - // update the right panel - if (success && waitedPreview) { - mWaitingToPreview = mStorageManager.getFileById(mWaitingToPreview.getFileId()); // update the file from database, for the local storage path - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new PreviewMediaFragment(mWaitingToPreview, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - transaction.commit(); - mWaitingToPreview = null; - - } else { - detailsFragment.updateFileDetails(false, (success)); + Account account = intent.getParcelableExtra(OperationsService.EXTRA_ACCOUNT); + RemoteOperationResult getSharesResult = (RemoteOperationResult)intent.getSerializableExtra(OperationsService.EXTRA_RESULT); + if (getAccount() != null && account.name.equals(getAccount().name) + && getStorageManager() != null + ) { + refeshListOfFilesFragment(); + } + if ((getSharesResult != null) && + RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED.equals(getSharesResult.getCode())) { + mLastSslUntrustedServerResult = getSharesResult; + showUntrustedCertDialog(mLastSslUntrustedServerResult); } + + //setSupportProgressBarIndeterminateVisibility(mRefreshSharesInProgress || mSyncInProgress); } + } + } - - /** - * {@inheritDoc} - */ - @Override - public DataStorageManager getStorageManager() { - return mStorageManager; + public void browseToRoot() { + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (listOfFiles != null) { // should never be null, indeed + while (mDirectories.getCount() > 1) { + popDirname(); + } + OCFile root = getStorageManager().getFileByPath(OCFile.ROOT_PATH); + listOfFiles.listDirectory(root); + setFile(listOfFiles.getCurrentFile()); + startSyncFolderOperation(root); + } + cleanSecondFragment(); } + + public void browseTo(OCFile folder) { + if (folder == null || !folder.isFolder()) { + throw new IllegalArgumentException("Trying to browse to invalid folder " + folder); + } + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (listOfFiles != null) { + setNavigationListWithFolder(folder); + listOfFiles.listDirectory(folder); + setFile(listOfFiles.getCurrentFile()); + startSyncFolderOperation(folder); + } else { + Log_OC.e(TAG, "Unexpected null when accessing list fragment"); + } + cleanSecondFragment(); + } + /** * {@inheritDoc} + * + * Updates action bar and second fragment, if in dual pane mode. */ @Override - public void onDirectoryClick(OCFile directory) { + public void onBrowsedDownTo(OCFile directory) { pushDirname(directory); - ActionBar actionBar = getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); + cleanSecondFragment(); + + // Sync Folder + startSyncFolderOperation(directory); - if (mDualPane) { - // Resets the FileDetailsFragment on Tablets so that it always displays - Fragment fileFragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fileFragment != null && (fileFragment instanceof PreviewMediaFragment || !((FileDetailFragment) fileFragment).isEmpty())) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment - transaction.commit(); - } - } } - - + /** - * {@inheritDoc} + * Opens the image gallery showing the image {@link OCFile} received as parameter. + * + * @param file Image {@link OCFile} to show. */ @Override - public void onFileClick(OCFile file) { - if (file != null && PreviewImageFragment.canBePreviewed(file)) { - // preview image - it handles the download, if needed - startPreviewImage(file); - - } else if (file != null && PreviewMediaFragment.canBePreviewed(file)) { - if (file.isDown()) { - // general preview - startMediaPreview(file); - - } else { - // automatic download, preview on finish - startDownloadForPreview(file); - - } - } else { - // details view - startDetails(file); - } - } - - private void startPreviewImage(OCFile file) { + public void startImagePreview(OCFile file) { Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); + showDetailsIntent.putExtra(EXTRA_FILE, file); + showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount()); startActivity(showDetailsIntent); } - - private void startMediaPreview(OCFile file) { - if (mDualPane) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new PreviewMediaFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - transaction.commit(); - - } else { - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); - startActivity(showDetailsIntent); - } - } - - private void startDownloadForPreview(OCFile file) { - if (mDualPane) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - transaction.commit(); - mWaitingToPreview = file; - requestForDownload(); - - } else { - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); - startActivity(showDetailsIntent); - } + + /** + * Stars the preview of an already down media {@link OCFile}. + * + * @param file Media {@link OCFile} to preview. + * @param startPlaybackPosition Media position where the playback will be started, in milliseconds. + * @param autoplay When 'true', the playback will start without user interactions. + */ + @Override + public void startMediaPreview(OCFile file, int startPlaybackPosition, boolean autoplay) { + Fragment mediaFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition, autoplay); + setSecondFragment(mediaFragment); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + setFile(file); } - - private void startDetails(OCFile file) { - if (mDualPane && !file.isImage()) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - transaction.commit(); - } else { - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); - startActivity(showDetailsIntent); - } + /** + * Requests the download of the received {@link OCFile} , updates the UI + * to monitor the download progress and prepares the activity to preview + * or open the file when the download finishes. + * + * @param file {@link OCFile} to download and preview. + */ + @Override + public void startDownloadForPreview(OCFile file) { + Fragment detailFragment = new FileDetailFragment(file, getAccount()); + setSecondFragment(detailFragment); + mWaitingToPreview = file; + requestForDownload(); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + setFile(file); } /** - * {@inheritDoc} + * Shows the information of the {@link OCFile} received as a + * parameter in the second fragment. + * + * @param file {@link OCFile} whose details will be shown */ @Override - public OCFile getInitialDirectory() { - return mCurrentDir; + public void showDetails(OCFile file) { + Fragment detailFragment = new FileDetailFragment(file, getAccount()); + setSecondFragment(detailFragment); + updateFragmentsVisibility(true); + updateNavigationElementsInActionBar(file); + setFile(file); } - - + + + /** + * TODO + */ + private void updateNavigationElementsInActionBar(OCFile chosenFile) { + ActionBar actionBar = getSupportActionBar(); + if (chosenFile == null || mDualPane) { + // only list of files - set for browsing through folders + OCFile currentDir = getCurrentDir(); + boolean noRoot = (currentDir != null && currentDir.getParentId() != 0); + actionBar.setDisplayHomeAsUpEnabled(noRoot); + actionBar.setDisplayShowTitleEnabled(!noRoot); + if (!noRoot) { + actionBar.setTitle(getString(R.string.default_display_name_for_root_folder)); + } + actionBar.setNavigationMode(!noRoot ? ActionBar.NAVIGATION_MODE_STANDARD : ActionBar.NAVIGATION_MODE_LIST); + actionBar.setListNavigationCallbacks(mDirectories, this); // assuming mDirectories is updated + + } else { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setTitle(chosenFile.getFileName()); + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD); + } + } + + /** * {@inheritDoc} */ @Override public void onFileStateChanged() { - OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList); - if (fileListFragment != null) { - fileListFragment.listDirectory(); - } + refeshListOfFilesFragment(); + updateNavigationElementsInActionBar(getSecondFragment().getFile()); } - + /** * {@inheritDoc} */ @@ -1184,7 +1239,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements return mDownloaderBinder; } - + /** * {@inheritDoc} */ @@ -1192,8 +1247,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements public FileUploaderBinder getFileUploaderBinder() { return mUploaderBinder; } - - + + /** Defines callbacks for service binding, passed to bindService() */ private class ListServiceConnection implements ServiceConnection { @@ -1205,7 +1260,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements if (mWaitingToPreview != null) { requestForDownload(); } - + } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { Log_OC.d(TAG, "Upload service connected"); mUploaderBinder = (FileUploaderBinder) service; @@ -1213,15 +1268,15 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements return; } // a new chance to get the mDownloadBinder through getFileDownloadBinder() - THIS IS A MESS - if (mFileList != null) - mFileList.listDirectory(); - if (mDualPane) { - Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (fragment != null && fragment instanceof FileDetailFragment) { - FileDetailFragment detailFragment = (FileDetailFragment)fragment; - detailFragment.listenForTransferProgress(); - detailFragment.updateFileDetails(false, false); - } + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (listOfFiles != null) { + listOfFiles.listDirectory(); + } + FileFragment secondFragment = getSecondFragment(); + if (secondFragment != null && secondFragment instanceof FileDetailFragment) { + FileDetailFragment detailFragment = (FileDetailFragment)secondFragment; + detailFragment.listenForTransferProgress(); + detailFragment.updateFileDetails(false, false); } } @@ -1237,8 +1292,8 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements } }; - - + + /** * Launch an intent to request the PIN code to the user before letting him use the app */ @@ -1256,7 +1311,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements @Override public void onSavedCertificate() { - startSynchronization(); + startSyncFolderOperation(getCurrentDir()); } @@ -1265,6 +1320,10 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements showDialog(DIALOG_CERT_NOT_SAVED); } + @Override + public void onCancelCertificate() { + // nothing to do + } /** * Updates the view associated to the activity after the finish of some operation over files @@ -1275,18 +1334,66 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements */ @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + super.onRemoteOperationFinish(operation, result); + if (operation instanceof RemoveFileOperation) { onRemoveFileOperationFinish((RemoveFileOperation)operation, result); - + } else if (operation instanceof RenameFileOperation) { onRenameFileOperationFinish((RenameFileOperation)operation, result); - + } else if (operation instanceof SynchronizeFileOperation) { onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result); - } + + } else if (operation instanceof CreateFolderOperation) { + onCreateFolderOperationFinish((CreateFolderOperation)operation, result); + + } else if (operation instanceof CreateShareOperation) { + onCreateShareOperationFinish((CreateShareOperation) operation, result); + + } else if (operation instanceof UnshareLinkOperation) { + onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result); + + } + } + + private void onCreateShareOperationFinish(CreateShareOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + refreshShowDetails(); + refeshListOfFilesFragment(); + } + } + + private void onUnshareLinkOperationFinish(UnshareLinkOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + refreshShowDetails(); + refeshListOfFilesFragment(); + } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { + cleanSecondFragment(); + refeshListOfFilesFragment(); + } + } + + private void refreshShowDetails() { + FileFragment details = getSecondFragment(); + if (details != null) { + OCFile file = details.getFile(); + if (file != null) { + file = getStorageManager().getFileByPath(file.getRemotePath()); + if (details instanceof PreviewMediaFragment) { + // Refresh OCFile of the fragment + ((PreviewMediaFragment) details).updateFile(file); + } else { + showDetails(file); + } + } + invalidateOptionsMenu(); + } + } + /** * Updates the view associated to the activity after the finish of an operation trying to remove a * file. @@ -1295,33 +1402,58 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements * @param result Result of the removal. */ private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - dismissDialog(DIALOG_SHORT_WAIT); + dismissLoadingDialog(); if (result.isSuccess()) { Toast msg = Toast.makeText(this, R.string.remove_success_msg, Toast.LENGTH_LONG); msg.show(); OCFile removedFile = operation.getFile(); - if (mDualPane) { - FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); - if (details != null && removedFile.equals(details.getFile())) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment - transaction.commit(); - } + getSecondFragment(); + FileFragment second = getSecondFragment(); + if (second != null && removedFile.equals(second.getFile())) { + cleanSecondFragment(); } - if (mStorageManager.getFileById(removedFile.getParentId()).equals(mCurrentDir)) { - mFileList.listDirectory(); + if (getStorageManager().getFileById(removedFile.getParentId()).equals(getCurrentDir())) { + refeshListOfFilesFragment(); } - + } else { Toast msg = Toast.makeText(this, R.string.remove_fail_msg, Toast.LENGTH_LONG); msg.show(); if (result.isSslRecoverableException()) { mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); + showUntrustedCertDialog(mLastSslUntrustedServerResult); + } + } + } + + /** + * Updates the view associated to the activity after the finish of an operation trying create a new folder + * + * @param operation Creation operation performed. + * @param result Result of the creation. + */ + private void onCreateFolderOperationFinish(CreateFolderOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + dismissLoadingDialog(); + refeshListOfFilesFragment(); + + } else { + dismissLoadingDialog(); + if (result.getCode() == ResultCode.INVALID_CHARACTER_IN_NAME) { + Toast.makeText(FileDisplayActivity.this, R.string.filename_forbidden_characters, Toast.LENGTH_LONG).show(); + } else { + try { + Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); + msg.show(); + + } catch (NotFoundException e) { + Log_OC.e(TAG, "Error while trying to show fail message " , e); + } } } } + /** * Updates the view associated to the activity after the finish of an operation trying to rename a * file. @@ -1330,30 +1462,33 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements * @param result Result of the renaming. */ private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { - dismissDialog(DIALOG_SHORT_WAIT); + dismissLoadingDialog(); OCFile renamedFile = operation.getFile(); if (result.isSuccess()) { if (mDualPane) { - FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + FileFragment details = getSecondFragment(); if (details != null && details instanceof FileDetailFragment && renamedFile.equals(details.getFile()) ) { - ((FileDetailFragment) details).updateFileDetails(renamedFile, AccountUtils.getCurrentOwnCloudAccount(this)); + ((FileDetailFragment) details).updateFileDetails(renamedFile, getAccount()); } } - if (mStorageManager.getFileById(renamedFile.getParentId()).equals(mCurrentDir)) { - mFileList.listDirectory(); + if (getStorageManager().getFileById(renamedFile.getParentId()).equals(getCurrentDir())) { + refeshListOfFilesFragment(); } - + } else { if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) { Toast msg = Toast.makeText(this, R.string.rename_local_fail_msg, Toast.LENGTH_LONG); msg.show(); // TODO throw again the new rename dialog + } if (result.getCode().equals(ResultCode.INVALID_CHARACTER_IN_NAME)) { + Toast msg = Toast.makeText(this, R.string.filename_forbidden_characters, Toast.LENGTH_LONG); + msg.show(); } else { Toast msg = Toast.makeText(this, R.string.rename_server_fail_msg, Toast.LENGTH_LONG); msg.show(); if (result.isSslRecoverableException()) { mLastSslUntrustedServerResult = result; - showDialog(DIALOG_SSL_VALIDATOR); + showUntrustedCertDialog(mLastSslUntrustedServerResult); } } } @@ -1361,25 +1496,22 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { - dismissDialog(DIALOG_SHORT_WAIT); + dismissLoadingDialog(); OCFile syncedFile = operation.getLocalFile(); if (!result.isSuccess()) { if (result.getCode() == ResultCode.SYNC_CONFLICT) { Intent i = new Intent(this, ConflictsResolveActivity.class); i.putExtra(ConflictsResolveActivity.EXTRA_FILE, syncedFile); - i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); + i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, getAccount()); startActivity(i); - - } else { - Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); - msg.show(); - } - + + } + } else { if (operation.transferWasRequested()) { - mFileList.listDirectory(); + refeshListOfFilesFragment(); onTransferStateChanged(syncedFile, true, true); - + } else { Toast msg = Toast.makeText(this, R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); msg.show(); @@ -1393,15 +1525,11 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements */ @Override public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading) { - /*OCFileListFragment fileListFragment = (OCFileListFragment) getSupportFragmentManager().findFragmentById(R.id.fileList); - if (fileListFragment != null) { - fileListFragment.listDirectory(); - }*/ if (mDualPane) { - FileFragment details = (FileFragment) getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG); + FileFragment details = getSecondFragment(); if (details != null && details instanceof FileDetailFragment && file.equals(details.getFile()) ) { if (downloading || uploading) { - ((FileDetailFragment)details).updateFileDetails(file, AccountUtils.getCurrentOwnCloudAccount(this)); + ((FileDetailFragment)details).updateFileDetails(file, getAccount()); } else { ((FileDetailFragment)details).updateFileDetails(false, true); } @@ -1410,51 +1538,30 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements } - @Override - public void showFragmentWithDetails(OCFile file) { - if (mDualPane) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG); - transaction.commit(); - - } else { - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); - showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS); - startActivity(showDetailsIntent); - } - } - public void onDismiss(EditNameDialog dialog) { - //dialog.dismiss(); if (dialog.getResult()) { String newDirectoryName = dialog.getNewFilename().trim(); - Log.d(TAG, "'create directory' dialog dismissed with new name " + newDirectoryName); + Log_OC.d(TAG, "'create directory' dialog dismissed with new name " + newDirectoryName); if (newDirectoryName.length() > 0) { - String path; - if (mCurrentDir == null) { - // this is just a patch; we should ensure that mCurrentDir never is null - if (!mStorageManager.fileExists(OCFile.PATH_SEPARATOR)) { - OCFile file = new OCFile(OCFile.PATH_SEPARATOR); - mStorageManager.saveFile(file); - } - mCurrentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); - } - path = FileDisplayActivity.this.mCurrentDir.getRemotePath(); - + String path = getCurrentDir().getRemotePath(); + // Create directory path += newDirectoryName + OCFile.PATH_SEPARATOR; - Thread thread = new Thread(new DirectoryCreator(path, AccountUtils.getCurrentOwnCloudAccount(FileDisplayActivity.this), new Handler())); - thread.start(); - - showDialog(DIALOG_SHORT_WAIT); + RemoteOperation operation = new CreateFolderOperation(path, false, getStorageManager()); + operation.execute( getAccount(), + FileDisplayActivity.this, + FileDisplayActivity.this, + getHandler(), + FileDisplayActivity.this); + + showLoadingDialog(); } } } + private void requestForDownload() { - Account account = AccountUtils.getCurrentOwnCloudAccount(this); + Account account = getAccount(); if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) { Intent i = new Intent(this, FileDownloader.class); i.putExtra(FileDownloader.EXTRA_ACCOUNT, account); @@ -1463,5 +1570,89 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements } } + + private OCFile getCurrentDir() { + OCFile file = getFile(); + if (file != null) { + if (file.isFolder()) { + return file; + } else if (getStorageManager() != null) { + String parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName())); + return getStorageManager().getFileByPath(parentPath); + } + } + return null; + } + + public void startSyncFolderOperation(OCFile folder) { + long currentSyncTime = System.currentTimeMillis(); + + mSyncInProgress = true; + + // perform folder synchronization + RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder, + currentSyncTime, + false, + getFileOperationsHelper().isSharedSupported(this), + getStorageManager(), + getAccount(), + getApplicationContext() + ); + synchFolderOp.execute(getAccount(), this, null, null, this); + + setSupportProgressBarIndeterminateVisibility(true); + } + + /* + private void startGetShares() { + // Get shared files/folders + Intent intent = new Intent(this, OperationsService.class); + intent.putExtra(OperationsService.EXTRA_ACCOUNT, getAccount()); + startService(intent); + + mRefreshSharesInProgress = true; + } + */ + + /** + * Show untrusted cert dialog + */ + public void showUntrustedCertDialog(RemoteOperationResult result) { + // Show a dialog with the certificate info + SslUntrustedCertDialog dialog = SslUntrustedCertDialog.newInstanceForFullSslError((CertificateCombinedException)result.getException()); + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + dialog.show(ft, DIALOG_UNTRUSTED_CERT); + } + + /** + * Requests the download of the received {@link OCFile} , updates the UI + * to monitor the download progress and prepares the activity to send the file + * when the download finishes. + * + * @param file {@link OCFile} to download and preview. + */ + @Override + public void startDownloadForSending(OCFile file) { + mWaitingToSend = file; + requestForDownload(mWaitingToSend); + boolean hasSecondFragment = (getSecondFragment()!= null); + updateFragmentsVisibility(hasSecondFragment); + } + + private void requestForDownload(OCFile file) { + Account account = getAccount(); + if (!mDownloaderBinder.isDownloading(account, file)) { + Intent i = new Intent(this, FileDownloader.class); + i.putExtra(FileDownloader.EXTRA_ACCOUNT, account); + i.putExtra(FileDownloader.EXTRA_FILE, file); + startService(i); + } + } + + private void sendDownloadedFile(){ + getFileOperationsHelper().sendDownloadedFile(mWaitingToSend, this); + mWaitingToSend = null; + } } diff --git a/src/com/owncloud/android/ui/activity/GenericExplanationActivity.java b/src/com/owncloud/android/ui/activity/GenericExplanationActivity.java index 4c433a93..901434e2 100644 --- a/src/com/owncloud/android/ui/activity/GenericExplanationActivity.java +++ b/src/com/owncloud/android/ui/activity/GenericExplanationActivity.java @@ -30,8 +30,11 @@ import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; +import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.owncloud.android.R; +import com.owncloud.android.utils.DisplayUtils; + /** * Activity showing a text message and, optionally, a couple list of single or paired text strings. @@ -73,6 +76,9 @@ public class GenericExplanationActivity extends SherlockFragmentActivity { } else { listView.setVisibility(View.GONE); } + + ActionBar actionBar = getSupportActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); } public class ExplanationListAdapterView extends ArrayAdapter { diff --git a/src/com/owncloud/android/ui/activity/HookActivity.java b/src/com/owncloud/android/ui/activity/HookActivity.java new file mode 100644 index 00000000..54d65b1b --- /dev/null +++ b/src/com/owncloud/android/ui/activity/HookActivity.java @@ -0,0 +1,24 @@ +/* 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 . + * + */ + +package com.owncloud.android.ui.activity; + +public abstract class HookActivity extends FileActivity { + + private static final String TAG = HookActivity.class.getName(); + +} diff --git a/src/com/owncloud/android/ui/activity/InstantUploadActivity.java b/src/com/owncloud/android/ui/activity/InstantUploadActivity.java index 8f34327b..0d455bd2 100644 --- a/src/com/owncloud/android/ui/activity/InstantUploadActivity.java +++ b/src/com/owncloud/android/ui/activity/InstantUploadActivity.java @@ -2,8 +2,8 @@ * 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 as published by - * the Free Software Foundation, either version 3 of the License. + * 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 @@ -19,6 +19,14 @@ package com.owncloud.android.ui.activity; import java.util.ArrayList; import java.util.List; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.db.DbHandler; +import com.owncloud.android.files.InstantUploadBroadcastReceiver; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; + import android.accounts.Account; import android.app.Activity; import android.content.Intent; @@ -26,7 +34,6 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; -import android.util.Log; import android.util.SparseArray; import android.view.Gravity; import android.view.View; @@ -42,13 +49,6 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; -import com.owncloud.android.db.DbHandler; -import com.owncloud.android.files.InstantUploadBroadcastReceiver; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.utils.FileStorageUtils; /** * This Activity is used to display a list with images they could not be @@ -59,16 +59,6 @@ import com.owncloud.android.utils.FileStorageUtils; * sub-menu underneath the 'Upload' menu-item * * @author andomaex / Matthias Baumann - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License. (at - * your option) any later version. - * - * 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 de/ */ public class InstantUploadActivity extends Activity { @@ -87,14 +77,14 @@ public class InstantUploadActivity extends Activity { super.onCreate(savedInstanceState); setContentView(R.layout.failed_upload_files); - Button delete_all_btn = (Button) findViewById(R.id.failed_upload_delete_all_btn); - delete_all_btn.setOnClickListener(getDeleteListner()); - Button retry_all_btn = (Button) findViewById(R.id.failed_upload_retry_all_btn); - retry_all_btn.setOnClickListener(getRetryListner()); + Button deleteAllBtn = (Button) findViewById(R.id.failed_upload_delete_all_btn); + deleteAllBtn.setOnClickListener(getDeleteListner()); + Button retryAllBtn = (Button) findViewById(R.id.failed_upload_retry_all_btn); + retryAllBtn.setOnClickListener(getRetryListner()); this.failed_upload_all_cb = (CheckBox) findViewById(R.id.failed_upload_headline_cb); failed_upload_all_cb.setOnCheckedChangeListener(getCheckAllListener()); listView = (LinearLayout) findViewById(R.id.failed_upload_scrollviewlayout); - + loadListView(true); } @@ -160,7 +150,7 @@ public class InstantUploadActivity extends Activity { loadmoreBtn = new Button(this); loadmoreBtn.setId(42); loadmoreBtn.setText(getString(R.string.failed_upload_load_more_images)); - loadmoreBtn.setBackgroundResource(R.color.owncloud_white); + loadmoreBtn.setBackgroundResource(R.color.background_color); loadmoreBtn.setTextSize(12); loadmoreBtn.setOnClickListener(new OnClickListener() { @Override @@ -339,14 +329,14 @@ public class InstantUploadActivity extends Activity { TextView failureTextView = new TextView(this); failureTextView.setText(getString(R.string.failed_upload_failure_text) + message); - failureTextView.setBackgroundResource(R.color.owncloud_white); + failureTextView.setBackgroundResource(R.color.background_color); failureTextView.setTextSize(8); failureTextView.setOnLongClickListener(getOnLongClickListener(message)); failureTextView.setPadding(5, 5, 5, 10); TextView retryButton = new TextView(this); retryButton.setId(id); retryButton.setText(img_path); - retryButton.setBackgroundResource(R.color.owncloud_white); + retryButton.setBackgroundResource(R.color.background_color); retryButton.setTextSize(8); retryButton.setOnClickListener(getImageButtonOnClickListener(img_path)); retryButton.setOnLongClickListener(getOnLongClickListener(message)); @@ -363,7 +353,7 @@ public class InstantUploadActivity extends Activity { @Override public boolean onLongClick(View v) { - Log.d(LOG_TAG, message); + Log_OC.d(LOG_TAG, message); Toast toast = Toast.makeText(InstantUploadActivity.this, getString(R.string.failed_upload_retry_text) + message, Toast.LENGTH_LONG); toast.show(); @@ -376,7 +366,7 @@ public class InstantUploadActivity extends Activity { private CheckBox getFileCheckbox(int id) { CheckBox retryCB = new CheckBox(this); retryCB.setId(id); - retryCB.setBackgroundResource(R.color.owncloud_white); + retryCB.setBackgroundResource(R.color.background_color); retryCB.setTextSize(8); retryCB.setTag(retry_chexbox_tag); return retryCB; diff --git a/src/com/owncloud/android/ui/activity/LandingActivity.java b/src/com/owncloud/android/ui/activity/LandingActivity.java deleted file mode 100644 index 7ae0e00e..00000000 --- a/src/com/owncloud/android/ui/activity/LandingActivity.java +++ /dev/null @@ -1,157 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 Bartek Przybylski - * - * 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 . - * - */ -package com.owncloud.android.ui.activity; - -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.owncloud.android.authenticator.AccountAuthenticator; -import com.owncloud.android.ui.adapter.LandingScreenAdapter; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.GridView; -import android.widget.Toast; -import com.owncloud.android.R; - -/** - * This activity is used as a landing page when the user first opens this app. - * - * @author Lennart Rosam - * - */ -public class LandingActivity extends SherlockFragmentActivity implements - OnClickListener, OnItemClickListener { - - public static final int DIALOG_SETUP_ACCOUNT = 1; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - - // Fill the grid view of the landing screen with icons - GridView landingScreenItems = (GridView) findViewById(R.id.homeScreenGrid); - landingScreenItems.setAdapter(new LandingScreenAdapter(this)); - landingScreenItems.setOnItemClickListener(this); - - // Check, if there are ownCloud accounts - if (!accountsAreSetup()) { - showDialog(DIALOG_SETUP_ACCOUNT); - } else { - // Start device tracking service - Intent locationServiceIntent = new Intent(); - locationServiceIntent - .setAction("com.owncloud.android.location.LocationLauncher"); - sendBroadcast(locationServiceIntent); - } - - } - - @Override - protected void onRestart() { - super.onRestart(); - // Check, if there are ownCloud accounts - if (!accountsAreSetup()) { - showDialog(DIALOG_SETUP_ACCOUNT); - } - } - - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - // Check, if there are ownCloud accounts - if (!accountsAreSetup()) { - showDialog(DIALOG_SETUP_ACCOUNT); - } - } - - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog; - switch (id) { - case DIALOG_SETUP_ACCOUNT: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.main_tit_accsetup); - builder.setMessage(R.string.main_wrn_accsetup); - builder.setCancelable(false); - builder.setPositiveButton(R.string.common_ok, this); - builder.setNegativeButton(R.string.common_cancel, this); - dialog = builder.create(); - break; - default: - dialog = null; - } - - return dialog; - } - - public void onClick(DialogInterface dialog, int which) { - // In any case - we won't need it anymore - dialog.dismiss(); - switch (which) { - case DialogInterface.BUTTON_POSITIVE: - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra("authorities", - new String[] { AccountAuthenticator.AUTH_TOKEN_TYPE }); - startActivity(intent); - break; - case DialogInterface.BUTTON_NEGATIVE: - finish(); - } - - } - - @Override - /** - * Start an activity based on the selection - * the user made - */ - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Intent intent; - intent = (Intent) parent.getAdapter().getItem(position); - if (intent != null) { - startActivity(intent); - } else { - // TODO: Implement all of this and make this text go away ;-) - Toast toast = Toast.makeText(this, "Not yet implemented!", - Toast.LENGTH_SHORT); - toast.show(); - } - } - - /** - * Checks, whether or not there are any ownCloud accounts setup. - * - * @return true, if there is at least one account. - */ - private boolean accountsAreSetup() { - AccountManager accMan = AccountManager.get(this); - Account[] accounts = accMan - .getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); - return accounts.length > 0; - } - -} diff --git a/src/com/owncloud/android/ui/activity/LogHistoryActivity.java b/src/com/owncloud/android/ui/activity/LogHistoryActivity.java index 0c99fcfa..ca454d41 100644 --- a/src/com/owncloud/android/ui/activity/LogHistoryActivity.java +++ b/src/com/owncloud/android/ui/activity/LogHistoryActivity.java @@ -34,10 +34,12 @@ import com.actionbarsherlock.app.SherlockPreferenceActivity; import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.R; import com.owncloud.android.ui.adapter.LogListAdapter; +import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; + public class LogHistoryActivity extends SherlockPreferenceActivity implements OnPreferenceChangeListener { String logpath = FileStorageUtils.getLogPath(); File logDIR = null; @@ -49,9 +51,11 @@ public class LogHistoryActivity extends SherlockPreferenceActivity implements On setContentView(R.layout.log_send_file); setTitle("Log History"); ActionBar actionBar = getSherlock().getActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); actionBar.setDisplayHomeAsUpEnabled(true); ListView listView = (ListView) findViewById(android.R.id.list); Button deleteHistoryButton = (Button) findViewById(R.id.deleteLogHistoryButton); + deleteHistoryButton.setOnClickListener(new OnClickListener() { @Override diff --git a/src/com/owncloud/android/ui/activity/PinCodeActivity.java b/src/com/owncloud/android/ui/activity/PinCodeActivity.java index 215ea45f..39b973d0 100644 --- a/src/com/owncloud/android/ui/activity/PinCodeActivity.java +++ b/src/com/owncloud/android/ui/activity/PinCodeActivity.java @@ -18,9 +18,10 @@ package com.owncloud.android.ui.activity; import java.util.Arrays; +import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.SherlockFragmentActivity; - import com.owncloud.android.R; +import com.owncloud.android.utils.DisplayUtils; import android.app.AlertDialog; import android.content.DialogInterface; @@ -45,23 +46,23 @@ public class PinCodeActivity extends SherlockFragmentActivity { public final static String EXTRA_ACTIVITY = "com.owncloud.android.ui.activity.PinCodeActivity.ACTIVITY"; public final static String EXTRA_NEW_STATE = "com.owncloud.android.ui.activity.PinCodeActivity.NEW_STATE"; - Button bCancel; - TextView mPinHdr; - TextView mPinHdrExplanation; - EditText mText1; - EditText mText2; - EditText mText3; - EditText mText4; + private Button mBCancel; + private TextView mPinHdr; + private TextView mPinHdrExplanation; + private EditText mText1; + private EditText mText2; + private EditText mText3; + private EditText mText4; - String [] tempText ={"","","",""}; + private String [] mTempText ={"","","",""}; - String activity; + private String mActivity; - boolean confirmingPinCode = false; - boolean pinCodeChecked = false; - boolean newPasswordEntered = false; - boolean bChange = true; // to control that only one blocks jump - int tCounter ; // Count the number of attempts an user could introduce the PIN code + private boolean mConfirmingPinCode = false; + private boolean mPinCodeChecked = false; + private boolean mNewPasswordEntered = false; + private boolean mBChange = true; // to control that only one blocks jump + //private int mTCounter ; // Count the number of attempts an user could introduce the PIN code protected void onCreate(Bundle savedInstanceState) { @@ -69,9 +70,9 @@ public class PinCodeActivity extends SherlockFragmentActivity { setContentView(R.layout.pincodelock); Intent intent = getIntent(); - activity = intent.getStringExtra(EXTRA_ACTIVITY); + mActivity = intent.getStringExtra(EXTRA_ACTIVITY); - bCancel = (Button) findViewById(R.id.cancel); + mBCancel = (Button) findViewById(R.id.cancel); mPinHdr = (TextView) findViewById(R.id.pinHdr); mPinHdrExplanation = (TextView) findViewById(R.id.pinHdrExpl); mText1 = (EditText) findViewById(R.id.txt1); @@ -81,8 +82,6 @@ public class PinCodeActivity extends SherlockFragmentActivity { mText3 = (EditText) findViewById(R.id.txt3); mText4 = (EditText) findViewById(R.id.txt4); - - SharedPreferences appPrefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()); @@ -91,23 +90,23 @@ public class PinCodeActivity extends SherlockFragmentActivity { // In a previous version settings is allow from start if ( (appPrefs.getString("PrefPinCode1", null) == null ) ){ setChangePincodeView(true); - pinCodeChecked = true; - newPasswordEntered = true; + mPinCodeChecked = true; + mNewPasswordEntered = true; }else{ if (appPrefs.getBoolean("set_pincode", false)){ // pincode activated - if (activity.equals("preferences")){ + if (mActivity.equals("preferences")){ // PIN has been activated yet mPinHdr.setText(R.string.pincode_configure_your_pin); mPinHdrExplanation.setVisibility(View.VISIBLE); - pinCodeChecked = true ; // No need to check it + mPinCodeChecked = true ; // No need to check it setChangePincodeView(true); }else{ // PIN active - bCancel.setVisibility(View.INVISIBLE); - bCancel.setVisibility(View.GONE); + mBCancel.setVisibility(View.INVISIBLE); + mBCancel.setVisibility(View.GONE); mPinHdr.setText(R.string.pincode_enter_pin_code); mPinHdrExplanation.setVisibility(View.INVISIBLE); setChangePincodeView(false); @@ -117,28 +116,29 @@ public class PinCodeActivity extends SherlockFragmentActivity { // pincode removal mPinHdr.setText(R.string.pincode_remove_your_pincode); mPinHdrExplanation.setVisibility(View.INVISIBLE); - pinCodeChecked = false; + mPinCodeChecked = false; setChangePincodeView(true); } } setTextListeners(); - + ActionBar actionBar = getSupportActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); } protected void setInitVars(){ - confirmingPinCode = false; - pinCodeChecked = false; - newPasswordEntered = false; + mConfirmingPinCode = false; + mPinCodeChecked = false; + mNewPasswordEntered = false; } protected void setInitView(){ - bCancel.setVisibility(View.INVISIBLE); - bCancel.setVisibility(View.GONE); + mBCancel.setVisibility(View.INVISIBLE); + mBCancel.setVisibility(View.GONE); mPinHdr.setText(R.string.pincode_enter_pin_code); mPinHdrExplanation.setVisibility(View.INVISIBLE); } @@ -147,8 +147,8 @@ public class PinCodeActivity extends SherlockFragmentActivity { protected void setChangePincodeView(boolean state){ if(state){ - bCancel.setVisibility(View.VISIBLE); - bCancel.setOnClickListener(new OnClickListener() { + mBCancel.setVisibility(View.VISIBLE); + mBCancel.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -195,8 +195,8 @@ public class PinCodeActivity extends SherlockFragmentActivity { @Override public void afterTextChanged(Editable s) { if (s.length() > 0) { - if (!confirmingPinCode){ - tempText[0] = mText1.getText().toString(); + if (!mConfirmingPinCode){ + mTempText[0] = mText1.getText().toString(); } mText2.requestFocus(); @@ -224,8 +224,8 @@ public class PinCodeActivity extends SherlockFragmentActivity { @Override public void afterTextChanged(Editable s) { if (s.length() > 0) { - if (!confirmingPinCode){ - tempText[1] = mText2.getText().toString(); + if (!mConfirmingPinCode){ + mTempText[1] = mText2.getText().toString(); } mText3.requestFocus(); @@ -237,16 +237,16 @@ public class PinCodeActivity extends SherlockFragmentActivity { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { + if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) { mText1.setText(""); mText1.requestFocus(); - if (!confirmingPinCode) - tempText[0] = ""; - bChange= false; + if (!mConfirmingPinCode) + mTempText[0] = ""; + mBChange= false; - }else if(!bChange){ - bChange=true; + }else if(!mBChange){ + mBChange=true; } return false; @@ -288,8 +288,8 @@ public class PinCodeActivity extends SherlockFragmentActivity { @Override public void afterTextChanged(Editable s) { if (s.length() > 0) { - if (!confirmingPinCode){ - tempText[2] = mText3.getText().toString(); + if (!mConfirmingPinCode){ + mTempText[2] = mText3.getText().toString(); } mText4.requestFocus(); } @@ -300,15 +300,15 @@ public class PinCodeActivity extends SherlockFragmentActivity { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { + if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) { mText2.requestFocus(); - if (!confirmingPinCode) - tempText[1] = ""; + if (!mConfirmingPinCode) + mTempText[1] = ""; mText2.setText(""); - bChange= false; + mBChange= false; - }else if(!bChange){ - bChange=true; + }else if(!mBChange){ + mBChange=true; } return false; @@ -356,18 +356,19 @@ public class PinCodeActivity extends SherlockFragmentActivity { public void afterTextChanged(Editable s) { if (s.length() > 0) { - if (!confirmingPinCode){ - tempText[3] = mText4.getText().toString(); + if (!mConfirmingPinCode){ + mTempText[3] = mText4.getText().toString(); } mText1.requestFocus(); - if (!pinCodeChecked){ - pinCodeChecked = checkPincode(); + if (!mPinCodeChecked){ + mPinCodeChecked = checkPincode(); } - if (pinCodeChecked && activity.equals("FileDisplayActivity")){ + if (mPinCodeChecked && + ( mActivity.equals("FileDisplayActivity") || mActivity.equals("PreviewImageActivity") ) ){ finish(); - } else if (pinCodeChecked){ + } else if (mPinCodeChecked){ Intent intent = getIntent(); String newState = intent.getStringExtra(EXTRA_NEW_STATE); @@ -383,7 +384,7 @@ public class PinCodeActivity extends SherlockFragmentActivity { }else{ - if (!confirmingPinCode){ + if (!mConfirmingPinCode){ pinCodeChangeRequest(); } else { @@ -403,15 +404,15 @@ public class PinCodeActivity extends SherlockFragmentActivity { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_DEL && bChange) { + if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) { mText3.requestFocus(); - if (!confirmingPinCode) - tempText[2]=""; + if (!mConfirmingPinCode) + mTempText[2]=""; mText3.setText(""); - bChange= false; + mBChange= false; - }else if(!bChange){ - bChange=true; + }else if(!mBChange){ + mBChange=true; } return false; } @@ -456,7 +457,7 @@ public class PinCodeActivity extends SherlockFragmentActivity { clearBoxes(); mPinHdr.setText(R.string.pincode_reenter_your_pincode); mPinHdrExplanation.setVisibility(View.INVISIBLE); - confirmingPinCode =true; + mConfirmingPinCode =true; } @@ -472,16 +473,16 @@ public class PinCodeActivity extends SherlockFragmentActivity { String pText3 = appPrefs.getString("PrefPinCode3", null); String pText4 = appPrefs.getString("PrefPinCode4", null); - if ( tempText[0].equals(pText1) && - tempText[1].equals(pText2) && - tempText[2].equals(pText3) && - tempText[3].equals(pText4) ) { + if ( mTempText[0].equals(pText1) && + mTempText[1].equals(pText2) && + mTempText[2].equals(pText3) && + mTempText[3].equals(pText4) ) { return true; }else { - Arrays.fill(tempText, null); + Arrays.fill(mTempText, null); AlertDialog aDialog = new AlertDialog.Builder(this).create(); CharSequence errorSeq = getString(R.string.common_error); aDialog.setTitle(errorSeq); @@ -500,8 +501,8 @@ public class PinCodeActivity extends SherlockFragmentActivity { clearBoxes(); mPinHdr.setText(R.string.pincode_enter_pin_code); mPinHdrExplanation.setVisibility(View.INVISIBLE); - newPasswordEntered = true; - confirmingPinCode = false; + mNewPasswordEntered = true; + mConfirmingPinCode = false; } @@ -511,23 +512,23 @@ public class PinCodeActivity extends SherlockFragmentActivity { protected void confirmPincode(){ - confirmingPinCode = false; + mConfirmingPinCode = false; String rText1 = mText1.getText().toString(); String rText2 = mText2.getText().toString(); String rText3 = mText3.getText().toString(); String rText4 = mText4.getText().toString(); - if ( tempText[0].equals(rText1) && - tempText[1].equals(rText2) && - tempText[2].equals(rText3) && - tempText[3].equals(rText4) ) { + if ( mTempText[0].equals(rText1) && + mTempText[1].equals(rText2) && + mTempText[2].equals(rText3) && + mTempText[3].equals(rText4) ) { savePincodeAndExit(); } else { - Arrays.fill(tempText, null); + Arrays.fill(mTempText, null); AlertDialog aDialog = new AlertDialog.Builder(this).create(); CharSequence errorSeq = getString(R.string.common_error); aDialog.setTitle(errorSeq); @@ -584,10 +585,10 @@ public class PinCodeActivity extends SherlockFragmentActivity { SharedPreferences.Editor appPrefs = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()).edit(); - appPrefs.putString("PrefPinCode1", tempText[0]); - appPrefs.putString("PrefPinCode2",tempText[1]); - appPrefs.putString("PrefPinCode3", tempText[2]); - appPrefs.putString("PrefPinCode4", tempText[3]); + appPrefs.putString("PrefPinCode1", mTempText[0]); + appPrefs.putString("PrefPinCode2",mTempText[1]); + appPrefs.putString("PrefPinCode3", mTempText[2]); + appPrefs.putString("PrefPinCode4", mTempText[3]); appPrefs.putBoolean("set_pincode",true); appPrefs.commit(); @@ -612,7 +613,7 @@ public class PinCodeActivity extends SherlockFragmentActivity { public boolean onKeyDown(int keyCode, KeyEvent event){ if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){ - if (activity.equals("preferences")){ + if (mActivity.equals("preferences")){ SharedPreferences.Editor appPrefsE = PreferenceManager .getDefaultSharedPreferences(getApplicationContext()).edit(); diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index b49bcb68..3ebb432c 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -17,51 +17,45 @@ */ package com.owncloud.android.ui.activity; -import java.io.File; -import java.util.Vector; - +import android.accounts.Account; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.preference.CheckBoxPreference; -import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceCategory; import android.preference.PreferenceManager; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.SherlockPreferenceActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; -import com.owncloud.android.Log_OC; -import com.owncloud.android.OwnCloudSession; import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.db.DbHandler; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.Log_OC; + /** * An Activity that allows the user to change the application's settings. * * @author Bartek Przybylski - * + * @author David A. Velasco */ -public class Preferences extends SherlockPreferenceActivity implements OnPreferenceChangeListener { +public class Preferences extends SherlockPreferenceActivity { private static final String TAG = "OwnCloudPreferences"; - private final int mNewSession = 47; - private final int mEditSession = 48; private DbHandler mDbHandler; - private Vector mSessions; - private ListPreference mTrackingUpdateInterval; - private CheckBoxPreference mDeviceTracking; private CheckBoxPreference pCode; - private CheckBoxPreference pLogging; - private Preference pLoggingHistory; + //private CheckBoxPreference pLogging; + //private Preference pLoggingHistory; private Preference pAboutApp; - private int mSelectedMenuItem; @SuppressWarnings("deprecation") @@ -69,11 +63,12 @@ public class Preferences extends SherlockPreferenceActivity implements OnPrefere public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mDbHandler = new DbHandler(getBaseContext()); - mSessions = new Vector(); addPreferencesFromResource(R.xml.preferences); //populateAccountList(); ActionBar actionBar = getSherlock().getActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); actionBar.setDisplayHomeAsUpEnabled(true); + Preference p = findPreference("manage_account"); if (p != null) p.setOnPreferenceClickListener(new OnPreferenceClickListener() { @@ -94,11 +89,125 @@ public class Preferences extends SherlockPreferenceActivity implements OnPrefere i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "preferences"); i.putExtra(PinCodeActivity.EXTRA_NEW_STATE, newValue.toString()); startActivity(i); + return true; } - }); + }); + + } + + + + PreferenceCategory preferenceCategory = (PreferenceCategory) findPreference("more"); + + boolean helpEnabled = getResources().getBoolean(R.bool.help_enabled); + Preference pHelp = findPreference("help"); + if (pHelp != null ){ + if (helpEnabled) { + pHelp.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String helpWeb =(String) getText(R.string.url_help); + if (helpWeb != null && helpWeb.length() > 0) { + Uri uriUrl = Uri.parse(helpWeb); + Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl); + startActivity(intent); + } + return true; + } + }); + } else { + preferenceCategory.removePreference(pHelp); + } + + } + + + boolean recommendEnabled = getResources().getBoolean(R.bool.recommend_enabled); + Preference pRecommend = findPreference("recommend"); + if (pRecommend != null){ + if (recommendEnabled) { + pRecommend.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setType("text/plain"); + intent.setData(Uri.parse(getString(R.string.mail_recommend))); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + String appName = getString(R.string.app_name); + String downloadUrl = getString(R.string.url_app_download); + Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(Preferences.this); + String username = currentAccount.name.substring(0, currentAccount.name.lastIndexOf('@')); + + String recommendSubject = String.format(getString(R.string.recommend_subject), appName); + String recommendText = String.format(getString(R.string.recommend_text), appName, downloadUrl, username); + + intent.putExtra(Intent.EXTRA_SUBJECT, recommendSubject); + intent.putExtra(Intent.EXTRA_TEXT, recommendText); + startActivity(intent); + + + return(true); + + } + }); + } else { + preferenceCategory.removePreference(pRecommend); + } - /* About App */ + } + + boolean feedbackEnabled = getResources().getBoolean(R.bool.feedback_enabled); + Preference pFeedback = findPreference("feedback"); + if (pFeedback != null){ + if (feedbackEnabled) { + pFeedback.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String feedbackMail =(String) getText(R.string.mail_feedback); + String feedback =(String) getText(R.string.prefs_feedback); + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_SUBJECT, feedback); + + intent.setData(Uri.parse(feedbackMail)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + + return true; + } + }); + } else { + preferenceCategory.removePreference(pFeedback); + } + + } + + boolean imprintEnabled = getResources().getBoolean(R.bool.imprint_enabled); + Preference pImprint = findPreference("imprint"); + if (pImprint != null) { + if (imprintEnabled) { + pImprint.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + String imprintWeb = (String) getText(R.string.url_imprint); + if (imprintWeb != null && imprintWeb.length() > 0) { + Uri uriUrl = Uri.parse(imprintWeb); + Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl); + startActivity(intent); + } + //ImprintDialog.newInstance(true).show(preference.get, "IMPRINT_DIALOG"); + return true; + } + }); + } else { + preferenceCategory.removePreference(pImprint); + } + } + + /* About App */ pAboutApp = (Preference) findPreference("about_app"); if (pAboutApp != null) { pAboutApp.setTitle(String.format(getString(R.string.about_android), getString(R.string.app_name))); @@ -111,6 +220,7 @@ public class Preferences extends SherlockPreferenceActivity implements OnPrefere } } + /* DISABLED FOR RELEASE UNTIL FIXED pLogging = (CheckBoxPreference) findPreference("log_to_file"); if (pLogging != null) { pLogging.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { @@ -145,7 +255,8 @@ public class Preferences extends SherlockPreferenceActivity implements OnPrefere } }); } - } + */ + } @Override @@ -168,21 +279,6 @@ public class Preferences extends SherlockPreferenceActivity implements OnPrefere Intent intent; switch (item.getItemId()) { - //case R.id.addSessionItem: - case 1: - intent = new Intent(this, PreferencesNewSession.class); - startActivityForResult(intent, mNewSession); - break; - case R.id.SessionContextEdit: - intent = new Intent(this, PreferencesNewSession.class); - intent.putExtra("sessionId", mSessions.get(mSelectedMenuItem) - .getEntryId()); - intent.putExtra("sessionName", mSessions.get(mSelectedMenuItem) - .getName()); - intent.putExtra("sessionURL", mSessions.get(mSelectedMenuItem) - .getUrl()); - startActivityForResult(intent, mEditSession); - break; case android.R.id.home: intent = new Intent(getBaseContext(), FileDisplayActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -206,35 +302,4 @@ public class Preferences extends SherlockPreferenceActivity implements OnPrefere super.onDestroy(); } - @Override - /** - * Updates various summaries after updates. Also starts and stops - * the - */ - public boolean onPreferenceChange(Preference preference, Object newValue) { - // Update current account summary - /*if (preference.equals(mAccountList)) { - mAccountList.setSummary(newValue.toString()); - } - - // Update tracking interval summary - else*/ if (preference.equals(mTrackingUpdateInterval)) { - String trackingSummary = getResources().getString( - R.string.prefs_trackmydevice_interval_summary); - trackingSummary = String.format(trackingSummary, - newValue.toString()); - mTrackingUpdateInterval.setSummary(trackingSummary); - } - - // Start or stop tracking service - else if (preference.equals(mDeviceTracking)) { - Intent locationServiceIntent = new Intent(); - locationServiceIntent - .setAction("com.owncloud.android.location.LocationLauncher"); - locationServiceIntent.putExtra("TRACKING_SETTING", - (Boolean) newValue); - sendBroadcast(locationServiceIntent); - } - return true; - } } diff --git a/src/com/owncloud/android/ui/activity/PreferencesNewSession.java b/src/com/owncloud/android/ui/activity/PreferencesNewSession.java deleted file mode 100644 index c43b29f2..00000000 --- a/src/com/owncloud/android/ui/activity/PreferencesNewSession.java +++ /dev/null @@ -1,116 +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 . - * - */ - -package com.owncloud.android.ui.activity; - -import android.accounts.AccountAuthenticatorActivity; -import android.app.Activity; -import android.os.Bundle; -import android.view.View; -import android.view.View.OnClickListener; - -public class PreferencesNewSession extends AccountAuthenticatorActivity - implements OnClickListener { - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // setContentView(R.layout.add_new_session); - /* - * EditText et;// = (EditText) - * findViewById(R.id.newSession_sessionName); - * - * et = (EditText) findViewById(R.id.newSession_URL); if - * (getIntent().hasExtra("sessionURL")) { try { URI uri = new - * URI(getIntent().getStringExtra("sessionURL")); String url = - * uri.getHost(); if (uri.getPort() != -1) { url += ":" + - * String.valueOf(uri.getPort()); } if (uri.getPath() != null) { url += - * uri.getPath(); } else { url += "/"; } et.setText(url); et = - * (EditText) findViewById(R.id.newSession_username); if - * (uri.getAuthority() != null) { if (uri.getUserInfo().indexOf(':') != - * -1) { et.setText(uri.getUserInfo().substring(0, - * uri.getUserInfo().indexOf(':'))); et = (EditText) - * findViewById(R.id.newSession_password); - * et.setText(uri.getUserInfo().substring - * (uri.getUserInfo().indexOf(':')+1)); } else { - * et.setText(uri.getUserInfo()); } } - * - * } catch (URISyntaxException e) { Log.e(TAG, "Incorrect URI syntax " + - * e.getLocalizedMessage()); } } - * - * mReturnData = new Intent(); setResult(Activity.RESULT_OK, - * mReturnData); ((Button) - * findViewById(R.id.button1)).setOnClickListener(this); ((Button) - * findViewById(R.id.button2)).setOnClickListener(this); - */ - } - - @Override - protected void onResume() { - super.onResume(); - } - - public void onClick(View v) { - /* - * switch (v.getId()) { case R.id.button1: Intent intent = new Intent(); - * if (getIntent().hasExtra("sessionId")) { intent.putExtra("sessionId", - * getIntent().getIntExtra("sessionId", -1)); } //String sessionName = - * ((EditText) - * findViewById(R.id.newSession_sessionName)).getText().toString(); // - * if (sessionName.trim().equals("") || !isNameValid(sessionName)) { // - * Toast.makeText(this, R.string.new_session_session_name_error, - * Toast.LENGTH_LONG).show(); // break; // } URI uri = prepareURI(); if - * (uri != null) { //intent.putExtra("sessionName", sessionName); - * intent.putExtra("sessionURL", uri.toString()); - * setResult(Activity.RESULT_OK, intent); AccountManager accMgr = - * AccountManager.get(this); Account a = new Account("OwnCloud", - * AccountAuthenticatorService.ACCOUNT_TYPE); - * accMgr.addAccountExplicitly(a, "asd", null); finish(); } break; case - * R.id.button2: setResult(Activity.RESULT_CANCELED); finish(); break; } - */ - } - - /* - * private URI prepareURI() { URI uri = null; String url = ""; try { String - * username = ((EditText) - * findViewById(R.id.newSession_username)).getText().toString().trim(); - * String password = ((EditText) - * findViewById(R.id.newSession_password)).getText().toString().trim(); - * String hostname = ((EditText) - * findViewById(R.id.newSession_URL)).getText().toString().trim(); String - * scheme; if (hostname.matches("[A-Za-z]://")) { scheme = - * hostname.substring(0, hostname.indexOf("://")+3); hostname = - * hostname.substring(hostname.indexOf("://")+3); } else { scheme = - * "http://"; } if (!username.equals("")) { if (!password.equals("")) { - * username += ":" + password + "@"; } else { username += "@"; } } url = - * scheme + username + hostname; Log.i(TAG, url); uri = new URI(url); } - * catch (URISyntaxException e) { Log.e(TAG, "Incorrect URI syntax " + - * e.getLocalizedMessage()); Toast.makeText(this, - * R.string.new_session_uri_error, Toast.LENGTH_LONG).show(); } return uri; - * } - * - * private boolean isNameValid(String string) { return - * string.matches("[A-Za-z0-9 _-]*"); } - */ - - @Override - public void onBackPressed() { - setResult(Activity.RESULT_CANCELED); - super.onBackPressed(); - } - -} diff --git a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java index 8a62219e..8b4a41d2 100644 --- a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java @@ -25,7 +25,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.DialogFragment; -import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -35,16 +34,16 @@ import android.widget.TextView; import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.ActionBar.OnNavigationListener; -import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.R; import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; import com.owncloud.android.ui.fragment.ConfirmationDialogFragment; import com.owncloud.android.ui.fragment.LocalFileListFragment; import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; +import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; +import com.owncloud.android.utils.Log_OC; -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; /** * Displays local files and let the user choose what of them wants to upload @@ -54,7 +53,7 @@ import com.owncloud.android.R; * */ -public class UploadFilesActivity extends SherlockFragmentActivity implements +public class UploadFilesActivity extends FileActivity implements LocalFileListFragment.ContainerActivity, OnNavigationListener, OnClickListener, ConfirmationDialogFragmentListener { private ArrayAdapter mDirectories; @@ -62,10 +61,9 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements private LocalFileListFragment mFileListFragment; private Button mCancelBtn; private Button mUploadBtn; - private Account mAccount; + private Account mAccountOnCreation; private DialogFragment mCurrentDialog; - public static final String EXTRA_ACCOUNT = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_ACCOUNT"; public static final String EXTRA_CHOSEN_FILES = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES"; public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER; @@ -87,7 +85,7 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements mCurrentDir = Environment.getExternalStorageDirectory(); } - mAccount = getIntent().getParcelableExtra(EXTRA_ACCOUNT); + mAccountOnCreation = getAccount(); /// USER INTERFACE @@ -110,9 +108,11 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements mCancelBtn.setOnClickListener(this); mUploadBtn = (Button) findViewById(R.id.upload_files_btn_upload); mUploadBtn.setOnClickListener(this); + // Action bar setup ActionBar actionBar = getSupportActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); actionBar.setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation actionBar.setDisplayHomeAsUpEnabled(mCurrentDir != null && mCurrentDir.getName() != null); actionBar.setDisplayShowTitleEnabled(false); @@ -319,7 +319,7 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements File localFile = new File(localPath); total += localFile.length(); } - return (FileStorageUtils.getUsableSpace(mAccount.name) >= total); + return (FileStorageUtils.getUsableSpace(mAccountOnCreation.name) >= total); } /** @@ -375,6 +375,22 @@ public class UploadFilesActivity extends SherlockFragmentActivity implements public void onCancel(String callerTag) { /// nothing to do; don't finish, let the user change the selection Log_OC.d(TAG, "Negative button in dialog was clicked; dialog tag is " + callerTag); + } + + + @Override + protected void onAccountSet(boolean stateWasRecovered) { + super.onAccountSet(stateWasRecovered); + if (getAccount() != null) { + if (!mAccountOnCreation.equals(getAccount())) { + setResult(RESULT_CANCELED); + finish(); + } + + } else { + setResult(RESULT_CANCELED); + finish(); + } } diff --git a/src/com/owncloud/android/ui/activity/Uploader.java b/src/com/owncloud/android/ui/activity/Uploader.java new file mode 100644 index 00000000..07e6e959 --- /dev/null +++ b/src/com/owncloud/android/ui/activity/Uploader.java @@ -0,0 +1,429 @@ +/* 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 . + * + */ + +package com.owncloud.android.ui.activity; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; +import java.util.Vector; + +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountAuthenticator; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.utils.Log_OC; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.ListActivity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcelable; +import android.provider.MediaStore.Audio; +import android.provider.MediaStore.Images; +import android.provider.MediaStore.Video; +import android.view.View; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.SimpleAdapter; +import android.widget.Toast; + + +/** + * This can be used to upload things to an ownCloud instance. + * + * @author Bartek Przybylski + * + */ +public class Uploader extends ListActivity implements OnItemClickListener, android.view.View.OnClickListener { + private static final String TAG = "ownCloudUploader"; + + private Account mAccount; + private AccountManager mAccountManager; + private Stack mParents; + private ArrayList mStreamsToUpload; + private boolean mCreateDir; + private String mUploadPath; + private FileDataStorageManager mStorageManager; + private OCFile mFile; + + private final static int DIALOG_NO_ACCOUNT = 0; + private final static int DIALOG_WAITING = 1; + private final static int DIALOG_NO_STREAM = 2; + private final static int DIALOG_MULTIPLE_ACCOUNT = 3; + + private final static int REQUEST_CODE_SETUP_ACCOUNT = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + mParents = new Stack(); + mParents.add(""); + if (prepareStreamsToUpload()) { + mAccountManager = (AccountManager) getSystemService(Context.ACCOUNT_SERVICE); + Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAccountType()); + if (accounts.length == 0) { + Log_OC.i(TAG, "No ownCloud account is available"); + showDialog(DIALOG_NO_ACCOUNT); + } else if (accounts.length > 1) { + Log_OC.i(TAG, "More then one ownCloud is available"); + showDialog(DIALOG_MULTIPLE_ACCOUNT); + } else { + mAccount = accounts[0]; + mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); + populateDirectoryList(); + } + } else { + showDialog(DIALOG_NO_STREAM); + } + } + + @Override + protected Dialog onCreateDialog(final int id) { + final AlertDialog.Builder builder = new Builder(this); + switch (id) { + case DIALOG_WAITING: + ProgressDialog pDialog = new ProgressDialog(this); + pDialog.setIndeterminate(false); + pDialog.setCancelable(false); + pDialog.setMessage(getResources().getString(R.string.uploader_info_uploading)); + return pDialog; + case DIALOG_NO_ACCOUNT: + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setTitle(R.string.uploader_wrn_no_account_title); + builder.setMessage(String.format(getString(R.string.uploader_wrn_no_account_text), getString(R.string.app_name))); + builder.setCancelable(false); + builder.setPositiveButton(R.string.uploader_wrn_no_account_setup_btn_text, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ECLAIR_MR1) { + // using string value since in API7 this + // constatn is not defined + // in API7 < this constatant is defined in + // Settings.ADD_ACCOUNT_SETTINGS + // and Settings.EXTRA_AUTHORITIES + Intent intent = new Intent(android.provider.Settings.ACTION_ADD_ACCOUNT); + intent.putExtra("authorities", new String[] { MainApp.getAuthTokenType() }); + startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); + } else { + // since in API7 there is no direct call for + // account setup, so we need to + // show our own AccountSetupAcricity, get + // desired results and setup + // everything for ourself + Intent intent = new Intent(getBaseContext(), AccountAuthenticator.class); + startActivityForResult(intent, REQUEST_CODE_SETUP_ACCOUNT); + } + } + }); + builder.setNegativeButton(R.string.uploader_wrn_no_account_quit_btn_text, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + return builder.create(); + case DIALOG_MULTIPLE_ACCOUNT: + CharSequence ac[] = new CharSequence[mAccountManager.getAccountsByType(MainApp.getAccountType()).length]; + for (int i = 0; i < ac.length; ++i) { + ac[i] = mAccountManager.getAccountsByType(MainApp.getAccountType())[i].name; + } + builder.setTitle(R.string.common_choose_account); + builder.setItems(ac, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mAccount = mAccountManager.getAccountsByType(MainApp.getAccountType())[which]; + mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); + populateDirectoryList(); + } + }); + builder.setCancelable(true); + builder.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + dialog.cancel(); + finish(); + } + }); + return builder.create(); + case DIALOG_NO_STREAM: + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setTitle(R.string.uploader_wrn_no_content_title); + builder.setMessage(R.string.uploader_wrn_no_content_text); + builder.setCancelable(false); + builder.setNegativeButton(R.string.common_cancel, new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }); + return builder.create(); + default: + throw new IllegalArgumentException("Unknown dialog id: " + id); + } + } + + class a implements OnClickListener { + String mPath; + EditText mDirname; + + public a(String path, EditText dirname) { + mPath = path; + mDirname = dirname; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + Uploader.this.mUploadPath = mPath + mDirname.getText().toString(); + Uploader.this.mCreateDir = true; + uploadFiles(); + } + } + + @Override + public void onBackPressed() { + + if (mParents.size() <= 1) { + super.onBackPressed(); + return; + } else { + mParents.pop(); + populateDirectoryList(); + } + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + // click on folder in the list + Log_OC.d(TAG, "on item click"); + Vector tmpfiles = mStorageManager.getFolderContent(mFile); + if (tmpfiles.size() <= 0) return; + // filter on dirtype + Vector files = new Vector(); + for (OCFile f : tmpfiles) + if (f.isFolder()) + files.add(f); + if (files.size() < position) { + throw new IndexOutOfBoundsException("Incorrect item selected"); + } + mParents.push(files.get(position).getFileName()); + populateDirectoryList(); + } + + @Override + public void onClick(View v) { + // click on button + switch (v.getId()) { + case R.id.uploader_choose_folder: + mUploadPath = ""; // first element in mParents is root dir, represented by ""; init mUploadPath with "/" results in a "//" prefix + for (String p : mParents) + mUploadPath += p + OCFile.PATH_SEPARATOR; + Log_OC.d(TAG, "Uploading file to dir " + mUploadPath); + + uploadFiles(); + + break; + default: + throw new IllegalArgumentException("Wrong element clicked"); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + Log_OC.i(TAG, "result received. req: " + requestCode + " res: " + resultCode); + if (requestCode == REQUEST_CODE_SETUP_ACCOUNT) { + dismissDialog(DIALOG_NO_ACCOUNT); + if (resultCode == RESULT_CANCELED) { + finish(); + } + Account[] accounts = mAccountManager.getAccountsByType(MainApp.getAuthTokenType()); + if (accounts.length == 0) { + showDialog(DIALOG_NO_ACCOUNT); + } else { + // there is no need for checking for is there more then one + // account at this point + // since account setup can set only one account at time + mAccount = accounts[0]; + populateDirectoryList(); + } + } + } + + private void populateDirectoryList() { + setContentView(R.layout.uploader_layout); + + String full_path = ""; + for (String a : mParents) + full_path += a + "/"; + + Log_OC.d(TAG, "Populating view with content of : " + full_path); + + mFile = mStorageManager.getFileByPath(full_path); + if (mFile != null) { + Vector files = mStorageManager.getFolderContent(mFile); + List> data = new LinkedList>(); + for (OCFile f : files) { + HashMap h = new HashMap(); + if (f.isFolder()) { + h.put("dirname", f.getFileName()); + data.add(h); + } + } + SimpleAdapter sa = new SimpleAdapter(this, + data, + R.layout.uploader_list_item_layout, + new String[] {"dirname"}, + new int[] {R.id.textView1}); + setListAdapter(sa); + Button btn = (Button) findViewById(R.id.uploader_choose_folder); + btn.setOnClickListener(this); + getListView().setOnItemClickListener(this); + } + } + + private boolean prepareStreamsToUpload() { + if (getIntent().getAction().equals(Intent.ACTION_SEND)) { + mStreamsToUpload = new ArrayList(); + mStreamsToUpload.add(getIntent().getParcelableExtra(Intent.EXTRA_STREAM)); + } else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { + mStreamsToUpload = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM); + } + return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null); + } + + public void uploadFiles() { + try { + //OwnCloudClient webdav = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); + + ArrayList local = new ArrayList(); + ArrayList remote = new ArrayList(); + + /* TODO - mCreateDir can never be true at this moment; we will replace wdc.createDirectory by CreateFolderOperation when that is fixed + OwnCloudClient wdc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getApplicationContext()); + // create last directory in path if necessary + if (mCreateDir) { + wdc.createDirectory(mUploadPath); + } + */ + + // this checks the mimeType + for (Parcelable mStream : mStreamsToUpload) { + + Uri uri = (Uri) mStream; + if (uri !=null) { + if (uri.getScheme().equals("content")) { + + String mimeType = getContentResolver().getType(uri); + + if (mimeType.contains("image")) { + String[] CONTENT_PROJECTION = { Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE}; + Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); + c.moveToFirst(); + int index = c.getColumnIndex(Images.Media.DATA); + String data = c.getString(index); + local.add(data); + remote.add(mUploadPath + c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME))); + + } + else if (mimeType.contains("video")) { + String[] CONTENT_PROJECTION = { Video.Media.DATA, Video.Media.DISPLAY_NAME, Video.Media.MIME_TYPE, Video.Media.SIZE, Video.Media.DATE_MODIFIED }; + Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); + c.moveToFirst(); + int index = c.getColumnIndex(Video.Media.DATA); + String data = c.getString(index); + local.add(data); + remote.add(mUploadPath + c.getString(c.getColumnIndex(Video.Media.DISPLAY_NAME))); + + } + else if (mimeType.contains("audio")) { + String[] CONTENT_PROJECTION = { Audio.Media.DATA, Audio.Media.DISPLAY_NAME, Audio.Media.MIME_TYPE, Audio.Media.SIZE }; + Cursor c = getContentResolver().query(uri, CONTENT_PROJECTION, null, null, null); + c.moveToFirst(); + int index = c.getColumnIndex(Audio.Media.DATA); + String data = c.getString(index); + local.add(data); + remote.add(mUploadPath + c.getString(c.getColumnIndex(Audio.Media.DISPLAY_NAME))); + + } + else { + String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""); + // cut everything whats before mnt. It occured to me that sometimes apps send their name into the URI + if (filePath.contains("mnt")) { + String splitedFilePath[] = filePath.split("/mnt"); + filePath = splitedFilePath[1]; + } + final File file = new File(filePath); + local.add(file.getAbsolutePath()); + remote.add(mUploadPath + file.getName()); + } + + } else if (uri.getScheme().equals("file")) { + String filePath = Uri.decode(uri.toString()).replace(uri.getScheme() + "://", ""); + if (filePath.contains("mnt")) { + String splitedFilePath[] = filePath.split("/mnt"); + filePath = splitedFilePath[1]; + } + final File file = new File(filePath); + local.add(file.getAbsolutePath()); + remote.add(mUploadPath + file.getName()); + } + else { + throw new SecurityException(); + } + } + else { + throw new SecurityException(); + } + + Intent intent = new Intent(getApplicationContext(), FileUploader.class); + intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES); + intent.putExtra(FileUploader.KEY_LOCAL_FILE, local.toArray(new String[local.size()])); + intent.putExtra(FileUploader.KEY_REMOTE_FILE, remote.toArray(new String[remote.size()])); + intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount); + startService(intent); + finish(); + } + + } catch (SecurityException e) { + String message = String.format(getString(R.string.uploader_error_forbidden_content), getString(R.string.app_name)); + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + } + +} diff --git a/src/com/owncloud/android/ui/adapter/CertificateCombinedExceptionViewAdapter.java b/src/com/owncloud/android/ui/adapter/CertificateCombinedExceptionViewAdapter.java new file mode 100644 index 00000000..b1c32634 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/CertificateCombinedExceptionViewAdapter.java @@ -0,0 +1,74 @@ +/* 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 . + * + */ +package com.owncloud.android.ui.adapter; + +import com.owncloud.android.R; +import com.owncloud.android.lib.common.network.CertificateCombinedException; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; + +import android.view.View; +import android.widget.TextView; + +/** + * TODO + * + * @author masensio + * @author David A. Velasco + * + */ +public class CertificateCombinedExceptionViewAdapter implements SslUntrustedCertDialog.ErrorViewAdapter { + + //private final static String TAG = CertificateCombinedExceptionViewAdapter.class.getSimpleName(); + + private CertificateCombinedException mSslException = null; + + public CertificateCombinedExceptionViewAdapter(CertificateCombinedException sslException) { + mSslException = sslException; + } + + @Override + public void updateErrorView(View dialogView) { + /// clean + dialogView.findViewById(R.id.reason_no_info_about_error).setVisibility(View.GONE); + + /// refresh + if (mSslException.getCertPathValidatorException() != null) { + ((TextView)dialogView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE); + } + + if (mSslException.getCertificateExpiredException() != null) { + ((TextView)dialogView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE); + } + + if (mSslException.getCertificateNotYetValidException() != null) { + ((TextView)dialogView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE); + } + + if (mSslException.getSslPeerUnverifiedException() != null) { + ((TextView)dialogView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE); + } + + } +} diff --git a/src/com/owncloud/android/ui/adapter/FileListActionListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListActionListAdapter.java deleted file mode 100644 index 94094ee2..00000000 --- a/src/com/owncloud/android/ui/adapter/FileListActionListAdapter.java +++ /dev/null @@ -1,159 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ - -package com.owncloud.android.ui.adapter; - -import java.io.File; - -import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavUtils; -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.database.DataSetObserver; -import android.net.Uri; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.TextView; - -public class FileListActionListAdapter implements ListAdapter { - - private Context mContext; - private Account mAccount; - private String mFilename, mFileType, mFilePath, mFileStoragePath; - - private final int ITEM_DOWNLOAD = 0; - - // private final int ITEM_SHARE = 1; - - public FileListActionListAdapter(Cursor c, Context co, Account account) { - mContext = co; - mFilename = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_NAME)); - mFileType = c.getString(c - .getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)); - mFilePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH)); - mFileStoragePath = c.getString(c - .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH)); - // mItemId = c.getString(c.getColumnIndex(ProviderTableMeta._ID)); - mAccount = account; - } - - public boolean areAllItemsEnabled() { - return true; - } - - public boolean isEnabled(int position) { - return true; - } - - public int getCount() { - return 1; - } - - public Object getItem(int position) { - if (position == 0) { - Intent intent = new Intent(Intent.ACTION_VIEW); - if (TextUtils.isEmpty(mFileStoragePath)) { - intent.putExtra("toDownload", true); - AccountManager accm = (AccountManager) mContext - .getSystemService(Context.ACCOUNT_SERVICE); - String ocurl = accm.getUserData(mAccount, - AccountUtils.constructFullURLForAccount(mContext, mAccount)); - ocurl += WebdavUtils.encodePath(mFilePath + mFilename); - intent.setData(Uri.parse(ocurl)); - } else { - intent.putExtra("toDownload", false); - intent.setDataAndType(Uri.fromFile(new File(mFileStoragePath)), - mFileType); - } - return intent; - } - return null; - } - - public long getItemId(int position) { - return 0; - } - - public int getItemViewType(int position) { - return 0; - } - - public View getView(int position, View convertView, ViewGroup parent) { - View v = convertView; - if (v == null) { - LayoutInflater vi = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - v = vi.inflate(R.layout.file_display_action_list_element, null); - } - - TextView tv; - ImageView iv; - switch (position) { - case ITEM_DOWNLOAD: - tv = (TextView) v.findViewById(R.id.textView1); - if (mFileStoragePath == null) { - tv.setText("Download"); - } else { - setActionName(tv); - } - iv = (ImageView) v.findViewById(R.id.imageView1); - iv.setImageResource(R.drawable.download); - break; - } - - return v; - } - - public int getViewTypeCount() { - return 2; - } - - public boolean hasStableIds() { - return false; - } - - public boolean isEmpty() { - return false; - } - - public void registerDataSetObserver(DataSetObserver observer) { } - - public void unregisterDataSetObserver(DataSetObserver observer) { } - - private void setActionName(TextView tv) { - if (mFileType.matches("image/.*")) { - tv.setText("View"); - } else if (mFileType.matches("audio/.*") - || mFileType.matches("video/.*")) { - tv.setText("Play"); - } else { - tv.setText("Open"); - } - } - -} diff --git a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java index 80f53580..11449af8 100644 --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@ -1,6 +1,6 @@ /* ownCloud Android client application * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2012-2013 ownCloud Inc. + * 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, @@ -17,18 +17,6 @@ */ package com.owncloud.android.ui.adapter; -import java.util.Vector; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.DisplayUtils; -import com.owncloud.android.datamodel.DataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.ui.activity.TransferServiceGetter; - -import com.owncloud.android.R; - import android.accounts.Account; import android.content.Context; import android.view.LayoutInflater; @@ -40,6 +28,19 @@ import android.widget.ListAdapter; import android.widget.ListView; import android.widget.TextView; + +import java.util.Vector; + +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.ui.activity.TransferServiceGetter; +import com.owncloud.android.utils.DisplayUtils; + + /** * This Adapter populates a ListView with all files and folders in an ownCloud * instance. @@ -51,19 +52,14 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { private Context mContext; private OCFile mFile = null; private Vector mFiles = null; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private Account mAccount; private TransferServiceGetter mTransferServiceGetter; - - public FileListListAdapter(OCFile file, DataStorageManager storage_man, - Context context, TransferServiceGetter transferServiceGetter) { - mStorageManager = storage_man; + + public FileListListAdapter(Context context, TransferServiceGetter transferServiceGetter) { mContext = context; mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); mTransferServiceGetter = transferServiceGetter; - swapDirectory(file, mStorageManager); - /*mFile = file; - mFiles = mStorageManager.getDirectoryContent(mFile);*/ } @Override @@ -108,6 +104,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflator.inflate(R.layout.list_item, null); } + if (mFiles != null && mFiles.size() > position) { OCFile file = mFiles.get(position); TextView fileName = (TextView) view.findViewById(R.id.Filename); @@ -131,13 +128,12 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { } else { localStateView.setVisibility(View.INVISIBLE); } - TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); TextView lastModV = (TextView) view.findViewById(R.id.last_mod); ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); - if (!file.isDirectory()) { + if (!file.isFolder()) { fileSizeV.setVisibility(View.VISIBLE); fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); lastModV.setVisibility(View.VISIBLE); @@ -162,11 +158,22 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { checkBoxV.setVisibility(View.VISIBLE); } + } + else { + + fileSizeV.setVisibility(View.INVISIBLE); + //fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); + lastModV.setVisibility(View.VISIBLE); + lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())); + checkBoxV.setVisibility(View.GONE); + view.findViewById(R.id.imageView3).setVisibility(View.GONE); + } + + ImageView shareIconV = (ImageView) view.findViewById(R.id.shareIcon); + if (file.isShareByLink()) { + shareIconV.setVisibility(View.VISIBLE); } else { - fileSizeV.setVisibility(View.GONE); - lastModV.setVisibility(View.GONE); - checkBoxV.setVisibility(View.GONE); - view.findViewById(R.id.imageView3).setVisibility(View.GONE); + shareIconV.setVisibility(View.INVISIBLE); } } @@ -193,14 +200,14 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". * @param updatedStorageManager Optional updated storage manager; used to replace mStorageManager if is different (and not NULL) */ - public void swapDirectory(OCFile directory, DataStorageManager updatedStorageManager) { + public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) { mFile = directory; if (updatedStorageManager != null && updatedStorageManager != mStorageManager) { mStorageManager = updatedStorageManager; mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); } if (mStorageManager != null) { - mFiles = mStorageManager.getDirectoryContent(mFile); + mFiles = mStorageManager.getFolderContent(mFile); } else { mFiles = null; } diff --git a/src/com/owncloud/android/ui/adapter/LandingScreenAdapter.java b/src/com/owncloud/android/ui/adapter/LandingScreenAdapter.java deleted file mode 100644 index 815d3aac..00000000 --- a/src/com/owncloud/android/ui/adapter/LandingScreenAdapter.java +++ /dev/null @@ -1,112 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ -package com.owncloud.android.ui.adapter; - -import com.owncloud.android.AccountUtils; -import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.ui.activity.Preferences; - -import android.content.Context; -import android.content.Intent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; -import com.owncloud.android.R; - -/** - * Populates the landing screen icons. - * - * @author Lennart Rosam - * - */ -public class LandingScreenAdapter extends BaseAdapter { - - private Context mContext; - - private final Integer[] mLandingScreenIcons = { R.drawable.home, - R.drawable.music, R.drawable.contacts, R.drawable.calendar, - android.R.drawable.ic_menu_agenda, R.drawable.settings }; - - private final Integer[] mLandingScreenTexts = { R.string.main_files, - R.string.main_music, R.string.main_contacts, - R.string.main_calendar, R.string.main_bookmarks, - R.string.main_settings }; - - public LandingScreenAdapter(Context context) { - mContext = context; - } - - @Override - public int getCount() { - return mLandingScreenIcons.length; - } - - @Override - /** - * Returns the Intent associated with this object - * or null if the functionality is not yet implemented - */ - public Object getItem(int position) { - Intent intent = new Intent(); - - switch (position) { - case 0: - /* - * The FileDisplayActivity requires the ownCloud account as an - * parcableExtra. We will put in the one that is selected in the - * preferences - */ - intent.setClass(mContext, FileDisplayActivity.class); - intent.putExtra("ACCOUNT", - AccountUtils.getCurrentOwnCloudAccount(mContext)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - break; - case 5: - intent.setClass(mContext, Preferences.class); - break; - default: - intent = null; - } - return intent; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - LayoutInflater inflator = LayoutInflater.from(mContext); - convertView = inflator.inflate(R.layout.landing_page_item, null); - - ImageView icon = (ImageView) convertView - .findViewById(R.id.gridImage); - TextView iconText = (TextView) convertView - .findViewById(R.id.gridText); - - icon.setImageResource(mLandingScreenIcons[position]); - iconText.setText(mLandingScreenTexts[position]); - } - return convertView; - } -} diff --git a/src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java b/src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java index c93a2d9c..5686874e 100644 --- a/src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/LocalFileListAdapter.java @@ -1,6 +1,6 @@ /* ownCloud Android client application * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2012-2013 ownCloud Inc. + * 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, @@ -21,8 +21,9 @@ import java.io.File; import java.util.Arrays; import java.util.Comparator; -import com.owncloud.android.DisplayUtils; import com.owncloud.android.R; +import com.owncloud.android.utils.DisplayUtils; + import android.content.Context; import android.view.LayoutInflater; @@ -134,6 +135,8 @@ public class LocalFileListAdapter extends BaseAdapter implements ListAdapter { 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); + + view.findViewById(R.id.shareIcon).setVisibility(View.GONE); } return view; diff --git a/src/com/owncloud/android/ui/adapter/LogListAdapter.java b/src/com/owncloud/android/ui/adapter/LogListAdapter.java index 6b6e8f6f..ae4335ef 100644 --- a/src/com/owncloud/android/ui/adapter/LogListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/LogListAdapter.java @@ -2,6 +2,8 @@ package com.owncloud.android.ui.adapter; import java.io.File; +import com.owncloud.android.R; + import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -13,7 +15,6 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; -import com.owncloud.android.R; public class LogListAdapter extends ArrayAdapter { diff --git a/src/com/owncloud/android/ui/adapter/SslCertificateViewAdapter.java b/src/com/owncloud/android/ui/adapter/SslCertificateViewAdapter.java new file mode 100644 index 00000000..a944eadb --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/SslCertificateViewAdapter.java @@ -0,0 +1,126 @@ +/* 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 . + * + */ +package com.owncloud.android.ui.adapter; + +import java.text.DateFormat; +import java.util.Date; + +import com.owncloud.android.R; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; +import android.net.http.SslCertificate; +import android.view.View; +import android.widget.TextView; + +/** + * TODO + * + * @author masensio + * @author David A. Velasco + */ +public class SslCertificateViewAdapter implements SslUntrustedCertDialog.CertificateViewAdapter { + + //private final static String TAG = SslCertificateViewAdapter.class.getSimpleName(); + + private SslCertificate mCertificate; + + + /** + * Constructor + * + * @param + */ + public SslCertificateViewAdapter(SslCertificate certificate) { + mCertificate = certificate; + } + + @Override + public void updateCertificateView(View dialogView) { + TextView nullCerView = (TextView) dialogView.findViewById(R.id.null_cert); + if (mCertificate != null) { + nullCerView.setVisibility(View.GONE); + showSubject(mCertificate.getIssuedTo(), dialogView); + showIssuer(mCertificate.getIssuedBy(), dialogView); + showValidity(mCertificate.getValidNotBeforeDate(), mCertificate.getValidNotAfterDate(), dialogView); + hideSignature(dialogView); + + } else { + nullCerView.setVisibility(View.VISIBLE); + } + } + + private void showValidity(Date notBefore, Date notAfter, View dialogView) { + TextView fromView = ((TextView)dialogView.findViewById(R.id.value_validity_from)); + TextView toView = ((TextView)dialogView.findViewById(R.id.value_validity_to)); + DateFormat dateFormat = DateFormat.getDateInstance(); + fromView.setText(dateFormat.format(notBefore)); + toView.setText(dateFormat.format(notAfter)); + } + + + private void showSubject(SslCertificate.DName subject, View dialogView) { + TextView cnView = ((TextView)dialogView.findViewById(R.id.value_subject_CN)); + cnView.setText(subject.getCName()); + cnView.setVisibility(View.VISIBLE); + + TextView oView = ((TextView)dialogView.findViewById(R.id.value_subject_O)); + oView.setText(subject.getOName()); + oView.setVisibility(View.VISIBLE); + + TextView ouView = ((TextView)dialogView.findViewById(R.id.value_subject_OU)); + ouView.setText(subject.getUName()); + ouView.setVisibility(View.VISIBLE); + + // SslCertificates don't offer this information + ((TextView)dialogView.findViewById(R.id.value_subject_C)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.value_subject_ST)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.value_subject_L)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.label_subject_C)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.label_subject_ST)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.label_subject_L)).setVisibility(View.GONE); + } + + + private void showIssuer(SslCertificate.DName issuer, View dialogView) { + TextView cnView = ((TextView)dialogView.findViewById(R.id.value_issuer_CN)); + cnView.setText(issuer.getCName()); + cnView.setVisibility(View.VISIBLE); + + TextView oView = ((TextView)dialogView.findViewById(R.id.value_issuer_O)); + oView.setText(issuer.getOName()); + oView.setVisibility(View.VISIBLE); + + TextView ouView = ((TextView)dialogView.findViewById(R.id.value_issuer_OU)); + ouView.setText(issuer.getUName()); + ouView.setVisibility(View.VISIBLE); + + // SslCertificates don't offer this information + ((TextView)dialogView.findViewById(R.id.value_issuer_C)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.value_issuer_ST)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.value_issuer_L)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.label_issuer_C)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.label_issuer_ST)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.label_issuer_L)).setVisibility(View.GONE); + } + + private void hideSignature(View dialogView) { + ((TextView)dialogView.findViewById(R.id.label_signature)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.label_signature_algorithm)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.value_signature_algorithm)).setVisibility(View.GONE); + ((TextView)dialogView.findViewById(R.id.value_signature)).setVisibility(View.GONE); + } + +} diff --git a/src/com/owncloud/android/ui/adapter/SslErrorViewAdapter.java b/src/com/owncloud/android/ui/adapter/SslErrorViewAdapter.java new file mode 100644 index 00000000..7d2e291b --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/SslErrorViewAdapter.java @@ -0,0 +1,73 @@ +/* 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 . + * + */ +package com.owncloud.android.ui.adapter; + +import com.owncloud.android.R; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; +import android.net.http.SslError; +import android.view.View; +import android.widget.TextView; + +/** + * Dialog to show an Untrusted Certificate + * + * @author masensio + * @author David A. Velasco + * + */ +public class SslErrorViewAdapter implements SslUntrustedCertDialog.ErrorViewAdapter { + + //private final static String TAG = SslErrorViewAdapter.class.getSimpleName(); + + private SslError mSslError; + + public SslErrorViewAdapter(SslError sslError) { + mSslError = sslError; + } + + @Override + public void updateErrorView(View dialogView) { + /// clean + dialogView.findViewById(R.id.reason_no_info_about_error).setVisibility(View.GONE); + + /// refresh + if (mSslError.hasError(SslError.SSL_UNTRUSTED)) { + ((TextView)dialogView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE); + } + + if (mSslError.hasError(SslError.SSL_EXPIRED)) { + ((TextView)dialogView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE); + } + + if (mSslError.getPrimaryError() == SslError.SSL_NOTYETVALID) { + ((TextView)dialogView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE); + } + + if (mSslError.getPrimaryError() == SslError.SSL_IDMISMATCH) { + ((TextView)dialogView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE); + } else { + dialogView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE); + } + } + +} diff --git a/src/com/owncloud/android/ui/adapter/X509CertificateViewAdapter.java b/src/com/owncloud/android/ui/adapter/X509CertificateViewAdapter.java new file mode 100644 index 00000000..a290dca2 --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/X509CertificateViewAdapter.java @@ -0,0 +1,204 @@ +/* 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 . + * + */ +package com.owncloud.android.ui.adapter; + +import java.security.cert.X509Certificate; +import java.text.DateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.x500.X500Principal; + +import com.owncloud.android.R; +import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; + +import android.view.View; +import android.widget.TextView; + +/** + * + * @author masensio + * @author David A. Velasco + * + */ +public class X509CertificateViewAdapter implements SslUntrustedCertDialog.CertificateViewAdapter { + + //private final static String TAG = X509CertificateViewAdapter.class.getSimpleName(); + + private X509Certificate mCertificate = null; + + public X509CertificateViewAdapter(X509Certificate certificate) { + mCertificate = certificate; + } + + @Override + public void updateCertificateView(View dialogView) { + TextView nullCerView = (TextView) dialogView.findViewById(R.id.null_cert); + + if (mCertificate != null) { + nullCerView.setVisibility(View.GONE); + showSubject(mCertificate.getSubjectX500Principal(), dialogView); + showIssuer(mCertificate.getIssuerX500Principal(), dialogView); + showValidity(mCertificate.getNotBefore(), mCertificate.getNotAfter(), dialogView); + showSignature(dialogView); + + } else { + nullCerView.setVisibility(View.VISIBLE); + } + } + + private void showSignature(View dialogView) { + TextView sigView = ((TextView)dialogView.findViewById(R.id.value_signature)); + TextView algorithmView = ((TextView)dialogView.findViewById(R.id.value_signature_algorithm)); + sigView.setText(getHex(mCertificate.getSignature())); + algorithmView.setText(mCertificate.getSigAlgName()); + } + + public String getHex(final byte [] raw) { + if (raw == null) { + return null; + } + final StringBuilder hex = new StringBuilder(2 * raw.length); + for (final byte b : raw) { + final int hiVal = (b & 0xF0) >> 4; + final int loVal = b & 0x0F; + hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7)))); + hex.append((char) ('0' + (loVal + (loVal / 10 * 7)))); + } + return hex.toString(); + } + + private void showValidity(Date notBefore, Date notAfter, View dialogView) { + TextView fromView = ((TextView)dialogView.findViewById(R.id.value_validity_from)); + TextView toView = ((TextView)dialogView.findViewById(R.id.value_validity_to)); + DateFormat dateFormat = DateFormat.getDateInstance(); + fromView.setText(dateFormat.format(notBefore)); + toView.setText(dateFormat.format(notAfter)); + } + + private void showSubject(X500Principal subject, View dialogView) { + Map s = parsePrincipal(subject); + TextView cnView = ((TextView)dialogView.findViewById(R.id.value_subject_CN)); + TextView oView = ((TextView)dialogView.findViewById(R.id.value_subject_O)); + TextView ouView = ((TextView)dialogView.findViewById(R.id.value_subject_OU)); + TextView cView = ((TextView)dialogView.findViewById(R.id.value_subject_C)); + TextView stView = ((TextView)dialogView.findViewById(R.id.value_subject_ST)); + TextView lView = ((TextView)dialogView.findViewById(R.id.value_subject_L)); + + if (s.get("CN") != null) { + cnView.setText(s.get("CN")); + cnView.setVisibility(View.VISIBLE); + } else { + cnView.setVisibility(View.GONE); + } + if (s.get("O") != null) { + oView.setText(s.get("O")); + oView.setVisibility(View.VISIBLE); + } else { + oView.setVisibility(View.GONE); + } + if (s.get("OU") != null) { + ouView.setText(s.get("OU")); + ouView.setVisibility(View.VISIBLE); + } else { + ouView.setVisibility(View.GONE); + } + if (s.get("C") != null) { + cView.setText(s.get("C")); + cView.setVisibility(View.VISIBLE); + } else { + cView.setVisibility(View.GONE); + } + if (s.get("ST") != null) { + stView.setText(s.get("ST")); + stView.setVisibility(View.VISIBLE); + } else { + stView.setVisibility(View.GONE); + } + if (s.get("L") != null) { + lView.setText(s.get("L")); + lView.setVisibility(View.VISIBLE); + } else { + lView.setVisibility(View.GONE); + } + } + + private void showIssuer(X500Principal issuer, View dialogView) { + Map s = parsePrincipal(issuer); + TextView cnView = ((TextView)dialogView.findViewById(R.id.value_issuer_CN)); + TextView oView = ((TextView)dialogView.findViewById(R.id.value_issuer_O)); + TextView ouView = ((TextView)dialogView.findViewById(R.id.value_issuer_OU)); + TextView cView = ((TextView)dialogView.findViewById(R.id.value_issuer_C)); + TextView stView = ((TextView)dialogView.findViewById(R.id.value_issuer_ST)); + TextView lView = ((TextView)dialogView.findViewById(R.id.value_issuer_L)); + + if (s.get("CN") != null) { + cnView.setText(s.get("CN")); + cnView.setVisibility(View.VISIBLE); + } else { + cnView.setVisibility(View.GONE); + } + if (s.get("O") != null) { + oView.setText(s.get("O")); + oView.setVisibility(View.VISIBLE); + } else { + oView.setVisibility(View.GONE); + } + if (s.get("OU") != null) { + ouView.setText(s.get("OU")); + ouView.setVisibility(View.VISIBLE); + } else { + ouView.setVisibility(View.GONE); + } + if (s.get("C") != null) { + cView.setText(s.get("C")); + cView.setVisibility(View.VISIBLE); + } else { + cView.setVisibility(View.GONE); + } + if (s.get("ST") != null) { + stView.setText(s.get("ST")); + stView.setVisibility(View.VISIBLE); + } else { + stView.setVisibility(View.GONE); + } + if (s.get("L") != null) { + lView.setText(s.get("L")); + lView.setVisibility(View.VISIBLE); + } else { + lView.setVisibility(View.GONE); + } + } + + + private Map parsePrincipal(X500Principal principal) { + Map result = new HashMap(); + String toParse = principal.getName(); + String[] pieces = toParse.split(","); + String[] tokens = {"CN", "O", "OU", "C", "ST", "L"}; + for (int i=0; i < pieces.length ; i++) { + for (int j=0; j. + * + */ +package com.owncloud.android.ui.dialog; + +import com.owncloud.android.R; + +import android.app.Dialog; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.TextView; + +public class LoadingDialog extends DialogFragment { + + private String mMessage; + + public LoadingDialog() { + super(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + setCancelable(false); + } + + public LoadingDialog(String message) { + this.mMessage = message; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Create a view by inflating desired layout + View v = inflater.inflate(R.layout.loading_dialog, container, false); + + // set value + TextView tv = (TextView) v.findViewById(R.id.loadingText); + tv.setText(mMessage); + + return v; + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + } + + @Override + public void onDestroyView() { + if (getDialog() != null && getRetainInstance()) + getDialog().setDismissMessage(null); + super.onDestroyView(); + } +} diff --git a/src/com/owncloud/android/ui/dialog/SamlWebViewDialog.java b/src/com/owncloud/android/ui/dialog/SamlWebViewDialog.java new file mode 100644 index 00000000..0c17a6f2 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/SamlWebViewDialog.java @@ -0,0 +1,270 @@ +/* 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 . + * + */ + +package com.owncloud.android.ui.dialog; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.FragmentManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.RelativeLayout; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.R; +import com.owncloud.android.authentication.SsoWebViewClient; +import com.owncloud.android.authentication.SsoWebViewClient.SsoWebViewClientListener; +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.utils.Log_OC; + + +/** + * Dialog to show the WebView for SAML Authentication + * + * @author Maria Asensio + * @author David A. Velasco + */ +public class SamlWebViewDialog extends SherlockDialogFragment { + + public final String SAML_DIALOG_TAG = "SamlWebViewDialog"; + + private final static String TAG = SamlWebViewDialog.class.getSimpleName(); + + private static final String ARG_INITIAL_URL = "INITIAL_URL"; + private static final String ARG_TARGET_URL = "TARGET_URL"; + + private WebView mSsoWebView; + private SsoWebViewClient mWebViewClient; + + private String mInitialUrl; + private String mTargetUrl; + + private Handler mHandler; + + private SsoWebViewClientListener mSsoWebViewClientListener; + + /** + * Public factory method to get dialog instances. + * + * @param handler + * @param Url Url to open at WebView + * @param targetURL mBaseUrl + AccountUtils.getWebdavPath(mDiscoveredVersion, mCurrentAuthTokenType) + * @return New dialog instance, ready to show. + */ + public static SamlWebViewDialog newInstance(String url, String targetUrl) { + Log_OC.d(TAG, "New instance"); + SamlWebViewDialog fragment = new SamlWebViewDialog(); + Bundle args = new Bundle(); + args.putString(ARG_INITIAL_URL, url); + args.putString(ARG_TARGET_URL, targetUrl); + fragment.setArguments(args); + return fragment; + } + + + public SamlWebViewDialog() { + super(); + Log_OC.d(TAG, "constructor"); + } + + + @Override + public void onAttach(Activity activity) { + Log_OC.d(TAG, "onAttach"); + super.onAttach(activity); + try { + mSsoWebViewClientListener = (SsoWebViewClientListener) activity; + mHandler = new Handler(); + mWebViewClient = new SsoWebViewClient(activity, mHandler, mSsoWebViewClientListener); + + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + SsoWebViewClientListener.class.getSimpleName()); + } + } + + + @SuppressLint("SetJavaScriptEnabled") + @Override + public void onCreate(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreate, savedInstanceState is " + savedInstanceState); + super.onCreate(savedInstanceState); + + setRetainInstance(true); + + CookieSyncManager.createInstance(getSherlockActivity().getApplicationContext()); + + if (savedInstanceState == null) { + mInitialUrl = getArguments().getString(ARG_INITIAL_URL); + mTargetUrl = getArguments().getString(ARG_TARGET_URL); + } else { + mInitialUrl = savedInstanceState.getString(ARG_INITIAL_URL); + mTargetUrl = savedInstanceState.getString(ARG_TARGET_URL); + } + + setStyle(SherlockDialogFragment.STYLE_NO_TITLE, R.style.Theme_ownCloud_Dialog); + } + + @SuppressWarnings("deprecation") + @SuppressLint("SetJavaScriptEnabled") + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreateView, savedInsanceState is " + savedInstanceState); + + // Inflate layout of the dialog + RelativeLayout ssoRootView = (RelativeLayout) inflater.inflate(R.layout.sso_dialog, container, false); // null parent view because it will go in the dialog layout + + if (mSsoWebView == null) { + // initialize the WebView + mSsoWebView = new SsoWebView(getSherlockActivity().getApplicationContext()); + mSsoWebView.setFocusable(true); + mSsoWebView.setFocusableInTouchMode(true); + mSsoWebView.setClickable(true); + + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptCookie(true); + cookieManager.removeAllCookie(); + mSsoWebView.loadUrl(mInitialUrl); + + WebSettings webSettings = mSsoWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setBuiltInZoomControls(false); + webSettings.setLoadWithOverviewMode(false); + webSettings.setSavePassword(false); + webSettings.setUserAgentString(OwnCloudClient.USER_AGENT); + webSettings.setSaveFormData(false); + } + + mWebViewClient.setTargetUrl(mTargetUrl); + mSsoWebView.setWebViewClient(mWebViewClient); + + // add the webview into the layout + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + ssoRootView.addView(mSsoWebView, layoutParams); + ssoRootView.requestLayout(); + + return ssoRootView; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + Log_OC.d(TAG, "onSaveInstanceState being CALLED"); + super.onSaveInstanceState(outState); + + // save URLs + outState.putString(ARG_INITIAL_URL, mInitialUrl); + outState.putString(ARG_TARGET_URL, mTargetUrl); + } + + @Override + public void onDestroyView() { + Log_OC.d(TAG, "onDestroyView"); + + if ((ViewGroup)mSsoWebView.getParent() != null) { + ((ViewGroup)mSsoWebView.getParent()).removeView(mSsoWebView); + } + + mSsoWebView.setWebViewClient(null); + + // Work around bug: http://code.google.com/p/android/issues/detail?id=17423 + Dialog dialog = getDialog(); + if ((dialog != null)) { + dialog.setOnDismissListener(null); + //dialog.dismiss(); + //dialog.setDismissMessage(null); + } + + super.onDestroyView(); + } + + @Override + public void onDestroy() { + Log_OC.d(TAG, "onDestroy"); + super.onDestroy(); + } + + @Override + public void onDetach() { + Log_OC.d(TAG, "onDetach"); + mSsoWebViewClientListener = null; + mWebViewClient = null; + super.onDetach(); + } + + @Override + public void onCancel (DialogInterface dialog) { + Log_OC.d(TAG, "onCancel"); + super.onCancel(dialog); + } + + @Override + public void onDismiss (DialogInterface dialog) { + Log_OC.d(TAG, "onDismiss"); + super.onDismiss(dialog); + } + + @Override + public void onStart() { + Log_OC.d(TAG, "onStart"); + super.onStart(); + } + + @Override + public void onStop() { + Log_OC.d(TAG, "onStop"); + super.onStop(); + } + + @Override + public void onResume() { + Log_OC.d(TAG, "onResume"); + super.onResume(); + mSsoWebView.onResume(); + } + + @Override + public void onPause() { + Log_OC.d(TAG, "onPause"); + mSsoWebView.onPause(); + super.onPause(); + } + + @Override + public int show (FragmentTransaction transaction, String tag) { + Log_OC.d(TAG, "show (transaction)"); + return super.show(transaction, tag); + } + + @Override + public void show (FragmentManager manager, String tag) { + Log_OC.d(TAG, "show (manager)"); + super.show(manager, tag); + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/dialog/ShareLinkToDialog.java b/src/com/owncloud/android/ui/dialog/ShareLinkToDialog.java new file mode 100644 index 00000000..3076e27e --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/ShareLinkToDialog.java @@ -0,0 +1,184 @@ +/* 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 . + * + */ + +package com.owncloud.android.ui.dialog; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.FileOperationsHelper; +import com.owncloud.android.ui.activity.CopyToClipboardActivity; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.utils.Log_OC; + +/** + * Dialog showing a list activities able to resolve a given Intent, + * filtering out the activities matching give package names. + * + * @author David A. Velasco + */ +public class ShareLinkToDialog extends SherlockDialogFragment { + + private final static String TAG = ShareLinkToDialog.class.getSimpleName(); + private final static String ARG_INTENT = ShareLinkToDialog.class.getSimpleName() + ".ARG_INTENT"; + private final static String ARG_PACKAGES_TO_EXCLUDE = ShareLinkToDialog.class.getSimpleName() + ".ARG_PACKAGES_TO_EXCLUDE"; + private final static String ARG_FILE_TO_SHARE = ShareLinkToDialog.class.getSimpleName() + ".FILE_TO_SHARE"; + + private ActivityAdapter mAdapter; + private OCFile mFile; + private Intent mIntent; + + public static ShareLinkToDialog newInstance(Intent intent, String[] packagesToExclude, OCFile fileToShare) { + ShareLinkToDialog f = new ShareLinkToDialog(); + Bundle args = new Bundle(); + args.putParcelable(ARG_INTENT, intent); + args.putStringArray(ARG_PACKAGES_TO_EXCLUDE, packagesToExclude); + args.putParcelable(ARG_FILE_TO_SHARE, fileToShare); + f.setArguments(args); + return f; + } + + public ShareLinkToDialog() { + super(); + Log_OC.d(TAG, "constructor"); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + mIntent = getArguments().getParcelable(ARG_INTENT); + String[] packagesToExclude = getArguments().getStringArray(ARG_PACKAGES_TO_EXCLUDE); + List packagesToExcludeList = Arrays.asList(packagesToExclude != null ? packagesToExclude : new String[0]); + mFile = getArguments().getParcelable(ARG_FILE_TO_SHARE); + + PackageManager pm= getSherlockActivity().getPackageManager(); + List activities = pm.queryIntentActivities(mIntent, PackageManager.MATCH_DEFAULT_ONLY); + Iterator it = activities.iterator(); + ResolveInfo resolveInfo; + while (it.hasNext()) { + resolveInfo = it.next(); + if (packagesToExcludeList.contains(resolveInfo.activityInfo.packageName.toLowerCase())) { + it.remove(); + } + } + + boolean sendAction = mIntent.getBooleanExtra(Intent.ACTION_SEND, false); + + if (!sendAction) { + // add activity for copy to clipboard + Intent copyToClipboardIntent = new Intent(getSherlockActivity(), CopyToClipboardActivity.class); + List copyToClipboard = pm.queryIntentActivities(copyToClipboardIntent, 0); + if (!copyToClipboard.isEmpty()) { + activities.add(copyToClipboard.get(0)); + } + } + + Collections.sort(activities, new ResolveInfo.DisplayNameComparator(pm)); + mAdapter = new ActivityAdapter(getSherlockActivity(), pm, activities); + + return createSelector(sendAction); + + } + + private AlertDialog createSelector(final boolean sendAction) { + + int titleId; + if (sendAction) { + titleId = R.string.activity_chooser_send_file_title; + } else { + titleId = R.string.activity_chooser_title; + } + + return new AlertDialog.Builder(getSherlockActivity()) + .setTitle(titleId) + .setAdapter(mAdapter, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Add the information of the chosen activity to the intent to send + ResolveInfo chosen = mAdapter.getItem(which); + ActivityInfo actInfo = chosen.activityInfo; + ComponentName name=new ComponentName(actInfo.applicationInfo.packageName, actInfo.name); + mIntent.setComponent(name); + + if (sendAction) { + dialog.dismiss(); // explicitly added for Android 2.x devices + + // Send the file + ((FileActivity)getSherlockActivity()).startActivity(mIntent); + + } else { + // Create a new share resource + FileOperationsHelper foh = new FileOperationsHelper(); + foh.shareFileWithLinkToApp(mFile, mIntent, (FileActivity)getSherlockActivity()); + } + } + }) + .create(); + } + + class ActivityAdapter extends ArrayAdapter { + + private PackageManager mPackageManager; + + ActivityAdapter(Context context, PackageManager pm, List apps) { + super(context, R.layout.activity_row, apps); + this.mPackageManager = pm; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = newView(parent); + } + bindView(position, convertView); + return convertView; + } + + private View newView(ViewGroup parent) { + return(((LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.activity_row, parent, false)); + } + + private void bindView(int position, View row) { + TextView label = (TextView) row.findViewById(R.id.title); + label.setText(getItem(position).loadLabel(mPackageManager)); + ImageView icon = (ImageView) row.findViewById(R.id.icon); + icon.setImageDrawable(getItem(position).loadIcon(mPackageManager)); + } + } + +} diff --git a/src/com/owncloud/android/ui/dialog/SslUntrustedCertDialog.java b/src/com/owncloud/android/ui/dialog/SslUntrustedCertDialog.java new file mode 100644 index 00000000..315ed194 --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/SslUntrustedCertDialog.java @@ -0,0 +1,241 @@ +/* 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 . + * + */ +package com.owncloud.android.ui.dialog; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; + +import android.app.Activity; +import android.app.Dialog; +import android.net.http.SslError; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.View.OnClickListener; +import android.webkit.SslErrorHandler; +import android.widget.Button; + +import com.actionbarsherlock.app.SherlockDialogFragment; +import com.owncloud.android.R; +import com.owncloud.android.lib.common.network.CertificateCombinedException; +import com.owncloud.android.lib.common.network.NetworkUtils; +import com.owncloud.android.ui.adapter.CertificateCombinedExceptionViewAdapter; +import com.owncloud.android.ui.adapter.SslCertificateViewAdapter; +import com.owncloud.android.ui.adapter.SslErrorViewAdapter; +import com.owncloud.android.ui.adapter.X509CertificateViewAdapter; +import com.owncloud.android.utils.Log_OC; + +/** + * Dialog to show information about an untrusted certificate and allow the user + * to decide trust on it or not. + * + * Abstract implementation of common functionality for different dialogs that + * get the information about the error and the certificate from different classes. + * + * @author masensio + * @author David A. Velasco + */ +public class SslUntrustedCertDialog extends SherlockDialogFragment { + + private final static String TAG = SslUntrustedCertDialog.class.getSimpleName(); + + protected View mView = null; + protected SslErrorHandler mHandler = null; + protected X509Certificate m509Certificate = null; + + private ErrorViewAdapter mErrorViewAdapter = null; + private CertificateViewAdapter mCertificateViewAdapter = null; + + public static SslUntrustedCertDialog newInstanceForEmptySslError(SslError error, SslErrorHandler handler) { + if (error == null) { + throw new IllegalArgumentException("Trying to create instance with parameter error == null"); + } + if (handler == null) { + throw new IllegalArgumentException("Trying to create instance with parameter handler == null"); + } + SslUntrustedCertDialog dialog = new SslUntrustedCertDialog(); + dialog.mHandler = handler; + dialog.mErrorViewAdapter = new SslErrorViewAdapter(error); + dialog.mCertificateViewAdapter = new SslCertificateViewAdapter(error.getCertificate()); + return dialog; + } + + public static SslUntrustedCertDialog newInstanceForFullSslError(CertificateCombinedException sslException) { + if (sslException == null) { + throw new IllegalArgumentException("Trying to create instance with parameter sslException == null"); + } + SslUntrustedCertDialog dialog = new SslUntrustedCertDialog(); + dialog.m509Certificate = sslException.getServerCertificate(); + dialog.mErrorViewAdapter = new CertificateCombinedExceptionViewAdapter(sslException); + dialog.mCertificateViewAdapter = new X509CertificateViewAdapter(sslException.getServerCertificate()); + return dialog; + } + + public static SslUntrustedCertDialog newInstanceForFullSslError(X509Certificate cert, SslError error, SslErrorHandler handler) { + if (cert == null) { + throw new IllegalArgumentException("Trying to create instance with parameter cert == null"); + } + if (error == null) { + throw new IllegalArgumentException("Trying to create instance with parameter error == null"); + } + if (handler == null) { + throw new IllegalArgumentException("Trying to create instance with parameter handler == null"); + } + SslUntrustedCertDialog dialog = new SslUntrustedCertDialog(); + dialog.m509Certificate = cert; + dialog.mHandler = handler; + dialog.mErrorViewAdapter = new SslErrorViewAdapter(error); + dialog.mCertificateViewAdapter = new X509CertificateViewAdapter(cert); + return dialog; + } + + + @Override + public void onAttach(Activity activity) { + Log_OC.d(TAG, "onAttach"); + super.onAttach(activity); + if (!(activity instanceof OnSslUntrustedCertListener)) { + throw new IllegalArgumentException("The host activity must implement " + OnSslUntrustedCertListener.class.getCanonicalName()); + } + } + + + @Override + public void onCreate(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreate, savedInstanceState is " + savedInstanceState); + super.onCreate(savedInstanceState); + setRetainInstance(true); // force to keep the state of the fragment on configuration changes (such as device rotations) + setCancelable(false); + mView = null; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreateView, savedInsanceState is " + savedInstanceState); + // Create a view by inflating desired layout + if (mView == null) { + mView = inflater.inflate(R.layout.ssl_untrusted_cert_layout, container, false); + mView.findViewById(R.id.details_scroll).setVisibility(View.GONE); + mErrorViewAdapter.updateErrorView(mView); + } else { + ((ViewGroup)mView.getParent()).removeView(mView); + } + + Button ok = (Button) mView.findViewById(R.id.ok); + ok.setOnClickListener(new OnCertificateTrusted()); + + Button cancel = (Button) mView.findViewById(R.id.cancel); + cancel.setOnClickListener(new OnCertificateNotTrusted()); + + Button details = (Button) mView.findViewById(R.id.details_btn); + details.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + View detailsScroll = mView.findViewById(R.id.details_scroll); + if (detailsScroll.getVisibility() == View.VISIBLE) { + detailsScroll.setVisibility(View.GONE); + ((Button) v).setText(R.string.ssl_validator_btn_details_see); + + } else { + detailsScroll.setVisibility(View.VISIBLE); + ((Button) v).setText(R.string.ssl_validator_btn_details_hide); + mCertificateViewAdapter.updateCertificateView(mView); + } + } + + }); + + return mView; + } + + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Log_OC.d(TAG, "onCreateDialog, savedInstanceState is " + savedInstanceState); + final Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + } + + @Override + public void onDestroyView() { + Log_OC.d(TAG, "onDestroyView"); + if (getDialog() != null && getRetainInstance()) + getDialog().setDismissMessage(null); + super.onDestroyView(); + } + + private class OnCertificateNotTrusted implements OnClickListener { + + @Override + public void onClick(View v) { + getDialog().cancel(); + if (mHandler != null) { + mHandler.cancel(); + } + ((OnSslUntrustedCertListener)getSherlockActivity()).onCancelCertificate(); + } + } + + + private class OnCertificateTrusted implements OnClickListener { + + @Override + public void onClick(View v) { + dismiss(); + if (mHandler != null) { + mHandler.proceed(); + } + if (m509Certificate != null) { + Activity activity = getSherlockActivity(); + try { + NetworkUtils.addCertToKnownServersStore(m509Certificate, activity); // TODO make this asynchronously, it can take some time + ((OnSslUntrustedCertListener)activity).onSavedCertificate(); + + } catch (GeneralSecurityException e) { + ((OnSslUntrustedCertListener)activity).onFailedSavingCertificate(); + Log_OC.e(TAG, "Server certificate could not be saved in the known-servers trust store ", e); + + } catch (IOException e) { + ((OnSslUntrustedCertListener)activity).onFailedSavingCertificate(); + Log_OC.e(TAG, "Server certificate could not be saved in the known-servers trust store ", e); + } + } + } + + } + + + public interface OnSslUntrustedCertListener { + public void onSavedCertificate(); + public void onFailedSavingCertificate(); + public void onCancelCertificate(); + } + + public interface ErrorViewAdapter { + void updateErrorView(View mView); + } + + public interface CertificateViewAdapter { + void updateCertificateView(View mView); + } + +} diff --git a/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java b/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java index 16e390da..0e955523 100644 --- a/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java +++ b/src/com/owncloud/android/ui/dialog/SslValidatorDialog.java @@ -18,6 +18,7 @@ package com.owncloud.android.ui.dialog; import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; @@ -28,20 +29,20 @@ import java.util.Map; import javax.security.auth.x500.X500Principal; +import com.owncloud.android.R; + import android.app.Dialog; import android.content.Context; import android.os.Bundle; -import android.util.Log; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.TextView; -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; -import com.owncloud.android.network.CertificateCombinedException; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.network.CertificateCombinedException; +import com.owncloud.android.lib.common.network.NetworkUtils; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.utils.Log_OC; /** * Dialog to request the user about a certificate that could not be validated with the certificates store in the system. @@ -112,7 +113,13 @@ public class SslValidatorDialog extends Dialog { else Log_OC.d(TAG, "Nobody there to notify the certificate was saved"); - } catch (Exception e) { + } catch (GeneralSecurityException e) { + dismiss(); + if (mListener != null) + mListener.onFailedSavingCertificate(); + Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e); + + } catch (IOException e) { dismiss(); if (mListener != null) mListener.onFailedSavingCertificate(); @@ -136,11 +143,11 @@ public class SslValidatorDialog extends Dialog { View detailsScroll = findViewById(R.id.details_scroll); if (detailsScroll.getVisibility() == View.VISIBLE) { detailsScroll.setVisibility(View.GONE); - ((Button)v).setText(R.string.ssl_validator_btn_details_see); + ((Button) v).setText(R.string.ssl_validator_btn_details_see); } else { detailsScroll.setVisibility(View.VISIBLE); - ((Button)v).setText(R.string.ssl_validator_btn_details_hide); + ((Button) v).setText(R.string.ssl_validator_btn_details_hide); } } }); @@ -216,6 +223,7 @@ public class SslValidatorDialog extends Dialog { return hex.toString(); } + @SuppressWarnings("deprecation") private void showValidity(Date notBefore, Date notAfter) { TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from)); TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to)); @@ -336,7 +344,7 @@ public class SslValidatorDialog extends Dialog { private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { if (mException.getServerCertificate() != null) { // TODO make this asynchronously, it can take some time - OwnCloudClientUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext()); + NetworkUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext()); } } diff --git a/src/com/owncloud/android/ui/dialog/SsoWebView.java b/src/com/owncloud/android/ui/dialog/SsoWebView.java new file mode 100644 index 00000000..3a71139d --- /dev/null +++ b/src/com/owncloud/android/ui/dialog/SsoWebView.java @@ -0,0 +1,40 @@ +/* ownCloud Android client application + * 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 . + * + */ + +package com.owncloud.android.ui.dialog; + +import android.content.Context; +import android.util.AttributeSet; +import android.webkit.WebView; + +public class SsoWebView extends WebView { + + public SsoWebView(Context context) { + super(context); + } + + public SsoWebView(Context context, AttributeSet attr) { + super(context, attr); + } + + @Override + public boolean onCheckIsTextEditor () { + return false; + } + +} + diff --git a/src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java b/src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java index 84a31b94..bb9adbe2 100644 --- a/src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java +++ b/src/com/owncloud/android/ui/fragment/ConfirmationDialogFragment.java @@ -22,10 +22,10 @@ import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.os.Bundle; -import android.util.Log; import com.actionbarsherlock.app.SherlockDialogFragment; -import com.owncloud.android.Log_OC; +import com.owncloud.android.utils.Log_OC; + public class ConfirmationDialogFragment extends SherlockDialogFragment { diff --git a/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java new file mode 100644 index 00000000..409c5e61 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java @@ -0,0 +1,119 @@ +/* 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 . + * + */ + +package com.owncloud.android.ui.fragment; + +import com.actionbarsherlock.app.SherlockFragment; +import com.owncloud.android.R; +import com.owncloud.android.ui.ExtendedListView; +import com.owncloud.android.utils.Log_OC; + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; + +/** + * TODO extending SherlockListFragment instead of SherlockFragment + */ +public class ExtendedListFragment extends SherlockFragment implements OnItemClickListener { + + private static final String TAG = ExtendedListFragment.class.getSimpleName(); + + private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION"; + + protected ExtendedListView mList; + + public void setListAdapter(ListAdapter listAdapter) { + mList.setAdapter(listAdapter); + mList.invalidate(); + } + + public ListView getListView() { + return mList; + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.e(TAG, "onCreateView"); + //mList = new ExtendedListView(getActivity()); + View v = inflater.inflate(R.layout.list_fragment, null); + mList = (ExtendedListView)(v.findViewById(R.id.list_root)); + mList.setOnItemClickListener(this); + //mList.setEmptyView(v.findViewById(R.id.empty_list_view)); // looks like it's not a cool idea + mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator)); + mList.setDividerHeight(1); + + if (savedInstanceState != null) { + int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION); + setReferencePosition(referencePosition); + } + + return v; + } + + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + Log_OC.e(TAG, "onSaveInstanceState()"); + savedInstanceState.putInt(KEY_SAVED_LIST_POSITION, getReferencePosition()); + } + + + /** + * 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. + * + * @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; + } else { + return 0; + } + } + + + /** + * Sets the visible part of the list from the reference position. + * + * @param position Reference position previously returned by {@link LocalFileListFragment#getReferencePosition()} + */ + protected void setReferencePosition(int position) { + if (mList != null) { + mList.setAndCenterSelection(position); + } + } + + @Override + public void onItemClick(AdapterView arg0, View arg1, int arg2, long arg3) { + // to be @overriden + } + + +} diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 32d8f9ad..dc8c9f62 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -22,99 +22,50 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.PostMethod; -import org.apache.commons.httpclient.methods.StringRequestEntity; -import org.apache.commons.httpclient.params.HttpConnectionManagerParams; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.HTTP; -import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; -import org.json.JSONObject; - import android.accounts.Account; -import android.accounts.AccountManager; -//import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.support.v4.app.FragmentTransaction; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.webkit.MimeTypeMap; -import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapFactory.Options; -import android.graphics.Point; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentTransaction; -import android.util.Log; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.webkit.MimeTypeMap; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - - -import com.actionbarsherlock.app.SherlockFragment; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.DisplayUtils; -import com.owncloud.android.Log_OC; -import com.owncloud.android.authenticator.AccountAuthenticator; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileObserverService; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; -import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +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.operations.RemoveFileOperation; import com.owncloud.android.operations.RenameFileOperation; import com.owncloud.android.operations.SynchronizeFileOperation; import com.owncloud.android.ui.activity.ConflictsResolveActivity; -import com.owncloud.android.ui.activity.FileDetailActivity; +import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.dialog.EditNameDialog; import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; -import com.owncloud.android.utils.OwnCloudVersion; - -import com.owncloud.android.R; - -import eu.alefzero.webdav.OnDatatransferProgressListener; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; +import com.owncloud.android.ui.preview.PreviewImageFragment; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.Log_OC; /** @@ -123,19 +74,14 @@ import eu.alefzero.webdav.WebdavUtils; * @author Bartek Przybylski * @author David A. Velasco */ -public class FileDetailFragment extends SherlockFragment implements +public class FileDetailFragment extends FileFragment implements OnClickListener, - ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener, - FileFragment { - - public static final String EXTRA_FILE = "FILE"; - public static final String EXTRA_ACCOUNT = "ACCOUNT"; + ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener { private FileFragment.ContainerActivity mContainerActivity; private int mLayout; private View mView; - private OCFile mFile; private Account mAccount; private FileDataStorageManager mStorageManager; @@ -146,7 +92,6 @@ public class FileDetailFragment extends SherlockFragment implements private RemoteOperation mLastRemoteOperation; private static final String TAG = FileDetailFragment.class.getSimpleName(); - public static final String FTAG = "FileDetails"; public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT"; @@ -156,14 +101,13 @@ public class FileDetailFragment extends SherlockFragment implements * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. */ public FileDetailFragment() { - mFile = null; + super(); mAccount = null; mStorageManager = null; mLayout = R.layout.file_details_empty; mProgressListener = null; } - /** * Creates a details fragment. * @@ -173,7 +117,7 @@ public class FileDetailFragment extends SherlockFragment implements * @param ocAccount An ownCloud account; needed to start downloads */ public FileDetailFragment(OCFile fileToDetail, Account ocAccount) { - mFile = fileToDetail; + super(fileToDetail); mAccount = ocAccount; mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment mLayout = R.layout.file_details_empty; @@ -185,43 +129,40 @@ public class FileDetailFragment extends SherlockFragment implements public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new Handler(); + setHasOptionsMenu(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - super.onCreateView(inflater, container, savedInstanceState); + //super.onCreateView(inflater, container, savedInstanceState); if (savedInstanceState != null) { - mFile = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE); - mAccount = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_ACCOUNT); + setFile((OCFile)savedInstanceState.getParcelable(FileActivity.EXTRA_FILE)); + mAccount = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT); } - if(mFile != null && mAccount != null) { + if(getFile() != null && mAccount != null) { mLayout = R.layout.file_details_fragment; } View view = null; - view = inflater.inflate(mLayout, container, false); + //view = inflater.inflate(mLayout, container, false); + view = inflater.inflate(mLayout, null); mView = view; if (mLayout == R.layout.file_details_fragment) { mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this); - mView.findViewById(R.id.fdRenameBtn).setOnClickListener(this); - mView.findViewById(R.id.fdDownloadBtn).setOnClickListener(this); - mView.findViewById(R.id.fdOpenBtn).setOnClickListener(this); - mView.findViewById(R.id.fdRemoveBtn).setOnClickListener(this); - //mView.findViewById(R.id.fdShareBtn).setOnClickListener(this); ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.fdProgressBar); mProgressListener = new ProgressListener(progressBar); + mView.findViewById(R.id.fdCancelBtn).setOnClickListener(this); } updateFileDetails(false, false); return view; } - /** * {@inheritDoc} */ @@ -245,6 +186,10 @@ public class FileDetailFragment extends SherlockFragment implements super.onActivityCreated(savedInstanceState); if (mAccount != null) { mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); + OCFile file = mStorageManager.getFileByPath(getFile().getRemotePath()); + if (file != null) { + setFile(file); + } } } @@ -252,8 +197,8 @@ public class FileDetailFragment extends SherlockFragment implements @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(FileDetailFragment.EXTRA_FILE, mFile); - outState.putParcelable(FileDetailFragment.EXTRA_ACCOUNT, mAccount); + outState.putParcelable(FileActivity.EXTRA_FILE, getFile()); + outState.putParcelable(FileActivity.EXTRA_ACCOUNT, mAccount); } @Override @@ -266,7 +211,7 @@ public class FileDetailFragment extends SherlockFragment implements public void onResume() { super.onResume(); mUploadFinishReceiver = new UploadFinishReceiver(); - IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); + IntentFilter filter = new IntentFilter(FileUploader.getUploadFinishMessage()); getActivity().registerReceiver(mUploadFinishReceiver, filter); } @@ -294,189 +239,298 @@ public class FileDetailFragment extends SherlockFragment implements return super.getView() == null ? mView : super.getView(); } + + /** + * {@inheritDoc} + */ + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.file_actions_menu, menu); + MenuItem item = menu.findItem(R.id.action_see_details); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + + // Send file + item = menu.findItem(R.id.action_send_file); + boolean sendEnabled = getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on"); + if (item != null) { + if (sendEnabled) { + item.setVisible(true); + item.setEnabled(true); + } else { + item.setVisible(false); + item.setEnabled(false); + + } + } + } + + + /** + * {@inheritDoc} + */ @Override - public void onClick(View v) { - switch (v.getId()) { - case R.id.fdDownloadBtn: { - FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) { - downloaderBinder.cancel(mAccount, mFile); - if (mFile.isDown()) { - setButtonsForDown(); - } else { - setButtonsForRemote(); - } + public void onPrepareOptionsMenu (Menu menu) { + super.onPrepareOptionsMenu(menu); + + List toHide = new ArrayList(); + List toShow = new ArrayList(); + OCFile file = getFile(); + + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + boolean downloading = downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file); + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + boolean uploading = uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile()); + + if (downloading || uploading) { + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_rename_file); + toHide.add(R.id.action_remove_file); + toHide.add(R.id.action_open_file_with); + if (!downloading) { + toHide.add(R.id.action_cancel_download); + toShow.add(R.id.action_cancel_upload); + } else { + toHide.add(R.id.action_cancel_upload); + toShow.add(R.id.action_cancel_download); + } - } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) { - uploaderBinder.cancel(mAccount, mFile); - if (!mFile.fileExists()) { - // TODO make something better - if (getActivity() instanceof FileDisplayActivity) { - // double pane - FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FTAG); // empty FileDetailFragment - transaction.commit(); - mContainerActivity.onFileStateChanged(); - } else { - getActivity().finish(); - } - - } else if (mFile.isDown()) { - setButtonsForDown(); - } else { - setButtonsForRemote(); - } + } else if (file != null && file.isDown()) { + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + + toShow.add(R.id.action_rename_file); + toShow.add(R.id.action_remove_file); + toShow.add(R.id.action_open_file_with); + toShow.add(R.id.action_sync_file); + + } else if (file != null) { + toHide.add(R.id.action_open_file_with); + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_sync_file); + + toShow.add(R.id.action_rename_file); + toShow.add(R.id.action_remove_file); + toShow.add(R.id.action_download_file); + + } else { + toHide.add(R.id.action_open_file_with); + toHide.add(R.id.action_cancel_download); + toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_sync_file); + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_rename_file); + toHide.add(R.id.action_remove_file); + + } + + // Options shareLink + if (!file.isShareByLink()) { + toHide.add(R.id.action_unshare_file); + } else { + toShow.add(R.id.action_unshare_file); + } + + MenuItem item = null; + for (int i : toHide) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + for (int i : toShow) { + item = menu.findItem(i); + if (item != null) { + item.setVisible(true); + item.setEnabled(true); + } + } + } + + + /** + * {@inheritDoc} + */ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_share_file: { + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + activity.getFileOperationsHelper().shareFileWithLink(getFile(), activity); + return true; + } + case R.id.action_unshare_file: { + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + activity.getFileOperationsHelper().unshareFileWithLink(getFile(), activity); + return true; + } + case R.id.action_open_file_with: { + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + activity.getFileOperationsHelper().openFile(getFile(), activity); + return true; + } + case R.id.action_remove_file: { + removeFile(); + return true; + } + case R.id.action_rename_file: { + renameFile(); + return true; + } + case R.id.action_download_file: + case R.id.action_cancel_download: + case R.id.action_cancel_upload: + case R.id.action_sync_file: { + synchronizeFile(); + return true; + } + case R.id.action_send_file: { + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + // Obtain the file + if (!getFile().isDown()) { // Download the file + Log_OC.d(TAG, getFile().getRemotePath() + " : File must be downloaded"); + activity.startDownloadForSending(getFile()); } else { - mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); - - // update ui - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); - + activity.getFileOperationsHelper().sendDownloadedFile(getFile(), activity); } - break; + return true; } + default: + return false; + } + } + + @Override + public void onClick(View v) { + switch (v.getId()) { case R.id.fdKeepInSync: { - CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync); - mFile.setKeepInSync(cb.isChecked()); - mStorageManager.saveFile(mFile); - - /// register the OCFile instance in the observer service to monitor local updates; - /// if necessary, the file is download - Intent intent = new Intent(getActivity().getApplicationContext(), - FileObserverService.class); - intent.putExtra(FileObserverService.KEY_FILE_CMD, - (cb.isChecked()? - FileObserverService.CMD_ADD_OBSERVED_FILE: - FileObserverService.CMD_DEL_OBSERVED_FILE)); - intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile); - intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); - getActivity().startService(intent); - - if (mFile.keepInSync()) { - onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization - } + toggleKeepInSync(); break; } - case R.id.fdRenameBtn: { - String fileName = mFile.getFileName(); - int extensionStart = mFile.isDirectory() ? -1 : fileName.lastIndexOf("."); - int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); - EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); - dialog.show(getFragmentManager(), "nameeditdialog"); - break; - } - case R.id.fdRemoveBtn: { - ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( - R.string.confirmation_remove_alert, - new String[]{mFile.getFileName()}, - mFile.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote, - mFile.isDown() ? R.string.confirmation_remove_local : -1, - R.string.common_cancel); - confDialog.setOnConfirmationListener(this); - confDialog.show(getFragmentManager(), FTAG_CONFIRMATION); - break; - } - case R.id.fdOpenBtn: { - openFile(); + case R.id.fdCancelBtn: { + synchronizeFile(); break; } default: Log_OC.e(TAG, "Incorrect view clicked!"); } - - /* else if (v.getId() == R.id.fdShareBtn) { - Thread t = new Thread(new ShareRunnable(mFile.getRemotePath())); - t.start(); - }*/ } - /** - * Opens mFile. - */ - private void openFile() { + private void toggleKeepInSync() { + CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync); + OCFile file = getFile(); + file.setKeepInSync(cb.isChecked()); + mStorageManager.saveFile(file); - String storagePath = mFile.getStoragePath(); - String encodedStoragePath = WebdavUtils.encodePath(storagePath); - try { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype()); - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - - } catch (Throwable t) { - Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype()); - boolean toastIt = true; - String mimeType = ""; - try { - Intent i = new Intent(Intent.ACTION_VIEW); - mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (mimeType == null || !mimeType.equals(mFile.getMimetype())) { - if (mimeType != null) { - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); - } else { - // desperate try - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*"); - } - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - toastIt = false; - } - - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); - - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); - - } catch (Throwable th) { - Log.e(TAG, "Unexpected problem when opening: " + storagePath, th); + /// register the OCFile instance in the observer service to monitor local updates; + /// if necessary, the file is download + Intent intent = new Intent(getActivity().getApplicationContext(), + FileObserverService.class); + intent.putExtra(FileObserverService.KEY_FILE_CMD, + (cb.isChecked()? + FileObserverService.CMD_ADD_OBSERVED_FILE: + FileObserverService.CMD_DEL_OBSERVED_FILE)); + intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, file); + intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount); + getActivity().startService(intent); + + if (file.keepInSync()) { + synchronizeFile(); // force an immediate synchronization + } + } + + private void removeFile() { + OCFile file = getFile(); + ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( + R.string.confirmation_remove_alert, + new String[]{file.getFileName()}, + file.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote, + file.isDown() ? R.string.confirmation_remove_local : -1, + R.string.common_cancel); + confDialog.setOnConfirmationListener(this); + confDialog.show(getFragmentManager(), FTAG_CONFIRMATION); + } + + + private void renameFile() { + OCFile file = getFile(); + String fileName = file.getFileName(); + int extensionStart = file.isFolder() ? -1 : fileName.lastIndexOf("."); + int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); + EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); + dialog.show(getFragmentManager(), "nameeditdialog"); + } + + private void synchronizeFile() { + OCFile file = getFile(); + FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); + FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) { + downloaderBinder.cancel(mAccount, file); + if (file.isDown()) { + setButtonsForDown(); + } else { + setButtonsForRemote(); + } + + } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) { + uploaderBinder.cancel(mAccount, file); + if (!file.fileExists()) { + // TODO make something better + ((FileDisplayActivity)getActivity()).cleanSecondFragment(); - } finally { - if (toastIt) { - Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show(); - } + } else if (file.isDown()) { + setButtonsForDown(); + } else { + setButtonsForRemote(); } + } else { + mLastRemoteOperation = new SynchronizeFileOperation(file, null, mStorageManager, mAccount, true, getActivity()); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + + // update ui + ((FileDisplayActivity) getActivity()).showLoadingDialog(); + } } - @Override public void onConfirmation(String callerTag) { + OCFile file = getFile(); if (callerTag.equals(FTAG_CONFIRMATION)) { - if (mStorageManager.getFileById(mFile.getFileId()) != null) { - mLastRemoteOperation = new RemoveFileOperation( mFile, + if (mStorageManager.getFileById(file.getFileId()) != null) { + mLastRemoteOperation = new RemoveFileOperation( file, true, mStorageManager); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); - - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + + ((FileDisplayActivity) getActivity()).showLoadingDialog(); } } } @Override public void onNeutral(String callerTag) { - File f = null; - if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) { - f.delete(); - mFile.setStoragePath(null); - mStorageManager.saveFile(mFile); - updateFileDetails(mFile, mAccount); + OCFile file = getFile(); + mStorageManager.removeFile(file, false, true); // TODO perform in background task / new thread + if (file.getStoragePath() != null) { + file.setStoragePath(null); + updateFileDetails(file, mAccount); } } @Override public void onCancel(String callerTag) { - Log.d(TAG, "REMOVAL CANCELED"); + Log_OC.d(TAG, "REMOVAL CANCELED"); } @@ -486,24 +540,17 @@ public class FileDetailFragment extends SherlockFragment implements * @return True when the fragment was created with the empty layout. */ public boolean isEmpty() { - return (mLayout == R.layout.file_details_empty || mFile == null || mAccount == null); + return (mLayout == R.layout.file_details_empty || getFile() == null || mAccount == null); } /** - * {@inheritDoc} - */ - public OCFile getFile(){ - return mFile; - } - - /** * Use this method to signal this Activity that it shall update its view. * * @param file : An {@link OCFile} */ public void updateFileDetails(OCFile file, Account ocAccount) { - mFile = file; + setFile(file); if (ocAccount != null && ( mStorageManager == null || (mAccount != null && !mAccount.equals(ocAccount)) @@ -532,30 +579,31 @@ public class FileDetailFragment extends SherlockFragment implements if (readyToShow()) { if (refresh && mStorageManager != null) { - mFile = mStorageManager.getFileByPath(mFile.getRemotePath()); + setFile(mStorageManager.getFileByPath(getFile().getRemotePath())); } + OCFile file = getFile(); // set file details - setFilename(mFile.getFileName()); - setFiletype(mFile.getMimetype()); - setFilesize(mFile.getFileLength()); + setFilename(file.getFileName()); + setFiletype(file.getMimetype()); + setFilesize(file.getFileLength()); if(ocVersionSupportsTimeCreated()){ - setTimeCreated(mFile.getCreationTimestamp()); + setTimeCreated(file.getCreationTimestamp()); } - setTimeModified(mFile.getModificationTimestamp()); + setTimeModified(file.getModificationTimestamp()); CheckBox cb = (CheckBox)getView().findViewById(R.id.fdKeepInSync); - cb.setChecked(mFile.keepInSync()); + cb.setChecked(file.keepInSync()); // configure UI for depending upon local state of the file //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) { FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) { + if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))) { setButtonsForTransferring(); - } else if (mFile.isDown()) { + } else if (file.isDown()) { setButtonsForDown(); @@ -567,14 +615,13 @@ public class FileDetailFragment extends SherlockFragment implements getView().invalidate(); } - /** * Checks if the fragment is ready to show details of a OCFile * * @return 'True' when the fragment is ready to show details of a file */ private boolean readyToShow() { - return (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment); + return (getFile() != null && mAccount != null && mLayout == R.layout.file_details_fragment); } @@ -644,26 +691,18 @@ public class FileDetailFragment extends SherlockFragment implements */ private void setButtonsForTransferring() { if (!isEmpty()) { - Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn); - downloadButton.setText(R.string.common_cancel); - //downloadButton.setEnabled(false); - // let's protect the user from himself ;) - ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false); - ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(false); - ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(false); getView().findViewById(R.id.fdKeepInSync).setEnabled(false); // show the progress bar for the transfer - ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar); - progressBar.setVisibility(View.VISIBLE); + getView().findViewById(R.id.fdProgressBlock).setVisibility(View.VISIBLE); TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); progressText.setVisibility(View.VISIBLE); FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder(); - if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) { + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) { progressText.setText(R.string.downloader_download_in_progress_ticker); - } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) { + } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) { progressText.setText(R.string.uploader_upload_in_progress_ticker); } } @@ -674,17 +713,10 @@ public class FileDetailFragment extends SherlockFragment implements */ private void setButtonsForDown() { if (!isEmpty()) { - Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn); - downloadButton.setText(R.string.filedetails_sync_file); - - ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true); - ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true); - ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true); getView().findViewById(R.id.fdKeepInSync).setEnabled(true); // hides the progress bar - ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar); - progressBar.setVisibility(View.GONE); + getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE); TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); progressText.setVisibility(View.GONE); } @@ -695,17 +727,10 @@ public class FileDetailFragment extends SherlockFragment implements */ private void setButtonsForRemote() { if (!isEmpty()) { - Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn); - downloadButton.setText(R.string.filedetails_download); - - ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false); - ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true); - ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true); getView().findViewById(R.id.fdKeepInSync).setEnabled(true); // hides the progress bar - ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar); - progressBar.setVisibility(View.GONE); + getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE); TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText); progressText.setVisibility(View.GONE); } @@ -750,11 +775,11 @@ public class FileDetailFragment extends SherlockFragment implements if (!isEmpty() && accountName.equals(mAccount.name)) { boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false); String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH); - boolean renamedInUpload = mFile.getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH)); - if (mFile.getRemotePath().equals(uploadRemotePath) || + boolean renamedInUpload = getFile().getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH)); + if (getFile().getRemotePath().equals(uploadRemotePath) || renamedInUpload) { if (uploadWasFine) { - mFile = mStorageManager.getFileByPath(uploadRemotePath); + setFile(mStorageManager.getFileByPath(uploadRemotePath)); } if (renamedInUpload) { String newName = (new File(uploadRemotePath)).getName(); @@ -762,129 +787,29 @@ public class FileDetailFragment extends SherlockFragment implements msg.show(); } getSherlockActivity().removeStickyBroadcast(intent); // not the best place to do this; a small refactorization of BroadcastReceivers should be done + updateFileDetails(false, false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server + + // Force the preview if the file is an image + if (uploadWasFine && PreviewImageFragment.canBePreviewed(getFile())) { + ((FileDisplayActivity) mContainerActivity).startImagePreview(getFile()); + } } } } } - // this is a temporary class for sharing purposes, it need to be replaced in transfer service - @SuppressWarnings("unused") - private class ShareRunnable implements Runnable { - private String mPath; - - public ShareRunnable(String path) { - mPath = path; - } - - public void run() { - AccountManager am = AccountManager.get(getActivity()); - Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity()); - OwnCloudVersion ocv = new OwnCloudVersion(am.getUserData(account, AccountAuthenticator.KEY_OC_VERSION)); - String url = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + AccountUtils.getWebdavPath(ocv); - - Log.d("share", "sharing for version " + ocv.toString()); - - if (ocv.compareTo(new OwnCloudVersion(0x040000)) >= 0) { - String APPS_PATH = "/apps/files_sharing/"; - String SHARE_PATH = "ajax/share.php"; - - String SHARED_PATH = "/apps/files_sharing/get.php?token="; - - final String WEBDAV_SCRIPT = "webdav.php"; - final String WEBDAV_FILES_LOCATION = "/files/"; - - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getActivity().getApplicationContext()); - HttpConnectionManagerParams params = new HttpConnectionManagerParams(); - params.setMaxConnectionsPerHost(wc.getHostConfiguration(), 5); - - //wc.getParams().setParameter("http.protocol.single-cookie-header", true); - //wc.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); - - PostMethod post = new PostMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + APPS_PATH + SHARE_PATH); - - post.addRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8" ); - post.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL)); - List formparams = new ArrayList(); - Log.d("share", mPath+""); - formparams.add(new BasicNameValuePair("sources",mPath)); - formparams.add(new BasicNameValuePair("uid_shared_with", "public")); - formparams.add(new BasicNameValuePair("permissions", "0")); - post.setRequestEntity(new StringRequestEntity(URLEncodedUtils.format(formparams, HTTP.UTF_8))); - - int status; - try { - PropFindMethod find = new PropFindMethod(url+"/"); - find.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL)); - Log.d("sharer", ""+ url+"/"); - - for (org.apache.commons.httpclient.Header a : find.getRequestHeaders()) { - Log.d("sharer-h", a.getName() + ":"+a.getValue()); - } - - int status2 = wc.executeMethod(find); - - Log.d("sharer", "propstatus "+status2); - - GetMethod get = new GetMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + "/"); - get.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL)); - - status2 = wc.executeMethod(get); - - Log.d("sharer", "getstatus "+status2); - Log.d("sharer", "" + get.getResponseBodyAsString()); - - for (org.apache.commons.httpclient.Header a : get.getResponseHeaders()) { - Log.d("sharer", a.getName() + ":"+a.getValue()); - } - - status = wc.executeMethod(post); - for (org.apache.commons.httpclient.Header a : post.getRequestHeaders()) { - Log.d("sharer-h", a.getName() + ":"+a.getValue()); - } - for (org.apache.commons.httpclient.Header a : post.getResponseHeaders()) { - Log.d("sharer", a.getName() + ":"+a.getValue()); - } - String resp = post.getResponseBodyAsString(); - Log.d("share", ""+post.getURI().toString()); - Log.d("share", "returned status " + status); - Log.d("share", " " +resp); - - if(status != HttpStatus.SC_OK ||resp == null || resp.equals("") || resp.startsWith("false")) { - return; - } - - JSONObject jsonObject = new JSONObject (resp); - String jsonStatus = jsonObject.getString("status"); - if(!jsonStatus.equals("success")) throw new Exception("Error while sharing file status != success"); - - String token = jsonObject.getString("data"); - String uri = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + SHARED_PATH + token; - Log.d("Actions:shareFile ok", "url: " + uri); - - } catch (Exception e) { - e.printStackTrace(); - } - - } else if (ocv.compareTo(new OwnCloudVersion(0x030000)) >= 0) { - - } - } - } - public void onDismiss(EditNameDialog dialog) { if (dialog.getResult()) { String newFilename = dialog.getNewFilename(); - Log.d(TAG, "name edit dialog dismissed with new name " + newFilename); - mLastRemoteOperation = new RenameFileOperation( mFile, + Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename); + mLastRemoteOperation = new RenameFileOperation( getFile(), mAccount, newFilename, new FileDataStorageManager(mAccount, getActivity().getContentResolver())); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); + ((FileDisplayActivity) getActivity()).showLoadingDialog(); } } @@ -909,22 +834,12 @@ public class FileDetailFragment extends SherlockFragment implements private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); - + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); if (result.isSuccess()) { Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); msg.show(); - if (inDisplayActivity) { - // double pane - FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment - transaction.commit(); - mContainerActivity.onFileStateChanged(); - } else { - getActivity().finish(); - } - + ((FileDisplayActivity)getActivity()).cleanSecondFragment(); + } else { Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG); msg.show(); @@ -935,8 +850,7 @@ public class FileDetailFragment extends SherlockFragment implements } private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) { - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); if (result.isSuccess()) { updateFileDetails(((RenameFileOperation)operation).getFile(), mAccount); @@ -947,6 +861,9 @@ public class FileDetailFragment extends SherlockFragment implements Toast msg = Toast.makeText(getActivity(), R.string.rename_local_fail_msg, Toast.LENGTH_LONG); msg.show(); // TODO throw again the new rename dialog + } if (result.getCode().equals(ResultCode.INVALID_CHARACTER_IN_NAME)) { + Toast msg = Toast.makeText(getActivity(), R.string.filename_forbidden_characters, Toast.LENGTH_LONG); + msg.show(); } else { Toast msg = Toast.makeText(getActivity(), R.string.rename_server_fail_msg, Toast.LENGTH_LONG); msg.show(); @@ -958,22 +875,18 @@ public class FileDetailFragment extends SherlockFragment implements } private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) { - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); - + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); + OCFile file = getFile(); if (!result.isSuccess()) { if (result.getCode() == ResultCode.SYNC_CONFLICT) { Intent i = new Intent(getActivity(), ConflictsResolveActivity.class); - i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile); + i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file); i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); startActivity(i); - } else { - Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); - msg.show(); - } + } - if (mFile.isDown()) { + if (file.isDown()) { setButtonsForDown(); } else { @@ -988,7 +901,7 @@ public class FileDetailFragment extends SherlockFragment implements } else { Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG); msg.show(); - if (mFile.isDown()) { + if (file.isDown()) { setButtonsForDown(); } else { @@ -998,14 +911,14 @@ public class FileDetailFragment extends SherlockFragment implements } } - + public void listenForTransferProgress() { if (mProgressListener != null) { if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile); + mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); } if (mContainerActivity.getFileUploaderBinder() != null) { - mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile); + mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); } } } @@ -1014,10 +927,10 @@ public class FileDetailFragment extends SherlockFragment implements public void leaveTransferProgress() { if (mProgressListener != null) { if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile); + mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); } if (mContainerActivity.getFileUploaderBinder() != null) { - mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile); + mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); } } } @@ -1038,11 +951,6 @@ public class FileDetailFragment extends SherlockFragment implements } @Override - public void onTransferProgress(long progressRate) { - // old method, nothing here - }; - - @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) { int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer)); if (percent != mLastPercent) { @@ -1057,4 +965,4 @@ public class FileDetailFragment extends SherlockFragment implements }; -} \ No newline at end of file +} diff --git a/src/com/owncloud/android/ui/fragment/FileFragment.java b/src/com/owncloud/android/ui/fragment/FileFragment.java index 30549513..2f1a49b1 100644 --- a/src/com/owncloud/android/ui/fragment/FileFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileFragment.java @@ -17,28 +17,56 @@ package com.owncloud.android.ui.fragment; -import android.content.Intent; import android.support.v4.app.Fragment; +import com.actionbarsherlock.app.SherlockFragment; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.ui.activity.TransferServiceGetter; + /** * Common methods for {@link Fragment}s containing {@link OCFile}s * * @author David A. Velasco * */ -public interface FileFragment { +public class FileFragment extends SherlockFragment { + private OCFile mFile; + + + /** + * Creates an empty fragment. + * + * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. + */ + public FileFragment() { + mFile = null; + } + + /** + * Creates an instance for a given {@OCFile}. + * + * @param file + */ + public FileFragment(OCFile file) { + mFile = file; + } + /** * Getter for the hold {@link OCFile} * * @return The {@link OCFile} hold */ - public OCFile getFile(); + public OCFile getFile() { + return mFile; + } + protected void setFile(OCFile file) { + mFile = file; + } + /** * Interface to implement by any Activity that includes some instance of FileFragment * @@ -65,9 +93,8 @@ public interface FileFragment { * * @param file File to show details */ - public void showFragmentWithDetails(OCFile file); - - + public void showDetails(OCFile file); + } } diff --git a/src/com/owncloud/android/ui/fragment/LandingPageFragment.java b/src/com/owncloud/android/ui/fragment/LandingPageFragment.java deleted file mode 100644 index 9d87a45a..00000000 --- a/src/com/owncloud/android/ui/fragment/LandingPageFragment.java +++ /dev/null @@ -1,58 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ -package com.owncloud.android.ui.fragment; - -import com.actionbarsherlock.app.SherlockFragment; -import com.owncloud.android.ui.activity.LandingActivity; -import com.owncloud.android.ui.adapter.LandingScreenAdapter; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; -import com.owncloud.android.R; - -/** - * Used on the Landing page to display what Components of the ownCloud there - * are. Like Files, Music, Contacts, etc. - * - * @author Lennart Rosam - * - */ -public class LandingPageFragment extends SherlockFragment { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.landing_page_fragment, container); - return root; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - ListView landingScreenItems = (ListView) getView().findViewById( - R.id.homeScreenList); - landingScreenItems.setAdapter(new LandingScreenAdapter(getActivity())); - landingScreenItems - .setOnItemClickListener((LandingActivity) getActivity()); - } - -} diff --git a/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java b/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java index fecf26cd..270a8d5c 100644 --- a/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/LocalFileListFragment.java @@ -19,13 +19,14 @@ package com.owncloud.android.ui.fragment; import java.io.File; -import com.owncloud.android.ui.FragmentListView; +import com.owncloud.android.R; import com.owncloud.android.ui.adapter.LocalFileListAdapter; +import com.owncloud.android.utils.Log_OC; + import android.app.Activity; import android.os.Bundle; import android.os.Environment; -import android.util.Log; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; @@ -34,8 +35,6 @@ import android.widget.AdapterView; import android.widget.ImageView; import android.widget.ListView; -import com.owncloud.android.Log_OC; -import com.owncloud.android.R; /** * A Fragment that lists all files and folders in a given LOCAL path. @@ -43,9 +42,8 @@ import com.owncloud.android.R; * @author David A. Velasco * */ -public class LocalFileListFragment extends FragmentListView { +public class LocalFileListFragment extends ExtendedListFragment { private static final String TAG = "LocalFileListFragment"; - private static final String SAVED_LIST_POSITION = "LIST_POSITION"; /** Reference to the Activity which this fragment is attached to. For callbacks */ private LocalFileListFragment.ContainerActivity mContainerActivity; @@ -95,27 +93,10 @@ public class LocalFileListFragment extends FragmentListView { mAdapter = new LocalFileListAdapter(mContainerActivity.getInitialDirectory(), getActivity()); setListAdapter(mAdapter); - if (savedInstanceState != null) { - Log_OC.i(TAG, "savedInstanceState is not null"); - int position = savedInstanceState.getInt(SAVED_LIST_POSITION); - setReferencePosition(position); - } - Log_OC.i(TAG, "onActivityCreated() stop"); } - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - Log_OC.i(TAG, "onSaveInstanceState() start"); - - savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition()); - - - Log_OC.i(TAG, "onSaveInstanceState() stop"); - } - - /** * Checks the file clicked over. Browses inside if it is a directory. Notifies the container activity in any case. */ diff --git a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java index dac32c85..cb370070 100644 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -21,45 +21,36 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.Log_OC; import com.owncloud.android.R; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.operations.RemoveFileOperation; import com.owncloud.android.operations.RenameFileOperation; import com.owncloud.android.operations.SynchronizeFileOperation; -import com.owncloud.android.ui.FragmentListView; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.TransferServiceGetter; import com.owncloud.android.ui.adapter.FileListListAdapter; import com.owncloud.android.ui.dialog.EditNameDialog; import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener; import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; - -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; +import com.owncloud.android.ui.preview.PreviewImageFragment; +import com.owncloud.android.ui.preview.PreviewMediaFragment; +import com.owncloud.android.utils.Log_OC; import android.accounts.Account; import android.app.Activity; -import android.content.ActivityNotFoundException; -import android.content.Intent; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; -import android.util.Log; import android.view.ContextMenu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.webkit.MimeTypeMap; import android.widget.AdapterView; -import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo; /** @@ -68,9 +59,12 @@ import android.widget.AdapterView.AdapterContextMenuInfo; * @author Bartek Przybylski * */ -public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener { - private static final String TAG = "FileListFragment"; - private static final String SAVED_LIST_POSITION = "LIST_POSITION"; +public class OCFileListFragment extends ExtendedListFragment implements EditNameDialogListener, ConfirmationDialogFragmentListener { + + private static final String TAG = OCFileListFragment.class.getSimpleName(); + + private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ? OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment"; + private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE"; private OCFileListFragment.ContainerActivity mContainerActivity; @@ -86,6 +80,7 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial @Override public void onAttach(Activity activity) { super.onAttach(activity); + Log_OC.e(TAG, "onAttach"); try { mContainerActivity = (ContainerActivity) activity; } catch (ClassCastException e) { @@ -99,52 +94,102 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial */ @Override public void onActivityCreated(Bundle savedInstanceState) { - Log_OC.i(TAG, "onActivityCreated() start"); - super.onActivityCreated(savedInstanceState); - mAdapter = new FileListListAdapter(mContainerActivity.getInitialDirectory(), mContainerActivity.getStorageManager(), getActivity(), mContainerActivity); - setListAdapter(mAdapter); - + Log_OC.e(TAG, "onActivityCreated() start"); + mAdapter = new FileListListAdapter(getActivity(), mContainerActivity); if (savedInstanceState != null) { - Log_OC.i(TAG, "savedInstanceState is not null"); - int position = savedInstanceState.getInt(SAVED_LIST_POSITION); - setReferencePosition(position); + mFile = savedInstanceState.getParcelable(EXTRA_FILE); } + setListAdapter(mAdapter); registerForContextMenu(getListView()); getListView().setOnCreateContextMenuListener(this); mHandler = new Handler(); - - Log_OC.i(TAG, "onActivityCreated() stop"); + } - - + /** + * Saves the current listed folder. + */ @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - Log_OC.i(TAG, "onSaveInstanceState() start"); + public void onSaveInstanceState (Bundle outState) { + super.onSaveInstanceState(outState); + outState.putParcelable(EXTRA_FILE, mFile); + } + + + /** + * Call this, when the user presses the up button. + * + * Tries to move up the current folder one level. If the parent folder was removed from the database, + * it continues browsing up until finding an existing folders. + * + * return Count of folder levels browsed up. + */ + public int onBrowseUp() { + OCFile parentDir = null; + int moveCount = 0; - savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition()); + if(mFile != null){ + FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); + + String parentPath = null; + if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) { + parentPath = new File(mFile.getRemotePath()).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + parentDir = storageManager.getFileByPath(parentPath); + moveCount++; + } else { + parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); // never returns null; keep the path in root folder + } + while (parentDir == null) { + parentPath = new File(parentPath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + parentDir = storageManager.getFileByPath(parentPath); + moveCount++; + } // exit is granted because storageManager.getFileByPath("/") never returns null + mFile = parentDir; + } - Log_OC.i(TAG, "onSaveInstanceState() stop"); + if (mFile != null) { + listDirectory(mFile); + + mContainerActivity.startSyncFolderOperation(mFile); + } // else - should never happen now + + return moveCount; } - @Override public void onItemClick(AdapterView l, View v, int position, long id) { OCFile file = (OCFile) mAdapter.getItem(position); if (file != null) { - /// Click on a directory - if (file.getMimetype().equals("DIR")) { - // just local updates - mFile = file; + if (file.isFolder()) { + // update state and view of this fragment listDirectory(file); - // any other updates are let to the container Activity - mContainerActivity.onDirectoryClick(file); - - } else { /// Click on a file - mContainerActivity.onFileClick(file); + // then, notify parent activity to let it update its state and view, and other fragments + mContainerActivity.onBrowsedDownTo(file); + + } else { /// Click on a file + if (PreviewImageFragment.canBePreviewed(file)) { + // preview image - it handles the download, if needed + mContainerActivity.startImagePreview(file); + + } else if (file.isDown()) { + if (PreviewMediaFragment.canBePreviewed(file)) { + // media preview + mContainerActivity.startMediaPreview(file, 0, true); + } else { + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + activity.getFileOperationsHelper().openFile(file, activity); + } + + } else { + // automatic download, preview on finish + mContainerActivity.startDownloadForPreview(file); + } + } } else { @@ -167,13 +212,15 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial List toDisable = new ArrayList(); MenuItem item = null; - if (targetFile.isDirectory()) { + if (targetFile.isFolder()) { // contextual menu for folders toHide.add(R.id.action_open_file_with); toHide.add(R.id.action_download_file); toHide.add(R.id.action_cancel_download); toHide.add(R.id.action_cancel_upload); + toHide.add(R.id.action_sync_file); toHide.add(R.id.action_see_details); + toHide.add(R.id.action_send_file); if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) || mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) { toDisable.add(R.id.action_rename_file); @@ -183,27 +230,25 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial } else { // contextual menu for regular files + + // new design: 'download' and 'open with' won't be available anymore in context menu + toHide.add(R.id.action_download_file); + toHide.add(R.id.action_open_file_with); + if (targetFile.isDown()) { toHide.add(R.id.action_cancel_download); toHide.add(R.id.action_cancel_upload); - item = menu.findItem(R.id.action_download_file); - if (item != null) { - item.setTitle(R.string.filedetails_sync_file); - } + } else { - toHide.add(R.id.action_open_file_with); + toHide.add(R.id.action_sync_file); } if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) { - toHide.add(R.id.action_download_file); toHide.add(R.id.action_cancel_upload); - toDisable.add(R.id.action_open_file_with); toDisable.add(R.id.action_rename_file); toDisable.add(R.id.action_remove_file); } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) { - toHide.add(R.id.action_download_file); toHide.add(R.id.action_cancel_download); - toDisable.add(R.id.action_open_file_with); toDisable.add(R.id.action_rename_file); toDisable.add(R.id.action_remove_file); @@ -212,7 +257,18 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial toHide.add(R.id.action_cancel_upload); } } + + // Options shareLink + if (!targetFile.isShareByLink()) { + toHide.add(R.id.action_unshare_file); + } + // Send file + boolean sendEnabled = getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on"); + if (!sendEnabled) { + toHide.add(R.id.action_send_file); + } + for (int i : toHide) { item = menu.findItem(i); if (item != null) { @@ -237,10 +293,20 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial public boolean onContextItemSelected (MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); mTargetFile = (OCFile) mAdapter.getItem(info.position); - switch (item.getItemId()) { + switch (item.getItemId()) { + case R.id.action_share_file: { + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + activity.getFileOperationsHelper().shareFileWithLink(mTargetFile, activity); + return true; + } + case R.id.action_unshare_file: { + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + activity.getFileOperationsHelper().unshareFileWithLink(mTargetFile, activity); + return true; + } case R.id.action_rename_file: { String fileName = mTargetFile.getFileName(); - int extensionStart = mTargetFile.isDirectory() ? -1 : fileName.lastIndexOf("."); + int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf("."); int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); dialog.show(getFragmentManager(), EditNameDialog.TAG); @@ -250,7 +316,7 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial int messageStringId = R.string.confirmation_remove_alert; int posBtnStringId = R.string.confirmation_remove_remote; int neuBtnStringId = -1; - if (mTargetFile.isDirectory()) { + if (mTargetFile.isFolder()) { messageStringId = R.string.confirmation_remove_folder_alert; posBtnStringId = R.string.confirmation_remove_remote_and_local; neuBtnStringId = R.string.confirmation_remove_folder_local; @@ -268,58 +334,11 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION); return true; } - case R.id.action_open_file_with: { - String storagePath = mTargetFile.getStoragePath(); - String encodedStoragePath = WebdavUtils.encodePath(storagePath); - try { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype()); - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - - } catch (Throwable t) { - Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype()); - boolean toastIt = true; - String mimeType = ""; - try { - Intent i = new Intent(Intent.ACTION_VIEW); - mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) { - if (mimeType != null) { - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); - } else { - // desperate try - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*"); - } - i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - startActivity(i); - toastIt = false; - } - - } catch (IndexOutOfBoundsException e) { - Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); - - } catch (ActivityNotFoundException e) { - Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); - - } catch (Throwable th) { - Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th); - - } finally { - if (toastIt) { - Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show(); - } - } - - } - return true; - } - case R.id.action_download_file: { + case R.id.action_sync_file: { Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()); - RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext()); - operation.execute(wc, mContainerActivity, mHandler); - getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT); + RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, getSherlockActivity()); + operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); + ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog(); return true; } case R.id.action_cancel_download: { @@ -343,28 +362,27 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial return true; } case R.id.action_see_details: { - ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mTargetFile); + ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile); + return true; + } + case R.id.action_send_file: { + // Obtain the file + if (!mTargetFile.isDown()) { // Download the file + Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded"); + mContainerActivity.startDownloadForSending(mTargetFile); + + } else { + + FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity(); + activity.getFileOperationsHelper().sendDownloadedFile(mTargetFile, activity); + } return true; } default: return super.onContextItemSelected(item); } } - - /** - * Call this, when the user presses the up button - */ - public void onNavigateUp() { - OCFile parentDir = null; - - if(mFile != null){ - DataStorageManager storageManager = mContainerActivity.getStorageManager(); - parentDir = storageManager.getFileById(mFile.getParentId()); - mFile = parentDir; - } - listDirectory(parentDir); - } /** * Use this to query the {@link OCFile} that is currently @@ -390,7 +408,7 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial * @param directory File to be listed */ public void listDirectory(OCFile directory) { - DataStorageManager storageManager = mContainerActivity.getStorageManager(); + FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); if (storageManager != null) { // Check input parameters for null @@ -405,7 +423,7 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial // If that's not a directory -> List its parent - if(!directory.isDirectory()){ + if(!directory.isFolder()){ Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString()); directory = storageManager.getFileById(directory.getParentId()); } @@ -428,31 +446,24 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener { /** - * Callback method invoked when a directory is clicked by the user on the files list + * Callback method invoked when a the user browsed into a different folder through the list of files * * @param file */ - public void onDirectoryClick(OCFile file); + public void onBrowsedDownTo(OCFile folder); + + public void startDownloadForPreview(OCFile file); + + public void startMediaPreview(OCFile file, int i, boolean b); + + public void startImagePreview(OCFile file); - /** - * Callback method invoked when a file (non directory) is clicked by the user on the files list - * - * @param file - */ - public void onFileClick(OCFile file); + public void startSyncFolderOperation(OCFile folder); /** * Getter for the current DataStorageManager in the container activity */ - public DataStorageManager getStorageManager(); - - - /** - * Callback method invoked when the parent activity is fully created to get the directory to list firstly. - * - * @return Directory to list firstly. Can be NULL. - */ - public OCFile getInitialDirectory(); + public FileDataStorageManager getStorageManager(); /** @@ -471,6 +482,8 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial * @param uploading Flag signaling if the file is now uploading. */ public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading); + + void startDownloadForSending(OCFile file); } @@ -484,9 +497,8 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial AccountUtils.getCurrentOwnCloudAccount(getActivity()), newFilename, mContainerActivity.getStorageManager()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext()); - operation.execute(wc, mContainerActivity, mHandler); - getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT); + operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); + ((FileDisplayActivity) getActivity()).showLoadingDialog(); } } @@ -498,26 +510,16 @@ public class OCFileListFragment extends FragmentListView implements EditNameDial RemoteOperation operation = new RemoveFileOperation( mTargetFile, true, mContainerActivity.getStorageManager()); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext()); - operation.execute(wc, mContainerActivity, mHandler); + operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); - getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT); + ((FileDisplayActivity) getActivity()).showLoadingDialog(); } } } @Override public void onNeutral(String callerTag) { - File f = null; - if (mTargetFile.isDirectory()) { - // TODO run in a secondary thread? - mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true); - - } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) { - f.delete(); - mTargetFile.setStoragePath(null); - mContainerActivity.getStorageManager().saveFile(mTargetFile); - } + mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true); // TODO perform in background task / new thread listDirectory(); mContainerActivity.onTransferStateChanged(mTargetFile, false, false); } diff --git a/src/com/owncloud/android/ui/preview/FileDownloadFragment.java b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java index b11d341c..c8fd2d29 100644 --- a/src/com/owncloud/android/ui/preview/FileDownloadFragment.java +++ b/src/com/owncloud/android/ui/preview/FileDownloadFragment.java @@ -3,8 +3,8 @@ * 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 as published by - * the Free Software Foundation, either version 3 of the License. + * 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 @@ -19,35 +19,33 @@ package com.owncloud.android.ui.preview; import java.lang.ref.WeakReference; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; +import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.utils.Log_OC; + import android.accounts.Account; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.FragmentStatePagerAdapter; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.widget.Button; +import android.widget.ImageButton; import android.widget.ProgressBar; import android.widget.TextView; -import com.actionbarsherlock.app.SherlockFragment; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.ui.fragment.FileFragment; - -import com.owncloud.android.R; +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; -import eu.alefzero.webdav.OnDatatransferProgressListener; /** * This Fragment is used to monitor the progress of a file downloading. * * @author David A. Velasco */ -public class FileDownloadFragment extends SherlockFragment implements OnClickListener, FileFragment { +public class FileDownloadFragment extends FileFragment implements OnClickListener { public static final String EXTRA_FILE = "FILE"; public static final String EXTRA_ACCOUNT = "ACCOUNT"; @@ -56,9 +54,7 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis private FileFragment.ContainerActivity mContainerActivity; private View mView; - private OCFile mFile; private Account mAccount; - private FileDataStorageManager mStorageManager; public ProgressListener mProgressListener; private boolean mListening; @@ -75,9 +71,8 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically. */ public FileDownloadFragment() { - mFile = null; + super(); mAccount = null; - mStorageManager = null; mProgressListener = null; mListening = false; mIgnoreFirstSavedState = false; @@ -95,9 +90,8 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution */ public FileDownloadFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) { - mFile = fileToDetail; + super(fileToDetail); mAccount = ocAccount; - mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment mProgressListener = null; mListening = false; mIgnoreFirstSavedState = ignoreFirstSavedState; @@ -118,7 +112,7 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis if (savedInstanceState != null) { if (!mIgnoreFirstSavedState) { - mFile = savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_FILE); + setFile((OCFile)savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_FILE)); mAccount = savedInstanceState.getParcelable(FileDownloadFragment.EXTRA_ACCOUNT); mError = savedInstanceState.getBoolean(FileDownloadFragment.EXTRA_ERROR); } else { @@ -133,7 +127,7 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.progressBar); mProgressListener = new ProgressListener(progressBar); - ((Button)mView.findViewById(R.id.cancelBtn)).setOnClickListener(this); + ((ImageButton)mView.findViewById(R.id.cancelBtn)).setOnClickListener(this); if (mError) { setButtonsForRemote(); @@ -167,7 +161,7 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (mAccount != null) { - mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());; + //mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());; } } @@ -175,7 +169,7 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(FileDownloadFragment.EXTRA_FILE, mFile); + outState.putParcelable(FileDownloadFragment.EXTRA_FILE, getFile()); outState.putParcelable(FileDownloadFragment.EXTRA_ACCOUNT, mAccount); outState.putBoolean(FileDownloadFragment.EXTRA_ERROR, mError); } @@ -224,8 +218,8 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis switch (v.getId()) { case R.id.cancelBtn: { FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder(); - if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) { - downloaderBinder.cancel(mAccount, mFile); + if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) { + downloaderBinder.cancel(mAccount, getFile()); getActivity().finish(); // :) /* leaveTransferProgress(); @@ -239,20 +233,12 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis break; } default: - Log.e(TAG, "Incorrect view clicked!"); + Log_OC.e(TAG, "Incorrect view clicked!"); } } /** - * {@inheritDoc} - */ - public OCFile getFile(){ - return mFile; - } - - - /** * Updates the view depending upon the state of the downloading file. * * @param transferring When true, the view must be updated assuming that the holded file is @@ -261,10 +247,10 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis 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, mFile))) { + if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile()))) { setButtonsForTransferring(); - } else if (mFile.isDown()) { + } else if (getFile().isDown()) { setButtonsForDown(); @@ -335,7 +321,7 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis public void listenForTransferProgress() { if (mProgressListener != null && !mListening) { if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile); + mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); mListening = true; setButtonsForTransferring(); } @@ -346,7 +332,7 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis public void leaveTransferProgress() { if (mProgressListener != null) { if (mContainerActivity.getFileDownloaderBinder() != null) { - mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile); + mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile()); mListening = false; } } @@ -367,11 +353,6 @@ public class FileDownloadFragment extends SherlockFragment implements OnClickLis } @Override - public void onTransferProgress(long progressRate) { - // old method, nothing here - }; - - @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) { int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer)); if (percent != mLastPercent) { diff --git a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java index be06c590..d7cc36d6 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -16,50 +16,56 @@ */ package com.owncloud.android.ui.preview; -import org.apache.commons.httpclient.methods.PostMethod; - -import android.accounts.Account; -import android.app.Dialog; -import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.IBinder; +import android.preference.PreferenceManager; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewPager; -import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import com.actionbarsherlock.app.ActionBar; -import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; 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.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; -import com.owncloud.android.ui.activity.FileDetailActivity; -import com.owncloud.android.ui.fragment.FileDetailFragment; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +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.operations.CreateShareOperation; +import com.owncloud.android.operations.UnshareLinkOperation; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.activity.PinCodeActivity; +import com.owncloud.android.ui.dialog.LoadingDialog; import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.Log_OC; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.R; /** - * Used as an utility to preview image files contained in an ownCloud account. + * Holds a swiping galley where image files contained in an ownCloud directory are shown * * @author David A. Velasco */ -public class PreviewImageActivity extends SherlockFragmentActivity implements FileFragment.ContainerActivity, ViewPager.OnPageChangeListener, OnTouchListener { +public class PreviewImageActivity extends FileActivity implements FileFragment.ContainerActivity, ViewPager.OnPageChangeListener, OnTouchListener , OnRemoteOperationListener{ public static final int DIALOG_SHORT_WAIT = 0; @@ -68,10 +74,7 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi public static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; private static final String KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER"; - private OCFile mFile; - private OCFile mParentFolder; - private Account mAccount; - private DataStorageManager mStorageManager; + private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; private ViewPager mViewPager; private PreviewImagePagerAdapter mPreviewImagePagerAdapter; @@ -86,58 +89,49 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi private boolean mFullScreen; - + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mFile = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE); - mAccount = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT); - if (mFile == null) { - throw new IllegalStateException("Instanced with a NULL OCFile"); - } - if (mAccount == null) { - throw new IllegalStateException("Instanced with a NULL ownCloud Account"); - } - if (!mFile.isImage()) { - throw new IllegalArgumentException("Non-image file passed as argument"); - } requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); setContentView(R.layout.preview_image_activity); - + ActionBar actionBar = getSupportActionBar(); + actionBar.setIcon(DisplayUtils.getSeasonalIconId()); actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setTitle(mFile.getFileName()); actionBar.hide(); - mFullScreen = true; + // PIN CODE request + if (getIntent().getExtras() != null && savedInstanceState == null && fromNotification()) { + requestPinCode(); + } - mStorageManager = new FileDataStorageManager(mAccount, getContentResolver()); - mParentFolder = mStorageManager.getFileById(mFile.getParentId()); - if (mParentFolder == null) { - // should not be necessary - mParentFolder = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); - } - + mFullScreen = true; if (savedInstanceState != null) { mRequestWaitingForBinder = savedInstanceState.getBoolean(KEY_WAITING_FOR_BINDER); } else { mRequestWaitingForBinder = false; } - createViewPager(); - } - private void createViewPager() { - mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), mParentFolder, mAccount, mStorageManager); + private void initViewPager() { + // get parent from path + String parentPath = getFile().getRemotePath().substring(0, getFile().getRemotePath().lastIndexOf(getFile().getFileName())); + OCFile parentFolder = getStorageManager().getFileByPath(parentPath); + if (parentFolder == null) { + // should not be necessary + parentFolder = getStorageManager().getFileByPath(OCFile.ROOT_PATH); + } + mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), parentFolder, getAccount(), getStorageManager()); mViewPager = (ViewPager) findViewById(R.id.fragmentPager); - int position = mPreviewImagePagerAdapter.getFilePosition(mFile); + int position = mPreviewImagePagerAdapter.getFilePosition(getFile()); position = (position >= 0) ? position : 0; mViewPager.setAdapter(mPreviewImagePagerAdapter); mViewPager.setOnPageChangeListener(this); mViewPager.setCurrentItem(position); - if (position == 0 && !mFile.isDown()) { + if (position == 0 && !getFile().isDown()) { // this is necessary because mViewPager.setCurrentItem(0) just after setting the adapter does not result in a call to #onPageSelected(0) mRequestWaitingForBinder = true; } @@ -159,7 +153,43 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi outState.putBoolean(KEY_WAITING_FOR_BINDER, mRequestWaitingForBinder); } - + @Override + public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { + super.onRemoteOperationFinish(operation, result); + + if (operation instanceof CreateShareOperation) { + onCreateShareOperationFinish((CreateShareOperation) operation, result); + + } else if (operation instanceof UnshareLinkOperation) { + onUnshareLinkOperationFinish((UnshareLinkOperation) operation, result); + + } + } + + + private void onUnshareLinkOperationFinish(UnshareLinkOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + OCFile file = getStorageManager().getFileByPath(getFile().getRemotePath()); + if (file != null) { + setFile(file); + } + invalidateOptionsMenu(); + } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { + backToDisplayActivity(); + } + + } + + private void onCreateShareOperationFinish(CreateShareOperation operation, RemoteOperationResult result) { + if (result.isSuccess()) { + OCFile file = getStorageManager().getFileByPath(getFile().getRemotePath()); + if (file != null) { + setFile(file); + } + invalidateOptionsMenu(); + } + } + /** Defines callbacks for service binding, passed to bindService() */ private class PreviewImageServiceConnection implements ServiceConnection { @@ -170,12 +200,12 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi mDownloaderBinder = (FileDownloaderBinder) service; if (mRequestWaitingForBinder) { mRequestWaitingForBinder = false; - Log.d(TAG, "Simulating reselection of current page after connection of download binder"); + Log_OC.d(TAG, "Simulating reselection of current page after connection of download binder"); onPageSelected(mViewPager.getCurrentItem()); } - + } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { - Log.d(TAG, "Upload service connected"); + Log_OC.d(TAG, "Upload service connected"); mUploaderBinder = (FileUploaderBinder) service; } else { return; @@ -186,10 +216,10 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi @Override public void onServiceDisconnected(ComponentName component) { if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) { - Log.d(TAG, "Download service suddenly disconnected"); + Log_OC.d(TAG, "Download service suddenly disconnected"); mDownloaderBinder = null; } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { - Log.d(TAG, "Upload service suddenly disconnected"); + Log_OC.d(TAG, "Upload service suddenly disconnected"); mUploaderBinder = null; } } @@ -215,7 +245,6 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi super.onDestroy(); } - @Override public boolean onOptionsItemSelected(MenuItem item) { boolean returnValue = false; @@ -238,8 +267,9 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi super.onResume(); //Log.e(TAG, "ACTIVITY, ONRESUME"); mDownloadFinishReceiver = new DownloadFinishReceiver(); - IntentFilter filter = new IntentFilter(FileDownloader.DOWNLOAD_FINISH_MESSAGE); - filter.addAction(FileDownloader.DOWNLOAD_ADDED_MESSAGE); + + IntentFilter filter = new IntentFilter(FileDownloader.getDownloadFinishMessage()); + filter.addAction(FileDownloader.getDownloadAddedMessage()); registerReceiver(mDownloadFinishReceiver, filter); } @@ -258,37 +288,32 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi private void backToDisplayActivity() { - /* - Intent intent = new Intent(this, FileDisplayActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - intent.putExtra(FileDetailFragment.EXTRA_FILE, mFile); - intent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, mAccount); - startActivity(intent); - */ finish(); } + /** + * Show loading dialog + */ + public void showLoadingDialog() { + // Construct dialog + LoadingDialog loading = new LoadingDialog(getResources().getString(R.string.wait_a_moment)); + FragmentManager fm = getSupportFragmentManager(); + FragmentTransaction ft = fm.beginTransaction(); + loading.show(ft, DIALOG_WAIT_TAG); + + } - @Override - protected Dialog onCreateDialog(int id) { - Dialog dialog = null; - switch (id) { - case DIALOG_SHORT_WAIT: { - ProgressDialog working_dialog = new ProgressDialog(this); - working_dialog.setMessage(getResources().getString( - R.string.wait_a_moment)); - working_dialog.setIndeterminate(true); - working_dialog.setCancelable(false); - dialog = working_dialog; - break; - } - default: - dialog = null; + /** + * Dismiss loading dialog + */ + public void dismissLoadingDialog(){ + Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG); + if (frag != null) { + LoadingDialog loading = (LoadingDialog) frag; + loading.dismiss(); } - return dialog; } - /** * {@inheritDoc} */ @@ -314,11 +339,11 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi @Override - public void showFragmentWithDetails(OCFile file) { - Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, file); - showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); - showDetailsIntent.putExtra(FileDetailActivity.EXTRA_MODE, FileDetailActivity.MODE_DETAILS); + public void showDetails(OCFile file) { + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.setAction(FileDisplayActivity.ACTION_DETAILS); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, file); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); startActivity(showDetailsIntent); int pos = mPreviewImagePagerAdapter.getFilePosition(file); file = mPreviewImagePagerAdapter.getFileAt(pos); @@ -328,11 +353,11 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi private void requestForDownload(OCFile file) { if (mDownloaderBinder == null) { - Log.d(TAG, "requestForDownload called without binder to download service"); + Log_OC.d(TAG, "requestForDownload called without binder to download service"); - } else if (!mDownloaderBinder.isDownloading(mAccount, file)) { + } else if (!mDownloaderBinder.isDownloading(getAccount(), file)) { Intent i = new Intent(this, FileDownloader.class); - i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount); + i.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount()); i.putExtra(FileDownloader.EXTRA_FILE, file); startService(i); } @@ -395,15 +420,15 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi public void onReceive(Context context, Intent intent) { String accountName = intent.getStringExtra(FileDownloader.ACCOUNT_NAME); String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - if (mAccount.name.equals(accountName) && + if (getAccount().name.equals(accountName) && downloadedRemotePath != null) { - OCFile file = mStorageManager.getFileByPath(downloadedRemotePath); + OCFile file = getStorageManager().getFileByPath(downloadedRemotePath); int position = mPreviewImagePagerAdapter.getFilePosition(file); boolean downloadWasFine = intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false); //boolean isOffscreen = Math.abs((mViewPager.getCurrentItem() - position)) <= mViewPager.getOffscreenPageLimit(); - if (position >= 0 && intent.getAction().equals(FileDownloader.DOWNLOAD_FINISH_MESSAGE)) { + if (position >= 0 && intent.getAction().equals(FileDownloader.getDownloadFinishMessage())) { if (downloadWasFine) { mPreviewImagePagerAdapter.updateFile(position, file); @@ -413,7 +438,7 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi mPreviewImagePagerAdapter.notifyDataSetChanged(); // will trigger the creation of new fragments } else { - Log.d(TAG, "Download finished, but the fragment is offscreen"); + Log_OC.d(TAG, "Download finished, but the fragment is offscreen"); } } @@ -443,6 +468,52 @@ public class PreviewImageActivity extends SherlockFragmentActivity implements Fi } mFullScreen = !mFullScreen; } + + @Override + protected void onAccountSet(boolean stateWasRecovered) { + super.onAccountSet(stateWasRecovered); + if (getAccount() != null) { + OCFile file = getFile(); + /// Validate handled file (first image to preview) + if (file == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (!file.isImage()) { + throw new IllegalArgumentException("Non-image file passed as argument"); + } + + // Update file according to DB file, if it is possible + if (file.getFileId() > FileDataStorageManager.ROOT_PARENT_ID) + file = getStorageManager().getFileById(file.getFileId()); + + if (file != null) { + /// Refresh the activity according to the Account and OCFile set + setFile(file); // reset after getting it fresh from storageManager + getSupportActionBar().setTitle(getFile().getFileName()); + //if (!stateWasRecovered) { + initViewPager(); + //} + + } else { + // handled file not in the current Account + finish(); + } + } + } + /** + * Launch an intent to request the PIN code to the user before letting him use the app + */ + private void requestPinCode() { + boolean pinStart = false; + SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + pinStart = appPrefs.getBoolean("set_pincode", false); + if (pinStart) { + Intent i = new Intent(getApplicationContext(), PinCodeActivity.class); + i.putExtra(PinCodeActivity.EXTRA_ACTIVITY, "PreviewImageActivity"); + startActivity(i); + } + } + } diff --git a/src/com/owncloud/android/ui/preview/PreviewImageFragment.java b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java index 2f51a631..3a374e08 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageFragment.java @@ -16,7 +16,6 @@ */ package com.owncloud.android.ui.preview; -import java.io.File; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -36,7 +35,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.FragmentStatePagerAdapter; -import android.util.Log; import android.view.Display; import android.view.LayoutInflater; import android.view.View; @@ -48,23 +46,21 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.actionbarsherlock.app.SherlockFragment; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.network.WebdavUtils; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.RemoveFileOperation; +import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.fragment.ConfirmationDialogFragment; import com.owncloud.android.ui.fragment.FileFragment; - -import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; +import com.owncloud.android.utils.Log_OC; /** @@ -76,14 +72,12 @@ import eu.alefzero.webdav.WebdavUtils; * * @author David A. Velasco */ -public class PreviewImageFragment extends SherlockFragment implements FileFragment, - OnRemoteOperationListener, +public class PreviewImageFragment extends FileFragment implements OnRemoteOperationListener, ConfirmationDialogFragment.ConfirmationDialogFragmentListener { public static final String EXTRA_FILE = "FILE"; public static final String EXTRA_ACCOUNT = "ACCOUNT"; private View mView; - private OCFile mFile; private Account mAccount; private FileDataStorageManager mStorageManager; private ImageView mImageView; @@ -110,7 +104,7 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag * @param ignoreFirstSavedState Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution */ public PreviewImageFragment(OCFile fileToDetail, Account ocAccount, boolean ignoreFirstSavedState) { - mFile = fileToDetail; + super(fileToDetail); mAccount = ocAccount; mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment mIgnoreFirstSavedState = ignoreFirstSavedState; @@ -125,7 +119,7 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction */ public PreviewImageFragment() { - mFile = null; + super(); mAccount = null; mStorageManager = null; mIgnoreFirstSavedState = false; @@ -182,19 +176,33 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); if (savedInstanceState != null) { if (!mIgnoreFirstSavedState) { - mFile = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_FILE); + OCFile file = (OCFile)savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_FILE); mAccount = savedInstanceState.getParcelable(PreviewImageFragment.EXTRA_ACCOUNT); + + // Update the file + if (mAccount!= null) { + mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); + OCFile updatedFile = mStorageManager.getFileByPath(file.getRemotePath()); + if (updatedFile != null) { + setFile(updatedFile); + } else { + setFile(file); + } + } else { + setFile(file); + } + } else { mIgnoreFirstSavedState = false; } } - if (mFile == null) { + if (getFile() == null) { throw new IllegalStateException("Instanced with a NULL OCFile"); } if (mAccount == null) { throw new IllegalStateException("Instanced with a NULL ownCloud Account"); } - if (!mFile.isDown()) { + if (!getFile().isDown()) { throw new IllegalStateException("There is no local file to preview"); } } @@ -206,7 +214,7 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(PreviewImageFragment.EXTRA_FILE, mFile); + outState.putParcelable(PreviewImageFragment.EXTRA_FILE, getFile()); outState.putParcelable(PreviewImageFragment.EXTRA_ACCOUNT, mAccount); } @@ -214,9 +222,9 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag @Override public void onStart() { super.onStart(); - if (mFile != null) { + if (getFile() != null) { BitmapLoader bl = new BitmapLoader(mImageView, mMessageView, mProgressWheel); - bl.execute(new String[]{mFile.getStoragePath()}); + bl.execute(new String[]{getFile().getStoragePath()}); } } @@ -236,7 +244,18 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag toHide.add(R.id.action_cancel_upload); toHide.add(R.id.action_download_file); toHide.add(R.id.action_rename_file); // by now + + // Options shareLink + if (!getFile().isShareByLink()) { + toHide.add(R.id.action_unshare_file); + } + // Send file + boolean sendEnabled = getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on"); + if (!sendEnabled) { + toHide.add(R.id.action_send_file); + } + for (int i : toHide) { item = menu.findItem(i); if (item != null) { @@ -247,6 +266,27 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag } + /** + * {@inheritDoc} + */ + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + MenuItem item = menu.findItem(R.id.action_unshare_file); + // Options shareLink + OCFile file = ((FileActivity) getSherlockActivity()).getFile(); + if (!file.isShareByLink()) { + item.setVisible(false); + item.setEnabled(false); + } else { + item.setVisible(true); + item.setEnabled(true); + } + + } + + /** * {@inheritDoc} @@ -254,6 +294,16 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.action_share_file: { + FileActivity act = (FileActivity)getSherlockActivity(); + act.getFileOperationsHelper().shareFileWithLink(getFile(), act); + return true; + } + case R.id.action_unshare_file: { + FileActivity act = (FileActivity)getSherlockActivity(); + act.getFileOperationsHelper().unshareFileWithLink(getFile(), act); + return true; + } case R.id.action_open_file_with: { openFile(); return true; @@ -266,50 +316,32 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag seeDetails(); return true; } + case R.id.action_send_file: { + FileActivity act = (FileActivity)getSherlockActivity(); + act.getFileOperationsHelper().sendDownloadedFile(getFile(), act); + return true; + } default: return false; } } - + private void seeDetails() { - ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mFile); + ((FileFragment.ContainerActivity)getActivity()).showDetails(getFile()); } @Override public void onResume() { super.onResume(); - //Log.e(TAG, "FRAGMENT, ONRESUME"); - /* - mDownloadFinishReceiver = new DownloadFinishReceiver(); - IntentFilter filter = new IntentFilter( - FileDownloader.DOWNLOAD_FINISH_MESSAGE); - getActivity().registerReceiver(mDownloadFinishReceiver, filter); - - mUploadFinishReceiver = new UploadFinishReceiver(); - filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE); - getActivity().registerReceiver(mUploadFinishReceiver, filter); - */ - } @Override public void onPause() { super.onPause(); - /* - if (mVideoPreview.getVisibility() == View.VISIBLE) { - mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); - }*/ - /* - getActivity().unregisterReceiver(mDownloadFinishReceiver); - mDownloadFinishReceiver = null; - - getActivity().unregisterReceiver(mUploadFinishReceiver); - mUploadFinishReceiver = null; - */ } @@ -330,22 +362,23 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag * available apps for the MIME type known from the file extension, to let the user choose */ private void openFile() { - String storagePath = mFile.getStoragePath(); + OCFile file = getFile(); + String storagePath = file.getStoragePath(); String encodedStoragePath = WebdavUtils.encodePath(storagePath); try { Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype()); + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivity(i); } catch (Throwable t) { - Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype()); + Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + file.getMimetype()); boolean toastIt = true; String mimeType = ""; try { Intent i = new Intent(Intent.ACTION_VIEW); mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (mimeType == null || !mimeType.equals(mFile.getMimetype())) { + if (mimeType == null || !mimeType.equals(file.getMimetype())) { if (mimeType != null) { i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); } else { @@ -358,17 +391,17 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag } } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); + Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); + Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); } catch (Throwable th) { - Log.e(TAG, "Unexpected problem when opening: " + storagePath, th); + Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th); } finally { if (toastIt) { - Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), "There is no application to handle file " + file.getFileName(), Toast.LENGTH_SHORT).show(); } } @@ -386,7 +419,7 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag private void removeFile() { ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( R.string.confirmation_remove_alert, - new String[]{mFile.getFileName()}, + new String[]{getFile().getFileName()}, R.string.confirmation_remove_remote_and_local, R.string.confirmation_remove_local, R.string.common_cancel); @@ -400,14 +433,13 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag */ @Override public void onConfirmation(String callerTag) { - if (mStorageManager.getFileById(mFile.getFileId()) != null) { // check that the file is still there; - mLastRemoteOperation = new RemoveFileOperation( mFile, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters + if (mStorageManager.getFileById(getFile().getFileId()) != null) { // check that the file is still there; + mLastRemoteOperation = new RemoveFileOperation( getFile(), // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters true, mStorageManager); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - getActivity().showDialog(PreviewImageActivity.DIALOG_SHORT_WAIT); + ((PreviewImageActivity) getActivity()).showLoadingDialog(); } } @@ -417,14 +449,9 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag */ @Override public void onNeutral(String callerTag) { - // TODO this code should be made in a secondary thread, - if (mFile.isDown()) { // checks it is still there - File f = new File(mFile.getStoragePath()); - f.delete(); - mFile.setStoragePath(null); - mStorageManager.saveFile(mFile); - finish(); - } + OCFile file = getFile(); + mStorageManager.removeFile(file, false, true); // TODO perform in background task / new thread + finish(); } /** @@ -436,33 +463,6 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag } - /** - * {@inheritDoc} - */ - public OCFile getFile(){ - return mFile; - } - - /* - /** - * Use this method to signal this Activity that it shall update its view. - * - * @param file : An {@link OCFile} - *-/ - public void updateFileDetails(OCFile file, Account ocAccount) { - mFile = file; - if (ocAccount != null && ( - mStorageManager == null || - (mAccount != null && !mAccount.equals(ocAccount)) - )) { - mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver()); - } - mAccount = ocAccount; - updateFileDetails(false); - } - */ - - private class BitmapLoader extends AsyncTask { /** @@ -557,24 +557,24 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag // really load the bitmap options.inJustDecodeBounds = false; // the next decodeFile call will be real result = BitmapFactory.decodeFile(storagePath, options); - //Log.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight); + //Log_OC.d(TAG, "Image loaded - width: " + options.outWidth + ", loaded height: " + options.outHeight); if (result == null) { mErrorMessageId = R.string.preview_image_error_unknown_format; - Log.e(TAG, "File could not be loaded as a bitmap: " + storagePath); + Log_OC.e(TAG, "File could not be loaded as a bitmap: " + storagePath); } } catch (OutOfMemoryError e) { mErrorMessageId = R.string.preview_image_error_unknown_format; - Log.e(TAG, "Out of memory occured for file " + storagePath, e); + Log_OC.e(TAG, "Out of memory occured for file " + storagePath, e); } catch (NoSuchFieldError e) { mErrorMessageId = R.string.common_error_unknown; - Log.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.e(TAG, "Unexpected error loading " + mFile.getStoragePath(), t); + Log_OC.e(TAG, "Unexpected error loading " + getFile().getStoragePath(), t); } return result; @@ -657,7 +657,7 @@ public class PreviewImageFragment extends SherlockFragment implements FileFrag } private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - getActivity().dismissDialog(PreviewImageActivity.DIALOG_SHORT_WAIT); + ((PreviewImageActivity) getActivity()).dismissLoadingDialog(); if (result.isSuccess()) { Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); diff --git a/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java index b5945bd0..de90b4bd 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java +++ b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java @@ -16,29 +16,22 @@ */ package com.owncloud.android.ui.preview; -import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Vector; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.ui.fragment.FileFragment; + import android.accounts.Account; -import android.os.Bundle; -import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.PagerAdapter; -import android.support.v4.view.ViewPager; -import android.util.Log; -import android.view.View; import android.view.ViewGroup; -import com.owncloud.android.datamodel.DataStorageManager; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.datamodel.FileDataStorageManager; /** * Adapter class that provides Fragment instances @@ -48,25 +41,15 @@ import com.owncloud.android.ui.fragment.FileFragment; //public class PreviewImagePagerAdapter extends PagerAdapter { public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { - private static final String TAG = PreviewImagePagerAdapter.class.getSimpleName(); - private Vector mImageFiles; private Account mAccount; private Set mObsoleteFragments; private Set mObsoletePositions; private Set mDownloadErrors; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private Map mCachedFragments; - /* - private final FragmentManager mFragmentManager; - private FragmentTransaction mCurTransaction = null; - private ArrayList mSavedState = new ArrayList(); - private ArrayList mFragments = new ArrayList(); - private Fragment mCurrentPrimaryItem = null; - */ - /** * Constructor. * @@ -74,7 +57,7 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { * @param parentFolder Folder where images will be searched for. * @param storageManager Bridge to database. */ - public PreviewImagePagerAdapter(FragmentManager fragmentManager, OCFile parentFolder, Account account, DataStorageManager storageManager) { + public PreviewImagePagerAdapter(FragmentManager fragmentManager, OCFile parentFolder, Account account, FileDataStorageManager storageManager) { super(fragmentManager); if (fragmentManager == null) { @@ -89,7 +72,7 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { mAccount = account; mStorageManager = storageManager; - mImageFiles = mStorageManager.getDirectoryImages(parentFolder); + mImageFiles = mStorageManager.getFolderImages(parentFolder); mObsoleteFragments = new HashSet(); mObsoletePositions = new HashSet(); mDownloadErrors = new HashSet(); @@ -196,8 +179,6 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { return mDownloadErrors.contains(Integer.valueOf(position)); } - - /* -* * Called when a change in the shown pages is going to start being made. * diff --git a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 6fbf7d66..7fa399ae 100644 --- a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -16,7 +16,6 @@ */ package com.owncloud.android.ui.preview; -import java.io.File; import java.util.ArrayList; import java.util.List; @@ -29,6 +28,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; +import android.content.res.Configuration; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; @@ -38,8 +38,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.support.v4.app.FragmentTransaction; -import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -50,29 +48,26 @@ import android.widget.ImageView; import android.widget.Toast; import android.widget.VideoView; -import com.actionbarsherlock.app.SherlockFragment; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; +import com.owncloud.android.R; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.media.MediaControlView; import com.owncloud.android.media.MediaService; import com.owncloud.android.media.MediaServiceBinder; -import com.owncloud.android.network.OwnCloudClientUtils; -import com.owncloud.android.operations.OnRemoteOperationListener; -import com.owncloud.android.operations.RemoteOperation; -import com.owncloud.android.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.network.WebdavUtils; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.operations.RemoveFileOperation; -import com.owncloud.android.ui.activity.FileDetailActivity; +import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.fragment.ConfirmationDialogFragment; -import com.owncloud.android.ui.fragment.FileDetailFragment; import com.owncloud.android.ui.fragment.FileFragment; +import com.owncloud.android.utils.Log_OC; -import com.owncloud.android.R; -import eu.alefzero.webdav.WebdavClient; -import eu.alefzero.webdav.WebdavUtils; /** * This fragment shows a preview of a downloaded media file (audio or video). @@ -83,8 +78,8 @@ import eu.alefzero.webdav.WebdavUtils; * * @author David A. Velasco */ -public class PreviewMediaFragment extends SherlockFragment implements - OnTouchListener , FileFragment, +public class PreviewMediaFragment extends FileFragment implements + OnTouchListener, ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener { public static final String EXTRA_FILE = "FILE"; @@ -93,7 +88,6 @@ public class PreviewMediaFragment extends SherlockFragment implements private static final String EXTRA_PLAYING = "PLAYING"; private View mView; - private OCFile mFile; private Account mAccount; private FileDataStorageManager mStorageManager; private ImageView mImagePreview; @@ -108,6 +102,7 @@ public class PreviewMediaFragment extends SherlockFragment implements private MediaServiceConnection mMediaServiceConnection = null; private VideoHelper mVideoHelper; private boolean mAutoplay; + public boolean mPrepared; private static final String TAG = PreviewMediaFragment.class.getSimpleName(); @@ -120,12 +115,12 @@ public class PreviewMediaFragment extends SherlockFragment implements * @param fileToDetail An {@link OCFile} to preview in the fragment * @param ocAccount An ownCloud account; needed to start downloads */ - public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount) { - mFile = fileToDetail; + public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount, int startPlaybackPosition, boolean autoplay) { + super(fileToDetail); mAccount = ocAccount; - mSavedPlaybackPosition = 0; + mSavedPlaybackPosition = startPlaybackPosition; mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment - mAutoplay = true; + mAutoplay = autoplay; } @@ -137,7 +132,7 @@ public class PreviewMediaFragment extends SherlockFragment implements * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction */ public PreviewMediaFragment() { - mFile = null; + super(); mAccount = null; mSavedPlaybackPosition = 0; mStorageManager = null; @@ -163,6 +158,8 @@ public class PreviewMediaFragment extends SherlockFragment implements public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); + Log_OC.e(TAG, "onCreateView"); + mView = inflater.inflate(R.layout.file_preview, container, false); @@ -182,6 +179,8 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override public void onAttach(Activity activity) { super.onAttach(activity); + Log_OC.e(TAG, "onAttach"); + if (!(activity instanceof FileFragment.ContainerActivity)) throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName()); } @@ -193,25 +192,27 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + Log_OC.e(TAG, "onActivityCreated"); mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver()); if (savedInstanceState != null) { - mFile = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE); + setFile((OCFile)savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE)); mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT); mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION); mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING); } - if (mFile == null) { + OCFile file = getFile(); + if (file == null) { throw new IllegalStateException("Instanced with a NULL OCFile"); } if (mAccount == null) { throw new IllegalStateException("Instanced with a NULL ownCloud Account"); } - if (!mFile.isDown()) { + if (!file.isDown()) { throw new IllegalStateException("There is no local file to preview"); } - if (mFile.isVideo()) { + if (file.isVideo()) { mVideoPreview.setVisibility(View.VISIBLE); mImagePreview.setVisibility(View.GONE); prepareVideo(); @@ -230,10 +231,12 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, mFile); + Log_OC.e(TAG, "onSaveInstanceState"); + + outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, getFile()); outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount); - if (mFile.isVideo()) { + if (getFile().isVideo()) { mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); mAutoplay = mVideoPreview.isPlaying(); outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mSavedPlaybackPosition); @@ -248,12 +251,14 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override public void onStart() { super.onStart(); + Log_OC.e(TAG, "onStart"); - if (mFile != null) { - if (mFile.isAudio()) { + OCFile file = getFile(); + if (file != null) { + if (file.isAudio()) { bindMediaService(); - } else if (mFile.isVideo()) { + } else if (file.isVideo()) { stopAudio(); playVideo(); } @@ -282,8 +287,20 @@ public class PreviewMediaFragment extends SherlockFragment implements toHide.add(R.id.action_cancel_download); toHide.add(R.id.action_cancel_upload); toHide.add(R.id.action_download_file); + toHide.add(R.id.action_sync_file); toHide.add(R.id.action_rename_file); // by now - + + // Options shareLink + if (!getFile().isShareByLink()) { + toHide.add(R.id.action_unshare_file); + } + + // Send file + boolean sendEnabled = getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on"); + if (!sendEnabled) { + toHide.add(R.id.action_send_file); + } + for (int i : toHide) { item = menu.findItem(i); if (item != null) { @@ -294,6 +311,25 @@ public class PreviewMediaFragment extends SherlockFragment implements } + + /** + * {@inheritDoc} + */ + @Override + public void onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + MenuItem item = menu.findItem(R.id.action_unshare_file); + // Options shareLink + if (!getFile().isShareByLink()) { + item.setVisible(false); + item.setEnabled(false); + } else { + item.setVisible(true); + item.setEnabled(true); + } + } + /** * {@inheritDoc} @@ -301,6 +337,14 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case R.id.action_share_file: { + shareFileWithLink(); + return true; + } + case R.id.action_unshare_file: { + unshareFileWithLink(); + return true; + } case R.id.action_open_file_with: { openFile(); return true; @@ -313,16 +357,48 @@ public class PreviewMediaFragment extends SherlockFragment implements seeDetails(); return true; } + case R.id.action_send_file: { + sendFile(); + } default: return false; } } + + + /** + * Update the file of the fragment with file value + * @param file + */ + public void updateFile(OCFile file){ + setFile(file); + } + private void unshareFileWithLink() { + stopPreview(false); + FileActivity activity = (FileActivity)((FileFragment.ContainerActivity)getActivity()); + activity.getFileOperationsHelper().unshareFileWithLink(getFile(), activity); + } + + private void shareFileWithLink() { + stopPreview(false); + FileActivity activity = (FileActivity)((FileFragment.ContainerActivity)getActivity()); + activity.getFileOperationsHelper().shareFileWithLink(getFile(), activity); + + } + + private void sendFile() { + stopPreview(false); + FileActivity activity = (FileActivity)((FileFragment.ContainerActivity)getActivity()); + activity.getFileOperationsHelper().sendDownloadedFile(getFile(), activity); + + } + private void seeDetails() { stopPreview(false); - ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mFile); + ((FileFragment.ContainerActivity)getActivity()).showDetails(getFile()); } @@ -339,7 +415,7 @@ public class PreviewMediaFragment extends SherlockFragment implements mMediaController.setMediaPlayer(mVideoPreview); // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called - mVideoPreview.setVideoPath(mFile.getStoragePath()); + mVideoPreview.setVideoPath(getFile().getStoragePath()); } @@ -354,13 +430,14 @@ public class PreviewMediaFragment extends SherlockFragment implements */ @Override public void onPrepared(MediaPlayer vp) { - Log.e(TAG, "onPrepared"); + Log_OC.e(TAG, "onPrepared"); mVideoPreview.seekTo(mSavedPlaybackPosition); if (mAutoplay) { mVideoPreview.start(); } mMediaController.setEnabled(true); mMediaController.updatePausePlay(); + mPrepared = true; } @@ -373,7 +450,7 @@ public class PreviewMediaFragment extends SherlockFragment implements */ @Override public void onCompletion(MediaPlayer mp) { - Log.e(TAG, "completed"); + Log_OC.e(TAG, "completed"); if (mp != null) { mVideoPreview.seekTo(0); // next lines are necessary to work around undesired video loops @@ -387,7 +464,7 @@ public class PreviewMediaFragment extends SherlockFragment implements mVideoPreview.stopPlayback(); mAutoplay = false; mSavedPlaybackPosition = 0; - mVideoPreview.setVideoPath(mFile.getStoragePath()); + mVideoPreview.setVideoPath(getFile().getStoragePath()); } } // else : called from onError() mMediaController.updatePausePlay(); @@ -424,11 +501,31 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override + public void onPause() { + super.onPause(); + Log_OC.e(TAG, "onPause"); + } + + @Override + public void onResume() { + super.onResume(); + Log_OC.e(TAG, "onResume"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log_OC.e(TAG, "onDestroy"); + } + + @Override public void onStop() { + Log_OC.e(TAG, "onStop"); super.onStop(); - + + mPrepared = false; if (mMediaServiceConnection != null) { - Log.d(TAG, "Unbinding from MediaService ..."); + Log_OC.d(TAG, "Unbinding from MediaService ..."); if (mMediaServiceBinder != null && mMediaController != null) { mMediaServiceBinder.unregisterMediaController(mMediaController); } @@ -450,17 +547,22 @@ public class PreviewMediaFragment extends SherlockFragment implements private void startFullScreenVideo() { Intent i = new Intent(getActivity(), PreviewVideoActivity.class); - i.putExtra(PreviewVideoActivity.EXTRA_ACCOUNT, mAccount); - i.putExtra(PreviewVideoActivity.EXTRA_FILE, mFile); + i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); + i.putExtra(FileActivity.EXTRA_FILE, getFile()); i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying()); mVideoPreview.pause(); i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition()); startActivityForResult(i, 0); } + @Override + public void onConfigurationChanged (Configuration newConfig) { + Log_OC.e(TAG, "onConfigurationChanged " + this); + } @Override public void onActivityResult (int requestCode, int resultCode, Intent data) { + Log_OC.e(TAG, "onActivityResult " + this); super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK) { mSavedPlaybackPosition = data.getExtras().getInt(PreviewVideoActivity.EXTRA_START_POSITION); @@ -470,9 +572,10 @@ public class PreviewMediaFragment extends SherlockFragment implements private void playAudio() { - if (!mMediaServiceBinder.isPlaying(mFile)) { - Log.d(TAG, "starting playback of " + mFile.getStoragePath()); - mMediaServiceBinder.start(mAccount, mFile, mAutoplay, mSavedPlaybackPosition); + OCFile file = getFile(); + if (!mMediaServiceBinder.isPlaying(file)) { + Log_OC.d(TAG, "starting playback of " + file.getStoragePath()); + mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition); } else { if (!mMediaServiceBinder.isPlaying() && mAutoplay) { @@ -484,7 +587,7 @@ public class PreviewMediaFragment extends SherlockFragment implements private void bindMediaService() { - Log.d(TAG, "Binding to MediaService..."); + Log_OC.d(TAG, "Binding to MediaService..."); if (mMediaServiceConnection == null) { mMediaServiceConnection = new MediaServiceConnection(); } @@ -500,17 +603,19 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override public void onServiceConnected(ComponentName component, IBinder service) { - if (component.equals(new ComponentName(getActivity(), MediaService.class))) { - Log.d(TAG, "Media service connected"); - mMediaServiceBinder = (MediaServiceBinder) service; - if (mMediaServiceBinder != null) { - prepareMediaController(); - playAudio(); // do not wait for the touch of nobody to play audio - - Log.d(TAG, "Successfully bound to MediaService, MediaController ready"); - - } else { - Log.e(TAG, "Unexpected response from MediaService while binding"); + if (getActivity() != null) { + if (component.equals(new ComponentName(getActivity(), MediaService.class))) { + Log_OC.d(TAG, "Media service connected"); + mMediaServiceBinder = (MediaServiceBinder) service; + if (mMediaServiceBinder != null) { + prepareMediaController(); + playAudio(); // do not wait for the touch of nobody to play audio + + Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready"); + + } else { + Log_OC.e(TAG, "Unexpected response from MediaService while binding"); + } } } } @@ -527,7 +632,7 @@ public class PreviewMediaFragment extends SherlockFragment implements @Override public void onServiceDisconnected(ComponentName component) { if (component.equals(new ComponentName(getActivity(), MediaService.class))) { - Log.e(TAG, "Media service suddenly disconnected"); + Log_OC.e(TAG, "Media service suddenly disconnected"); if (mMediaController != null) { mMediaController.setMediaPlayer(null); } else { @@ -549,23 +654,24 @@ public class PreviewMediaFragment extends SherlockFragment implements * available apps for the MIME type known from the file extension, to let the user choose */ private void openFile() { + OCFile file = getFile(); stopPreview(true); - String storagePath = mFile.getStoragePath(); + String storagePath = file.getStoragePath(); String encodedStoragePath = WebdavUtils.encodePath(storagePath); try { Intent i = new Intent(Intent.ACTION_VIEW); - i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype()); + i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), file.getMimetype()); i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivity(i); } catch (Throwable t) { - Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype()); + Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + file.getMimetype()); boolean toastIt = true; String mimeType = ""; try { Intent i = new Intent(Intent.ACTION_VIEW); mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1)); - if (mimeType == null || !mimeType.equals(mFile.getMimetype())) { + if (mimeType == null || !mimeType.equals(file.getMimetype())) { if (mimeType != null) { i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType); } else { @@ -578,17 +684,17 @@ public class PreviewMediaFragment extends SherlockFragment implements } } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); + Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath); } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); + Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension"); } catch (Throwable th) { - Log.e(TAG, "Unexpected problem when opening: " + storagePath, th); + Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th); } finally { if (toastIt) { - Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), "There is no application to handle file " + file.getFileName(), Toast.LENGTH_SHORT).show(); } } @@ -605,7 +711,7 @@ public class PreviewMediaFragment extends SherlockFragment implements private void removeFile() { ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance( R.string.confirmation_remove_alert, - new String[]{mFile.getFileName()}, + new String[]{getFile().getFileName()}, R.string.confirmation_remove_remote_and_local, R.string.confirmation_remove_local, R.string.common_cancel); @@ -619,16 +725,15 @@ public class PreviewMediaFragment extends SherlockFragment implements */ @Override public void onConfirmation(String callerTag) { - if (mStorageManager.getFileById(mFile.getFileId()) != null) { // check that the file is still there; + OCFile file = getFile(); + if (mStorageManager.getFileById(file.getFileId()) != null) { // check that the file is still there; stopPreview(true); - mLastRemoteOperation = new RemoveFileOperation( mFile, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters + mLastRemoteOperation = new RemoveFileOperation( file, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters true, mStorageManager); - WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext()); - mLastRemoteOperation.execute(wc, this, mHandler); + mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); + ((FileDisplayActivity) getActivity()).showLoadingDialog(); } } @@ -638,15 +743,10 @@ public class PreviewMediaFragment extends SherlockFragment implements */ @Override public void onNeutral(String callerTag) { - // TODO this code should be made in a secondary thread, - if (mFile.isDown()) { // checks it is still there - stopPreview(true); - File f = new File(mFile.getStoragePath()); - f.delete(); - mFile.setStoragePath(null); - mStorageManager.saveFile(mFile); - finish(); - } + OCFile file = getFile(); + stopPreview(true); + mStorageManager.removeFile(file, false, true); // TODO perform in background task / new thread + finish(); } /** @@ -659,33 +759,6 @@ public class PreviewMediaFragment extends SherlockFragment implements /** - * {@inheritDoc} - */ - public OCFile getFile(){ - return mFile; - } - - /* - /** - * Use this method to signal this Activity that it shall update its view. - * - * @param file : An {@link OCFile} - *-/ - public void updateFileDetails(OCFile file, Account ocAccount) { - mFile = file; - if (ocAccount != null && ( - mStorageManager == null || - (mAccount != null && !mAccount.equals(ocAccount)) - )) { - mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver()); - } - mAccount = ocAccount; - updateFileDetails(false); - } - */ - - - /** * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed. * * @param file File to test if can be previewed. @@ -708,9 +781,7 @@ public class PreviewMediaFragment extends SherlockFragment implements } private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { - boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity; - getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT); - + ((FileDisplayActivity) getActivity()).dismissLoadingDialog(); if (result.isSuccess()) { Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG); msg.show(); @@ -726,10 +797,11 @@ public class PreviewMediaFragment extends SherlockFragment implements } private void stopPreview(boolean stopAudio) { - if (mFile.isAudio() && stopAudio) { + OCFile file = getFile(); + if (file.isAudio() && stopAudio) { mMediaServiceBinder.pause(); - } else if (mFile.isVideo()) { + } else if (file.isVideo()) { mVideoPreview.stopPlayback(); } } @@ -740,16 +812,23 @@ public class PreviewMediaFragment extends SherlockFragment implements * Finishes the preview */ private void finish() { - Activity container = getActivity(); - if (container instanceof FileDisplayActivity) { - // double pane - FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction(); - transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment - transaction.commit(); - ((FileFragment.ContainerActivity)container).onFileStateChanged(); - } else { - container.finish(); + getActivity().onBackPressed(); + } + + + public int getPosition() { + if (mPrepared) { + mSavedPlaybackPosition = mVideoPreview.getCurrentPosition(); + } + Log_OC.e(TAG, "getting position: " + mSavedPlaybackPosition); + return mSavedPlaybackPosition; + } + + public boolean isPlaying() { + if (mPrepared) { + mAutoplay = mVideoPreview.isPlaying(); } + return mAutoplay; } } diff --git a/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java index c335bf4a..c674e911 100644 --- a/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java @@ -17,8 +17,13 @@ package com.owncloud.android.ui.preview; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.media.MediaService; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.utils.Log_OC; + import android.accounts.Account; -import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; @@ -28,15 +33,11 @@ import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.os.Bundle; -import android.util.Log; -import android.view.MotionEvent; import android.widget.MediaController; import android.widget.VideoView; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.R; -import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.media.MediaService; +import com.owncloud.android.lib.common.accounts.AccountUtils; +import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException; /** * Activity implementing a basic video player. @@ -48,14 +49,8 @@ import com.owncloud.android.media.MediaService; * * @author David A. Velasco */ -public class PreviewVideoActivity extends Activity implements OnCompletionListener, OnPreparedListener, OnErrorListener { +public class PreviewVideoActivity extends FileActivity implements OnCompletionListener, OnPreparedListener, OnErrorListener { - /** Key to receive an {@link OCFile} to play as an extra value in an {@link Intent} */ - public static final String EXTRA_FILE = "FILE"; - - /** Key to receive the ownCloud {@link Account} where the file to play is saved as an extra value in an {@link Intent} */ - public static final String EXTRA_ACCOUNT = "ACCOUNT"; - /** Key to receive a flag signaling if the video should be started immediately */ public static final String EXTRA_AUTOPLAY = "AUTOPLAY"; @@ -64,8 +59,6 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen private static final String TAG = PreviewVideoActivity.class.getSimpleName(); - private OCFile mFile; // video file to play - private Account mAccount; // ownCloud account holding mFile private int mSavedPlaybackPosition; // in the unit time handled by MediaPlayer.getCurrentPosition() private boolean mAutoplay; // when 'true', the playback starts immediately with the activity private VideoView mVideoPlayer; // view to play the file; both performs and show the playback @@ -84,20 +77,16 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Log.e(TAG, "ACTIVITY\t\tonCreate"); + Log_OC.e(TAG, "ACTIVITY\t\tonCreate"); setContentView(R.layout.video_layout); if (savedInstanceState == null) { Bundle extras = getIntent().getExtras(); - mFile = extras.getParcelable(EXTRA_FILE); - mAccount = extras.getParcelable(EXTRA_ACCOUNT); mSavedPlaybackPosition = extras.getInt(EXTRA_START_POSITION); mAutoplay = extras.getBoolean(EXTRA_AUTOPLAY); } else { - mFile = savedInstanceState.getParcelable(EXTRA_FILE); - mAccount = savedInstanceState.getParcelable(EXTRA_ACCOUNT); mSavedPlaybackPosition = savedInstanceState.getInt(EXTRA_START_POSITION); mAutoplay = savedInstanceState.getBoolean(EXTRA_AUTOPLAY); } @@ -111,29 +100,6 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen // keep the screen on while the playback is performed (prevents screen off by battery save) mVideoPlayer.setKeepScreenOn(true); - - if (mFile != null) { - if (mFile.isDown()) { - mVideoPlayer.setVideoPath(mFile.getStoragePath()); - - } else if (mAccount != null) { - // not working now - String url = AccountUtils.constructFullURLForAccount(this, mAccount) + mFile.getRemotePath(); - mVideoPlayer.setVideoURI(Uri.parse(url)); - - } else { - onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_no_account); - } - - // create and prepare control panel for the user - mMediaController = new MediaController(this); - mMediaController.setMediaPlayer(mVideoPlayer); - mMediaController.setAnchorView(mVideoPlayer); - mVideoPlayer.setMediaController(mMediaController); - - } else { - onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_nothing_to_play); - } } @@ -143,9 +109,7 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - Log.e(TAG, "ACTIVITY\t\tonSaveInstanceState"); - outState.putParcelable(PreviewVideoActivity.EXTRA_FILE, mFile); - outState.putParcelable(PreviewVideoActivity.EXTRA_ACCOUNT, mAccount); + Log_OC.e(TAG, "ACTIVITY\t\tonSaveInstanceState"); outState.putInt(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition()); outState.putBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY , mVideoPlayer.isPlaying()); } @@ -153,7 +117,7 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen @Override public void onBackPressed() { - Log.e(TAG, "ACTIVTIY\t\tonBackPressed"); + Log_OC.e(TAG, "ACTIVTIY\t\tonBackPressed"); Intent i = new Intent(); i.putExtra(EXTRA_AUTOPLAY, mVideoPlayer.isPlaying()); i.putExtra(EXTRA_START_POSITION, mVideoPlayer.getCurrentPosition()); @@ -162,39 +126,6 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen } - @Override - public void onResume() { - super.onResume(); - Log.e(TAG, "ACTIVTIY\t\tonResume"); - } - - - @Override - public void onStart() { - super.onStart(); - Log.e(TAG, "ACTIVTIY\t\tonStart"); - } - - @Override - public void onDestroy() { - super.onDestroy(); - Log.e(TAG, "ACTIVITY\t\tonDestroy"); - } - - @Override - public void onStop() { - super.onStop(); - Log.e(TAG, "ACTIVTIY\t\tonStop"); - } - - - @Override - public void onPause() { - super.onPause(); - Log.e(TAG, "ACTIVTIY\t\tonPause"); - } - - /** * Called when the file is ready to be played. * @@ -204,7 +135,7 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen */ @Override public void onPrepared(MediaPlayer mp) { - Log.e(TAG, "ACTIVITY\t\tonPrepare"); + Log_OC.e(TAG, "ACTIVITY\t\tonPrepare"); mVideoPlayer.seekTo(mSavedPlaybackPosition); if (mAutoplay) { mVideoPlayer.start(); @@ -235,7 +166,7 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen */ @Override public boolean onError(MediaPlayer mp, int what, int extra) { - Log.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra); + Log_OC.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra); if (mMediaController != null) { mMediaController.hide(); @@ -257,26 +188,47 @@ public class PreviewVideoActivity extends Activity implements OnCompletionListen return true; } - - /** - * Screen touches trigger the appearance of the control panel for a limited time. - * - * {@inheritDoc} - */ @Override - public boolean onTouchEvent (MotionEvent ev){ - /*if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (mMediaController.isShowing()) { - mMediaController.hide(); + protected void onAccountSet(boolean stateWasRecovered) { + super.onAccountSet(stateWasRecovered); + if (getAccount() != null) { + OCFile file = getFile(); + /// Validate handled file (first image to preview) + if (file == null) { + throw new IllegalStateException("Instanced with a NULL OCFile"); + } + if (!file.isVideo()) { + throw new IllegalArgumentException("Non-video file passed as argument"); + } + file = getStorageManager().getFileById(file.getFileId()); + if (file != null) { + if (file.isDown()) { + mVideoPlayer.setVideoPath(file.getStoragePath()); + + } else { + // not working yet + String url; + try { + url = AccountUtils.constructFullURLForAccount(this, getAccount()) + file.getRemotePath(); + mVideoPlayer.setVideoURI(Uri.parse(url)); + } catch (AccountNotFoundException e) { + onError(null, MediaService.OC_MEDIA_ERROR, R.string.media_err_no_account); + } + } + + // create and prepare control panel for the user + mMediaController = new MediaController(this); + mMediaController.setMediaPlayer(mVideoPlayer); + mMediaController.setAnchorView(mVideoPlayer); + mVideoPlayer.setMediaController(mMediaController); + } else { - mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE); + finish(); } - return true; } else { - return false; - }*/ - return false; - } + finish(); + } + } } \ No newline at end of file diff --git a/src/com/owncloud/android/utils/DisplayUtils.java b/src/com/owncloud/android/utils/DisplayUtils.java new file mode 100644 index 00000000..680107b8 --- /dev/null +++ b/src/com/owncloud/android/utils/DisplayUtils.java @@ -0,0 +1,202 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 . + * + */ + +package com.owncloud.android.utils; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import com.owncloud.android.R; + +/** + * A helper class for some string operations. + * + * @author Bartek Przybylski + * @author David A. Velasco + */ +public class DisplayUtils { + + //private static String TAG = DisplayUtils.class.getSimpleName(); + + private static final String[] sizeSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + + private static HashMap mimeType2HUmanReadable; + static { + mimeType2HUmanReadable = new HashMap(); + // images + mimeType2HUmanReadable.put("image/jpeg", "JPEG image"); + mimeType2HUmanReadable.put("image/jpg", "JPEG image"); + mimeType2HUmanReadable.put("image/png", "PNG image"); + mimeType2HUmanReadable.put("image/bmp", "Bitmap image"); + mimeType2HUmanReadable.put("image/gif", "GIF image"); + mimeType2HUmanReadable.put("image/svg+xml", "JPEG image"); + mimeType2HUmanReadable.put("image/tiff", "TIFF image"); + // music + mimeType2HUmanReadable.put("audio/mpeg", "MP3 music file"); + mimeType2HUmanReadable.put("application/ogg", "OGG music file"); + + } + + private static final String TYPE_APPLICATION = "application"; + private static final String TYPE_AUDIO = "audio"; + private static final String TYPE_IMAGE = "image"; + private static final String TYPE_TXT = "text"; + private static final String TYPE_VIDEO = "video"; + + private static final String SUBTYPE_PDF = "pdf"; + private static final String[] SUBTYPES_DOCUMENT = { "msword", "mspowerpoint", "msexcel", + "vnd.oasis.opendocument.presentation", + "vnd.oasis.opendocument.spreadsheet", + "vnd.oasis.opendocument.text" + }; + private static Set SUBTYPES_DOCUMENT_SET = new HashSet(Arrays.asList(SUBTYPES_DOCUMENT)); + private static final String[] SUBTYPES_COMPRESSED = {"x-tar", "x-gzip", "zip"}; + private static final Set SUBTYPES_COMPRESSED_SET = new HashSet(Arrays.asList(SUBTYPES_COMPRESSED)); + + /** + * Converts the file size in bytes to human readable output. + * + * @param bytes Input file size + * @return Like something readable like "12 MB" + */ + public static String bytesToHumanReadable(long bytes) { + double result = bytes; + int attachedsuff = 0; + while (result > 1024 && attachedsuff < sizeSuffixes.length) { + result /= 1024.; + attachedsuff++; + } + result = ((int) (result * 100)) / 100.; + return result + " " + sizeSuffixes[attachedsuff]; + } + + /** + * Removes special HTML entities from a string + * + * @param s Input string + * @return A cleaned version of the string + */ + public static String HtmlDecode(String s) { + /* + * TODO: Perhaps we should use something more proven like: + * http://commons.apache.org/lang/api-2.6/org/apache/commons/lang/StringEscapeUtils.html#unescapeHtml%28java.lang.String%29 + */ + + String ret = ""; + for (int i = 0; i < s.length(); ++i) { + if (s.charAt(i) == '%') { + ret += (char) Integer.parseInt(s.substring(i + 1, i + 3), 16); + i += 2; + } else { + ret += s.charAt(i); + } + } + return ret; + } + + /** + * Converts MIME types like "image/jpg" to more end user friendly output + * like "JPG image". + * + * @param mimetype MIME type to convert + * @return A human friendly version of the MIME type + */ + public static String convertMIMEtoPrettyPrint(String mimetype) { + if (mimeType2HUmanReadable.containsKey(mimetype)) { + return mimeType2HUmanReadable.get(mimetype); + } + if (mimetype.split("/").length >= 2) + return mimetype.split("/")[1].toUpperCase() + " file"; + return "Unknown type"; + } + + + /** + * Returns the resource identifier of an image resource to use as icon associated to a + * known MIME type. + * + * @param mimetype MIME type string. + * @return Resource identifier of an image resource. + */ + public static int getResourceId(String mimetype) { + + if (mimetype == null || "DIR".equals(mimetype)) { + return R.drawable.ic_menu_archive; + + } else { + String [] parts = mimetype.split("/"); + String type = parts[0]; + String subtype = (parts.length > 1) ? parts[1] : ""; + + if(TYPE_TXT.equals(type)) { + return R.drawable.file_doc; + + } else if(TYPE_IMAGE.equals(type)) { + return R.drawable.file_image; + + } else if(TYPE_VIDEO.equals(type)) { + return R.drawable.file_movie; + + } else if(TYPE_AUDIO.equals(type)) { + return R.drawable.file_sound; + + } else if(TYPE_APPLICATION.equals(type)) { + + if (SUBTYPE_PDF.equals(subtype)) { + return R.drawable.file_pdf; + + } else if (SUBTYPES_DOCUMENT_SET.contains(subtype)) { + return R.drawable.file_doc; + + } else if (SUBTYPES_COMPRESSED_SET.contains(subtype)) { + return R.drawable.file_zip; + } + + } + // problems: RAR, RTF, 3GP are send as application/octet-stream from the server ; extension in the filename should be explicitly reviewed + } + + // default icon + return R.drawable.file; + } + + + + /** + * Converts Unix time to human readable format + * @param miliseconds that have passed since 01/01/1970 + * @return The human readable time for the users locale + */ + public static String unixTimeToHumanReadable(long milliseconds) { + Date date = new Date(milliseconds); + return date.toLocaleString(); + } + + + public static int getSeasonalIconId() { + if (Calendar.getInstance().get(Calendar.DAY_OF_YEAR) >= 354) { + return R.drawable.winter_holidays_icon; + } else { + return R.drawable.icon; + } + } +} diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index 1c7acaf9..e8e8f48b 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -19,15 +19,17 @@ package com.owncloud.android.utils; import java.io.File; +import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.lib.resources.files.RemoteFile; + import android.annotation.SuppressLint; import android.content.Context; import android.net.Uri; import android.os.Environment; import android.os.StatFs; -import android.util.Log; -import com.owncloud.android.R; -import com.owncloud.android.datamodel.OCFile; /** * Static methods to help in access to local file system. @@ -35,11 +37,11 @@ import com.owncloud.android.datamodel.OCFile; * @author David A. Velasco */ public class FileStorageUtils { - private static final String LOG_TAG = "FileStorageUtils"; + //private static final String LOG_TAG = "FileStorageUtils"; public static final String getSavePath(String accountName) { File sdCard = Environment.getExternalStorageDirectory(); - return sdCard.getAbsolutePath() + "/owncloud/" + Uri.encode(accountName, "@"); + return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + Uri.encode(accountName, "@"); // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B } @@ -49,7 +51,7 @@ public class FileStorageUtils { public static final String getTemporalPath(String accountName) { File sdCard = Environment.getExternalStorageDirectory(); - return sdCard.getAbsolutePath() + "/owncloud/tmp/" + Uri.encode(accountName, "@"); + return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/tmp/" + Uri.encode(accountName, "@"); // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B } @@ -67,7 +69,7 @@ public class FileStorageUtils { } public static final String getLogPath() { - return Environment.getExternalStorageDirectory() + File.separator + "owncloud" + File.separator + "log"; + return Environment.getExternalStorageDirectory() + File.separator + MainApp.getDataFolder() + File.separator + "log"; } public static String getInstantUploadFilePath(Context context, String fileName) { @@ -75,4 +77,44 @@ public class FileStorageUtils { String value = uploadPath + OCFile.PATH_SEPARATOR + (fileName == null ? "" : fileName); return value; } + + public static String getParentPath(String remotePath) { + String parentPath = new File(remotePath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + return parentPath; + } + + /** + * 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. + */ + public static 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()); + + return file; + } + + /** + * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}. + * + * @param oCFile OCFile + * @return New RemoteFile instance representing the resource described by ocFile. + */ + public static RemoteFile fillRemoteFile(OCFile ocFile){ + RemoteFile file = new RemoteFile(ocFile.getRemotePath()); + file.setCreationTimestamp(ocFile.getCreationTimestamp()); + file.setLength(ocFile.getFileLength()); + file.setMimeType(ocFile.getMimetype()); + file.setModifiedTimestamp(ocFile.getModificationTimestamp()); + file.setEtag(ocFile.getEtag()); + return file; + } + } \ No newline at end of file diff --git a/src/com/owncloud/android/utils/Log_OC.java b/src/com/owncloud/android/utils/Log_OC.java new file mode 100644 index 00000000..098625ad --- /dev/null +++ b/src/com/owncloud/android/utils/Log_OC.java @@ -0,0 +1,129 @@ +package com.owncloud.android.utils; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import com.owncloud.android.MainApp; + +import android.util.Log; + + + +public class Log_OC { + + + private static boolean isEnabled = false; + private static File logFile; + private static File folder; + private static BufferedWriter buf; + + public static void i(String TAG, String message){ + // Printing the message to LogCat console + int a = Log.i(TAG, message); + // Write the log message to the file + appendLog(TAG+" : "+message); + } + + public static void d(String TAG, String message){ + Log.d(TAG, message); + appendLog(TAG + " : " + message); + } + public static void d(String TAG, String message, Exception e) { + Log.d(TAG, message, e); + appendLog(TAG + " : " + message + " Exception : "+ e.getStackTrace()); + } + public static void e(String TAG, String message){ + Log.e(TAG, message); + appendLog(TAG + " : " + message); + } + + public static void e(String TAG, String message, Throwable e) { + Log.e(TAG, message, e); + appendLog(TAG+" : " + message +" Exception : " + e.getStackTrace()); + } + + public static void v(String TAG, String message){ + Log.v(TAG, message); + appendLog(TAG+" : "+ message); + } + + public static void w(String TAG, String message) { + Log.w(TAG,message); + appendLog(TAG+" : "+ message); + } + + public static void wtf(String TAG, String message) { + Log.wtf(TAG,message); + appendLog(TAG+" : "+ message); + } + + public static void startLogging(String logPath) { + folder = new File(logPath); + logFile = new File(folder + File.separator + "log.txt"); + + if (!folder.exists()) { + folder.mkdirs(); + } + if (logFile.exists()) { + logFile.delete(); + } + try { + logFile.createNewFile(); + buf = new BufferedWriter(new FileWriter(logFile, true)); + isEnabled = true; + appendPhoneInfo(); + }catch (IOException e){ + e.printStackTrace(); + } + } + + public static void stopLogging() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()); + String currentDateandTime = sdf.format(new Date()); + if (logFile != null) { + logFile.renameTo(new File(folder + File.separator + MainApp.getLogName() + currentDateandTime+".log")); + + isEnabled = false; + try { + buf.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + } + + private static void appendPhoneInfo() { + appendLog("Model : " + android.os.Build.MODEL); + appendLog("Brand : " + android.os.Build.BRAND); + appendLog("Product : " + android.os.Build.PRODUCT); + appendLog("Device : " + android.os.Build.DEVICE); + appendLog("Version-Codename : " + android.os.Build.VERSION.CODENAME); + appendLog("Version-Release : " + android.os.Build.VERSION.RELEASE); + } + + private static void appendLog(String text) { + if (isEnabled) { + try { + buf.append(text); + buf.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} + + + + + + + + +} diff --git a/src/com/owncloud/android/utils/NotificationBuilderWithProgressBar.java b/src/com/owncloud/android/utils/NotificationBuilderWithProgressBar.java new file mode 100644 index 00000000..c47e469e --- /dev/null +++ b/src/com/owncloud/android/utils/NotificationBuilderWithProgressBar.java @@ -0,0 +1,131 @@ +/* ownCloud Android client application + * Copyright (C) 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 . + * + */ + +package com.owncloud.android.utils; + +import com.owncloud.android.R; + +import android.app.Notification; +import android.content.Context; +import android.os.Build; +import android.support.v4.app.NotificationCompat; +import android.view.View; +import android.widget.RemoteViews; + +/** + * Extends the support class {@link NotificationCompat.Builder} to grant that + * a progress bar is available in every Android version, because + * {@link NotificationCompat.Builder#setProgress(int, int, boolean)} has no + * real effect for Android < 4.0 + * + * @author David A. Velasco + */ +public class NotificationBuilderWithProgressBar extends NotificationCompat.Builder { + + /** + * Custom view to replace the original layout of the notifications + */ + private RemoteViews mContentView = null; + + /** + * Fatory method. + * + * Instances of this class will be only returned in Android versions needing it. + * + * @param context Context that will use the builder to create notifications + * @return An instance of this class, or of the regular + * {@link NotificationCompat.Builder}, when it is good enough. + */ + public static NotificationCompat.Builder newNotificationBuilderWithProgressBar(Context context) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + return new NotificationBuilderWithProgressBar(context); + } else { + return new NotificationCompat.Builder(context); + } + } + + /** + * Constructor. + * + * @param context Context that will use the builder to create notifications. + */ + private NotificationBuilderWithProgressBar(Context context) { + super(context); + mContentView = new RemoteViews(context.getPackageName(), R.layout.notification_with_progress_bar); + setContent(mContentView); + } + + /** + * {@inheritDoc} + */ + @Override + public NotificationCompat.Builder setProgress(int max, int progress, boolean indeterminate) { + mContentView.setProgressBar(R.id.progress, max, progress, indeterminate); + if (max > 0) { + mContentView.setViewVisibility(R.id.progressHolder, View.VISIBLE); + } else { + mContentView.setViewVisibility(R.id.progressHolder, View.GONE); + } + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public NotificationCompat.Builder setSmallIcon(int icon) { + super.setSmallIcon(icon); // necessary + mContentView.setImageViewResource(R.id.icon, icon); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public NotificationCompat.Builder setContentTitle(CharSequence title) { + super.setContentTitle(title); + mContentView.setTextViewText(R.id.title, title); + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public NotificationCompat.Builder setContentText(CharSequence text) { + super.setContentText(text); + mContentView.setTextViewText(R.id.text, text); + if (text != null && text.length() > 0) { + mContentView.setViewVisibility(R.id.text, View.VISIBLE); + } else { + mContentView.setViewVisibility(R.id.text, View.GONE); + } + return this; + } + + @Override + public Notification build() { + Notification result = super.build(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + // super.build() in Android 2.x totally ruins whatever was made #setContent + result.contentView = mContentView; + } + return result; + } + +} diff --git a/src/com/owncloud/android/utils/OwnCloudSession.java b/src/com/owncloud/android/utils/OwnCloudSession.java new file mode 100644 index 00000000..13ead88b --- /dev/null +++ b/src/com/owncloud/android/utils/OwnCloudSession.java @@ -0,0 +1,56 @@ +/* ownCloud Android client application + * Copyright (C) 2011 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 . + * + */ +package com.owncloud.android.utils; + +/** + * Represents a session to an ownCloud instance + * + * @author Bartek Przybylski + * + */ +public class OwnCloudSession { + private String mSessionName; + private String mSessionUrl; + private int mEntryId; + + public OwnCloudSession(String name, String url, int entryId) { + mSessionName = name; + mSessionUrl = url; + mEntryId = entryId; + } + + public void setName(String name) { + mSessionName = name; + } + + public String getName() { + return mSessionName; + } + + public void setUrl(String url) { + mSessionUrl = url; + } + + public String getUrl() { + return mSessionUrl; + } + + public int getEntryId() { + return mEntryId; + } +} diff --git a/src/com/owncloud/android/utils/OwnCloudVersion.java b/src/com/owncloud/android/utils/OwnCloudVersion.java deleted file mode 100644 index 630e7acd..00000000 --- a/src/com/owncloud/android/utils/OwnCloudVersion.java +++ /dev/null @@ -1,85 +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 . - * - */ - -package com.owncloud.android.utils; - -public class OwnCloudVersion implements Comparable { - public static final OwnCloudVersion owncloud_v1 = new OwnCloudVersion( - 0x010000); - public static final OwnCloudVersion owncloud_v2 = new OwnCloudVersion( - 0x020000); - public static final OwnCloudVersion owncloud_v3 = new OwnCloudVersion( - 0x030000); - public static final OwnCloudVersion owncloud_v4 = new OwnCloudVersion( - 0x040000); - public static final OwnCloudVersion owncloud_v4_5 = new OwnCloudVersion( - 0x040500); - - // format is in version - // 0xAABBCC - // for version AA.BB.CC - // ie version 2.0.3 will be stored as 0x030003 - private int mVersion; - private boolean mIsValid; - - public OwnCloudVersion(int version) { - mVersion = version; - mIsValid = true; - } - - public OwnCloudVersion(String version) { - mVersion = 0; - mIsValid = false; - parseVersionString(version); - } - - public String toString() { - return ((mVersion >> 16) % 256) + "." + ((mVersion >> 8) % 256) + "." - + ((mVersion) % 256); - } - - public boolean isVersionValid() { - return mIsValid; - } - - @Override - public int compareTo(OwnCloudVersion another) { - return another.mVersion == mVersion ? 0 - : another.mVersion < mVersion ? 1 : -1; - } - - private void parseVersionString(String version) { - try { - String[] nums = version.split("\\."); - if (nums.length > 0) { - mVersion += Integer.parseInt(nums[0]); - } - mVersion = mVersion << 8; - if (nums.length > 1) { - mVersion += Integer.parseInt(nums[1]); - } - mVersion = mVersion << 8; - if (nums.length > 2) { - mVersion += Integer.parseInt(nums[2]); - } - mIsValid = true; - } catch (Exception e) { - mIsValid = false; - } - } -} diff --git a/src/com/owncloud/android/widgets/ActionEditText.java b/src/com/owncloud/android/widgets/ActionEditText.java index efa6655b..1077d154 100644 --- a/src/com/owncloud/android/widgets/ActionEditText.java +++ b/src/com/owncloud/android/widgets/ActionEditText.java @@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import com.owncloud.android.R; + import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; diff --git a/src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java b/src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java deleted file mode 100644 index 3f453968..00000000 --- a/src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java +++ /dev/null @@ -1,143 +0,0 @@ -/* ownCloud Android client application - * 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 . - * - */ - -package eu.alefzero.webdav; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import org.apache.commons.httpclient.methods.RequestEntity; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.network.ProgressiveDataTransferer; - -import eu.alefzero.webdav.OnDatatransferProgressListener; - - -/** - * A RequestEntity that represents a PIECE of a file. - * - * @author David A. Velasco - */ -public class ChunkFromFileChannelRequestEntity implements RequestEntity, ProgressiveDataTransferer { - - private static final String TAG = ChunkFromFileChannelRequestEntity.class.getSimpleName(); - - //private final File mFile; - private final FileChannel mChannel; - private final String mContentType; - private final long mChunkSize; - private final File mFile; - private long mOffset; - private long mTransferred; - Set mDataTransferListeners = new HashSet(); - private ByteBuffer mBuffer = ByteBuffer.allocate(4096); - - public ChunkFromFileChannelRequestEntity(final FileChannel channel, final String contentType, long chunkSize, final File file) { - super(); - if (channel == null) { - throw new IllegalArgumentException("File may not be null"); - } - if (chunkSize <= 0) { - throw new IllegalArgumentException("Chunk size must be greater than zero"); - } - mChannel = channel; - mContentType = contentType; - mChunkSize = chunkSize; - mFile = file; - mOffset = 0; - mTransferred = 0; - } - - public void setOffset(long offset) { - mOffset = offset; - } - - public long getContentLength() { - try { - return Math.min(mChunkSize, mChannel.size() - mChannel.position()); - } catch (IOException e) { - return mChunkSize; - } - } - - public String getContentType() { - return mContentType; - } - - public boolean isRepeatable() { - return true; - } - - @Override - public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - } - - @Override - public void addDatatransferProgressListeners(Collection listeners) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.addAll(listeners); - } - } - - @Override - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - } - - - public void writeRequest(final OutputStream out) throws IOException { - int readCount = 0; - Iterator it = null; - - try { - mChannel.position(mOffset); - long size = mFile.length(); - if (size == 0) size = -1; - while (mChannel.position() < mOffset + mChunkSize && mChannel.position() < mChannel.size()) { - readCount = mChannel.read(mBuffer); - out.write(mBuffer.array(), 0, readCount); - mBuffer.clear(); - mTransferred += readCount; - synchronized (mDataTransferListeners) { - it = mDataTransferListeners.iterator(); - while (it.hasNext()) { - it.next().onTransferProgress(readCount, mTransferred, size, mFile.getName()); - } - } - } - - } catch (IOException io) { - Log_OC.e(TAG, io.getMessage()); - throw new RuntimeException("Ugly solution to workaround the default policy of retries when the server falls while uploading ; temporal fix; really", io); - - } - } - -} \ No newline at end of file diff --git a/src/eu/alefzero/webdav/FileRequestEntity.java b/src/eu/alefzero/webdav/FileRequestEntity.java deleted file mode 100644 index a8f01d63..00000000 --- a/src/eu/alefzero/webdav/FileRequestEntity.java +++ /dev/null @@ -1,133 +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 . - * - */ - -package eu.alefzero.webdav; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import org.apache.commons.httpclient.methods.RequestEntity; - -import com.owncloud.android.Log_OC; -import com.owncloud.android.network.ProgressiveDataTransferer; - -import eu.alefzero.webdav.OnDatatransferProgressListener; - - -/** - * A RequestEntity that represents a File. - * - */ -public class FileRequestEntity implements RequestEntity, ProgressiveDataTransferer { - - final File mFile; - final String mContentType; - Set mDataTransferListeners = new HashSet(); - - public FileRequestEntity(final File file, final String contentType) { - super(); - this.mFile = file; - this.mContentType = contentType; - if (file == null) { - throw new IllegalArgumentException("File may not be null"); - } - } - - @Override - public long getContentLength() { - return mFile.length(); - } - - @Override - public String getContentType() { - return mContentType; - } - - @Override - public boolean isRepeatable() { - return true; - } - - @Override - public void addDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.add(listener); - } - } - - @Override - public void addDatatransferProgressListeners(Collection listeners) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.addAll(listeners); - } - } - - @Override - public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) { - synchronized (mDataTransferListeners) { - mDataTransferListeners.remove(listener); - } - } - - - @Override - public void writeRequest(final OutputStream out) throws IOException { - //byte[] tmp = new byte[4096]; - ByteBuffer tmp = ByteBuffer.allocate(4096); - int readResult = 0; - - // TODO(bprzybylski): each mem allocation can throw OutOfMemoryError we need to handle it - // globally in some fashionable manner - RandomAccessFile raf = new RandomAccessFile(mFile, "r"); - FileChannel channel = raf.getChannel(); - Iterator it = null; - long transferred = 0; - long size = mFile.length(); - if (size == 0) size = -1; - try { - while ((readResult = channel.read(tmp)) >= 0) { - out.write(tmp.array(), 0, readResult); - tmp.clear(); - transferred += readResult; - synchronized (mDataTransferListeners) { - it = mDataTransferListeners.iterator(); - while (it.hasNext()) { - it.next().onTransferProgress(readResult, transferred, size, mFile.getName()); - } - } - } - - } catch (IOException io) { - Log_OC.e("FileRequestException", io.getMessage()); - throw new RuntimeException("Ugly solution to workaround the default policy of retries when the server falls while uploading ; temporal fix; really", io); - - } finally { - channel.close(); - raf.close(); - } - } - -} diff --git a/src/eu/alefzero/webdav/OnDatatransferProgressListener.java b/src/eu/alefzero/webdav/OnDatatransferProgressListener.java deleted file mode 100644 index 5c4783a1..00000000 --- a/src/eu/alefzero/webdav/OnDatatransferProgressListener.java +++ /dev/null @@ -1,24 +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 . - * - */ - -package eu.alefzero.webdav; - -public interface OnDatatransferProgressListener { - public void onTransferProgress(long progressRate); - public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName); -} diff --git a/src/eu/alefzero/webdav/WebdavClient.java b/src/eu/alefzero/webdav/WebdavClient.java deleted file mode 100644 index 9830f668..00000000 --- a/src/eu/alefzero/webdav/WebdavClient.java +++ /dev/null @@ -1,341 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 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 . - * - */ -package eu.alefzero.webdav; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import org.apache.commons.httpclient.Credentials; -import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.HttpException; -import org.apache.commons.httpclient.HttpMethodBase; -import org.apache.commons.httpclient.HttpVersion; -import org.apache.commons.httpclient.UsernamePasswordCredentials; -import org.apache.commons.httpclient.auth.AuthScope; -import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.HeadMethod; -import org.apache.commons.httpclient.methods.PutMethod; -import org.apache.commons.httpclient.params.HttpMethodParams; -import org.apache.http.HttpStatus; -import org.apache.http.params.CoreProtocolPNames; -import org.apache.jackrabbit.webdav.client.methods.DavMethod; -import org.apache.jackrabbit.webdav.client.methods.DeleteMethod; -import org.apache.jackrabbit.webdav.client.methods.MkColMethod; - -import com.owncloud.android.Log_OC; - -import android.net.Uri; -import android.util.Log; - -public class WebdavClient extends HttpClient { - private Uri mUri; - private Credentials mCredentials; - final private static String TAG = "WebdavClient"; - private static final String USER_AGENT = "Android-ownCloud"; - - private OnDatatransferProgressListener mDataTransferListener; - static private byte[] sExhaustBuffer = new byte[1024]; - - /** - * Constructor - */ - public WebdavClient(HttpConnectionManager connectionMgr) { - super(connectionMgr); - Log_OC.d(TAG, "Creating WebdavClient"); - getParams().setParameter(HttpMethodParams.USER_AGENT, USER_AGENT); - getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); - } - - public void setCredentials(String username, String password) { - getParams().setAuthenticationPreemptive(true); - getState().setCredentials(AuthScope.ANY, - getCredentials(username, password)); - } - - private Credentials getCredentials(String username, String password) { - if (mCredentials == null) - mCredentials = new UsernamePasswordCredentials(username, password); - return mCredentials; - } - - /** - * Downloads a file in remoteFilepath to the local targetPath. - * - * @param remoteFilepath Path to the file in the remote server, URL DECODED. - * @param targetFile Local path to save the downloaded file. - * @return 'True' when the file is successfully downloaded. - */ - public boolean downloadFile(String remoteFilePath, File targetFile) { - boolean ret = false; - GetMethod get = new GetMethod(mUri.toString() + WebdavUtils.encodePath(remoteFilePath)); - - try { - int status = executeMethod(get); - if (status == HttpStatus.SC_OK) { - targetFile.createNewFile(); - BufferedInputStream bis = new BufferedInputStream( - get.getResponseBodyAsStream()); - FileOutputStream fos = new FileOutputStream(targetFile); - - byte[] bytes = new byte[4096]; - int readResult; - while ((readResult = bis.read(bytes)) != -1) { - if (mDataTransferListener != null) - mDataTransferListener.onTransferProgress(readResult); - fos.write(bytes, 0, readResult); - } - fos.close(); - ret = true; - } else { - exhaustResponse(get.getResponseBodyAsStream()); - } - Log_OC.e(TAG, "Download of " + remoteFilePath + " to " + targetFile + " finished with HTTP status " + status + (!ret?"(FAIL)":"")); - - } catch (Exception e) { - logException(e, "dowloading " + remoteFilePath); - - } finally { - if (!ret && targetFile.exists()) { - targetFile.delete(); - } - get.releaseConnection(); // let the connection available for other methods - } - return ret; - } - - /** - * Deletes a remote file via webdav - * @param remoteFilePath Remote file path of the file to delete, in URL DECODED format. - * @return - */ - public boolean deleteFile(String remoteFilePath) { - boolean ret = false; - DavMethod delete = new DeleteMethod(mUri.toString() + WebdavUtils.encodePath(remoteFilePath)); - try { - int status = executeMethod(delete); - ret = (status == HttpStatus.SC_OK || status == HttpStatus.SC_ACCEPTED || status == HttpStatus.SC_NO_CONTENT); - exhaustResponse(delete.getResponseBodyAsStream()); - - Log_OC.e(TAG, "DELETE of " + remoteFilePath + " finished with HTTP status " + status + (!ret?"(FAIL)":"")); - - } catch (Exception e) { - logException(e, "deleting " + remoteFilePath); - - } finally { - delete.releaseConnection(); // let the connection available for other methods - } - return ret; - } - - - public void setDataTransferProgressListener(OnDatatransferProgressListener listener) { - mDataTransferListener = listener; - } - - /** - * Creates or update a file in the remote server with the contents of a local file. - * - * @param localFile Path to the local file to upload. - * @param remoteTarget Remote path to the file to create or update, URL DECODED - * @param contentType MIME type of the file. - * @return Status HTTP code returned by the server. - * @throws IOException When a transport error that could not be recovered occurred while uploading the file to the server. - * @throws HttpException When a violation of the HTTP protocol occurred. - */ - public int putFile(String localFile, String remoteTarget, String contentType) throws HttpException, IOException { - int status = -1; - PutMethod put = new PutMethod(mUri.toString() + WebdavUtils.encodePath(remoteTarget)); - - try { - File f = new File(localFile); - FileRequestEntity entity = new FileRequestEntity(f, contentType); - entity.addDatatransferProgressListener(mDataTransferListener); - put.setRequestEntity(entity); - status = executeMethod(put); - - exhaustResponse(put.getResponseBodyAsStream()); - - } finally { - put.releaseConnection(); // let the connection available for other methods - } - return status; - } - - /** - * Tries to log in to the current URI, with the current credentials - * - * @return A {@link HttpStatus}-Code of the result. SC_OK is good. - */ - public int tryToLogin() { - int status = 0; - HeadMethod head = new HeadMethod(mUri.toString()); - try { - status = executeMethod(head); - boolean result = status == HttpStatus.SC_OK; - Log_OC.d(TAG, "HEAD for " + mUri + " finished with HTTP status " + status + (!result?"(FAIL)":"")); - exhaustResponse(head.getResponseBodyAsStream()); - - } catch (Exception e) { - logException(e, "trying to login at " + mUri.toString()); - - } finally { - head.releaseConnection(); - } - return status; - } - - /** - * Creates a remote directory with the received path. - * - * @param path Path of the directory to create, URL DECODED - * @return 'True' when the directory is successfully created - */ - public boolean createDirectory(String path) { - boolean result = false; - int status = -1; - MkColMethod mkcol = new MkColMethod(mUri.toString() + WebdavUtils.encodePath(path)); - try { - Log_OC.d(TAG, "Creating directory " + path); - status = executeMethod(mkcol); - Log_OC.d(TAG, "Status returned: " + status); - result = mkcol.succeeded(); - - Log_OC.d(TAG, "MKCOL to " + path + " finished with HTTP status " + status + (!result?"(FAIL)":"")); - exhaustResponse(mkcol.getResponseBodyAsStream()); - - } catch (Exception e) { - logException(e, "creating directory " + path); - - } finally { - mkcol.releaseConnection(); // let the connection available for other methods - } - return result; - } - - - /** - * Check if a file exists in the OC server - * - * @return 'true' if the file exists; 'false' it doesn't exist - * @throws Exception When the existence could not be determined - */ - public boolean existsFile(String path) throws IOException, HttpException { - HeadMethod head = new HeadMethod(mUri.toString() + WebdavUtils.encodePath(path)); - try { - int status = executeMethod(head); - Log_OC.d(TAG, "HEAD to " + path + " finished with HTTP status " + status + ((status != HttpStatus.SC_OK)?"(FAIL)":"")); - exhaustResponse(head.getResponseBodyAsStream()); - return (status == HttpStatus.SC_OK); - - } finally { - head.releaseConnection(); // let the connection available for other methods - } - } - - /** - * Requests the received method with the received timeout (milliseconds). - * - * Executes the method through the inherited HttpClient.executedMethod(method). - * - * Sets the socket and connection timeouts only for the method received. - * - * The timeouts are both in milliseconds; 0 means 'infinite'; < 0 means 'do not change the default' - * - * @param method HTTP method request. - * @param readTimeout Timeout to set for data reception - * @param conntionTimout Timeout to set for connection establishment - */ - public int executeMethod(HttpMethodBase method, int readTimeout, int connectionTimeout) throws HttpException, IOException { - int oldSoTimeout = getParams().getSoTimeout(); - int oldConnectionTimeout = getHttpConnectionManager().getParams().getConnectionTimeout(); - try { - if (readTimeout >= 0) { - method.getParams().setSoTimeout(readTimeout); // this should be enough... - getParams().setSoTimeout(readTimeout); // ... but this looks like necessary for HTTPS - } - if (connectionTimeout >= 0) { - getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout); - } - return executeMethod(method); - } finally { - getParams().setSoTimeout(oldSoTimeout); - getHttpConnectionManager().getParams().setConnectionTimeout(oldConnectionTimeout); - } - } - - /** - * Exhausts a not interesting HTTP response. Encouraged by HttpClient documentation. - * - * @param responseBodyAsStream InputStream with the HTTP response to exhaust. - */ - public void exhaustResponse(InputStream responseBodyAsStream) { - if (responseBodyAsStream != null) { - try { - while (responseBodyAsStream.read(sExhaustBuffer) >= 0); - responseBodyAsStream.close(); - - } catch (IOException io) { - Log_OC.e(TAG, "Unexpected exception while exhausting not interesting HTTP response; will be IGNORED", io); - } - } - } - - /** - * Logs an exception triggered in a HTTP request. - * - * @param e Caught exception. - * @param doing Suffix to add at the end of the logged message. - */ - private void logException(Exception e, String doing) { - if (e instanceof HttpException) { - Log_OC.e(TAG, "HTTP violation while " + doing, e); - - } else if (e instanceof IOException) { - Log_OC.e(TAG, "Unrecovered transport exception while " + doing, e); - - } else { - Log_OC.e(TAG, "Unexpected exception while " + doing, e); - } - } - - - /** - * Sets the connection and wait-for-data timeouts to be applied by default to the methods performed by this client. - */ - public void setDefaultTimeouts(int defaultDataTimeout, int defaultConnectionTimeout) { - getParams().setSoTimeout(defaultDataTimeout); - getHttpConnectionManager().getParams().setConnectionTimeout(defaultConnectionTimeout); - } - - /** - * Sets the base URI for the helper methods that receive paths as parameters, instead of full URLs - * @param uri - */ - public void setBaseUri(Uri uri) { - mUri = uri; - } - - public Uri getBaseUri() { - return mUri; - } - -} diff --git a/src/eu/alefzero/webdav/WebdavEntry.java b/src/eu/alefzero/webdav/WebdavEntry.java deleted file mode 100644 index 450ca6bc..00000000 --- a/src/eu/alefzero/webdav/WebdavEntry.java +++ /dev/null @@ -1,138 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 ownCloud - * - * 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 . - * - */ -package eu.alefzero.webdav; - -import java.util.Date; - -import org.apache.jackrabbit.webdav.MultiStatusResponse; -import org.apache.jackrabbit.webdav.property.DavProperty; -import org.apache.jackrabbit.webdav.property.DavPropertyName; -import org.apache.jackrabbit.webdav.property.DavPropertySet; - -import com.owncloud.android.Log_OC; - -import android.net.Uri; -import android.util.Log; - -public class WebdavEntry { - private String mName, mPath, mUri, mContentType; - private long mContentLength, mCreateTimestamp, mModifiedTimestamp; - - public WebdavEntry(MultiStatusResponse ms, String splitElement) { - resetData(); - if (ms.getStatus().length != 0) { - mUri = ms.getHref(); - - mPath = mUri.split(splitElement, 2)[1]; - - int status = ms.getStatus()[0].getStatusCode(); - DavPropertySet propSet = ms.getProperties(status); - @SuppressWarnings("rawtypes") - DavProperty prop = propSet.get(DavPropertyName.DISPLAYNAME); - if (prop != null) - mName = (String) prop.getName().toString(); - else { - String[] tmp = mPath.split("/"); - if (tmp.length > 0) - mName = tmp[tmp.length - 1]; - } - - // use unknown mimetype as default behavior - mContentType = "application/octet-stream"; - prop = propSet.get(DavPropertyName.GETCONTENTTYPE); - if (prop != null) { - mContentType = (String) prop.getValue(); - // dvelasco: some builds of ownCloud server 4.0.x added a trailing ';' to the MIME type ; if looks fixed, but let's be cautious - if (mContentType.indexOf(";") >= 0) { - mContentType = mContentType.substring(0, mContentType.indexOf(";")); - } - } - - // check if it's a folder in the standard way: see RFC2518 12.2 . RFC4918 14.3 - prop = propSet.get(DavPropertyName.RESOURCETYPE); - if (prop!= null) { - Object value = prop.getValue(); - if (value != null) { - mContentType = "DIR"; // a specific attribute would be better, but this is enough; unless while we have no reason to distinguish MIME types for folders - } - } - - prop = propSet.get(DavPropertyName.GETCONTENTLENGTH); - if (prop != null) - mContentLength = Long.parseLong((String) prop.getValue()); - - prop = propSet.get(DavPropertyName.GETLASTMODIFIED); - if (prop != null) { - Date d = WebdavUtils - .parseResponseDate((String) prop.getValue()); - mModifiedTimestamp = (d != null) ? d.getTime() : 0; - } - - prop = propSet.get(DavPropertyName.CREATIONDATE); - if (prop != null) { - Date d = WebdavUtils - .parseResponseDate((String) prop.getValue()); - mCreateTimestamp = (d != null) ? d.getTime() : 0; - } - - } else { - Log_OC.e("WebdavEntry", - "General fuckup, no status for webdav response"); - } - } - - public String path() { - return mPath; - } - - public String decodedPath() { - return Uri.decode(mPath); - } - - public String name() { - return mName; - } - - public boolean isDirectory() { - return mContentType.equals("DIR"); - } - - public String contentType() { - return mContentType; - } - - public String uri() { - return mUri; - } - - public long contentLength() { - return mContentLength; - } - - public long createTimestamp() { - return mCreateTimestamp; - } - - public long modifiedTimestamp() { - return mModifiedTimestamp; - } - - private void resetData() { - mName = mUri = mContentType = null; - mContentLength = mCreateTimestamp = mModifiedTimestamp = 0; - } -} diff --git a/src/eu/alefzero/webdav/WebdavUtils.java b/src/eu/alefzero/webdav/WebdavUtils.java deleted file mode 100644 index 132a1f92..00000000 --- a/src/eu/alefzero/webdav/WebdavUtils.java +++ /dev/null @@ -1,76 +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 . - * - */ - -package eu.alefzero.webdav; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -import android.net.Uri; - -public class WebdavUtils { - public static final SimpleDateFormat DISPLAY_DATE_FORMAT = new SimpleDateFormat( - "dd.MM.yyyy hh:mm"); - private static final SimpleDateFormat DATETIME_FORMATS[] = { - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US), - new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sss'Z'", Locale.US), - new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US), - new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US), - new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), - new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) }; - - public static String prepareXmlForPropFind() { - String ret = ""; - return ret; - } - - public static String prepareXmlForPatch() { - return ""; - } - - public static Date parseResponseDate(String date) { - Date returnDate = null; - for (int i = 0; i < DATETIME_FORMATS.length; ++i) { - try { - returnDate = DATETIME_FORMATS[i].parse(date); - return returnDate; - } catch (ParseException e) { - } - } - return null; - } - - /** - * Encodes a path according to URI RFC 2396. - * - * If the received path doesn't start with "/", the method adds it. - * - * @param remoteFilePath Path - * @return Encoded path according to RFC 2396, always starting with "/" - */ - public static String encodePath(String remoteFilePath) { - String encodedPath = Uri.encode(remoteFilePath, "/"); - if (!encodedPath.startsWith("/")) - encodedPath = "/" + encodedPath; - return encodedPath; - } - -} diff --git a/tests/.classpath b/tests/.classpath index 4abbae7c..9b141f6f 100644 --- a/tests/.classpath +++ b/tests/.classpath @@ -1,10 +1,10 @@ - - - - + + + + diff --git a/tests/.settings/org.eclipse.jdt.core.prefs b/tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..877df2d9 --- /dev/null +++ b/tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,16 @@ +eclipse.preferences.version=1 +<<<<<<< HEAD +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +======= +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +>>>>>>> develop +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml index 8c613c20..025e6bd9 100644 --- a/tests/AndroidManifest.xml +++ b/tests/AndroidManifest.xml @@ -11,11 +11,11 @@ + android:targetPackage="com.owncloud.android" + android:label="Tests for com.owncloud.android"/> diff --git a/tests/ant.properties b/tests/ant.properties new file mode 100644 index 00000000..16244024 --- /dev/null +++ b/tests/ant.properties @@ -0,0 +1,18 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked into Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + +tested.project.dir=.. diff --git a/tests/build.xml b/tests/build.xml new file mode 100644 index 00000000..3e821961 --- /dev/null +++ b/tests/build.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/project.properties b/tests/project.properties index 8937e94b..4ab12569 100644 --- a/tests/project.properties +++ b/tests/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-14 +target=android-19 diff --git a/tests/src/com/owncloud/android/test/AccountUtilsTest.java b/tests/src/com/owncloud/android/test/AccountUtilsTest.java index db504a80..fc6dc212 100644 --- a/tests/src/com/owncloud/android/test/AccountUtilsTest.java +++ b/tests/src/com/owncloud/android/test/AccountUtilsTest.java @@ -18,10 +18,11 @@ package com.owncloud.android.test; +import com.owncloud.android.lib.common.accounts.AccountUtils; +import com.owncloud.android.lib.resources.status.OwnCloudVersion; + import android.test.AndroidTestCase; -import com.owncloud.android.AccountUtils; -import com.owncloud.android.utils.OwnCloudVersion; public class AccountUtilsTest extends AndroidTestCase { @@ -34,14 +35,21 @@ public class AccountUtilsTest extends AndroidTestCase { OwnCloudVersion ocv45 = new OwnCloudVersion(0x040500); OwnCloudVersion ocv70 = new OwnCloudVersion(0x070000); - assertTrue(AccountUtils.getWebdavPath(ocv12).equals("/webdav/owncloud.php")); - assertTrue(AccountUtils.getWebdavPath(ocv12s).equals("/webdav/owncloud.php")); - assertTrue(AccountUtils.getWebdavPath(ocv22).equals("/files/webdav.php")); - assertTrue(AccountUtils.getWebdavPath(ocv30).equals("/files/webdav.php")); - assertTrue(AccountUtils.getWebdavPath(ocv33s).equals("/files/webdav.php")); - assertTrue(AccountUtils.getWebdavPath(ocv45).equals("/remote.php/webdav")); - assertTrue(AccountUtils.getWebdavPath(ocv70).equals("/remote.php/webdav")); - assertNull(AccountUtils.getWebdavPath(null)); + assertTrue(AccountUtils.getWebdavPath(ocv12, false, false).equals("/webdav/owncloud.php")); + assertTrue(AccountUtils.getWebdavPath(ocv12s, false, false).equals("/webdav/owncloud.php")); + assertTrue(AccountUtils.getWebdavPath(ocv22, false, false).equals("/files/webdav.php")); + assertTrue(AccountUtils.getWebdavPath(ocv30,false, false).equals("/files/webdav.php")); + assertTrue(AccountUtils.getWebdavPath(ocv33s, false, false).equals("/files/webdav.php")); + assertTrue(AccountUtils.getWebdavPath(ocv45, false, false).equals("/remote.php/webdav")); + assertTrue(AccountUtils.getWebdavPath(ocv70, false, false).equals("/remote.php/webdav")); + assertNull(AccountUtils.getWebdavPath(null, false, false)); + assertTrue(AccountUtils.getWebdavPath(ocv12, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv12s, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv22, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv30, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv33s, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv45, true, false).equals("/remote.php/odav")); + assertTrue(AccountUtils.getWebdavPath(ocv70, true, false).equals("/remote.php/odav")); OwnCloudVersion invalidVer = new OwnCloudVersion("a.b.c"); assertFalse(invalidVer.isVersionValid()); diff --git a/tests/src/com/owncloud/android/test/FileContentProviderTest.java b/tests/src/com/owncloud/android/test/FileContentProviderTest.java new file mode 100644 index 00000000..8f6fe81d --- /dev/null +++ b/tests/src/com/owncloud/android/test/FileContentProviderTest.java @@ -0,0 +1,53 @@ +package com.owncloud.android.test; + +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; +import com.owncloud.android.providers.FileContentProvider; +import android.annotation.TargetApi; +import android.net.Uri; +import android.os.Build; +import android.test.ProviderTestCase2; +import android.test.mock.MockContentResolver; +import android.util.Log; + +@TargetApi(Build.VERSION_CODES.CUPCAKE) +public class FileContentProviderTest extends ProviderTestCase2 { + + private static final String TAG = FileContentProvider.class.getName(); + + private static MockContentResolver resolve; + + public FileContentProviderTest(Class providerClass, + String providerAuthority) { + super(providerClass, providerAuthority); + // TODO Auto-generated constructor stub + } + + public FileContentProviderTest() { + super(FileContentProvider.class, "com.owncloud.android.providers.FileContentProvider"); + } + + @Override + public void setUp() { + Log.i(TAG, "Entered setup"); + try { + super.setUp(); + resolve = this.getMockContentResolver(); + } catch (Exception e) { + + } + } + + public void testGetTypeFile() { + Uri testuri = Uri.parse("content://org.owncloud/file/"); + assertEquals(ProviderTableMeta.CONTENT_TYPE_ITEM, resolve.getType(testuri)); + + testuri = Uri.parse("content://org.owncloud/file/123"); + assertEquals(ProviderTableMeta.CONTENT_TYPE_ITEM, resolve.getType(testuri)); + } + + public void testGetTypeRoot() { + Uri testuri = Uri.parse("content://org.owncloud/"); + assertEquals(ProviderTableMeta.CONTENT_TYPE, resolve.getType(testuri)); + } + +} diff --git a/third_party/android-support-library/android-support-v4.jar b/third_party/android-support-library/android-support-v4.jar index feaf44f8..96644edb 100644 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/third_party/transifex-client/.gitignore b/third_party/transifex-client/.gitignore deleted file mode 100644 index 72bd50ca..00000000 --- a/third_party/transifex-client/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.tx -*pyc -*pyo -*~ -*egg-info* diff --git a/third_party/transifex-client/DEVELOPMENT.rst b/third_party/transifex-client/DEVELOPMENT.rst deleted file mode 100644 index 992e518f..00000000 --- a/third_party/transifex-client/DEVELOPMENT.rst +++ /dev/null @@ -1,21 +0,0 @@ -Releasing -========= - -To create a new release: - -1. Update local rep and update the version in ``setup.py``:: - - $ hg pull -u - $ vim setup.py - -2. Test:: - - $ python setup.py clean sdist - $ cd dist - $ tar zxf ... - $ cd transifex-client - ...test - -3. Package and upload on PyPI:: - - $ python setup.py clean sdist bdist_egg upload diff --git a/third_party/transifex-client/LICENSE b/third_party/transifex-client/LICENSE deleted file mode 100644 index db860a38..00000000 --- a/third_party/transifex-client/LICENSE +++ /dev/null @@ -1,343 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software - interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Program does not specify a -version number of this License, you may choose any version ever -published by the Free Software Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these -terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - - 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, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. - diff --git a/third_party/transifex-client/MANIFEST.in b/third_party/transifex-client/MANIFEST.in deleted file mode 100644 index 83126ac6..00000000 --- a/third_party/transifex-client/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include tx - -# Docs -include LICENSE README.rst -recursive-include docs * - diff --git a/third_party/transifex-client/README.rst b/third_party/transifex-client/README.rst deleted file mode 100644 index bb887503..00000000 --- a/third_party/transifex-client/README.rst +++ /dev/null @@ -1,30 +0,0 @@ - -============================= - Transifex Command-Line Tool -============================= - -The Transifex Command-line Client is a command line tool that enables -you to easily manage your translations within a project without the need -of an elaborate UI system. - -You can use the command line client to easily create new resources, map -locale files to translations and synchronize your Transifex project with -your local repository and vice verca. Translators and localization -managers can also use it to handle large volumes of translation files -easily and without much hassle. - -Check the full documentation at -http://help.transifex.com/user-guide/client/ - - -Installing -========== - -You can install the latest version of transifex-client running ``pip -install transifex-client`` or ``easy_install transifex-client`` -You can also install the `in-development version`_ of transifex-client -with ``pip install transifex-client==dev`` or ``easy_install -transifex-client==dev``. - -.. _in-development version: http://code.transifex.com/transifex-client/ - diff --git a/third_party/transifex-client/setup.py b/third_party/transifex-client/setup.py deleted file mode 100755 index 626303d5..00000000 --- a/third_party/transifex-client/setup.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import os -import glob -from codecs import BOM - -from setuptools import setup, find_packages -from setuptools.command.build_py import build_py as _build_py - -from txclib import get_version - -readme_file = open(u'README.rst') -long_description = readme_file.read() -readme_file.close() -if long_description.startswith(BOM): - long_description = long_description.lstrip(BOM) -long_description = long_description.decode('utf-8') - -package_data = { - '': ['LICENSE', 'README.rst'], -} - -scripts = ['tx'] - -install_requires = [] -try: - import json -except ImportError: - install_requires.append('simplejson') - -setup( - name="transifex-client", - version=get_version(), - scripts=scripts, - description="A command line interface for Transifex", - long_description=long_description, - author="Transifex", - author_email="info@transifex.com", - url="https://www.transifex.com", - license="GPLv2", - dependency_links = [ - ], - setup_requires = [ - ], - install_requires = install_requires, - tests_require = ["mock", ], - data_files=[ - ], - test_suite="tests", - zip_safe=False, - packages=['txclib', ], - include_package_data=True, - package_data = package_data, - keywords = ('translation', 'localization', 'internationalization',), -) diff --git a/third_party/transifex-client/tests/__init__.py b/third_party/transifex-client/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/third_party/transifex-client/tests/test_processors.py b/third_party/transifex-client/tests/test_processors.py deleted file mode 100644 index dd7d7d95..00000000 --- a/third_party/transifex-client/tests/test_processors.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Unit tests for processor functions. -""" - -import unittest -from urlparse import urlparse -from txclib.processors import hostname_tld_migration, hostname_ssl_migration - - -class TestHostname(unittest.TestCase): - """Test for hostname processors.""" - - def test_tld_migration_needed(self): - """ - Test the tld migration of Transifex, when needed. - """ - hostnames = [ - 'http://transifex.net', 'http://www.transifex.net', - 'https://fedora.transifex.net', - ] - for h in hostnames: - hostname = hostname_tld_migration(h) - self.assertTrue(hostname.endswith('com')) - orig_hostname = 'http://www.transifex.net/path/' - hostname = hostname_tld_migration(orig_hostname) - self.assertEqual(hostname, orig_hostname.replace('net', 'com', 1)) - - def test_tld_migration_needed(self): - """ - Test that unneeded tld migrations are detected correctly. - """ - hostnames = [ - 'https://www.transifex.com', 'http://fedora.transifex.com', - 'http://www.example.net/path/' - ] - for h in hostnames: - hostname = hostname_tld_migration(h) - self.assertEqual(hostname, h) - - def test_no_scheme_specified(self): - """ - Test that, if no scheme has been specified, the https one will be used. - """ - hostname = '//transifex.net' - hostname = hostname_ssl_migration(hostname) - self.assertTrue(hostname.startswith('https://')) - - def test_http_replacement(self): - """Test the replacement of http with https.""" - hostnames = [ - 'http://transifex.com', 'http://transifex.net/http/', - 'http://www.transifex.com/path/' - ] - for h in hostnames: - hostname = hostname_ssl_migration(h) - self.assertEqual(hostname[:8], 'https://') - self.assertEqual(hostname[7:], h[6:]) - - def test_no_http_replacement_needed(self): - """Test that http will not be replaces with https, when not needed.""" - for h in ['http://example.com', 'http://example.com/http/']: - hostname = hostname_ssl_migration(h) - self.assertEqual(hostname, hostname) diff --git a/third_party/transifex-client/tests/test_project.py b/third_party/transifex-client/tests/test_project.py deleted file mode 100644 index 3b421214..00000000 --- a/third_party/transifex-client/tests/test_project.py +++ /dev/null @@ -1,531 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import with_statement -import unittest -import contextlib -import itertools -try: - import json -except ImportError: - import simplejson as json -from mock import Mock, patch - -from txclib.project import Project -from txclib.config import Flipdict - - -class TestProject(unittest.TestCase): - - def test_extract_fields(self): - """Test the functions that extract a field from a stats object.""" - stats = { - 'completed': '80%', - 'last_update': '00:00', - 'foo': 'bar', - } - self.assertEqual( - stats['completed'], '%s%%' % Project._extract_completed(stats) - ) - self.assertEqual(stats['last_update'], Project._extract_updated(stats)) - - def test_specifying_resources(self): - """Test the various ways to specify resources in a project.""" - p = Project(init=False) - resources = [ - 'proj1.res1', - 'proj2.res2', - 'transifex.txn', - 'transifex.txo', - ] - with patch.object(p, 'get_resource_list') as mock: - mock.return_value = resources - cmd_args = [ - 'proj1.res1', '*1*', 'transifex*', '*r*', - '*o', 'transifex.tx?', 'transifex.txn', - ] - results = [ - ['proj1.res1', ], - ['proj1.res1', ], - ['transifex.txn', 'transifex.txo', ], - ['proj1.res1', 'proj2.res2', 'transifex.txn', 'transifex.txo', ], - ['transifex.txo', ], - ['transifex.txn', 'transifex.txo', ], - ['transifex.txn', ], - [], - ] - - for i, arg in enumerate(cmd_args): - resources = [arg] - self.assertEqual(p.get_chosen_resources(resources), results[i]) - - # wrong argument - resources = ['*trasnifex*', ] - self.assertRaises(Exception, p.get_chosen_resources, resources) - - -class TestProjectMinimumPercent(unittest.TestCase): - """Test the minimum-perc option.""" - - def setUp(self): - super(TestProjectMinimumPercent, self).setUp() - self.p = Project(init=False) - self.p.minimum_perc = None - self.p.resource = "resource" - - def test_cmd_option(self): - """Test command-line option.""" - self.p.minimum_perc = 20 - results = itertools.cycle([80, 90 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '12%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '20%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '30%'})) - - def test_global_only(self): - """Test only global option.""" - results = itertools.cycle([80, None ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_local_lower_than_global(self): - """Test the case where the local option is lower than the global.""" - results = itertools.cycle([80, 70 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_local_higher_than_global(self): - """Test the case where the local option is lower than the global.""" - results = itertools.cycle([60, 70 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_local_only(self): - """Test the case where the local option is lower than the global.""" - results = itertools.cycle([None, 70 ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertFalse(self.p._satisfies_min_translated({'completed': '60%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '70%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '80%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - def test_no_option(self): - """"Test the case there is nothing defined.""" - results = itertools.cycle([None, None ]) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "get_resource_option") as mock: - mock.side_effect = side_effect - self.assertTrue(self.p._satisfies_min_translated({'completed': '0%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '10%'})) - self.assertTrue(self.p._satisfies_min_translated({'completed': '90%'})) - - -class TestProjectFilters(unittest.TestCase): - """Test filters used to decide whether to push/pull a translation or not.""" - - def setUp(self): - super(TestProjectFilters, self).setUp() - self.p = Project(init=False) - self.p.minimum_perc = None - self.p.resource = "resource" - self.stats = { - 'en': { - 'completed': '100%', 'last_update': '2011-11-01 15:00:00', - }, 'el': { - 'completed': '60%', 'last_update': '2011-11-01 15:00:00', - }, 'pt': { - 'completed': '70%', 'last_update': '2011-11-01 15:00:00', - }, - } - self.langs = self.stats.keys() - - def test_add_translation(self): - """Test filters for adding translations. - - We do not test here for minimum percentages. - """ - with patch.object(self.p, "get_resource_option") as mock: - mock.return_value = None - should_add = self.p._should_add_translation - for force in [True, False]: - for lang in self.langs: - self.assertTrue(should_add(lang, self.stats, force)) - - # unknown language - self.assertFalse(should_add('es', self.stats)) - - def test_update_translation(self): - """Test filters for updating a translation. - - We do not test here for minimum percentages. - """ - with patch.object(self.p, "get_resource_option") as mock: - mock.return_value = None - - should_update = self.p._should_update_translation - force = True - for lang in self.langs: - self.assertTrue(should_update(lang, self.stats, 'foo', force)) - - force = False # reminder - local_file = 'foo' - - # unknown language - self.assertFalse(should_update('es', self.stats, local_file)) - - # no local file - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.return_value = None - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertTrue( - should_update(lang, self.stats, local_file) - ) - - # older local files - local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertTrue( - should_update(lang, self.stats, local_file) - ) - - # newer local files - local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertFalse( - should_update(lang, self.stats, local_file) - ) - - def test_push_translation(self): - """Test filters for pushing a translation file.""" - with patch.object(self.p, "get_resource_option") as mock: - mock.return_value = None - - local_file = 'foo' - should_push = self.p._should_push_translation - force = True - for lang in self.langs: - self.assertTrue(should_push(lang, self.stats, local_file, force)) - - force = False # reminder - - # unknown language - self.assertTrue(should_push('es', self.stats, local_file)) - - # older local files - local_times = [self.p._generate_timestamp('2011-11-01 14:00:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertFalse( - should_push(lang, self.stats, local_file) - ) - - # newer local files - local_times = [self.p._generate_timestamp('2011-11-01 15:01:59')] - results = itertools.cycle(local_times) - def side_effect(*args): - return results.next() - - with patch.object(self.p, "_get_time_of_local_file") as time_mock: - time_mock.side_effect = side_effect - with patch.object(self.p, "get_full_path") as path_mock: - path_mock.return_value = "foo" - for lang in self.langs: - self.assertTrue( - should_push(lang, self.stats, local_file) - ) - - -class TestProjectPull(unittest.TestCase): - """Test bits & pieces of the pull method.""" - - def setUp(self): - super(TestProjectPull, self).setUp() - self.p = Project(init=False) - self.p.minimum_perc = None - self.p.resource = "resource" - self.p.host = 'foo' - self.p.project_slug = 'foo' - self.p.resource_slug = 'foo' - self.stats = { - 'en': { - 'completed': '100%', 'last_update': '2011-11-01 15:00:00', - }, 'el': { - 'completed': '60%', 'last_update': '2011-11-01 15:00:00', - }, 'pt': { - 'completed': '70%', 'last_update': '2011-11-01 15:00:00', - }, - } - self.langs = self.stats.keys() - self.files = dict(zip(self.langs, itertools.repeat(None))) - self.details = {'available_languages': []} - for lang in self.langs: - self.details['available_languages'].append({'code': lang}) - self.slang = 'en' - self.lang_map = Flipdict() - - def test_new_translations(self): - """Test finding new transaltions to add.""" - with patch.object(self.p, 'do_url_request') as resource_mock: - resource_mock.return_value = json.dumps(self.details) - files_keys = self.langs - new_trans = self.p._new_translations_to_add - for force in [True, False]: - res = new_trans( - self.files, self.slang, self.lang_map, self.stats, force - ) - self.assertEquals(res, set([])) - - with patch.object(self.p, '_should_add_translation') as filter_mock: - filter_mock.return_value = True - for force in [True, False]: - res = new_trans( - {'el': None}, self.slang, self.lang_map, self.stats, force - ) - self.assertEquals(res, set(['pt'])) - for force in [True, False]: - res = new_trans( - {}, self.slang, self.lang_map, self.stats, force - ) - self.assertEquals(res, set(['el', 'pt'])) - - files = {} - files['pt_PT'] = None - lang_map = {'pt': 'pt_PT'} - for force in [True, False]: - res = new_trans( - files, self.slang, lang_map, self.stats, force - ) - self.assertEquals(res, set(['el'])) - - def test_languages_to_pull_empty_initial_list(self): - """Test determining the languages to pull, when the initial - list is empty. - """ - languages = [] - force = False - - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['el', 'en', 'pt'])) - self.assertFalse(new) - - del self.files['el'] - self.files['el-gr'] = None - self.lang_map['el'] = 'el-gr' - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['el', 'en', 'pt'])) - self.assertFalse(new) - - def test_languages_to_pull_with_initial_list(self): - """Test determining the languages to pull, then there is a - language selection from the user. - """ - languages = ['el', 'en'] - self.lang_map['el'] = 'el-gr' - del self.files['el'] - self.files['el-gr'] = None - force = False - - with patch.object(self.p, '_should_add_translation') as mock: - mock.return_value = True - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', 'el-gr', ])) - self.assertFalse(new) - - mock.return_value = False - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', 'el-gr', ])) - self.assertFalse(new) - - del self.files['el-gr'] - mock.return_value = True - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', ])) - self.assertEquals(new, set(['el', ])) - - mock.return_value = False - res = self.p._languages_to_pull( - languages, self.files, self.lang_map, self.stats, force - ) - existing = res[0] - new = res[1] - self.assertEquals(existing, set(['en', ])) - self.assertEquals(new, set([])) - - def test_in_combination_with_force_option(self): - """Test the minumum-perc option along with -f.""" - with patch.object(self.p, 'get_resource_option') as mock: - mock.return_value = 70 - - res = self.p._should_download('de', self.stats, None, False) - self.assertEquals(res, False) - res = self.p._should_download('el', self.stats, None, False) - self.assertEquals(res, False) - res = self.p._should_download('el', self.stats, None, True) - self.assertEquals(res, False) - res = self.p._should_download('en', self.stats, None, False) - self.assertEquals(res, True) - res = self.p._should_download('en', self.stats, None, True) - self.assertEquals(res, True) - - with patch.object(self.p, '_remote_is_newer') as local_file_mock: - local_file_mock = False - res = self.p._should_download('pt', self.stats, None, False) - self.assertEquals(res, True) - res = self.p._should_download('pt', self.stats, None, True) - self.assertEquals(res, True) - - -class TestFormats(unittest.TestCase): - """Tests for the supported formats.""" - - def setUp(self): - self.p = Project(init=False) - - def test_extensions(self): - """Test returning the correct extension for a format.""" - sample_formats = { - 'PO': {'file-extensions': '.po, .pot'}, - 'QT': {'file-extensions': '.ts'}, - } - extensions = ['.po', '.ts', '', ] - with patch.object(self.p, "do_url_request") as mock: - mock.return_value = json.dumps(sample_formats) - for (type_, ext) in zip(['PO', 'QT', 'NONE', ], extensions): - extension = self.p._extension_for(type_) - self.assertEquals(extension, ext) - - -class TestOptions(unittest.TestCase): - """Test the methods related to parsing the configuration file.""" - - def setUp(self): - self.p = Project(init=False) - - def test_get_option(self): - """Test _get_option method.""" - with contextlib.nested( - patch.object(self.p, 'get_resource_option'), - patch.object(self.p, 'config', create=True) - ) as (rmock, cmock): - rmock.return_value = 'resource' - cmock.has_option.return_value = 'main' - cmock.get.return_value = 'main' - self.assertEqual(self.p._get_option(None, None), 'resource') - rmock.return_value = None - cmock.has_option.return_value = 'main' - cmock.get.return_value = 'main' - self.assertEqual(self.p._get_option(None, None), 'main') - cmock.has_option.return_value = None - self.assertIs(self.p._get_option(None, None), None) - - -class TestConfigurationOptions(unittest.TestCase): - """Test the various configuration options.""" - - def test_i18n_type(self): - p = Project(init=False) - type_string = 'type' - i18n_type = 'PO' - with patch.object(p, 'config', create=True) as config_mock: - p.set_i18n_type([], i18n_type) - calls = config_mock.method_calls - self.assertEquals('set', calls[0][0]) - self.assertEquals('main', calls[0][1][0]) - p.set_i18n_type(['transifex.txo'], 'PO') - calls = config_mock.method_calls - self.assertEquals('set', calls[0][0]) - p.set_i18n_type(['transifex.txo', 'transifex.txn'], 'PO') - calls = config_mock.method_calls - self.assertEquals('set', calls[0][0]) - self.assertEquals('set', calls[1][0]) - - -class TestStats(unittest.TestCase): - """Test the access to the stats objects.""" - - def setUp(self): - self.stats = Mock() - self.stats.__getitem__ = Mock() - self.stats.__getitem__.return_value = '12%' - - def test_field_used_per_mode(self): - """Test the fields used for each mode.""" - Project._extract_completed(self.stats, 'translate') - self.stats.__getitem__.assert_called_with('completed') - Project._extract_completed(self.stats, 'reviewed') - self.stats.__getitem__.assert_called_with('reviewed_percentage') - diff --git a/third_party/transifex-client/tx b/third_party/transifex-client/tx deleted file mode 100755 index dfb4a4c1..00000000 --- a/third_party/transifex-client/tx +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from optparse import OptionParser, OptionValueError -import os -import sys - -from txclib import utils -from txclib import get_version -from txclib.log import set_log_level, logger - -reload(sys) # WTF? Otherwise setdefaultencoding doesn't work - -# This block ensures that ^C interrupts are handled quietly. -try: - import signal - - def exithandler(signum,frame): - signal.signal(signal.SIGINT, signal.SIG_IGN) - signal.signal(signal.SIGTERM, signal.SIG_IGN) - sys.exit(1) - - signal.signal(signal.SIGINT, exithandler) - signal.signal(signal.SIGTERM, exithandler) - if hasattr(signal, 'SIGPIPE'): - signal.signal(signal.SIGPIPE, signal.SIG_DFL) - -except KeyboardInterrupt: - sys.exit(1) - -# When we open file with f = codecs.open we specifi FROM what encoding to read -# This sets the encoding for the strings which are created with f.read() -sys.setdefaultencoding('utf-8') - - -def main(argv): - """ - Here we parse the flags (short, long) and we instantiate the classes. - """ - usage = "usage: %prog [options] command [cmd_options]" - description = "This is the Transifex command line client which"\ - " allows you to manage your translations locally and sync"\ - " them with the master Transifex server.\nIf you'd like to"\ - " check the available commands issue `%prog help` or if you"\ - " just want help with a specific command issue `%prog help"\ - " command`" - - parser = OptionParser( - usage=usage, version=get_version(), description=description - ) - parser.disable_interspersed_args() - parser.add_option( - "-d", "--debug", action="store_true", dest="debug", - default=False, help=("enable debug messages") - ) - parser.add_option( - "-q", "--quiet", action="store_true", dest="quiet", - default=False, help="don't print status messages to stdout" - ) - parser.add_option( - "-r", "--root", action="store", dest="root_dir", type="string", - default=None, help="change root directory (default is cwd)" - ) - parser.add_option( - "--traceback", action="store_true", dest="trace", default=False, - help="print full traceback on exceptions" - ) - parser.add_option( - "--disable-colors", action="store_true", dest="color_disable", - default=(os.name == 'nt' or not sys.stdout.isatty()), - help="disable colors in the output of commands" - ) - (options, args) = parser.parse_args() - - if len(args) < 1: - parser.error("No command was given") - - utils.DISABLE_COLORS = options.color_disable - - # set log level - if options.quiet: - set_log_level('WARNING') - elif options.debug: - set_log_level('DEBUG') - - # find .tx - path_to_tx = options.root_dir or utils.find_dot_tx() - - - cmd = args[0] - try: - utils.exec_command(cmd, args[1:], path_to_tx) - except utils.UnknownCommandError: - logger.error("tx: Command %s not found" % cmd) - except SystemExit: - sys.exit() - except: - import traceback - if options.trace: - traceback.print_exc() - else: - formatted_lines = traceback.format_exc().splitlines() - logger.error(formatted_lines[-1]) - sys.exit(1) - -# Run baby :) ... run -if __name__ == "__main__": - # sys.argv[0] is the name of the script that we’re running. - main(sys.argv[1:]) diff --git a/third_party/transifex-client/txclib/__init__.py b/third_party/transifex-client/txclib/__init__.py deleted file mode 100644 index 814773c8..00000000 --- a/third_party/transifex-client/txclib/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Copyright (C) 2010 by Indifex (www.indifex.com), see AUTHORS. -License: BSD, see LICENSE for details. - -For further information visit http://code.indifex.com/transifex-client -""" - - -VERSION = (0, 9, 0, 'devel') - -def get_version(): - version = '%s.%s' % (VERSION[0], VERSION[1]) - if VERSION[2]: - version = '%s.%s' % (version, VERSION[2]) - if VERSION[3] != 'final': - version = '%s %s' % (version, VERSION[3]) - return version diff --git a/third_party/transifex-client/txclib/commands.py b/third_party/transifex-client/txclib/commands.py deleted file mode 100644 index 282287fc..00000000 --- a/third_party/transifex-client/txclib/commands.py +++ /dev/null @@ -1,576 +0,0 @@ -# -*- coding: utf-8 -*- -""" -In this file we have all the top level commands for the transifex client. -Since we're using a way to automatically list them and execute them, when -adding code to this file you must take care of the following: - * Added functions must begin with 'cmd_' followed by the actual name of the - command being used in the command line (eg cmd_init) - * The description for each function that we display to the user is read from - the func_doc attribute which reads the doc string. So, when adding - docstring to a new function make sure you add an oneliner which is - descriptive and is meant to be seen by the user. - * When including libraries, it's best if you include modules instead of - functions because that way our function resolution will work faster and the - chances of overlapping are minimal - * All functions should use the OptionParser and should have a usage and - descripition field. -""" -import os -import re, shutil -import sys -from optparse import OptionParser, OptionGroup -import ConfigParser - - -from txclib import utils, project -from txclib.utils import parse_json, compile_json, relpath -from txclib.config import OrderedRawConfigParser -from txclib.exceptions import UnInitializedError -from txclib.parsers import delete_parser, help_parser, parse_csv_option, \ - status_parser, pull_parser, set_parser, push_parser, init_parser -from txclib.log import logger - - -def cmd_init(argv, path_to_tx): - "Initialize a new transifex project." - parser = init_parser() - (options, args) = parser.parse_args(argv) - if len(args) > 1: - parser.error("Too many arguments were provided. Aborting...") - if args: - path_to_tx = args[0] - else: - path_to_tx = os.getcwd() - - if os.path.isdir(os.path.join(path_to_tx,".tx")): - logger.info("tx: There is already a tx folder!") - reinit = raw_input("Do you want to delete it and reinit the project? [y/N]: ") - while (reinit != 'y' and reinit != 'Y' and reinit != 'N' and reinit != 'n' and reinit != ''): - reinit = raw_input("Do you want to delete it and reinit the project? [y/N]: ") - if not reinit or reinit in ['N', 'n', 'NO', 'no', 'No']: - return - # Clean the old settings - # FIXME: take a backup - else: - rm_dir = os.path.join(path_to_tx, ".tx") - shutil.rmtree(rm_dir) - - logger.info("Creating .tx folder...") - os.mkdir(os.path.join(path_to_tx,".tx")) - - # Handle the credentials through transifexrc - home = os.path.expanduser("~") - txrc = os.path.join(home, ".transifexrc") - config = OrderedRawConfigParser() - - default_transifex = "https://www.transifex.com" - transifex_host = options.host or raw_input("Transifex instance [%s]: " % default_transifex) - - if not transifex_host: - transifex_host = default_transifex - if not transifex_host.startswith(('http://', 'https://')): - transifex_host = 'https://' + transifex_host - - config_file = os.path.join(path_to_tx, ".tx", "config") - if not os.path.exists(config_file): - # The path to the config file (.tx/config) - logger.info("Creating skeleton...") - config = OrderedRawConfigParser() - config.add_section('main') - config.set('main', 'host', transifex_host) - # Touch the file if it doesn't exist - logger.info("Creating config file...") - fh = open(config_file, 'w') - config.write(fh) - fh.close() - - prj = project.Project(path_to_tx) - prj.getset_host_credentials(transifex_host, user=options.user, - password=options.password) - prj.save() - logger.info("Done.") - - -def cmd_set(argv, path_to_tx): - "Add local or remote files under transifex" - parser = set_parser() - (options, args) = parser.parse_args(argv) - - # Implement options/args checks - # TODO !!!!!!! - if options.local: - try: - expression = args[0] - except IndexError: - parser.error("Please specify an expression.") - if not options.resource: - parser.error("Please specify a resource") - if not options.source_language: - parser.error("Please specify a source language.") - if not '' in expression: - parser.error("The expression you have provided is not valid.") - if not utils.valid_slug(options.resource): - parser.error("Invalid resource slug. The format is "\ - ". and the valid characters include [_-\w].") - _auto_local(path_to_tx, options.resource, - source_language=options.source_language, - expression = expression, source_file=options.source_file, - execute=options.execute, regex=False) - if options.execute: - _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) - _set_mode(options.resource, options.mode, path_to_tx) - _set_type(options.resource, options.i18n_type, path_to_tx) - return - - if options.remote: - try: - url = args[0] - except IndexError: - parser.error("Please specify an remote url") - _auto_remote(path_to_tx, url) - _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) - _set_mode(options.resource, options.mode, path_to_tx) - return - - if options.is_source: - resource = options.resource - if not resource: - parser.error("You must specify a resource name with the" - " -r|--resource flag.") - - lang = options.language - if not lang: - parser.error("Please specify a source language.") - - if len(args) != 1: - parser.error("Please specify a file.") - - if not utils.valid_slug(resource): - parser.error("Invalid resource slug. The format is "\ - ". and the valid characters include [_-\w].") - - file = args[0] - # Calculate relative path - path_to_file = relpath(file, path_to_tx) - _set_source_file(path_to_tx, resource, options.language, path_to_file) - elif options.resource or options.language: - resource = options.resource - lang = options.language - - if len(args) != 1: - parser.error("Please specify a file") - - # Calculate relative path - path_to_file = relpath(args[0], path_to_tx) - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - if not utils.valid_slug(resource): - parser.error("Invalid resource slug. The format is "\ - ". and the valid characters include [_-\w].") - _set_translation(path_to_tx, resource, lang, path_to_file) - - _set_mode(options.resource, options.mode, path_to_tx) - _set_type(options.resource, options.i18n_type, path_to_tx) - _set_minimum_perc(options.resource, options.minimum_perc, path_to_tx) - - logger.info("Done.") - return - - -def _auto_local(path_to_tx, resource, source_language, expression, execute=False, - source_file=None, regex=False): - """Auto configure local project.""" - # The path everything will be relative to - curpath = os.path.abspath(os.curdir) - - # Force expr to be a valid regex expr (escaped) but keep intact - expr_re = utils.regex_from_filefilter(expression, curpath) - expr_rec = re.compile(expr_re) - - if not execute: - logger.info("Only printing the commands which will be run if the " - "--execute switch is specified.") - - # First, let's construct a dictionary of all matching files. - # Note: Only the last matching file of a language will be stored. - translation_files = {} - for root, dirs, files in os.walk(curpath): - for f in files: - f_path = os.path.abspath(os.path.join(root, f)) - match = expr_rec.match(f_path) - if match: - lang = match.group(1) - f_path = os.path.abspath(f_path) - if lang == source_language and not source_file: - source_file = f_path - else: - translation_files[lang] = f_path - - if not source_file: - raise Exception("Could not find a source language file. Please run" - " set --source manually and then re-run this command or provide" - " the source file with the -s flag.") - if execute: - logger.info("Updating source for resource %s ( %s -> %s )." % (resource, - source_language, relpath(source_file, path_to_tx))) - _set_source_file(path_to_tx, resource, source_language, - relpath(source_file, path_to_tx)) - else: - logger.info('\ntx set --source -r %(res)s -l %(lang)s %(file)s\n' % { - 'res': resource, - 'lang': source_language, - 'file': relpath(source_file, curpath)}) - - prj = project.Project(path_to_tx) - root_dir = os.path.abspath(path_to_tx) - - if execute: - try: - prj.config.get("%s" % resource, "source_file") - except ConfigParser.NoSectionError: - raise Exception("No resource with slug \"%s\" was found.\nRun 'tx set --auto" - "-local -r %s \"expression\"' to do the initial configuration." % resource) - - # Now let's handle the translation files. - if execute: - logger.info("Updating file expression for resource %s ( %s )." % (resource, - expression)) - # Eval file_filter relative to root dir - file_filter = relpath(os.path.join(curpath, expression), - path_to_tx) - prj.config.set("%s" % resource, "file_filter", file_filter) - else: - for (lang, f_path) in sorted(translation_files.items()): - logger.info('tx set -r %(res)s -l %(lang)s %(file)s' % { - 'res': resource, - 'lang': lang, - 'file': relpath(f_path, curpath)}) - - if execute: - prj.save() - - -def _auto_remote(path_to_tx, url): - """ - Initialize a remote release/project/resource to the current directory. - """ - logger.info("Auto configuring local project from remote URL...") - - type, vars = utils.parse_tx_url(url) - prj = project.Project(path_to_tx) - username, password = prj.getset_host_credentials(vars['hostname']) - - if type == 'project': - logger.info("Getting details for project %s" % vars['project']) - proj_info = utils.get_details('project_details', - username, password, - hostname = vars['hostname'], project = vars['project']) - resources = [ '.'.join([vars['project'], r['slug']]) for r in proj_info['resources'] ] - logger.info("%s resources found. Configuring..." % len(resources)) - elif type == 'release': - logger.info("Getting details for release %s" % vars['release']) - rel_info = utils.get_details('release_details', - username, password, hostname = vars['hostname'], - project = vars['project'], release = vars['release']) - resources = [] - for r in rel_info['resources']: - if r.has_key('project'): - resources.append('.'.join([r['project']['slug'], r['slug']])) - else: - resources.append('.'.join([vars['project'], r['slug']])) - logger.info("%s resources found. Configuring..." % len(resources)) - elif type == 'resource': - logger.info("Getting details for resource %s" % vars['resource']) - resources = [ '.'.join([vars['project'], vars['resource']]) ] - else: - raise("Url '%s' is not recognized." % url) - - for resource in resources: - logger.info("Configuring resource %s." % resource) - proj, res = resource.split('.') - res_info = utils.get_details('resource_details', - username, password, hostname = vars['hostname'], - project = proj, resource=res) - try: - source_lang = res_info['source_language_code'] - i18n_type = res_info['i18n_type'] - except KeyError: - raise Exception("Remote server seems to be running an unsupported version" - " of Transifex. Either update your server software of fallback" - " to a previous version of transifex-client.") - prj.set_remote_resource( - resource=resource, - host = vars['hostname'], - source_lang = source_lang, - i18n_type = i18n_type) - - prj.save() - - -def cmd_push(argv, path_to_tx): - "Push local files to remote server" - parser = push_parser() - (options, args) = parser.parse_args(argv) - force_creation = options.force_creation - languages = parse_csv_option(options.languages) - resources = parse_csv_option(options.resources) - skip = options.skip_errors - prj = project.Project(path_to_tx) - if not (options.push_source or options.push_translations): - parser.error("You need to specify at least one of the -s|--source," - " -t|--translations flags with the push command.") - - prj.push( - force=force_creation, resources=resources, languages=languages, - skip=skip, source=options.push_source, - translations=options.push_translations, - no_interactive=options.no_interactive - ) - logger.info("Done.") - - -def cmd_pull(argv, path_to_tx): - "Pull files from remote server to local repository" - parser = pull_parser() - (options, args) = parser.parse_args(argv) - if options.fetchall and options.languages: - parser.error("You can't user a language filter along with the"\ - " -a|--all option") - languages = parse_csv_option(options.languages) - resources = parse_csv_option(options.resources) - skip = options.skip_errors - minimum_perc = options.minimum_perc or None - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - # instantiate the project.Project - prj = project.Project(path_to_tx) - prj.pull( - languages=languages, resources=resources, overwrite=options.overwrite, - fetchall=options.fetchall, fetchsource=options.fetchsource, - force=options.force, skip=skip, minimum_perc=minimum_perc, - mode=options.mode - ) - logger.info("Done.") - - -def _set_source_file(path_to_tx, resource, lang, path_to_file): - """Reusable method to set source file.""" - proj, res = resource.split('.') - if not proj or not res: - raise Exception("\"%s.%s\" is not a valid resource identifier. It should" - " be in the following format project_slug.resource_slug." % - (proj, res)) - if not lang: - raise Exception("You haven't specified a source language.") - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - if not os.path.exists(path_to_file): - raise Exception("tx: File ( %s ) does not exist." % - os.path.join(path_to_tx, path_to_file)) - - # instantiate the project.Project - prj = project.Project(path_to_tx) - root_dir = os.path.abspath(path_to_tx) - - if root_dir not in os.path.normpath(os.path.abspath(path_to_file)): - raise Exception("File must be under the project root directory.") - - logger.info("Setting source file for resource %s.%s ( %s -> %s )." % ( - proj, res, lang, path_to_file)) - - path_to_file = relpath(path_to_file, root_dir) - - prj = project.Project(path_to_tx) - - # FIXME: Check also if the path to source file already exists. - try: - try: - prj.config.get("%s.%s" % (proj, res), "source_file") - except ConfigParser.NoSectionError: - prj.config.add_section("%s.%s" % (proj, res)) - except ConfigParser.NoOptionError: - pass - finally: - prj.config.set("%s.%s" % (proj, res), "source_file", - path_to_file) - prj.config.set("%s.%s" % (proj, res), "source_lang", - lang) - - prj.save() - - -def _set_translation(path_to_tx, resource, lang, path_to_file): - """Reusable method to set translation file.""" - - proj, res = resource.split('.') - if not project or not resource: - raise Exception("\"%s\" is not a valid resource identifier. It should" - " be in the following format project_slug.resource_slug." % - resource) - - try: - _go_to_dir(path_to_tx) - except UnInitializedError, e: - utils.logger.error(e) - return - - # Warn the user if the file doesn't exist - if not os.path.exists(path_to_file): - logger.info("Warning: File '%s' doesn't exist." % path_to_file) - - # instantiate the project.Project - prj = project.Project(path_to_tx) - root_dir = os.path.abspath(path_to_tx) - - if root_dir not in os.path.normpath(os.path.abspath(path_to_file)): - raise Exception("File must be under the project root directory.") - - if lang == prj.config.get("%s.%s" % (proj, res), "source_lang"): - raise Exception("tx: You cannot set translation file for the source language." - " Source languages contain the strings which will be translated!") - - logger.info("Updating translations for resource %s ( %s -> %s )." % (resource, - lang, path_to_file)) - path_to_file = relpath(path_to_file, root_dir) - prj.config.set("%s.%s" % (proj, res), "trans.%s" % lang, - path_to_file) - - prj.save() - - -def cmd_status(argv, path_to_tx): - "Print status of current project" - parser = status_parser() - (options, args) = parser.parse_args(argv) - resources = parse_csv_option(options.resources) - prj = project.Project(path_to_tx) - resources = prj.get_chosen_resources(resources) - resources_num = len(resources) - for idx, res in enumerate(resources): - p, r = res.split('.') - logger.info("%s -> %s (%s of %s)" % (p, r, idx + 1, resources_num)) - logger.info("Translation Files:") - slang = prj.get_resource_option(res, 'source_lang') - sfile = prj.get_resource_option(res, 'source_file') or "N/A" - lang_map = prj.get_resource_lang_mapping(res) - logger.info(" - %s: %s (%s)" % (utils.color_text(slang, "RED"), - sfile, utils.color_text("source", "YELLOW"))) - files = prj.get_resource_files(res) - fkeys = files.keys() - fkeys.sort() - for lang in fkeys: - local_lang = lang - if lang in lang_map.values(): - local_lang = lang_map.flip[lang] - logger.info(" - %s: %s" % (utils.color_text(local_lang, "RED"), - files[lang])) - logger.info("") - - -def cmd_help(argv, path_to_tx): - """List all available commands""" - parser = help_parser() - (options, args) = parser.parse_args(argv) - if len(args) > 1: - parser.error("Multiple arguments received. Exiting...") - - # Get all commands - fns = utils.discover_commands() - - # Print help for specific command - if len(args) == 1: - try: - fns[argv[0]](['--help'], path_to_tx) - except KeyError: - utils.logger.error("Command %s not found" % argv[0]) - # or print summary of all commands - - # the code below will only be executed if the KeyError exception is thrown - # becuase in all other cases the function called with --help will exit - # instead of return here - keys = fns.keys() - keys.sort() - - logger.info("Transifex command line client.\n") - logger.info("Available commands are:") - for key in keys: - logger.info(" %-15s\t%s" % (key, fns[key].func_doc)) - logger.info("\nFor more information run %s command --help" % sys.argv[0]) - - -def cmd_delete(argv, path_to_tx): - "Delete an accessible resource or translation in a remote server." - parser = delete_parser() - (options, args) = parser.parse_args(argv) - languages = parse_csv_option(options.languages) - resources = parse_csv_option(options.resources) - skip = options.skip_errors - force = options.force_delete - prj = project.Project(path_to_tx) - prj.delete(resources, languages, skip, force) - logger.info("Done.") - - -def _go_to_dir(path): - """Change the current working directory to the directory specified as - argument. - - Args: - path: The path to chdor to. - Raises: - UnInitializedError, in case the directory has not been initialized. - """ - if path is None: - raise UnInitializedError( - "Directory has not been initialzied. " - "Did you forget to run 'tx init' first?" - ) - os.chdir(path) - - -def _set_minimum_perc(resource, value, path_to_tx): - """Set the minimum percentage in the .tx/config file.""" - args = (resource, 'minimum_perc', value, path_to_tx, 'set_min_perc') - _set_project_option(*args) - - -def _set_mode(resource, value, path_to_tx): - """Set the mode in the .tx/config file.""" - args = (resource, 'mode', value, path_to_tx, 'set_default_mode') - _set_project_option(*args) - - -def _set_type(resource, value, path_to_tx): - """Set the i18n type in the .tx/config file.""" - args = (resource, 'type', value, path_to_tx, 'set_i18n_type') - _set_project_option(*args) - - -def _set_project_option(resource, name, value, path_to_tx, func_name): - """Save the option to the project config file.""" - if value is None: - return - if not resource: - logger.debug("Setting the %s for all resources." % name) - resources = [] - else: - logger.debug("Setting the %s for resource %s." % (name, resource)) - resources = [resource, ] - prj = project.Project(path_to_tx) - getattr(prj, func_name)(resources, value) - prj.save() diff --git a/third_party/transifex-client/txclib/config.py b/third_party/transifex-client/txclib/config.py deleted file mode 100644 index a9d055f5..00000000 --- a/third_party/transifex-client/txclib/config.py +++ /dev/null @@ -1,115 +0,0 @@ -import ConfigParser - - -class OrderedRawConfigParser( ConfigParser.RawConfigParser ): - """ - Overload standard Class ConfigParser.RawConfigParser - """ - def write(self, fp): - """Write an .ini-format representation of the configuration state.""" - if self._defaults: - fp.write("[%s]\n" % DEFAULTSECT) - for key in sorted( self._defaults ): - fp.write( "%s = %s\n" % (key, str( self._defaults[ key ] - ).replace('\n', '\n\t')) ) - fp.write("\n") - for section in self._sections: - fp.write("[%s]\n" % section) - for key in sorted( self._sections[section] ): - if key != "__name__": - fp.write("%s = %s\n" % - (key, str( self._sections[section][ key ] - ).replace('\n', '\n\t'))) - fp.write("\n") - - optionxform = str - - -_NOTFOUND = object() - - -class Flipdict(dict): - """An injective (one-to-one) python dict. Ensures that each key maps - to a unique value, and each value maps back to that same key. - - Code mostly taken from here: - http://code.activestate.com/recipes/576968-flipdict-python-dict-that-also-maintains-a-one-to-/ - """ - - def __init__(self, *args, **kw): - self._flip = dict.__new__(self.__class__) - setattr(self._flip, "_flip", self) - for key, val in dict(*args, **kw).iteritems(): - self[key] = val - - @property - def flip(self): - """The inverse mapping.""" - return self._flip - - def __repr__(self): - return "%s(%r)" % (self.__class__.__name__, dict(self)) - - __str__ = __repr__ - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, keys, value=None): - return cls(dict.fromkeys(keys, value)) - - def __setitem__(self, key, val): - k = self._flip.get(val, _NOTFOUND) - if not (k is _NOTFOUND or k==key): - raise KeyError('(key,val) would erase mapping for value %r' % val) - - v = self.get(key, _NOTFOUND) - if v is not _NOTFOUND: - dict.__delitem__(self._flip, v) - - dict.__setitem__(self, key, val) - dict.__setitem__(self._flip, val, key) - - def setdefault(self, key, default = None): - # Copied from python's UserDict.DictMixin code. - try: - return self[key] - except KeyError: - self[key] = default - return default - - def update(self, other = None, **kwargs): - # Copied from python's UserDict.DictMixin code. - # Make progressively weaker assumptions about "other" - if other is None: - pass - elif hasattr(other, 'iteritems'): # iteritems saves memory and lookups - for k, v in other.iteritems(): - self[k] = v - elif hasattr(other, 'keys'): - for k in other.keys(): - self[k] = other[k] - else: - for k, v in other: - self[k] = v - if kwargs: - self.update(kwargs) - - def __delitem__(self, key): - val = dict.pop(self, key) - dict.__delitem__(self._flip, val) - - def pop(self, key, *args): - val = dict.pop(self, key, *args) - dict.__delitem__(self._flip, val) - return val - - def popitem(self): - key, val = dict.popitem(self) - dict.__delitem__(self._flip, val) - return key, val - - def clear(self): - dict.clear(self) - dict.clear(self._flip) diff --git a/third_party/transifex-client/txclib/exceptions.py b/third_party/transifex-client/txclib/exceptions.py deleted file mode 100644 index 8766a018..00000000 --- a/third_party/transifex-client/txclib/exceptions.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Exception classes for the tx client. -""" - - -class UnInitializedError(Exception): - """The project directory has not been initialized.""" - - -class UnknownCommandError(Exception): - """The provided command is not supported.""" diff --git a/third_party/transifex-client/txclib/http_utils.py b/third_party/transifex-client/txclib/http_utils.py deleted file mode 100644 index 3243149c..00000000 --- a/third_party/transifex-client/txclib/http_utils.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -HTTP-related utility functions. -""" - -from __future__ import with_statement -import gzip -try: - import cStringIO as StringIO -except ImportError: - import StringIO - - -def _gzip_decode(gzip_data): - """ - Unzip gzipped data and return them. - - :param gzip_data: Gzipped data. - :returns: The actual data. - """ - try: - gzip_data = StringIO.StringIO(gzip_data) - gzip_file = gzip.GzipFile(fileobj=gzip_data) - data = gzip_file.read() - return data - finally: - gzip_data.close() - - -def http_response(response): - """ - Return the response of a HTTP request. - - If the response has been gzipped, gunzip it first. - - :param response: The raw response of a HTTP request. - :returns: A response suitable to be used by clients. - """ - metadata = response.info() - data = response.read() - response.close() - if metadata.get('content-encoding') == 'gzip': - return _gzip_decode(data) - else: - return data diff --git a/third_party/transifex-client/txclib/log.py b/third_party/transifex-client/txclib/log.py deleted file mode 100644 index 9baf3220..00000000 --- a/third_party/transifex-client/txclib/log.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Add logging capabilities to tx-client. -""" - -import sys -import logging - -_logger = logging.getLogger('txclib') -_logger.setLevel(logging.INFO) - -_formatter = logging.Formatter('%(message)s') - -_error_handler = logging.StreamHandler(sys.stderr) -_error_handler.setLevel(logging.ERROR) -_error_handler.setFormatter(_formatter) -_logger.addHandler(_error_handler) - -_msg_handler = logging.StreamHandler(sys.stdout) -_msg_handler.setLevel(logging.DEBUG) -_msg_handler.setFormatter(_formatter) -_msg_filter = logging.Filter() -_msg_filter.filter = lambda r: r.levelno < logging.ERROR -_msg_handler.addFilter(_msg_filter) -_logger.addHandler(_msg_handler) - -logger = _logger - - -def set_log_level(level): - """Set the level for the logger. - - Args: - level: A string among DEBUG, INFO, WARNING, ERROR, CRITICAL. - """ - logger.setLevel(getattr(logging, level)) diff --git a/third_party/transifex-client/txclib/parsers.py b/third_party/transifex-client/txclib/parsers.py deleted file mode 100644 index fd3237d2..00000000 --- a/third_party/transifex-client/txclib/parsers.py +++ /dev/null @@ -1,241 +0,0 @@ -# -*- coding: utf-8 -*- - -from optparse import OptionParser, OptionGroup - - -class EpilogParser(OptionParser): - def format_epilog(self, formatter): - return self.epilog - - -def delete_parser(): - """Return the command-line parser for the delete command.""" - usage = "usage: %prog [tx_options] delete OPTION [OPTIONS]" - description = ( - "This command deletes translations for a resource in the remote server." - ) - epilog = ( - "\nExamples:\n" - " To delete a translation:\n " - "$ tx delete -r project.resource -l \n\n" - " To delete a resource:\n $ tx delete -r project.resource\n" - ) - parser = EpilogParser(usage=usage, description=description, epilog=epilog) - parser.add_option( - "-r", "--resource", action="store", dest="resources", default=None, - help="Specify the resource you want to delete (defaults to all)" - ) - parser.add_option( - "-l","--language", action="store", dest="languages", - default=None, help="Specify the translation you want to delete" - ) - parser.add_option( - "--skip", action="store_true", dest="skip_errors", default=False, - help="Don't stop on errors." - ) - parser.add_option( - "-f","--force", action="store_true", dest="force_delete", - default=False, help="Delete an entity forcefully." - ) - return parser - - -def help_parser(): - """Return the command-line parser for the help command.""" - usage="usage: %prog help command" - description="Lists all available commands in the transifex command"\ - " client. If a command is specified, the help page of the specific"\ - " command is displayed instead." - - parser = OptionParser(usage=usage, description=description) - return parser - - -def init_parser(): - """Return the command-line parser for the init command.""" - usage="usage: %prog [tx_options] init " - description="This command initializes a new project for use with"\ - " transifex. It is recommended to execute this command in the"\ - " top level directory of your project so that you can include"\ - " all files under it in transifex. If no path is provided, the"\ - " current working dir will be used." - parser = OptionParser(usage=usage, description=description) - parser.add_option("--host", action="store", dest="host", - default=None, help="Specify a default Transifex host.") - parser.add_option("--user", action="store", dest="user", - default=None, help="Specify username for Transifex server.") - parser.add_option("--pass", action="store", dest="password", - default=None, help="Specify password for Transifex server.") - return parser - - -def pull_parser(): - """Return the command-line parser for the pull command.""" - usage="usage: %prog [tx_options] pull [options]" - description="This command pulls all outstanding changes from the remote"\ - " Transifex server to the local repository. By default, only the"\ - " files that are watched by Transifex will be updated but if you"\ - " want to fetch the translations for new languages as well, use the"\ - " -a|--all option. (Note: new translations are saved in the .tx folder"\ - " and require the user to manually rename them and add then in "\ - " transifex using the set_translation command)." - parser = OptionParser(usage=usage,description=description) - parser.add_option("-l","--language", action="store", dest="languages", - default=[], help="Specify which translations you want to pull" - " (defaults to all)") - parser.add_option("-r","--resource", action="store", dest="resources", - default=[], help="Specify the resource for which you want to pull" - " the translations (defaults to all)") - parser.add_option("-a","--all", action="store_true", dest="fetchall", - default=False, help="Fetch all translation files from server (even new" - " ones)") - parser.add_option("-s","--source", action="store_true", dest="fetchsource", - default=False, help="Force the fetching of the source file (default:" - " False)") - parser.add_option("-f","--force", action="store_true", dest="force", - default=False, help="Force download of translations files.") - parser.add_option("--skip", action="store_true", dest="skip_errors", - default=False, help="Don't stop on errors. Useful when pushing many" - " files concurrently.") - parser.add_option("--disable-overwrite", action="store_false", - dest="overwrite", default=True, - help="By default transifex will fetch new translations files and"\ - " replace existing ones. Use this flag if you want to disable"\ - " this feature") - parser.add_option("--minimum-perc", action="store", type="int", - dest="minimum_perc", default=0, - help="Specify the minimum acceptable percentage of a translation " - "in order to download it.") - parser.add_option( - "--mode", action="store", dest="mode", help=( - "Specify the mode of the translation file to pull (e.g. " - "'reviewed'). See http://bit.ly/txcmod1 for available values." - ) - ) - return parser - - -def push_parser(): - """Return the command-line parser for the push command.""" - usage="usage: %prog [tx_options] push [options]" - description="This command pushes all local files that have been added to"\ - " Transifex to the remote server. All new translations are merged"\ - " with existing ones and if a language doesn't exists then it gets"\ - " created. If you want to push the source file as well (either"\ - " because this is your first time running the client or because"\ - " you just have updated with new entries), use the -f|--force option."\ - " By default, this command will push all files which are watched by"\ - " Transifex but you can filter this per resource or/and language." - parser = OptionParser(usage=usage, description=description) - parser.add_option("-l","--language", action="store", dest="languages", - default=None, help="Specify which translations you want to push" - " (defaults to all)") - parser.add_option("-r","--resource", action="store", dest="resources", - default=None, help="Specify the resource for which you want to push" - " the translations (defaults to all)") - parser.add_option("-f","--force", action="store_true", dest="force_creation", - default=False, help="Push source files without checking modification" - " times.") - parser.add_option("--skip", action="store_true", dest="skip_errors", - default=False, help="Don't stop on errors. Useful when pushing many" - " files concurrently.") - parser.add_option("-s", "--source", action="store_true", dest="push_source", - default=False, help="Push the source file to the server.") - - parser.add_option("-t", "--translations", action="store_true", dest="push_translations", - default=False, help="Push the translation files to the server") - parser.add_option("--no-interactive", action="store_true", dest="no_interactive", - default=False, help="Don't require user input when forcing a push.") - return parser - - -def set_parser(): - """Return the command-line parser for the set command.""" - usage="usage: %prog [tx_options] set [options] [args]" - description="This command can be used to create a mapping between files"\ - " and projects either using local files or using files from a remote"\ - " Transifex server." - epilog="\nExamples:\n"\ - " To set the source file:\n $ tx set -r project.resource --source -l en \n\n"\ - " To set a single translation file:\n $ tx set -r project.resource -l de \n\n"\ - " To automatically detect and assign the source files and translations:\n"\ - " $ tx set --auto-local -r project.resource 'expr' --source-lang en\n\n"\ - " To set a specific file as a source and auto detect translations:\n"\ - " $ tx set --auto-local -r project.resource 'expr' --source-lang en"\ - " --source-file \n\n"\ - " To set a remote release/resource/project:\n"\ - " $ tx set --auto-remote \n" - parser = EpilogParser(usage=usage, description=description, epilog=epilog) - parser.add_option("--auto-local", action="store_true", dest="local", - default=False, help="Used when auto configuring local project.") - parser.add_option("--auto-remote", action="store_true", dest="remote", - default=False, help="Used when adding remote files from Transifex" - " server.") - parser.add_option("-r","--resource", action="store", dest="resource", - default=None, help="Specify the slug of the resource that you're" - " setting up (This must be in the following format:" - " `project_slug.resource_slug`).") - parser.add_option( - "--source", action="store_true", dest="is_source", default=False, - help=( - "Specify that the given file is a source file " - "[doesn't work with the --auto-* commands]." - ) - ) - parser.add_option("-l","--language", action="store", dest="language", - default=None, help="Specify which translations you want to pull" - " [doesn't work with the --auto-* commands].") - parser.add_option("-t", "--type", action="store", dest="i18n_type", - help=( - "Specify the i18n type of the resource(s). This is only needed, if " - "the resource(s) does not exist yet in Transifex. For a list of " - "available i18n types, see " - "http://help.transifex.com/features/formats.html" - ) - ) - parser.add_option("--minimum-perc", action="store", dest="minimum_perc", - help=( - "Specify the minimum acceptable percentage of a translation " - "in order to download it." - ) - ) - parser.add_option( - "--mode", action="store", dest="mode", help=( - "Specify the mode of the translation file to pull (e.g. " - "'reviewed'). See http://help.transifex.com/features/client/" - "index.html#defining-the-mode-of-the-translated-file for the" - "available values." - ) - ) - group = OptionGroup(parser, "Extended options", "These options can only be" - " used with the --auto-local command.") - group.add_option("-s","--source-language", action="store", - dest="source_language", - default=None, help="Specify the source language of a resource" - " [requires --auto-local].") - group.add_option("-f","--source-file", action="store", dest="source_file", - default=None, help="Specify the source file of a resource [requires" - " --auto-local].") - group.add_option("--execute", action="store_true", dest="execute", - default=False, help="Execute commands [requires --auto-local].") - parser.add_option_group(group) - return parser - - -def status_parser(): - """Return the command-line parser for the status command.""" - usage="usage: %prog [tx_options] status [options]" - description="Prints the status of the current project by reading the"\ - " data in the configuration file." - parser = OptionParser(usage=usage,description=description) - parser.add_option("-r","--resource", action="store", dest="resources", - default=[], help="Specify resources") - return parser - - -def parse_csv_option(option): - """Return a list out of the comma-separated option or an empty list.""" - if option: - return option.split(',') - else: - return [] diff --git a/third_party/transifex-client/txclib/processors.py b/third_party/transifex-client/txclib/processors.py deleted file mode 100644 index dc3a73f9..00000000 --- a/third_party/transifex-client/txclib/processors.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -Module for API-related calls. -""" - -import urlparse - - -def hostname_tld_migration(hostname): - """ - Migrate transifex.net to transifex.com. - - :param hostname: The hostname to migrate (if needed). - :returns: A hostname with the transifex.com domain (if needed). - """ - parts = urlparse.urlparse(hostname) - if parts.hostname.endswith('transifex.net'): - hostname = hostname.replace('transifex.net', 'transifex.com', 1) - return hostname - - -def hostname_ssl_migration(hostname): - """ - Migrate Transifex hostnames to use HTTPS. - - :param hostname: The hostname to migrate (if needed). - :returns: A https hostname (if needed). - """ - parts = urlparse.urlparse(hostname) - is_transifex = ( - parts.hostname[-14:-3] == '.transifex.' or - parts.hostname == 'transifex.net' or - parts.hostname == 'transifex.com' - ) - is_https = parts.scheme == 'https' - if is_transifex and not is_https: - if not parts.scheme: - hostname = 'https:' + hostname - else: - hostname = hostname.replace(parts.scheme, 'https', 1) - return hostname - - -def visit_hostname(hostname): - """ - Have a chance to visit a hostname before actually using it. - - :param hostname: The original hostname. - :returns: The hostname with the necessary changes. - """ - for processor in [hostname_ssl_migration, hostname_tld_migration, ]: - hostname = processor(hostname) - return hostname diff --git a/third_party/transifex-client/txclib/project.py b/third_party/transifex-client/txclib/project.py deleted file mode 100644 index 88bb46bf..00000000 --- a/third_party/transifex-client/txclib/project.py +++ /dev/null @@ -1,1233 +0,0 @@ -# -*- coding: utf-8 -*- -import base64 -import copy -import getpass -import os -import re -import fnmatch -import urllib2 -import datetime, time -import ConfigParser - -from txclib.web import * -from txclib.utils import * -from txclib.urls import API_URLS -from txclib.config import OrderedRawConfigParser, Flipdict -from txclib.log import logger -from txclib.http_utils import http_response -from txclib.processors import visit_hostname - - -class ProjectNotInit(Exception): - pass - - -class Project(object): - """ - Represents an association between the local and remote project instances. - """ - - def __init__(self, path_to_tx=None, init=True): - """ - Initialize the Project attributes. - """ - if init: - self._init(path_to_tx) - - def _init(self, path_to_tx=None): - instructions = "Run 'tx init' to initialize your project first!" - try: - self.root = self._get_tx_dir_path(path_to_tx) - self.config_file = self._get_config_file_path(self.root) - self.config = self._read_config_file(self.config_file) - self.txrc_file = self._get_transifex_file() - self.txrc = self._get_transifex_config(self.txrc_file) - except ProjectNotInit, e: - logger.error('\n'.join([unicode(e), instructions])) - raise - - def _get_config_file_path(self, root_path): - """Check the .tx/config file exists.""" - config_file = os.path.join(root_path, ".tx", "config") - logger.debug("Config file is %s" % config_file) - if not os.path.exists(config_file): - msg = "Cannot find the config file (.tx/config)!" - raise ProjectNotInit(msg) - return config_file - - def _get_tx_dir_path(self, path_to_tx): - """Check the .tx directory exists.""" - root_path = path_to_tx or find_dot_tx() - logger.debug("Path to tx is %s." % root_path) - if not root_path: - msg = "Cannot find any .tx directory!" - raise ProjectNotInit(msg) - return root_path - - def _read_config_file(self, config_file): - """Parse the config file and return its contents.""" - config = OrderedRawConfigParser() - try: - config.read(config_file) - except Exception, err: - msg = "Cannot open/parse .tx/config file: %s" % err - raise ProjectNotInit(msg) - return config - - def _get_transifex_config(self, txrc_file): - """Read the configuration from the .transifexrc file.""" - txrc = OrderedRawConfigParser() - try: - txrc.read(txrc_file) - except Exception, e: - msg = "Cannot read global configuration file: %s" % e - raise ProjectNotInit(msg) - self._migrate_txrc_file(txrc) - return txrc - - def _migrate_txrc_file(self, txrc): - """Migrate the txrc file, if needed.""" - for section in txrc.sections(): - orig_hostname = txrc.get(section, 'hostname') - hostname = visit_hostname(orig_hostname) - if hostname != orig_hostname: - msg = "Hostname %s should be changed to %s." - logger.info(msg % (orig_hostname, hostname)) - if (sys.stdin.isatty() and sys.stdout.isatty() and - confirm('Change it now? ', default=True)): - txrc.set(section, 'hostname', hostname) - msg = 'Hostname changed' - logger.info(msg) - else: - hostname = orig_hostname - self._save_txrc_file(txrc) - return txrc - - def _get_transifex_file(self, directory=None): - """Fetch the path of the .transifexrc file. - - It is in the home directory ofthe user by default. - """ - if directory is None: - directory = os.path.expanduser('~') - txrc_file = os.path.join(directory, ".transifexrc") - logger.debug(".transifexrc file is at %s" % directory) - if not os.path.exists(txrc_file): - msg = "No authentication data found." - logger.info(msg) - mask = os.umask(077) - open(txrc_file, 'w').close() - os.umask(mask) - return txrc_file - - def validate_config(self): - """ - To ensure the json structure is correctly formed. - """ - pass - - def getset_host_credentials(self, host, user=None, password=None): - """ - Read .transifexrc and report user,pass for a specific host else ask the - user for input. - """ - try: - username = self.txrc.get(host, 'username') - passwd = self.txrc.get(host, 'password') - except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): - logger.info("No entry found for host %s. Creating..." % host) - username = user or raw_input("Please enter your transifex username: ") - while (not username): - username = raw_input("Please enter your transifex username: ") - passwd = password - while (not passwd): - passwd = getpass.getpass() - - logger.info("Updating %s file..." % self.txrc_file) - self.txrc.add_section(host) - self.txrc.set(host, 'username', username) - self.txrc.set(host, 'password', passwd) - self.txrc.set(host, 'token', '') - self.txrc.set(host, 'hostname', host) - - return username, passwd - - def set_remote_resource(self, resource, source_lang, i18n_type, host, - file_filter="translations%(proj)s.%(res)s.%(extension)s"): - """ - Method to handle the add/conf of a remote resource. - """ - if not self.config.has_section(resource): - self.config.add_section(resource) - - p_slug, r_slug = resource.split('.') - file_filter = file_filter.replace("", r"%s" % os.path.sep) - self.url_info = { - 'host': host, - 'project': p_slug, - 'resource': r_slug - } - extension = self._extension_for(i18n_type)[1:] - - self.config.set(resource, 'source_lang', source_lang) - self.config.set( - resource, 'file_filter', - file_filter % {'proj': p_slug, 'res': r_slug, 'extension': extension} - ) - if host != self.config.get('main', 'host'): - self.config.set(resource, 'host', host) - - def get_resource_host(self, resource): - """ - Returns the host that the resource is configured to use. If there is no - such option we return the default one - """ - if self.config.has_option(resource, 'host'): - return self.config.get(resource, 'host') - return self.config.get('main', 'host') - - def get_resource_lang_mapping(self, resource): - """ - Get language mappings for a specific resource. - """ - lang_map = Flipdict() - try: - args = self.config.get("main", "lang_map") - for arg in args.replace(' ', '').split(','): - k,v = arg.split(":") - lang_map.update({k:v}) - except ConfigParser.NoOptionError: - pass - except (ValueError, KeyError): - raise Exception("Your lang map configuration is not correct.") - - if self.config.has_section(resource): - res_lang_map = Flipdict() - try: - args = self.config.get(resource, "lang_map") - for arg in args.replace(' ', '').split(','): - k,v = arg.split(":") - res_lang_map.update({k:v}) - except ConfigParser.NoOptionError: - pass - except (ValueError, KeyError): - raise Exception("Your lang map configuration is not correct.") - - # merge the lang maps and return result - lang_map.update(res_lang_map) - - return lang_map - - - def get_resource_files(self, resource): - """ - Get a dict for all files assigned to a resource. First we calculate the - files matching the file expression and then we apply all translation - excpetions. The resulting dict will be in this format: - - { 'en': 'path/foo/en/bar.po', 'de': 'path/foo/de/bar.po', 'es': 'path/exceptions/es.po'} - - NOTE: All paths are relative to the root of the project - """ - tr_files = {} - if self.config.has_section(resource): - try: - file_filter = self.config.get(resource, "file_filter") - except ConfigParser.NoOptionError: - file_filter = "$^" - source_lang = self.config.get(resource, "source_lang") - source_file = self.get_resource_option(resource, 'source_file') or None - expr_re = regex_from_filefilter(file_filter, self.root) - expr_rec = re.compile(expr_re) - for root, dirs, files in os.walk(self.root): - for f in files: - f_path = os.path.abspath(os.path.join(root, f)) - match = expr_rec.match(f_path) - if match: - lang = match.group(1) - if lang != source_lang: - f_path = relpath(f_path, self.root) - if f_path != source_file: - tr_files.update({lang: f_path}) - - for (name, value) in self.config.items(resource): - if name.startswith("trans."): - lang = name.split('.')[1] - # delete language which has same file - if value in tr_files.values(): - keys = [] - for k, v in tr_files.iteritems(): - if v == value: - keys.append(k) - if len(keys) == 1: - del tr_files[keys[0]] - else: - raise Exception("Your configuration seems wrong."\ - " You have multiple languages pointing to"\ - " the same file.") - # Add language with correct file - tr_files.update({lang:value}) - - return tr_files - - return None - - def get_resource_option(self, resource, option): - """ - Return the requested option for a specific resource - - If there is no such option, we return None - """ - - if self.config.has_section(resource): - if self.config.has_option(resource, option): - return self.config.get(resource, option) - return None - - def get_resource_list(self, project=None): - """ - Parse config file and return tuples with the following format - - [ (project_slug, resource_slug), (..., ...)] - """ - - resource_list= [] - for r in self.config.sections(): - if r == 'main': - continue - p_slug, r_slug = r.split('.', 1) - if project and p_slug != project: - continue - resource_list.append(r) - - return resource_list - - def save(self): - """ - Store the config dictionary in the .tx/config file of the project. - """ - self._save_tx_config() - self._save_txrc_file() - - def _save_tx_config(self, config=None): - """Save the local config file.""" - if config is None: - config = self.config - fh = open(self.config_file,"w") - config.write(fh) - fh.close() - - def _save_txrc_file(self, txrc=None): - """Save the .transifexrc file.""" - if txrc is None: - txrc = self.txrc - mask = os.umask(077) - fh = open(self.txrc_file, 'w') - txrc.write(fh) - fh.close() - os.umask(mask) - - def get_full_path(self, relpath): - if relpath[0] == "/": - return relpath - else: - return os.path.join(self.root, relpath) - - def pull(self, languages=[], resources=[], overwrite=True, fetchall=False, - fetchsource=False, force=False, skip=False, minimum_perc=0, mode=None): - """Pull all translations file from transifex server.""" - self.minimum_perc = minimum_perc - resource_list = self.get_chosen_resources(resources) - - if mode == 'reviewed': - url = 'pull_reviewed_file' - elif mode == 'translator': - url = 'pull_translator_file' - elif mode == 'developer': - url = 'pull_developer_file' - else: - url = 'pull_file' - - for resource in resource_list: - logger.debug("Handling resource %s" % resource) - self.resource = resource - project_slug, resource_slug = resource.split('.') - files = self.get_resource_files(resource) - slang = self.get_resource_option(resource, 'source_lang') - sfile = self.get_resource_option(resource, 'source_file') - lang_map = self.get_resource_lang_mapping(resource) - host = self.get_resource_host(resource) - logger.debug("Language mapping is: %s" % lang_map) - if mode is None: - mode = self._get_option(resource, 'mode') - self.url_info = { - 'host': host, - 'project': project_slug, - 'resource': resource_slug - } - logger.debug("URL data are: %s" % self.url_info) - - stats = self._get_stats_for_resource() - - - try: - file_filter = self.config.get(resource, 'file_filter') - except ConfigParser.NoOptionError: - file_filter = None - - # Pull source file - pull_languages = set([]) - new_translations = set([]) - - if fetchall: - new_translations = self._new_translations_to_add( - files, slang, lang_map, stats, force - ) - if new_translations: - msg = "New translations found for the following languages: %s" - logger.info(msg % ', '.join(new_translations)) - - existing, new = self._languages_to_pull( - languages, files, lang_map, stats, force - ) - pull_languages |= existing - new_translations |= new - logger.debug("Adding to new translations: %s" % new) - - if fetchsource: - if sfile and slang not in pull_languages: - pull_languages.add(slang) - elif slang not in new_translations: - new_translations.add(slang) - - if pull_languages: - logger.debug("Pulling languages for: %s" % pull_languages) - msg = "Pulling translations for resource %s (source: %s)" - logger.info(msg % (resource, sfile)) - - for lang in pull_languages: - local_lang = lang - if lang in lang_map.values(): - remote_lang = lang_map.flip[lang] - else: - remote_lang = lang - if languages and lang not in pull_languages: - logger.debug("Skipping language %s" % lang) - continue - if lang != slang: - local_file = files.get(lang, None) or files[lang_map[lang]] - else: - local_file = sfile - logger.debug("Using file %s" % local_file) - - kwargs = { - 'lang': remote_lang, - 'stats': stats, - 'local_file': local_file, - 'force': force, - 'mode': mode, - } - if not self._should_update_translation(**kwargs): - msg = "Skipping '%s' translation (file: %s)." - logger.info( - msg % (color_text(remote_lang, "RED"), local_file) - ) - continue - - if not overwrite: - local_file = ("%s.new" % local_file) - logger.warning( - " -> %s: %s" % (color_text(remote_lang, "RED"), local_file) - ) - try: - r = self.do_url_request(url, language=remote_lang) - except Exception,e: - if not skip: - raise e - else: - logger.error(e) - continue - base_dir = os.path.split(local_file)[0] - mkdir_p(base_dir) - fd = open(local_file, 'wb') - fd.write(r) - fd.close() - - if new_translations: - msg = "Pulling new translations for resource %s (source: %s)" - logger.info(msg % (resource, sfile)) - for lang in new_translations: - if lang in lang_map.keys(): - local_lang = lang_map[lang] - else: - local_lang = lang - remote_lang = lang - if file_filter: - local_file = relpath(os.path.join(self.root, - file_filter.replace('', local_lang)), os.curdir) - else: - trans_dir = os.path.join(self.root, ".tx", resource) - if not os.path.exists(trans_dir): - os.mkdir(trans_dir) - local_file = relpath(os.path.join(trans_dir, '%s_translation' % - local_lang, os.curdir)) - - if lang != slang: - satisfies_min = self._satisfies_min_translated( - stats[remote_lang], mode - ) - if not satisfies_min: - msg = "Skipping language %s due to used options." - logger.info(msg % lang) - continue - logger.warning( - " -> %s: %s" % (color_text(remote_lang, "RED"), local_file) - ) - r = self.do_url_request(url, language=remote_lang) - - base_dir = os.path.split(local_file)[0] - mkdir_p(base_dir) - fd = open(local_file, 'wb') - fd.write(r) - fd.close() - - def push(self, source=False, translations=False, force=False, resources=[], languages=[], - skip=False, no_interactive=False): - """ - Push all the resources - """ - resource_list = self.get_chosen_resources(resources) - self.skip = skip - self.force = force - for resource in resource_list: - push_languages = [] - project_slug, resource_slug = resource.split('.') - files = self.get_resource_files(resource) - slang = self.get_resource_option(resource, 'source_lang') - sfile = self.get_resource_option(resource, 'source_file') - lang_map = self.get_resource_lang_mapping(resource) - host = self.get_resource_host(resource) - logger.debug("Language mapping is: %s" % lang_map) - logger.debug("Using host %s" % host) - self.url_info = { - 'host': host, - 'project': project_slug, - 'resource': resource_slug - } - - logger.info("Pushing translations for resource %s:" % resource) - - stats = self._get_stats_for_resource() - - if force and not no_interactive: - answer = raw_input("Warning: By using --force, the uploaded" - " files will overwrite remote translations, even if they" - " are newer than your uploaded files.\nAre you sure you" - " want to continue? [y/N] ") - - if not answer in ["", 'Y', 'y', "yes", 'YES']: - return - - if source: - if sfile == None: - logger.error("You don't seem to have a proper source file" - " mapping for resource %s. Try without the --source" - " option or set a source file first and then try again." % - resource) - continue - # Push source file - try: - logger.warning("Pushing source file (%s)" % sfile) - if not self._resource_exists(stats): - logger.info("Resource does not exist. Creating...") - fileinfo = "%s;%s" % (resource_slug, slang) - filename = self.get_full_path(sfile) - self._create_resource(resource, project_slug, fileinfo, filename) - self.do_url_request( - 'push_source', multipart=True, method="PUT", - files=[( - "%s;%s" % (resource_slug, slang) - , self.get_full_path(sfile) - )], - ) - except Exception, e: - if not skip: - raise - else: - logger.error(e) - else: - try: - self.do_url_request('resource_details') - except Exception, e: - code = getattr(e, 'code', None) - if code == 404: - msg = "Resource %s doesn't exist on the server." - logger.error(msg % resource) - continue - - if translations: - # Check if given language codes exist - if not languages: - push_languages = files.keys() - else: - push_languages = [] - f_langs = files.keys() - for l in languages: - if l in lang_map.keys(): - l = lang_map[l] - push_languages.append(l) - if l not in f_langs: - msg = "Warning: No mapping found for language code '%s'." - logger.error(msg % color_text(l,"RED")) - logger.debug("Languages to push are %s" % push_languages) - - # Push translation files one by one - for lang in push_languages: - local_lang = lang - if lang in lang_map.values(): - remote_lang = lang_map.flip[lang] - else: - remote_lang = lang - - local_file = files[local_lang] - - kwargs = { - 'lang': remote_lang, - 'stats': stats, - 'local_file': local_file, - 'force': force, - } - if not self._should_push_translation(**kwargs): - msg = "Skipping '%s' translation (file: %s)." - logger.info(msg % (color_text(lang, "RED"), local_file)) - continue - - msg = "Pushing '%s' translations (file: %s)" - logger.warning( - msg % (color_text(remote_lang, "RED"), local_file) - ) - try: - self.do_url_request( - 'push_translation', multipart=True, method='PUT', - files=[( - "%s;%s" % (resource_slug, remote_lang), - self.get_full_path(local_file) - )], language=remote_lang - ) - logger.debug("Translation %s pushed." % remote_lang) - except Exception, e: - if not skip: - raise e - else: - logger.error(e) - - def delete(self, resources=[], languages=[], skip=False, force=False): - """Delete translations.""" - resource_list = self.get_chosen_resources(resources) - self.skip = skip - self.force = force - - if not languages: - delete_func = self._delete_resource - else: - delete_func = self._delete_translations - - for resource in resource_list: - project_slug, resource_slug = resource.split('.') - host = self.get_resource_host(resource) - self.url_info = { - 'host': host, - 'project': project_slug, - 'resource': resource_slug - } - logger.debug("URL data are: %s" % self.url_info) - project_details = parse_json( - self.do_url_request('project_details', project=self) - ) - teams = project_details['teams'] - stats = self._get_stats_for_resource() - delete_func(project_details, resource, stats, languages) - - def _delete_resource(self, project_details, resource, stats, *args): - """Delete a resource from Transifex.""" - project_slug, resource_slug = resource.split('.') - project_resource_slugs = [ - r['slug'] for r in project_details['resources'] - ] - logger.info("Deleting resource %s:" % resource) - if resource_slug not in project_resource_slugs: - if not self.skip: - msg = "Skipping: %s : Resource does not exist." - logger.info(msg % resource) - return - if not self.force: - slang = self.get_resource_option(resource, 'source_lang') - for language in stats: - if language == slang: - continue - if int(stats[language]['translated_entities']) > 0: - msg = ( - "Skipping: %s : Unable to delete resource because it " - "has a not empty %s translation.\nPlease use -f or " - "--force option to delete this resource." - ) - logger.info(msg % (resource, language)) - return - try: - self.do_url_request('delete_resource', method="DELETE") - self.config.remove_section(resource) - self.save() - msg = "Deleted resource %s of project %s." - logger.info(msg % (resource_slug, project_slug)) - except Exception, e: - msg = "Unable to delete resource %s of project %s." - logger.error(msg % (resource_slug, project_slug)) - if not self.skip: - raise - - def _delete_translations(self, project_details, resource, stats, languages): - """Delete the specified translations for the specified resource.""" - logger.info("Deleting translations from resource %s:" % resource) - for language in languages: - self._delete_translation(project_details, resource, stats, language) - - def _delete_translation(self, project_details, resource, stats, language): - """Delete a specific translation from the specified resource.""" - project_slug, resource_slug = resource.split('.') - if language not in stats: - if not self.skip: - msg = "Skipping %s: Translation does not exist." - logger.warning(msg % (language)) - return - if not self.force: - teams = project_details['teams'] - if language in teams: - msg = ( - "Skipping %s: Unable to delete translation because it is " - "associated with a team.\nPlease use -f or --force option " - "to delete this translation." - ) - logger.warning(msg % language) - return - if int(stats[language]['translated_entities']) > 0: - msg = ( - "Skipping %s: Unable to delete translation because it " - "is not empty.\nPlease use -f or --force option to delete " - "this translation." - ) - logger.warning(msg % language) - return - try: - self.do_url_request( - 'delete_translation', language=language, method="DELETE" - ) - msg = "Deleted language %s from resource %s of project %s." - logger.info(msg % (language, resource_slug, project_slug)) - except Exception, e: - msg = "Unable to delete translation %s" - logger.error(msg % language) - if not self.skip: - raise - - def do_url_request(self, api_call, multipart=False, data=None, - files=[], encoding=None, method="GET", **kwargs): - """ - Issues a url request. - """ - # Read the credentials from the config file (.transifexrc) - host = self.url_info['host'] - try: - username = self.txrc.get(host, 'username') - passwd = self.txrc.get(host, 'password') - token = self.txrc.get(host, 'token') - hostname = self.txrc.get(host, 'hostname') - except ConfigParser.NoSectionError: - raise Exception("No user credentials found for host %s. Edit" - " ~/.transifexrc and add the appropriate info in there." % - host) - - # Create the Url - kwargs['hostname'] = hostname - kwargs.update(self.url_info) - url = (API_URLS[api_call] % kwargs).encode('UTF-8') - logger.debug(url) - - opener = None - headers = None - req = None - - if multipart: - opener = urllib2.build_opener(MultipartPostHandler) - for info,filename in files: - data = { "resource" : info.split(';')[0], - "language" : info.split(';')[1], - "uploaded_file" : open(filename,'rb') } - - urllib2.install_opener(opener) - req = RequestWithMethod(url=url, data=data, method=method) - else: - req = RequestWithMethod(url=url, data=data, method=method) - if encoding: - req.add_header("Content-Type",encoding) - - base64string = base64.encodestring('%s:%s' % (username, passwd))[:-1] - authheader = "Basic %s" % base64string - req.add_header("Authorization", authheader) - req.add_header("Accept-Encoding", "gzip,deflate") - req.add_header("User-Agent", user_agent_identifier()) - - try: - response = urllib2.urlopen(req, timeout=300) - return http_response(response) - except urllib2.HTTPError, e: - if e.code in [401, 403, 404]: - raise e - elif 200 <= e.code < 300: - return None - else: - # For other requests, we should print the message as well - raise Exception("Remote server replied: %s" % e.read()) - except urllib2.URLError, e: - error = e.args[0] - raise Exception("Remote server replied: %s" % error[1]) - - - def _should_update_translation(self, lang, stats, local_file, force=False, - mode=None): - """Whether a translation should be udpated from Transifex. - - We use the following criteria for that: - - If user requested to force the download. - - If language exists in Transifex. - - If the local file is older than the Transifex's file. - - If the user requested a x% completion. - - Args: - lang: The language code to check. - stats: The (global) statistics object. - local_file: The local translation file. - force: A boolean flag. - mode: The mode for the translation. - Returns: - True or False. - """ - return self._should_download(lang, stats, local_file, force) - - def _should_add_translation(self, lang, stats, force=False, mode=None): - """Whether a translation should be added from Transifex. - - We use the following criteria for that: - - If user requested to force the download. - - If language exists in Transifex. - - If the user requested a x% completion. - - Args: - lang: The language code to check. - stats: The (global) statistics object. - force: A boolean flag. - mode: The mode for the translation. - Returns: - True or False. - """ - return self._should_download(lang, stats, None, force) - - def _should_download(self, lang, stats, local_file=None, force=False, - mode=None): - """Return whether a translation should be downloaded. - - If local_file is None, skip the timestamps check (the file does - not exist locally). - """ - try: - lang_stats = stats[lang] - except KeyError, e: - logger.debug("No lang %s in statistics" % lang) - return False - - satisfies_min = self._satisfies_min_translated(lang_stats, mode) - if not satisfies_min: - return False - - if force: - logger.debug("Downloading translation due to -f") - return True - - if local_file is not None: - remote_update = self._extract_updated(lang_stats) - if not self._remote_is_newer(remote_update, local_file): - logger.debug("Local is newer than remote for lang %s" % lang) - return False - return True - - def _should_push_translation(self, lang, stats, local_file, force=False): - """Return whether a local translation file should be - pushed to Trasnifex. - - We use the following criteria for that: - - If user requested to force the upload. - - If language exists in Transifex. - - If local file is younger than the remote file. - - Args: - lang: The language code to check. - stats: The (global) statistics object. - local_file: The local translation file. - force: A boolean flag. - Returns: - True or False. - """ - if force: - logger.debug("Push translation due to -f.") - return True - try: - lang_stats = stats[lang] - except KeyError, e: - logger.debug("Language %s does not exist in Transifex." % lang) - return True - if local_file is not None: - remote_update = self._extract_updated(lang_stats) - if self._remote_is_newer(remote_update, local_file): - msg = "Remote translation is newer than local file for lang %s" - logger.debug(msg % lang) - return False - return True - - def _generate_timestamp(self, update_datetime): - """Generate a UNIX timestamp from the argument. - - Args: - update_datetime: The datetime in the format used by Transifex. - Returns: - A float, representing the timestamp that corresponds to the - argument. - """ - time_format = "%Y-%m-%d %H:%M:%S" - return time.mktime( - datetime.datetime( - *time.strptime(update_datetime, time_format)[0:5] - ).utctimetuple() - ) - - def _get_time_of_local_file(self, path): - """Get the modified time of the path_. - - Args: - path: The path we want the mtime for. - Returns: - The time as a timestamp or None, if the file does not exist - """ - if not os.path.exists(path): - return None - return time.mktime(time.gmtime(os.path.getmtime(path))) - - def _satisfies_min_translated(self, stats, mode=None): - """Check whether a translation fulfills the filter used for - minimum translated percentage. - - Args: - perc: The current translation percentage. - Returns: - True or False - """ - cur = self._extract_completed(stats, mode) - option_name = 'minimum_perc' - if self.minimum_perc is not None: - minimum_percent = self.minimum_perc - else: - global_minimum = int( - self.get_resource_option('main', option_name) or 0 - ) - resource_minimum = int( - self.get_resource_option( - self.resource, option_name - ) or global_minimum - ) - minimum_percent = resource_minimum - return cur >= minimum_percent - - def _remote_is_newer(self, remote_updated, local_file): - """Check whether the remote translation is newer that the local file. - - Args: - remote_updated: The date and time the translation was last - updated remotely. - local_file: The local file. - Returns: - True or False. - """ - if remote_updated is None: - logger.debug("No remote time") - return False - remote_time = self._generate_timestamp(remote_updated) - local_time = self._get_time_of_local_file( - self.get_full_path(local_file) - ) - logger.debug( - "Remote time is %s and local %s" % (remote_time, local_time) - ) - if local_time is not None and remote_time < local_time: - return False - return True - - @classmethod - def _extract_completed(cls, stats, mode=None): - """Extract the information for the translated percentage from the stats. - - Args: - stats: The stats object for a language as returned by Transifex. - mode: The mode of translations requested. - Returns: - The percentage of translation as integer. - """ - if mode == 'reviewed': - key = 'reviewed_percentage' - else: - key = 'completed' - try: - return int(stats[key][:-1]) - except KeyError, e: - return 0 - - @classmethod - def _extract_updated(cls, stats): - """Extract the information for the last update of a translation. - - Args: - stats: The stats object for a language as returned by Transifex. - Returns: - The last update field. - """ - try: - return stats['last_update'] - except KeyError, e: - return None - - def _new_translations_to_add(self, files, slang, lang_map, - stats, force=False): - """Return a list of translations which are new to the - local installation. - """ - new_translations = [] - timestamp = time.time() - langs = stats.keys() - logger.debug("Available languages are: %s" % langs) - - for lang in langs: - lang_exists = lang in files.keys() - lang_is_source = lang == slang - mapped_lang_exists = ( - lang in lang_map and lang_map[lang] in files.keys() - ) - if lang_exists or lang_is_source or mapped_lang_exists: - continue - if self._should_add_translation(lang, stats, force): - new_translations.append(lang) - return set(new_translations) - - def _get_stats_for_resource(self): - """Get the statistics information for a resource.""" - try: - r = self.do_url_request('resource_stats') - logger.debug("Statistics response is %s" % r) - stats = parse_json(r) - except urllib2.HTTPError, e: - logger.debug("Resource not found: %s" % e) - stats = {} - except Exception,e: - logger.debug("Network error: %s" % e) - raise - return stats - - def get_chosen_resources(self, resources): - """Get the resources the user selected. - - Support wildcards in the resources specified by the user. - - Args: - resources: A list of resources as specified in command-line or - an empty list. - Returns: - A list of resources. - """ - configured_resources = self.get_resource_list() - if not resources: - return configured_resources - - selected_resources = [] - for resource in resources: - found = False - for full_name in configured_resources: - if fnmatch.fnmatch(full_name, resource): - selected_resources.append(full_name) - found = True - if not found: - msg = "Specified resource '%s' does not exist." - raise Exception(msg % resource) - logger.debug("Operating on resources: %s" % selected_resources) - return selected_resources - - def _languages_to_pull(self, languages, files, lang_map, stats, force): - """Get a set of langauges to pull. - - Args: - languages: A list of languages the user selected in cmd. - files: A dictionary of current local translation files. - Returns: - A tuple of a set of existing languages and new translations. - """ - if not languages: - pull_languages = set([]) - pull_languages |= set(files.keys()) - mapped_files = [] - for lang in pull_languages: - if lang in lang_map.flip: - mapped_files.append(lang_map.flip[lang]) - pull_languages -= set(lang_map.flip.keys()) - pull_languages |= set(mapped_files) - return (pull_languages, set([])) - else: - pull_languages = [] - new_translations = [] - f_langs = files.keys() - for l in languages: - if l not in f_langs and not (l in lang_map and lang_map[l] in f_langs): - if self._should_add_translation(l, stats, force): - new_translations.append(l) - else: - if l in lang_map.keys(): - l = lang_map[l] - pull_languages.append(l) - return (set(pull_languages), set(new_translations)) - - def _extension_for(self, i18n_type): - """Return the extension used for the specified type.""" - try: - res = parse_json(self.do_url_request('formats')) - return res[i18n_type]['file-extensions'].split(',')[0] - except Exception,e: - logger.error(e) - return '' - - def _resource_exists(self, stats): - """Check if resource exists. - - Args: - stats: The statistics dict as returned by Tx. - Returns: - True, if the resource exists in the server. - """ - return bool(stats) - - def _create_resource(self, resource, pslug, fileinfo, filename, **kwargs): - """Create a resource. - - Args: - resource: The full resource name. - pslug: The slug of the project. - fileinfo: The information of the resource. - filename: The name of the file. - Raises: - URLError, in case of a problem. - """ - multipart = True - method = "POST" - api_call = 'create_resource' - - host = self.url_info['host'] - try: - username = self.txrc.get(host, 'username') - passwd = self.txrc.get(host, 'password') - token = self.txrc.get(host, 'token') - hostname = self.txrc.get(host, 'hostname') - except ConfigParser.NoSectionError: - raise Exception("No user credentials found for host %s. Edit" - " ~/.transifexrc and add the appropriate info in there." % - host) - - # Create the Url - kwargs['hostname'] = hostname - kwargs.update(self.url_info) - kwargs['project'] = pslug - url = (API_URLS[api_call] % kwargs).encode('UTF-8') - - opener = None - headers = None - req = None - - i18n_type = self._get_option(resource, 'type') - if i18n_type is None: - logger.error( - "Please define the resource type in .tx/config (eg. type = PO)." - " More info: http://bit.ly/txcl-rt" - ) - - opener = urllib2.build_opener(MultipartPostHandler) - data = { - "slug": fileinfo.split(';')[0], - "name": fileinfo.split(';')[0], - "uploaded_file": open(filename,'rb'), - "i18n_type": i18n_type - } - urllib2.install_opener(opener) - req = RequestWithMethod(url=url, data=data, method=method) - - base64string = base64.encodestring('%s:%s' % (username, passwd))[:-1] - authheader = "Basic %s" % base64string - req.add_header("Authorization", authheader) - - try: - fh = urllib2.urlopen(req) - except urllib2.HTTPError, e: - if e.code in [401, 403, 404]: - raise e - else: - # For other requests, we should print the message as well - raise Exception("Remote server replied: %s" % e.read()) - except urllib2.URLError, e: - error = e.args[0] - raise Exception("Remote server replied: %s" % error[1]) - - raw = fh.read() - fh.close() - return raw - - def _get_option(self, resource, option): - """Get the value for the option in the config file. - - If the option is not in the resource section, look for it in - the project. - - Args: - resource: The resource name. - option: The option the value of which we are interested in. - Returns: - The option value or None, if it does not exist. - """ - value = self.get_resource_option(resource, option) - if value is None: - if self.config.has_option('main', option): - return self.config.get('main', option) - return value - - def set_i18n_type(self, resources, i18n_type): - """Set the type for the specified resources.""" - self._set_resource_option(resources, key='type', value=i18n_type) - - def set_min_perc(self, resources, perc): - """Set the minimum percentage for the resources.""" - self._set_resource_option(resources, key='minimum_perc', value=perc) - - def set_default_mode(self, resources, mode): - """Set the default mode for the specified resources.""" - self._set_resource_option(resources, key='mode', value=mode) - - def _set_resource_option(self, resources, key, value): - """Set options in the config file. - - If resources is empty. set the option globally. - """ - if not resources: - self.config.set('main', key, value) - return - for r in resources: - self.config.set(r, key, value) diff --git a/third_party/transifex-client/txclib/urls.py b/third_party/transifex-client/txclib/urls.py deleted file mode 100644 index 0bb74fde..00000000 --- a/third_party/transifex-client/txclib/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -# These are the Transifex API urls - -API_URLS = { - 'get_resources': '%(hostname)s/api/2/project/%(project)s/resources/', - 'project_details': '%(hostname)s/api/2/project/%(project)s/?details', - 'resource_details': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/', - 'release_details': '%(hostname)s/api/2/project/%(project)s/release/%(release)s/', - 'pull_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file', - 'pull_reviewed_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file&mode=reviewed', - 'pull_translator_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file&mode=translated', - 'pull_developer_file': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/?file&mode=default', - 'resource_stats': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/stats/', - 'create_resource': '%(hostname)s/api/2/project/%(project)s/resources/', - 'push_source': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/content/', - 'push_translation': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/', - 'delete_translation': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/translation/%(language)s/', - 'formats': '%(hostname)s/api/2/formats/', - 'delete_resource': '%(hostname)s/api/2/project/%(project)s/resource/%(resource)s/', -} - - diff --git a/third_party/transifex-client/txclib/utils.py b/third_party/transifex-client/txclib/utils.py deleted file mode 100644 index 318bee91..00000000 --- a/third_party/transifex-client/txclib/utils.py +++ /dev/null @@ -1,259 +0,0 @@ -import os, sys, re, errno -try: - from json import loads as parse_json, dumps as compile_json -except ImportError: - from simplejson import loads as parse_json, dumps as compile_json -import urllib2 # This should go and instead use do_url_request everywhere - -from urls import API_URLS -from txclib.log import logger -from txclib.exceptions import UnknownCommandError - - -def find_dot_tx(path = os.path.curdir, previous = None): - """ - Return the path where .tx folder is found. - - The 'path' should be a DIRECTORY. - This process is functioning recursively from the current directory to each - one of the ancestors dirs. - """ - path = os.path.abspath(path) - if path == previous: - return None - joined = os.path.join(path, ".tx") - if os.path.isdir(joined): - return path - else: - return find_dot_tx(os.path.dirname(path), path) - - -################################################# -# Parse file filter expressions and create regex - -def regex_from_filefilter(file_filter, root_path = os.path.curdir): - """ - Create proper regex from expression - """ - # Force expr to be a valid regex expr (escaped) but keep intact - expr_re = re.escape(os.path.join(root_path, file_filter)) - expr_re = expr_re.replace("\\", '').replace( - '', '([^%(sep)s]+)' % { 'sep': re.escape(os.path.sep)}) - - return "^%s$" % expr_re - - -TX_URLS = { - 'resource': '(?Phttps?://(\w|\.|:|-)+)/projects/p/(?P(\w|-)+)/resource/(?P(\w|-)+)/?$', - 'release': '(?Phttps?://(\w|\.|:|-)+)/projects/p/(?P(\w|-)+)/r/(?P(\w|-)+)/?$', - 'project': '(?Phttps?://(\w|\.|:|-)+)/projects/p/(?P(\w|-)+)/?$', -} - - -def parse_tx_url(url): - """ - Try to match given url to any of the valid url patterns specified in - TX_URLS. If not match is found, we raise exception - """ - for type in TX_URLS.keys(): - pattern = TX_URLS[type] - m = re.match(pattern, url) - if m: - return type, m.groupdict() - - raise Exception("tx: Malformed url given. Please refer to our docs: http://bit.ly/txautor") - - -def get_details(api_call, username, password, *args, **kwargs): - """ - Get the tx project info through the API. - - This function can also be used to check the existence of a project. - """ - import base64 - url = (API_URLS[api_call] % (kwargs)).encode('UTF-8') - - req = urllib2.Request(url=url) - base64string = base64.encodestring('%s:%s' % (username, password))[:-1] - authheader = "Basic %s" % base64string - req.add_header("Authorization", authheader) - - try: - fh = urllib2.urlopen(req) - raw = fh.read() - fh.close() - remote_project = parse_json(raw) - except urllib2.HTTPError, e: - if e.code in [401, 403, 404]: - raise e - else: - # For other requests, we should print the message as well - raise Exception("Remote server replied: %s" % e.read()) - except urllib2.URLError, e: - error = e.args[0] - raise Exception("Remote server replied: %s" % error[1]) - - return remote_project - - -def valid_slug(slug): - """ - Check if a slug contains only valid characters. - - Valid chars include [-_\w] - """ - try: - a, b = slug.split('.') - except ValueError: - return False - else: - if re.match("^[A-Za-z0-9_-]*$", a) and re.match("^[A-Za-z0-9_-]*$", b): - return True - return False - - -def discover_commands(): - """ - Inspect commands.py and find all available commands - """ - import inspect - from txclib import commands - - command_table = {} - fns = inspect.getmembers(commands, inspect.isfunction) - - for name, fn in fns: - if name.startswith("cmd_"): - command_table.update({ - name.split("cmd_")[1]:fn - }) - - return command_table - - -def exec_command(command, *args, **kwargs): - """ - Execute given command - """ - commands = discover_commands() - try: - cmd_fn = commands[command] - except KeyError: - raise UnknownCommandError - cmd_fn(*args,**kwargs) - - -def mkdir_p(path): - try: - if path: - os.makedirs(path) - except OSError, exc: # Python >2.5 - if exc.errno == errno.EEXIST: - pass - else: - raise - - -def confirm(prompt='Continue?', default=True): - """ - Prompt the user for a Yes/No answer. - - Args: - prompt: The text displayed to the user ([Y/n] will be appended) - default: If the default value will be yes or no - """ - valid_yes = ['Y', 'y', 'Yes', 'yes', ] - valid_no = ['N', 'n', 'No', 'no', ] - if default: - prompt = prompt + '[Y/n]' - valid_yes.append('') - else: - prompt = prompt + '[y/N]' - valid_no.append('') - - ans = raw_input(prompt) - while (ans not in valid_yes and ans not in valid_no): - ans = raw_input(prompt) - - return ans in valid_yes - - -# Stuff for command line colored output - -COLORS = [ - 'BLACK', 'RED', 'GREEN', 'YELLOW', - 'BLUE', 'MAGENTA', 'CYAN', 'WHITE' -] - -DISABLE_COLORS = False - - -def color_text(text, color_name, bold=False): - """ - This command can be used to colorify command line output. If the shell - doesn't support this or the --disable-colors options has been set, it just - returns the plain text. - - Usage: - print "%s" % color_text("This text is red", "RED") - """ - if color_name in COLORS and not DISABLE_COLORS: - return '\033[%s;%sm%s\033[0m' % ( - int(bold), COLORS.index(color_name) + 30, text) - else: - return text - - -############################################## -# relpath implementation taken from Python 2.7 - -if not hasattr(os.path, 'relpath'): - if os.path is sys.modules.get('ntpath'): - def relpath(path, start=os.path.curdir): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - start_list = os.path.abspath(start).split(os.path.sep) - path_list = os.path.abspath(path).split(os.path.sep) - if start_list[0].lower() != path_list[0].lower(): - unc_path, rest = os.path.splitunc(path) - unc_start, rest = os.path.splitunc(start) - if bool(unc_path) ^ bool(unc_start): - raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" - % (path, start)) - else: - raise ValueError("path is on drive %s, start on drive %s" - % (path_list[0], start_list[0])) - # Work out how much of the filepath is shared by start and path. - for i in range(min(len(start_list), len(path_list))): - if start_list[i].lower() != path_list[i].lower(): - break - else: - i += 1 - - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) - - else: - # default to posixpath definition - def relpath(path, start=os.path.curdir): - """Return a relative version of a path""" - - if not path: - raise ValueError("no path specified") - - start_list = os.path.abspath(start).split(os.path.sep) - path_list = os.path.abspath(path).split(os.path.sep) - - # Work out how much of the filepath is shared by start and path. - i = len(os.path.commonprefix([start_list, path_list])) - - rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] - if not rel_list: - return os.path.curdir - return os.path.join(*rel_list) -else: - from os.path import relpath diff --git a/third_party/transifex-client/txclib/web.py b/third_party/transifex-client/txclib/web.py deleted file mode 100644 index a3cb3f00..00000000 --- a/third_party/transifex-client/txclib/web.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -import urllib2 -import itertools, mimetools, mimetypes -import platform -from txclib import get_version - -# Helper class to enable urllib2 to handle PUT/DELETE requests as well -class RequestWithMethod(urllib2.Request): - """Workaround for using DELETE with urllib2""" - def __init__(self, url, method, data=None, headers={}, - origin_req_host=None, unverifiable=False): - self._method = method - urllib2.Request.__init__(self, url, data=data, headers=headers, - origin_req_host=None, unverifiable=False) - - def get_method(self): - return self._method - -import urllib -import os, stat -from cStringIO import StringIO - -class Callable: - def __init__(self, anycallable): - self.__call__ = anycallable - -# Controls how sequences are uncoded. If true, elements may be given multiple -# values by assigning a sequence. -doseq = 1 - -class MultipartPostHandler(urllib2.BaseHandler): - handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first - - def http_request(self, request): - data = request.get_data() - if data is not None and type(data) != str: - v_files = [] - v_vars = [] - try: - for(key, value) in data.items(): - if type(value) == file: - v_files.append((key, value)) - else: - v_vars.append((key, value)) - except TypeError: - systype, value, traceback = sys.exc_info() - raise TypeError, "not a valid non-string sequence or mapping object", traceback - - if len(v_files) == 0: - data = urllib.urlencode(v_vars, doseq) - else: - boundary, data = self.multipart_encode(v_vars, v_files) - - contenttype = 'multipart/form-data; boundary=%s' % boundary - if(request.has_header('Content-Type') - and request.get_header('Content-Type').find('multipart/form-data') != 0): - print "Replacing %s with %s" % (request.get_header('content-type'), 'multipart/form-data') - request.add_unredirected_header('Content-Type', contenttype) - - request.add_data(data) - - return request - - def multipart_encode(vars, files, boundary = None, buf = None): - if boundary is None: - boundary = mimetools.choose_boundary() - if buf is None: - buf = StringIO() - for(key, value) in vars: - buf.write('--%s\r\n' % boundary) - buf.write('Content-Disposition: form-data; name="%s"' % key) - buf.write('\r\n\r\n' + value + '\r\n') - for(key, fd) in files: - file_size = os.fstat(fd.fileno())[stat.ST_SIZE] - filename = fd.name.split('/')[-1] - contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream' - buf.write('--%s\r\n' % boundary) - buf.write('Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)) - buf.write('Content-Type: %s\r\n' % contenttype) - # buffer += 'Content-Length: %s\r\n' % file_size - fd.seek(0) - buf.write('\r\n' + fd.read() + '\r\n') - buf.write('--' + boundary + '--\r\n\r\n') - buf = buf.getvalue() - return boundary, buf - multipart_encode = Callable(multipart_encode) - - https_request = http_request - - -def user_agent_identifier(): - """Return the user agent for the client.""" - client_info = (get_version(), platform.system(), platform.machine()) - return "txclient/%s (%s %s)" % client_info