Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b9ba7d33b | |||
| c6950493e6 | |||
| b71740d044 | |||
| dafe262c1c | |||
| acb1f75c5c | |||
| 03cc4f931d | |||
| 967d65b3f9 | |||
| ce6e7e2130 | |||
| 809eff7354 | |||
| 944cecce1f | |||
| 31abff58c1 | |||
| ef401378e2 | |||
| 2c0be54e61 | |||
| cb4b838082 | |||
| 6a6b40a89d | |||
| 1b7dbd91e6 | |||
| cb7a2d362f | |||
| ef2d7fe9d7 | |||
| 0bd96302e0 | |||
| e0a95a0314 | |||
| c2e7f7cbc2 | |||
| 631a2a4fe3 | |||
| 36e54f5d24 | |||
| 3506df6b22 | |||
| 2e1369d9a8 | |||
| a3495784f7 | |||
| 82c92c9031 | |||
| bd5d89c158 | |||
| 9cf96d86dd | |||
| f4c1e6a0f0 | |||
| 036d3846bc | |||
| 6696f0ff88 |
@@ -0,0 +1,9 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[syncthing-lite.stringsxml]
|
||||
file_filter = app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = app/src/main/res/values/strings.xml
|
||||
source_lang = en
|
||||
type = ANDROID
|
||||
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, 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, 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, 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, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, 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_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw, id:in
|
||||
@@ -13,8 +13,14 @@ example, mobile devices with limited storage available, wishing to access a sync
|
||||
|
||||
This project is based on [syncthing-java][3], a java implementation of Syncthing protocols.
|
||||
|
||||
|
||||
[<img alt="Get it on F-Droid" src="https://f-droid.org/badge/get-it-on.png" height="80">](https://f-droid.org/packages/net.syncthing.lite/)
|
||||
[<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" height="80">](https://play.google.com/store/apps/details?id=net.syncthing.lite)
|
||||
|
||||
## Translations
|
||||
|
||||
The project is translated on [Transifex](https://www.transifex.com/syncthing-android/syncthing-lite/).
|
||||
|
||||
## Building
|
||||
|
||||
The project uses a standard Android build, and requires the Android SDK. The easiest option is if
|
||||
|
||||
+31
-9
@@ -1,6 +1,7 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'com.github.ben-manes.versions'
|
||||
|
||||
android {
|
||||
compileSdkVersion 27
|
||||
@@ -8,10 +9,10 @@ android {
|
||||
dataBinding.enabled = true
|
||||
defaultConfig {
|
||||
applicationId "net.syncthing.lite"
|
||||
minSdkVersion 19
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 3
|
||||
versionName "0.1.2"
|
||||
versionCode 7
|
||||
versionName "0.2.0"
|
||||
multiDexEnabled true
|
||||
}
|
||||
sourceSets {
|
||||
@@ -21,26 +22,47 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile = {
|
||||
def path = System.getenv("SYNCTHING_LITE_RELEASE_STORE_FILE")
|
||||
return (path) ? file(path) : null
|
||||
}()
|
||||
storePassword System.getenv("SIGNING_PASSWORD") ?: ""
|
||||
keyAlias System.getenv("SYNCTHING_LITE_RELEASE_KEY_ALIAS") ?: ""
|
||||
keyPassword System.getenv("SIGNING_PASSWORD") ?: ""
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
exclude 'META-INF/*'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation "org.jetbrains.anko:anko-commons:0.10.4"
|
||||
implementation "org.jetbrains.anko:anko-commons:$anko_version"
|
||||
implementation "org.jetbrains.anko:anko-coroutines:$anko_version"
|
||||
kapt "com.android.databinding:compiler:$build_tools_version"
|
||||
implementation "com.android.support:appcompat-v7:$support_version"
|
||||
implementation "com.android.support:support-v4:$support_version"
|
||||
implementation "com.android.support:design:$support_version"
|
||||
implementation "com.android.support:cardview-v7:$support_version"
|
||||
implementation ("com.github.Nutomic:syncthing-java:0.1.2") {
|
||||
implementation ("com.github.Nutomic:syncthing-java:0.2.0") {
|
||||
exclude group: 'commons-logging', module:'commons-logging'
|
||||
exclude group: 'commons-codec'
|
||||
exclude group: 'org.apache.httpcomponents', module:'httpclient'
|
||||
exclude group: 'org.slf4j'
|
||||
exclude group: 'ch.qos.logback'
|
||||
}
|
||||
// NOTE: httpclient-android seems to be used via reflection somehow. Removing this dependency
|
||||
// silently breaks the app.
|
||||
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5.1'
|
||||
implementation 'sk.baka.slf4j:slf4j-handroid:1.7.26'
|
||||
implementation 'com.google.zxing:android-integration:3.3.0'
|
||||
implementation 'com.nononsenseapps:filepicker:2.5.2'
|
||||
implementation 'uk.co.markormesher:android-fab:2.0.0'
|
||||
implementation ('uk.co.markormesher:android-fab:2.0.0') {
|
||||
exclude group: "org.jetbrains.kotlin"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
<issue id="MissingTranslation" severity="ignore" />
|
||||
|
||||
<issue id="GoogleAppIndexingWarning" severity="ignore" />
|
||||
|
||||
<issue id="InvalidPackage" severity="ignore" />
|
||||
|
||||
<issue id="OldTargetApi" severity="ignore" />
|
||||
</lint>
|
||||
@@ -2,7 +2,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.syncthing.lite">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -20,8 +19,25 @@
|
||||
</activity>
|
||||
<activity android:name=".activities.FolderBrowserActivity"
|
||||
android:parentActivityName=".activities.MainActivity"/>
|
||||
<activity android:name=".activities.FilePickerActivity"
|
||||
android:parentActivityName=".activities.FolderBrowserActivity" />
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="net.syncthing.lite.fileprovider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
<provider
|
||||
android:name=".library.SyncthingProvider"
|
||||
android:authorities="net.syncthing.lite.documents"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package net.syncthing.lite.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import net.syncthing.lite.R
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Activity that allows selecting a directory in the local file system.
|
||||
*/
|
||||
class FilePickerActivity : SyncthingActivity(), AdapterView.OnItemClickListener {
|
||||
|
||||
private lateinit var mListView: ListView
|
||||
private lateinit var mFilesAdapter: FileAdapter
|
||||
private lateinit var mLocation: File
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.activity_folder_picker)
|
||||
mListView = findViewById(android.R.id.list)
|
||||
mListView.onItemClickListener = this
|
||||
mListView.emptyView = findViewById(android.R.id.empty)
|
||||
mFilesAdapter = FileAdapter(this)
|
||||
mListView.adapter = mFilesAdapter
|
||||
|
||||
displayFolder(Environment.getExternalStorageDirectory())
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the ListView to show the contents of the location in ``mLocation.peek()}.
|
||||
*/
|
||||
private fun displayFolder(location: File) {
|
||||
mLocation = location
|
||||
mFilesAdapter.clear()
|
||||
// In case we don't have read access to the location, just display nothing.
|
||||
val contents = location.listFiles() ?: arrayOf()
|
||||
|
||||
Arrays.sort(contents) { f1, f2 ->
|
||||
if (f1.isDirectory && f2.isFile)
|
||||
return@sort -1
|
||||
if (f1.isFile && f2.isDirectory)
|
||||
return@sort 1
|
||||
f1.name.compareTo(f2.name)
|
||||
}
|
||||
|
||||
for (f in contents) {
|
||||
mFilesAdapter.add(f)
|
||||
}
|
||||
mListView.adapter = mFilesAdapter
|
||||
}
|
||||
|
||||
override fun onItemClick(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
|
||||
val f = mFilesAdapter.getItem(i)
|
||||
if (f!!.isDirectory) {
|
||||
displayFolder(f)
|
||||
} else {
|
||||
val intent = Intent()
|
||||
intent.data = Uri.fromFile(f)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class FileAdapter(context: Context) : ArrayAdapter<File>(context, R.layout.item_folder_picker) {
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val view = super.getView(position, convertView, parent)
|
||||
val title = view.findViewById<TextView>(android.R.id.text1)
|
||||
val f = getItem(position)!!
|
||||
title.text = f.name
|
||||
val icon =
|
||||
if (f.isDirectory) R.drawable.ic_folder_black_24dp
|
||||
else R.drawable.ic_image_black_24dp
|
||||
title.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0)
|
||||
|
||||
return view
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (mLocation != Environment.getExternalStorageDirectory()) {
|
||||
displayFolder(mLocation.parentFile)
|
||||
} else {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +1,34 @@
|
||||
package net.syncthing.lite.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.databinding.DataBindingUtil
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.ActivityCompat
|
||||
import android.support.v4.content.ContextCompat
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import com.google.common.base.Preconditions.checkArgument
|
||||
import net.syncthing.java.bep.IndexBrowser
|
||||
import net.syncthing.java.core.beans.FileInfo
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.java.core.utils.FileInfoOrdering
|
||||
import net.syncthing.java.core.utils.PathUtils
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.adapters.FolderContentsAdapter
|
||||
import net.syncthing.lite.databinding.ActivityFolderBrowserBinding
|
||||
import net.syncthing.lite.library.DownloadFileTask
|
||||
import net.syncthing.lite.library.UploadFileTask
|
||||
import org.jetbrains.anko.intentFor
|
||||
import net.syncthing.lite.utils.FileDownloadDialog
|
||||
import net.syncthing.lite.utils.FileUploadDialog
|
||||
|
||||
class FolderBrowserActivity : SyncthingActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = "FolderBrowserActivity"
|
||||
private val REQUEST_WRITE_STORAGE = 142
|
||||
private val REQUEST_SELECT_UPLOAD_FILE = 171
|
||||
private const val TAG = "FolderBrowserActivity"
|
||||
private const val REQUEST_SELECT_UPLOAD_FILE = 171
|
||||
|
||||
val EXTRA_FOLDER_NAME = "folder_name"
|
||||
const val EXTRA_FOLDER_NAME = "folder_name"
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityFolderBrowserBinding
|
||||
private lateinit var indexBrowser: IndexBrowser
|
||||
private lateinit var adapter: FolderContentsAdapter
|
||||
private var runWhenPermissionsReceived: Runnable? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -52,14 +41,10 @@ class FolderBrowserActivity : SyncthingActivity() {
|
||||
navigateToFolder(fileInfo)
|
||||
}
|
||||
val folder = intent.getStringExtra(EXTRA_FOLDER_NAME)
|
||||
indexBrowser = syncthingClient().indexHandler
|
||||
.newIndexBrowserBuilder()
|
||||
.setOrdering(FileInfoOrdering.ALPHA_ASC_DIR_FIRST)
|
||||
.includeParentInList(true)
|
||||
.allowParentInRoot(true)
|
||||
.setFolder(folder)
|
||||
.build()
|
||||
indexBrowser.setOnFolderChangedListener(this::onFolderChanged)
|
||||
libraryHandler?.syncthingClient {
|
||||
indexBrowser = it.indexHandler.newIndexBrowser(folder, true, true)
|
||||
indexBrowser.setOnFolderChangedListener(this::onFolderChanged)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -78,30 +63,32 @@ class FolderBrowserActivity : SyncthingActivity() {
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
if (requestCode == REQUEST_SELECT_UPLOAD_FILE && resultCode == Activity.RESULT_OK) {
|
||||
UploadFileTask(this, syncthingClient(), intent!!.data, indexBrowser.folder,
|
||||
indexBrowser.currentPath, { this.updateFolderListView() }).uploadFile()
|
||||
libraryHandler?.syncthingClient { syncthingClient ->
|
||||
FileUploadDialog(this@FolderBrowserActivity, syncthingClient, intent!!.data,
|
||||
indexBrowser.folder, indexBrowser.currentPath,
|
||||
{ showFolderListView(indexBrowser.currentPath) } )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFolderListView(path: String) {
|
||||
indexBrowser.navigateToNearestPath(path)
|
||||
navigateToFolder(indexBrowser.currentPathInfo)
|
||||
navigateToFolder(indexBrowser.currentPathInfo())
|
||||
}
|
||||
|
||||
private fun navigateToFolder(fileInfo: FileInfo) {
|
||||
Log.d(TAG, "navigate to path = '" + fileInfo.path + "' from path = '" + indexBrowser.currentPath + "'")
|
||||
if (indexBrowser.isRoot && PathUtils.isParent(fileInfo.path)) {
|
||||
if (indexBrowser.isRoot() && PathUtils.isParent(fileInfo.path)) {
|
||||
finish()
|
||||
} else {
|
||||
if (fileInfo.isDirectory) {
|
||||
if (fileInfo.isDirectory()) {
|
||||
indexBrowser.navigateTo(fileInfo)
|
||||
Log.d(TAG, "load folder cache bg")
|
||||
binding.listView.visibility = View.GONE
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
Log.i(TAG, "pulling file = " + fileInfo)
|
||||
executeWithPermissions(
|
||||
Runnable { DownloadFileTask(this, syncthingClient(), fileInfo).downloadFile() })
|
||||
libraryHandler?.syncthingClient { FileDownloadDialog(this, it, fileInfo) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,17 +100,17 @@ class FolderBrowserActivity : SyncthingActivity() {
|
||||
val list = indexBrowser.listFiles()
|
||||
Log.i("navigateToFolder", "list for path = '" + indexBrowser.currentPath + "' list = " + list.size + " records")
|
||||
Log.d("navigateToFolder", "list for path = '" + indexBrowser.currentPath + "' list = " + list)
|
||||
checkArgument(!list.isEmpty())//list must contain at least the 'parent' path
|
||||
assert(!list.isEmpty())//list must contain at least the 'parent' path
|
||||
adapter.clear()
|
||||
adapter.addAll(list)
|
||||
adapter.notifyDataSetChanged()
|
||||
binding.listView.setSelection(0)
|
||||
val title =
|
||||
if (indexBrowser.isRoot)
|
||||
folderBrowser()?.getFolderInfo(indexBrowser.folder)?.label
|
||||
else
|
||||
indexBrowser.currentPathInfo.fileName
|
||||
supportActionBar!!.setTitle(title)
|
||||
if (indexBrowser.isRoot())
|
||||
libraryHandler?.folderBrowser {
|
||||
supportActionBar?.title = it.getFolderInfo(indexBrowser.folder)?.label
|
||||
}
|
||||
else
|
||||
supportActionBar?.title = indexBrowser.currentPathInfo().fileName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,49 +119,14 @@ class FolderBrowserActivity : SyncthingActivity() {
|
||||
}
|
||||
|
||||
private fun showUploadHereDialog() {
|
||||
executeWithPermissions(Runnable {
|
||||
startActivityForResult(intentFor<FilePickerActivity>(), REQUEST_SELECT_UPLOAD_FILE)
|
||||
})
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
intent.type = "*/*"
|
||||
startActivityForResult(intent, REQUEST_SELECT_UPLOAD_FILE)
|
||||
}
|
||||
|
||||
override fun onIndexUpdateProgress(folder: FolderInfo, percentage: Int) {
|
||||
binding.indexUpdate.visibility = View.VISIBLE
|
||||
binding.indexUpdateLabel.text = (getString(R.string.index_update_folder)
|
||||
+ folder.label + " " + percentage + getString(R.string.index_update_percent_synchronized))
|
||||
override fun onIndexUpdateComplete(folderInfo: FolderInfo) {
|
||||
super.onIndexUpdateComplete(folderInfo)
|
||||
updateFolderListView()
|
||||
}
|
||||
|
||||
override fun onIndexUpdateComplete() {
|
||||
binding.indexUpdate.visibility = View.GONE
|
||||
updateFolderListView()
|
||||
}
|
||||
|
||||
private fun executeWithPermissions(runnable: Runnable) {
|
||||
val permissionState = ContextCompat.checkSelfPermission(this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
if (permissionState != PackageManager.PERMISSION_GRANTED) {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
|
||||
REQUEST_WRITE_STORAGE)
|
||||
runWhenPermissionsReceived = runnable
|
||||
} else {
|
||||
runnable.run()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
|
||||
grantResults: IntArray) {
|
||||
when (requestCode) {
|
||||
REQUEST_WRITE_STORAGE -> {
|
||||
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||
Toast.makeText(this, R.string.toast_write_storage_permission_required,
|
||||
Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
runWhenPermissionsReceived!!.run()
|
||||
}
|
||||
runWhenPermissionsReceived = null
|
||||
}
|
||||
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,13 @@ import android.os.Bundle
|
||||
import android.support.v7.app.ActionBarDrawerToggle
|
||||
import android.view.Gravity
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.databinding.ActivityMainBinding
|
||||
import net.syncthing.lite.fragments.DevicesFragment
|
||||
import net.syncthing.lite.fragments.FoldersFragment
|
||||
import net.syncthing.lite.fragments.SyncthingFragment
|
||||
import net.syncthing.lite.library.UpdateIndexTask
|
||||
|
||||
class MainActivity : SyncthingActivity() {
|
||||
|
||||
@@ -50,10 +49,6 @@ class MainActivity : SyncthingActivity() {
|
||||
onNavigationItemSelectedListener(selection)
|
||||
}
|
||||
|
||||
override fun onLibraryLoaded() {
|
||||
currentFragment?.onLibraryLoaded()
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
drawerToggle!!.onConfigurationChanged(newConfig)
|
||||
@@ -72,7 +67,6 @@ class MainActivity : SyncthingActivity() {
|
||||
when (menuItem.itemId) {
|
||||
R.id.folders -> setContentFragment(FoldersFragment())
|
||||
R.id.devices -> setContentFragment(DevicesFragment())
|
||||
R.id.update_index -> UpdateIndexTask(this, syncthingClient()).updateIndex()
|
||||
R.id.clear_index -> AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.clear_cache_and_index_title))
|
||||
.setMessage(getString(R.string.clear_cache_and_index_body))
|
||||
@@ -94,17 +88,9 @@ class MainActivity : SyncthingActivity() {
|
||||
}
|
||||
|
||||
private fun cleanCacheAndIndex() {
|
||||
syncthingClient().clearCacheAndIndex()
|
||||
recreate()
|
||||
}
|
||||
|
||||
override fun onIndexUpdateProgress(folder: FolderInfo, percentage: Int) {
|
||||
binding.indexUpdate.visibility = View.VISIBLE
|
||||
binding.indexUpdateLabel.text = (getString(R.string.index_update_folder) + " "
|
||||
+ folder.label + " " + percentage + getString(R.string.index_update_percent_synchronized))
|
||||
}
|
||||
|
||||
override fun onIndexUpdateComplete() {
|
||||
binding.indexUpdate.visibility = View.GONE
|
||||
async(UI) {
|
||||
libraryHandler?.syncthingClient { it.clearCacheAndIndex() }
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,85 +3,65 @@ package net.syncthing.lite.activities
|
||||
import android.app.AlertDialog
|
||||
import android.databinding.DataBindingUtil
|
||||
import android.os.Bundle
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import android.view.LayoutInflater
|
||||
import net.syncthing.java.bep.FolderBrowser
|
||||
import net.syncthing.java.client.SyncthingClient
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.java.core.configuration.ConfigurationService
|
||||
import net.syncthing.lite.BuildConfig
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.databinding.DialogLoadingBinding
|
||||
import net.syncthing.lite.library.InitLibraryTask
|
||||
import net.syncthing.lite.library.LibraryHandler
|
||||
import org.jetbrains.anko.contentView
|
||||
import org.slf4j.impl.HandroidLoggerAdapter
|
||||
|
||||
abstract class SyncthingActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
private var activityCount = 0
|
||||
private var libraryHandler: LibraryHandler? = null
|
||||
}
|
||||
|
||||
var libraryHandler: LibraryHandler? = null
|
||||
private set
|
||||
private var loadingDialog: AlertDialog? = null
|
||||
|
||||
fun syncthingClient(): SyncthingClient {
|
||||
if (isDestroyed)
|
||||
throw IllegalStateException("activity is already destroyed")
|
||||
return libraryHandler!!.syncthingClient!!
|
||||
}
|
||||
|
||||
fun configuration(): ConfigurationService {
|
||||
if (isDestroyed)
|
||||
throw IllegalStateException("activity is already destroyed")
|
||||
return libraryHandler!!.configuration!!
|
||||
}
|
||||
|
||||
fun folderBrowser(): FolderBrowser? {
|
||||
if (isDestroyed)
|
||||
throw IllegalStateException("activity is already destroyed")
|
||||
return libraryHandler?.folderBrowser
|
||||
}
|
||||
private var snackBar: Snackbar? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
HandroidLoggerAdapter.DEBUG = BuildConfig.DEBUG
|
||||
activityCount++
|
||||
if (libraryHandler == null) {
|
||||
val binding = DataBindingUtil.inflate<DialogLoadingBinding>(
|
||||
LayoutInflater.from(this), R.layout.dialog_loading, null, false)
|
||||
binding.loadingText.text = getString(R.string.loading_config_starting_syncthing_client)
|
||||
loadingDialog = AlertDialog.Builder(this)
|
||||
.setCancelable(false)
|
||||
.setView(binding.root)
|
||||
.show()
|
||||
InitLibraryTask(this, this::onLibraryLoaded, this::onIndexUpdateProgress, this::onIndexUpdateComplete)
|
||||
}
|
||||
|
||||
val binding = DataBindingUtil.inflate<DialogLoadingBinding>(
|
||||
LayoutInflater.from(this), R.layout.dialog_loading, null, false)
|
||||
binding.loadingText.text = getString(R.string.loading_config_starting_syncthing_client)
|
||||
loadingDialog = AlertDialog.Builder(this)
|
||||
.setCancelable(false)
|
||||
.setView(binding.root)
|
||||
.show()
|
||||
LibraryHandler(this, this::onLibraryLoadedInternal,
|
||||
this::onIndexUpdateProgress, this::onIndexUpdateComplete)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
activityCount--
|
||||
Thread {
|
||||
if (activityCount == 0) {
|
||||
libraryHandler?.destroy()
|
||||
libraryHandler = null
|
||||
}
|
||||
}.start()
|
||||
libraryHandler?.close()
|
||||
loadingDialog?.dismiss()
|
||||
}
|
||||
|
||||
private fun onLibraryLoaded(libraryHandler: LibraryHandler) {
|
||||
if (activityCount == 0)
|
||||
return
|
||||
|
||||
SyncthingActivity.libraryHandler = libraryHandler
|
||||
loadingDialog!!.cancel()
|
||||
private fun onLibraryLoadedInternal(libraryHandler: LibraryHandler) {
|
||||
this.libraryHandler = libraryHandler
|
||||
if (!isDestroyed) {
|
||||
loadingDialog?.dismiss()
|
||||
}
|
||||
onLibraryLoaded()
|
||||
}
|
||||
|
||||
open fun onIndexUpdateProgress(folder: FolderInfo, percentage: Int) {}
|
||||
open fun onIndexUpdateProgress(folderInfo: FolderInfo, percentage: Int) {
|
||||
val message = getString(R.string.index_update_progress_label, folderInfo.label, percentage)
|
||||
snackBar?.setText(message) ?: run {
|
||||
snackBar = Snackbar.make(contentView!!, message, Snackbar.LENGTH_INDEFINITE)
|
||||
snackBar?.show()
|
||||
}
|
||||
}
|
||||
|
||||
open fun onIndexUpdateComplete() {}
|
||||
open fun onIndexUpdateComplete(folderInfo: FolderInfo) {
|
||||
snackBar?.dismiss()
|
||||
snackBar = null
|
||||
}
|
||||
|
||||
open fun onLibraryLoaded() {}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,12 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import com.google.common.collect.Lists
|
||||
import net.syncthing.java.core.beans.DeviceStats
|
||||
import net.syncthing.java.core.beans.DeviceInfo
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.databinding.ListviewDeviceBinding
|
||||
|
||||
class DevicesAdapter(context: Context) :
|
||||
ArrayAdapter<DeviceStats>(context, R.layout.listview_device, Lists.newArrayList()) {
|
||||
ArrayAdapter<DeviceInfo>(context, R.layout.listview_device, mutableListOf()) {
|
||||
|
||||
override fun getView(position: Int, v: View?, parent: ViewGroup): View {
|
||||
val binding: ListviewDeviceBinding
|
||||
@@ -24,10 +23,10 @@ class DevicesAdapter(context: Context) :
|
||||
val deviceStats = getItem(position)
|
||||
binding.deviceName.text = deviceStats!!.name
|
||||
val icon =
|
||||
when (deviceStats.status) {
|
||||
DeviceStats.DeviceStatus.OFFLINE -> R.drawable.ic_laptop_red_24dp
|
||||
DeviceStats.DeviceStatus.ONLINE_INACTIVE,
|
||||
DeviceStats.DeviceStatus.ONLINE_ACTIVE -> R.drawable.ic_laptop_green_24dp
|
||||
if (deviceStats.isConnected!!) {
|
||||
R.drawable.ic_laptop_green_24dp
|
||||
} else {
|
||||
R.drawable.ic_laptop_red_24dp
|
||||
}
|
||||
binding.deviceIcon.setImageResource(icon)
|
||||
return binding.root
|
||||
|
||||
@@ -7,14 +7,13 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import com.google.common.collect.Lists
|
||||
import net.syncthing.java.core.beans.FileInfo
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.databinding.ListviewFileBinding
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
class FolderContentsAdapter(context: Context) :
|
||||
ArrayAdapter<FileInfo>(context, R.layout.listview_file, Lists.newArrayList()) {
|
||||
ArrayAdapter<FileInfo>(context, R.layout.listview_file, mutableListOf()) {
|
||||
|
||||
override fun getView(position: Int, v: View?, parent: ViewGroup): View {
|
||||
val binding: ListviewFileBinding =
|
||||
@@ -25,15 +24,15 @@ class FolderContentsAdapter(context: Context) :
|
||||
}
|
||||
val fileInfo = getItem(position)
|
||||
binding.fileLabel.text = fileInfo!!.fileName
|
||||
if (fileInfo.isDirectory) {
|
||||
if (fileInfo.isDirectory()) {
|
||||
binding.fileIcon.setImageResource(R.drawable.ic_folder_black_24dp)
|
||||
binding.fileSize.visibility = View.GONE
|
||||
} else {
|
||||
binding.fileIcon.setImageResource(R.drawable.ic_image_black_24dp)
|
||||
binding.fileSize.visibility = View.VISIBLE
|
||||
binding.fileSize.text = (FileUtils.byteCountToDisplaySize(fileInfo.size!!)
|
||||
+ context.getString(R.string.last_modified)
|
||||
+ DateUtils.getRelativeDateTimeString(context, fileInfo.lastModified.time, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0))
|
||||
binding.fileSize.text = context.getString(R.string.file_info,
|
||||
FileUtils.byteCountToDisplaySize(fileInfo.size!!),
|
||||
DateUtils.getRelativeDateTimeString(context, fileInfo.lastModified.time, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0))
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.java.core.beans.FolderStats
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.databinding.ListviewFolderBinding
|
||||
import org.apache.commons.lang3.tuple.Pair
|
||||
|
||||
class FoldersListAdapter(context: Context?, list: List<Pair<FolderInfo, FolderStats>>) :
|
||||
ArrayAdapter<Pair<FolderInfo, FolderStats>>(context, R.layout.listview_folder, list) {
|
||||
@@ -23,15 +22,13 @@ class FoldersListAdapter(context: Context?, list: List<Pair<FolderInfo, FolderSt
|
||||
} else {
|
||||
DataBindingUtil.bind(v)
|
||||
}
|
||||
val folderInfo = getItem(position)!!.left
|
||||
val folderStats = getItem(position)!!.right
|
||||
binding.folderName.text = "${folderInfo.label} (${folderInfo.folder})"
|
||||
binding.folderLastmodInfo.text =
|
||||
if (folderStats.lastUpdate == null)
|
||||
context.getString(R.string.last_modified_unknown)
|
||||
else context.getString(R.string.last_modified_known) + " " +
|
||||
DateUtils.getRelativeDateTimeString(context, folderStats.lastUpdate.time, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0)
|
||||
binding.folderContentInfo.text = "${folderStats.describeSize()}, ${folderStats.fileCount} files, ${folderStats.dirCount} dirs"
|
||||
val folderInfo = getItem(position)!!.first
|
||||
val folderStats = getItem(position)!!.second
|
||||
binding.folderName.text = context.getString(R.string.folder_label_format, folderInfo.label, folderInfo.folderId)
|
||||
|
||||
binding.folderLastmodInfo.text = context.getString(R.string.last_modified_time,
|
||||
DateUtils.getRelativeDateTimeString(context, folderStats.lastUpdate.time, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0))
|
||||
binding.folderContentInfo.text = context.getString(R.string.folder_content_info, folderStats.describeSize(), folderStats.fileCount, folderStats.dirCount)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.databinding.DataBindingUtil
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -13,25 +12,22 @@ import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import net.syncthing.java.core.beans.DeviceId
|
||||
import net.syncthing.java.core.beans.DeviceInfo
|
||||
import net.syncthing.java.core.beans.DeviceStats
|
||||
import net.syncthing.java.core.security.KeystoreHandler
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.adapters.DevicesAdapter
|
||||
import net.syncthing.lite.databinding.FragmentDevicesBinding
|
||||
import net.syncthing.lite.library.UpdateIndexTask
|
||||
import net.syncthing.lite.utils.FragmentIntentIntegrator
|
||||
import org.apache.commons.lang3.StringUtils.isBlank
|
||||
import uk.co.markormesher.android_fab.SpeedDialMenuAdapter
|
||||
import uk.co.markormesher.android_fab.SpeedDialMenuItem
|
||||
import java.io.IOException
|
||||
import java.security.InvalidParameterException
|
||||
|
||||
class DevicesFragment : SyncthingFragment() {
|
||||
|
||||
companion object {
|
||||
private val TAG = "DevicesFragment"
|
||||
}
|
||||
|
||||
private lateinit var binding: FragmentDevicesBinding
|
||||
private lateinit var adapter: DevicesAdapter
|
||||
|
||||
@@ -43,7 +39,21 @@ class DevicesFragment : SyncthingFragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onLibraryLoadedAndActivityCreated() {
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
libraryHandler?.syncthingClient { it.addOnConnectionChangedListener(this::onConnectionChanged) }
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
libraryHandler?.syncthingClient { it.removeOnConnectionChangedListener(this::onConnectionChanged) }
|
||||
}
|
||||
|
||||
private fun onConnectionChanged(deviceId: DeviceId) {
|
||||
updateDeviceList()
|
||||
}
|
||||
|
||||
override fun onLibraryLoaded() {
|
||||
initDeviceList()
|
||||
updateDeviceList()
|
||||
}
|
||||
@@ -52,23 +62,31 @@ class DevicesFragment : SyncthingFragment() {
|
||||
adapter = DevicesAdapter(context!!)
|
||||
binding.list.adapter = adapter
|
||||
binding.list.setOnItemLongClickListener { _, _, position, _ ->
|
||||
val deviceId = (binding.list.getItemAtPosition(position) as DeviceStats).deviceId
|
||||
val device = adapter.getItem(position)
|
||||
AlertDialog.Builder(context)
|
||||
.setTitle(getString(R.string.remove_device_title) + " " + deviceId.substring(0, 7) + "?")
|
||||
.setMessage(getString(R.string.remove_device_body_1) + " " + deviceId.substring(0, 7) + " " + getString(R.string.remove_device_body_2))
|
||||
.setTitle(getString(R.string.remove_device_title, device.name))
|
||||
.setMessage(getString(R.string.remove_device_message, device.deviceId.deviceId.substring(0, 7)))
|
||||
.setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
getSyncthingActivity().configuration().edit().removePeer(deviceId).persistLater() }
|
||||
libraryHandler?.configuration { config ->
|
||||
config.peers = config.peers.filterNot { it.deviceId == device.deviceId }.toSet()
|
||||
config.persistLater()
|
||||
updateDeviceList()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show()
|
||||
Log.d(TAG, "showFolderListView delete device = '$deviceId'")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDeviceList() {
|
||||
adapter.clear()
|
||||
adapter.addAll(getSyncthingActivity().syncthingClient().devicesHandler.deviceStatsList)
|
||||
adapter.notifyDataSetChanged()
|
||||
async(UI) {
|
||||
libraryHandler?.syncthingClient { syncthingClient ->
|
||||
adapter.clear()
|
||||
adapter.addAll(syncthingClient.getPeerStatus())
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
@@ -82,22 +100,26 @@ class DevicesFragment : SyncthingFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun importDeviceId(deviceId: String) {
|
||||
try {
|
||||
KeystoreHandler.validateDeviceId(deviceId)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Toast.makeText(context, R.string.invalid_device_id, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
private fun importDeviceId(deviceIdString: String) {
|
||||
libraryHandler?.configuration { configuration ->
|
||||
async(UI) {
|
||||
val deviceId =
|
||||
try {
|
||||
DeviceId(deviceIdString)
|
||||
} catch (e: IOException) {
|
||||
Toast.makeText(this@DevicesFragment.context, R.string.invalid_device_id, Toast.LENGTH_SHORT).show()
|
||||
return@async
|
||||
}
|
||||
|
||||
val modified = getSyncthingActivity().configuration().edit().addPeers(DeviceInfo(deviceId, null))
|
||||
if (modified) {
|
||||
getSyncthingActivity().configuration().edit().persistLater()
|
||||
Toast.makeText(context, getString(R.string.device_import_success) + " " + deviceId, Toast.LENGTH_SHORT).show()
|
||||
updateDeviceList()//TODO remove this if event triggered (and handler trigger update)
|
||||
UpdateIndexTask(context!!, getSyncthingActivity().syncthingClient()).updateIndex()
|
||||
} else {
|
||||
Toast.makeText(context, getString(R.string.device_already_known) + " " + deviceId, Toast.LENGTH_SHORT).show()
|
||||
if (!configuration.peerIds.contains(deviceId)) {
|
||||
configuration.peers = configuration.peers + DeviceInfo(deviceId, null)
|
||||
configuration.persistLater()
|
||||
Toast.makeText(this@DevicesFragment.context, getString(R.string.device_import_success, deviceId), Toast.LENGTH_SHORT).show()
|
||||
updateDeviceList()//TODO remove this if event triggered (and handler trigger update)
|
||||
} else {
|
||||
Toast.makeText(this@DevicesFragment.context, getString(R.string.device_already_known, deviceId), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,23 +6,16 @@ import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.common.collect.Lists
|
||||
import com.google.common.collect.Ordering
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.java.core.beans.FolderStats
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.activities.FolderBrowserActivity
|
||||
import net.syncthing.lite.adapters.FoldersListAdapter
|
||||
import net.syncthing.lite.databinding.FragmentFoldersBinding
|
||||
import org.apache.commons.lang3.tuple.Pair
|
||||
import org.jetbrains.anko.intentFor
|
||||
import java.util.*
|
||||
|
||||
class FoldersFragment : SyncthingFragment() {
|
||||
|
||||
companion object {
|
||||
private val TAG = "FoldersFragment"
|
||||
}
|
||||
private val TAG = "FoldersFragment"
|
||||
|
||||
private lateinit var binding: FragmentFoldersBinding
|
||||
|
||||
@@ -33,21 +26,26 @@ class FoldersFragment : SyncthingFragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onLibraryLoadedAndActivityCreated() {
|
||||
override fun onLibraryLoaded() {
|
||||
showAllFoldersListView()
|
||||
}
|
||||
|
||||
private fun showAllFoldersListView() {
|
||||
val list = Lists.newArrayList(getSyncthingActivity().folderBrowser()!!.folderInfoAndStatsList)
|
||||
Collections.sort(list, Ordering.natural<Comparable<String>>()
|
||||
.onResultOf<Pair<FolderInfo, FolderStats>> { input -> input?.left?.label })
|
||||
Log.i(TAG, "list folders = " + list + " (" + list.size + " records)")
|
||||
val adapter = FoldersListAdapter(context, list)
|
||||
binding.list.adapter = adapter
|
||||
binding.list.setOnItemClickListener { _, _, position, _ ->
|
||||
val folder = adapter.getItem(position)!!.left.folder
|
||||
val intent = context?.intentFor<FolderBrowserActivity>(FolderBrowserActivity.EXTRA_FOLDER_NAME to folder)
|
||||
startActivity(intent)
|
||||
libraryHandler?.folderBrowser { folderBrowser ->
|
||||
val list = folderBrowser.folderInfoAndStatsList()
|
||||
Log.i(TAG, "list folders = " + list + " (" + list.size + " records)")
|
||||
val adapter = FoldersListAdapter(context, list)
|
||||
binding.list.adapter = adapter
|
||||
binding.list.setOnItemClickListener { _, _, position, _ ->
|
||||
val folder = adapter.getItem(position)!!.first.folderId
|
||||
val intent = context?.intentFor<FolderBrowserActivity>(FolderBrowserActivity.EXTRA_FOLDER_NAME to folder)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIndexUpdateComplete(folderInfo: FolderInfo) {
|
||||
super.onIndexUpdateComplete(folderInfo)
|
||||
showAllFoldersListView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,33 @@ package net.syncthing.lite.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import net.syncthing.lite.activities.SyncthingActivity
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.lite.library.LibraryHandler
|
||||
|
||||
/**
|
||||
* Handle connection to [[SyncthingActivity]], and make sure device rotation are handled correctly.
|
||||
*/
|
||||
abstract class SyncthingFragment : Fragment() {
|
||||
|
||||
protected fun getSyncthingActivity() = activity as SyncthingActivity
|
||||
var libraryHandler: LibraryHandler? = null
|
||||
private set
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
checkConditions()
|
||||
LibraryHandler(context!!, this::onLibraryLoadedInternal, this::onIndexUpdateProgress,
|
||||
this::onIndexUpdateComplete)
|
||||
}
|
||||
|
||||
fun onLibraryLoaded() {
|
||||
checkConditions()
|
||||
private fun onLibraryLoadedInternal(libraryHandler: LibraryHandler) {
|
||||
this.libraryHandler = libraryHandler
|
||||
onLibraryLoaded()
|
||||
}
|
||||
|
||||
private fun checkConditions() {
|
||||
if (activity != null && getSyncthingActivity().folderBrowser() != null ) {
|
||||
onLibraryLoadedAndActivityCreated()
|
||||
}
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
libraryHandler?.close()
|
||||
}
|
||||
|
||||
open fun onLibraryLoadedAndActivityCreated() {
|
||||
}
|
||||
open fun onLibraryLoaded() {}
|
||||
|
||||
open fun onIndexUpdateProgress(folderInfo: FolderInfo, percentage: Int) {}
|
||||
|
||||
open fun onIndexUpdateComplete(folderInfo: FolderInfo) {}
|
||||
}
|
||||
@@ -1,107 +1,49 @@
|
||||
package net.syncthing.lite.library
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.support.annotation.StringRes
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import net.syncthing.java.bep.BlockPuller
|
||||
import net.syncthing.java.client.SyncthingClient
|
||||
import net.syncthing.java.core.beans.FileInfo
|
||||
import net.syncthing.lite.R
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.io.FilenameUtils
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.newTask
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class DownloadFileTask(private val mContext: Context, private val mSyncthingClient: SyncthingClient,
|
||||
private val mFileInfo: FileInfo) {
|
||||
class DownloadFileTask(private val context: Context, syncthingClient: SyncthingClient,
|
||||
private val fileInfo: FileInfo,
|
||||
private val onProgress: (DownloadFileTask, BlockPuller.FileDownloadObserver) -> Unit,
|
||||
private val onComplete: (File) -> Unit,
|
||||
private val onError: () -> Unit) {
|
||||
|
||||
private val TAG = "DownloadFileTask"
|
||||
private lateinit var progressDialog: ProgressDialog
|
||||
private var cancelled = false
|
||||
private val Tag = "DownloadFileTask"
|
||||
private var isCancelled = false
|
||||
|
||||
fun downloadFile() {
|
||||
showDialog()
|
||||
mSyncthingClient.pullFile(mFileInfo, { observer ->
|
||||
onProgress(observer)
|
||||
init {
|
||||
syncthingClient.getBlockPuller(fileInfo.folder, { blockPuller ->
|
||||
val observer = blockPuller.pullFile(fileInfo)
|
||||
onProgress(this, observer)
|
||||
try {
|
||||
while (!observer.isCompleted) {
|
||||
if (cancelled)
|
||||
return@pullFile
|
||||
while (!observer.isCompleted()) {
|
||||
if (isCancelled)
|
||||
return@getBlockPuller
|
||||
|
||||
observer.waitForProgressUpdate()
|
||||
Log.i("pullFile", "download progress = " + observer.progressMessage)
|
||||
onProgress(observer)
|
||||
Log.i("pullFile", "download progress = " + observer.progressMessage())
|
||||
onProgress(this, observer)
|
||||
}
|
||||
|
||||
val outputDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||
val outputFile = File(outputDir, mFileInfo.fileName)
|
||||
FileUtils.copyInputStreamToFile(observer.inputStream, outputFile)
|
||||
Log.i(TAG, "downloaded file = " + mFileInfo.path)
|
||||
val outputFile = File("${context.externalCacheDir}/${fileInfo.folder}/${fileInfo.path}")
|
||||
FileUtils.copyInputStreamToFile(observer.inputStream(), outputFile)
|
||||
Log.i(Tag, "Downloaded file $fileInfo")
|
||||
onComplete(outputFile)
|
||||
} catch (e: IOException) {
|
||||
onError(R.string.toast_file_download_failed)
|
||||
Log.w(TAG, "Failed to download file " + mFileInfo, e)
|
||||
} catch (e: InterruptedException) {
|
||||
onError(R.string.toast_file_download_failed)
|
||||
Log.w(TAG, "Failed to download file " + mFileInfo, e)
|
||||
onError()
|
||||
Log.w(Tag, "Failed to download file $fileInfo", e)
|
||||
}
|
||||
}) { onError(R.string.toast_file_download_failed) }
|
||||
}, { onError() })
|
||||
}
|
||||
|
||||
private fun showDialog() {
|
||||
progressDialog = ProgressDialog(mContext)
|
||||
progressDialog.setMessage(mContext.getString(R.string.dialog_downloading_file, mFileInfo.fileName))
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
progressDialog.setCancelable(true)
|
||||
progressDialog.setOnCancelListener { cancelled = true }
|
||||
progressDialog.isIndeterminate = true
|
||||
progressDialog.show()
|
||||
}
|
||||
|
||||
private fun onProgress(fileDownloadObserver: BlockPuller.FileDownloadObserver) {
|
||||
doAsync {
|
||||
uiThread {
|
||||
progressDialog.isIndeterminate = false
|
||||
progressDialog.max = (mFileInfo.size as Long).toInt()
|
||||
progressDialog.progress = (fileDownloadObserver.progress * mFileInfo.size!!).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onComplete(file: File) {
|
||||
progressDialog.dismiss()
|
||||
if (cancelled)
|
||||
return
|
||||
|
||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FilenameUtils.getExtension(file.name))
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.setDataAndType(Uri.fromFile(file), mimeType)
|
||||
intent.newTask()
|
||||
try {
|
||||
mContext.startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
onError(R.string.toast_open_file_failed)
|
||||
Log.w(TAG, "No handler found for file " + file.name, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun onError(@StringRes error: Int) {
|
||||
doAsync {
|
||||
uiThread {
|
||||
progressDialog.dismiss()
|
||||
mContext.toast(error)
|
||||
}
|
||||
}
|
||||
fun cancel() {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package net.syncthing.lite.library
|
||||
|
||||
import android.content.Context
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.util.*
|
||||
|
||||
class InitLibraryTask(private val context: Context, private val onLibraryLoaded: (LibraryHandler) -> Unit,
|
||||
private val onIndexUpdateProgressListener: (FolderInfo, Int) -> Unit,
|
||||
private val onIndexUpdateCompleteListener: () -> Unit) {
|
||||
|
||||
private val TAG = "InitLibraryTask"
|
||||
|
||||
init {
|
||||
doAsync {
|
||||
val libraryHandler = LibraryHandler()
|
||||
libraryHandler.init(context)
|
||||
libraryHandler.setOnIndexUpdatedListener(object : LibraryHandler.OnIndexUpdatedListener {
|
||||
override fun onIndexUpdateProgress(folder: FolderInfo, percentage: Int) {
|
||||
onIndexUpdateProgressListener(folder, percentage)
|
||||
}
|
||||
|
||||
override fun onIndexUpdateComplete() {
|
||||
onIndexUpdateCompleteListener()
|
||||
}
|
||||
})
|
||||
//trigger update if last was more than 10mins ago
|
||||
val lastUpdateMillis = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.getLong(UpdateIndexTask.LAST_INDEX_UPDATE_TS_PREF, -1)
|
||||
val lastUpdateTimeAgo = Date().time - lastUpdateMillis
|
||||
if (lastUpdateMillis == -1L || lastUpdateTimeAgo > 10 * 60 * 1000) {
|
||||
Log.d(TAG, "trigger index update, last was " + Date(lastUpdateMillis))
|
||||
UpdateIndexTask(context, libraryHandler.syncthingClient!!).updateIndex()
|
||||
}
|
||||
uiThread {
|
||||
onLibraryLoaded(libraryHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,151 @@
|
||||
package net.syncthing.lite.library
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import com.google.common.eventbus.Subscribe
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import net.syncthing.java.bep.FolderBrowser
|
||||
import net.syncthing.java.bep.IndexHandler
|
||||
import net.syncthing.java.client.SyncthingClient
|
||||
import net.syncthing.java.core.beans.FileInfo
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.java.core.configuration.ConfigurationService
|
||||
import net.syncthing.java.core.security.KeystoreHandler
|
||||
import net.syncthing.java.core.beans.IndexInfo
|
||||
import net.syncthing.java.core.configuration.Configuration
|
||||
import net.syncthing.lite.utils.Util
|
||||
import org.apache.commons.io.FileUtils
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import org.jetbrains.anko.doAsync
|
||||
import java.util.*
|
||||
|
||||
class LibraryHandler {
|
||||
class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit,
|
||||
private val onIndexUpdateProgressListener: (FolderInfo, Int) -> Unit,
|
||||
private val onIndexUpdateCompleteListener: (FolderInfo) -> Unit) {
|
||||
|
||||
private val TAG = "LibConnectionHandler"
|
||||
|
||||
private var mOnIndexUpdatedListener: OnIndexUpdatedListener? = null
|
||||
var configuration: ConfigurationService? = null
|
||||
private set
|
||||
var syncthingClient: SyncthingClient? = null
|
||||
private set
|
||||
var folderBrowser: FolderBrowser? = null
|
||||
private set
|
||||
|
||||
interface OnIndexUpdatedListener {
|
||||
fun onIndexUpdateProgress(folder: FolderInfo, percentage: Int)
|
||||
fun onIndexUpdateComplete()
|
||||
companion object {
|
||||
private var instanceCount = 0
|
||||
private var configuration: Configuration? = null
|
||||
private var syncthingClient: SyncthingClient? = null
|
||||
private var folderBrowser: FolderBrowser? = null
|
||||
private val callbacks = ArrayList<(Configuration, SyncthingClient, FolderBrowser) -> Unit>()
|
||||
private var isLoading = false
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
configuration = ConfigurationService.newLoader()
|
||||
.setCache(File(context.externalCacheDir, "cache"))
|
||||
.setDatabase(File(context.getExternalFilesDir(null), "database"))
|
||||
.loadFrom(File(context.getExternalFilesDir(null), "config.properties"))
|
||||
configuration!!.edit().setDeviceName(Util.getDeviceName())
|
||||
try {
|
||||
FileUtils.cleanDirectory(configuration!!.temp)
|
||||
} catch (ex: IOException) {
|
||||
Log.e(TAG, "error", ex)
|
||||
destroy()
|
||||
private val TAG = "LibraryHandler"
|
||||
|
||||
init {
|
||||
instanceCount++
|
||||
if (configuration == null && !isLoading) {
|
||||
isLoading = true
|
||||
doAsync {
|
||||
init(context)
|
||||
async(UI) {
|
||||
onLibraryLoaded(this@LibraryHandler)
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
} else {
|
||||
onLibraryLoaded(this)
|
||||
}
|
||||
|
||||
KeystoreHandler.newLoader().loadAndStore(configuration!!)
|
||||
configuration!!.edit().persistLater()
|
||||
Log.i(TAG, "loaded mConfiguration = " + configuration!!.newWriter().dumpToString())
|
||||
Log.i(TAG, "storage space = " + configuration!!.storageInfo.dumpAvailableSpace())
|
||||
syncthingClient = net.syncthing.java.client.SyncthingClient(configuration!!)
|
||||
syncthingClient {
|
||||
it.indexHandler.registerOnIndexRecordAcquiredListener(this::onIndexRecordAcquired)
|
||||
it.indexHandler.registerOnFullIndexAcquiredListenersListener(this::onRemoteIndexAcquired)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onIndexRecordAcquired(folderInfo: FolderInfo, newRecords: List<FileInfo>, indexInfo: IndexInfo) {
|
||||
Log.i(TAG, "handleIndexRecordEvent trigger folder list update from index record acquired")
|
||||
|
||||
async(UI) {
|
||||
onIndexUpdateProgressListener(folderInfo, (indexInfo.getCompleted() * 100).toInt())
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRemoteIndexAcquired(folderInfo: FolderInfo) {
|
||||
Log.i(TAG, "handleIndexAcquiredEvent trigger folder list update from index acquired")
|
||||
|
||||
async(UI) {
|
||||
onIndexUpdateCompleteListener(folderInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun init(context: Context) {
|
||||
val configuration = Configuration(configFolder = context.filesDir, cacheFolder = context.externalCacheDir)
|
||||
configuration.localDeviceName = Util.getDeviceName()
|
||||
configuration.persistLater()
|
||||
val syncthingClient = SyncthingClient(configuration)
|
||||
//TODO listen for device events, update device list
|
||||
folderBrowser = syncthingClient!!.indexHandler.newFolderBrowser()
|
||||
val folderBrowser = syncthingClient.indexHandler.newFolderBrowser()
|
||||
|
||||
if (instanceCount == 0) {
|
||||
Log.d(TAG, "All LibraryHandler instances were closed during init")
|
||||
syncthingClient.close()
|
||||
folderBrowser.close()
|
||||
}
|
||||
|
||||
async(UI) {
|
||||
callbacks.forEach { it(configuration, syncthingClient, folderBrowser) }
|
||||
}
|
||||
LibraryHandler.configuration = configuration
|
||||
LibraryHandler.syncthingClient = syncthingClient
|
||||
LibraryHandler.folderBrowser = folderBrowser
|
||||
}
|
||||
|
||||
fun setOnIndexUpdatedListener(onIndexUpdatedListener: OnIndexUpdatedListener) {
|
||||
mOnIndexUpdatedListener = onIndexUpdatedListener
|
||||
syncthingClient!!.indexHandler.eventBus.register(object : Any() {
|
||||
private fun library(callback: (Configuration, SyncthingClient, FolderBrowser) -> Unit) {
|
||||
val nullCount = listOf(configuration, syncthingClient, folderBrowser).count { it == null }
|
||||
assert(nullCount == 0 || nullCount == 3, { "Inconsistent library state" })
|
||||
|
||||
@Subscribe
|
||||
fun handleIndexRecordAquiredEvent(event: IndexHandler.IndexRecordAquiredEvent) {
|
||||
val folder = syncthingClient!!.indexHandler.getFolderInfo(event.folder)
|
||||
val indexInfo = event.indexInfo
|
||||
event.newRecords.size
|
||||
Log.i(TAG, "handleIndexRecordEvent trigger folder list update from index record acquired")
|
||||
mOnIndexUpdatedListener!!.onIndexUpdateProgress(folder, (indexInfo.completed * 100).toInt())
|
||||
// https://stackoverflow.com/a/35522422/1837158
|
||||
fun <T1: Any, T2: Any, T3: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3)->R?): R? {
|
||||
return if (p1 != null && p2 != null && p3 != null) block(p1, p2, p3) else null
|
||||
}
|
||||
safeLet(configuration, syncthingClient, folderBrowser) { c, s, f ->
|
||||
callback(c, s, f)
|
||||
} ?: run {
|
||||
if (isLoading) {
|
||||
callbacks.add(callback)
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun handleRemoteIndexAquiredEvent(event: IndexHandler.FullIndexAquiredEvent) {
|
||||
Log.i(TAG, "handleIndexAquiredEvent trigger folder list update from index acquired")
|
||||
mOnIndexUpdatedListener!!.onIndexUpdateComplete()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
folderBrowser!!.close()
|
||||
syncthingClient!!.close()
|
||||
configuration!!.close()
|
||||
fun syncthingClient(callback: (SyncthingClient) -> Unit) {
|
||||
library { _, s, _ -> callback(s) }
|
||||
}
|
||||
|
||||
fun configuration(callback: (Configuration) -> Unit) {
|
||||
library { c, _, _ -> callback(c) }
|
||||
}
|
||||
|
||||
fun folderBrowser(callback: (FolderBrowser) -> Unit) {
|
||||
library { _, _, f -> callback(f) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters index update listener and decreases instance count.
|
||||
*
|
||||
* We wait a bit before closing [[syncthingClient]] etc, in case LibraryHandler is opened again
|
||||
* soon (eg in case of device rotation).
|
||||
*/
|
||||
fun close() {
|
||||
syncthingClient {
|
||||
try {
|
||||
it.indexHandler.unregisterOnIndexRecordAcquiredListener(this::onIndexRecordAcquired)
|
||||
it.indexHandler.unregisterOnFullIndexAcquiredListenersListener(this::onRemoteIndexAcquired)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// ignored, no idea why this is thrown
|
||||
}
|
||||
}
|
||||
|
||||
instanceCount--
|
||||
Handler().postDelayed({
|
||||
Thread {
|
||||
if (instanceCount == 0) {
|
||||
folderBrowser?.close()
|
||||
folderBrowser = null
|
||||
syncthingClient?.close()
|
||||
syncthingClient = null
|
||||
configuration = null
|
||||
}
|
||||
}.start()
|
||||
}, 60 * 1000)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package net.syncthing.lite.library
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.os.CancellationSignal
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract.Document
|
||||
import android.provider.DocumentsContract.Root
|
||||
import android.provider.DocumentsProvider
|
||||
import android.util.Log
|
||||
import net.syncthing.java.bep.IndexBrowser
|
||||
import net.syncthing.java.core.beans.FileInfo
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.java.core.beans.FolderStats
|
||||
import net.syncthing.lite.R
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.net.URLConnection
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class SyncthingProvider : DocumentsProvider() {
|
||||
|
||||
companion object {
|
||||
private const val Tag = "SyncthingProvider"
|
||||
private val DefaultRootProjection = arrayOf(
|
||||
Root.COLUMN_ROOT_ID,
|
||||
Root.COLUMN_FLAGS,
|
||||
Root.COLUMN_TITLE,
|
||||
Root.COLUMN_SUMMARY,
|
||||
Root.COLUMN_DOCUMENT_ID,
|
||||
Root.COLUMN_ICON)
|
||||
private val DefaultDocumentProjection = arrayOf(
|
||||
Document.COLUMN_DOCUMENT_ID,
|
||||
Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_SIZE,
|
||||
Document.COLUMN_MIME_TYPE,
|
||||
Document.COLUMN_LAST_MODIFIED,
|
||||
Document.COLUMN_FLAGS)
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
Log.d(Tag, "onCreate()")
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getLibraryHandler(): LibraryHandler {
|
||||
val latch = CountDownLatch(1)
|
||||
val libraryHandler = LibraryHandler(context, { latch.countDown() }, { _, _ -> }, {})
|
||||
latch.await()
|
||||
return libraryHandler
|
||||
}
|
||||
|
||||
override fun queryRoots(projection: Array<String>?): Cursor {
|
||||
Log.d(Tag, "queryRoots($projection)")
|
||||
val latch = CountDownLatch(1)
|
||||
var folders: List<Pair<FolderInfo, FolderStats>>? = null
|
||||
getLibraryHandler().folderBrowser { folderBrowser ->
|
||||
folders = folderBrowser.folderInfoAndStatsList()
|
||||
latch.countDown()
|
||||
}
|
||||
latch.await()
|
||||
|
||||
val result = MatrixCursor(projection ?: DefaultRootProjection)
|
||||
folders!!.forEach { folder ->
|
||||
val row = result.newRow()
|
||||
row.add(Root.COLUMN_ROOT_ID, folder.first.folderId)
|
||||
row.add(Root.COLUMN_SUMMARY, folder.first.label)
|
||||
row.add(Root.COLUMN_FLAGS, 0)
|
||||
row.add(Root.COLUMN_TITLE, context.getString(R.string.app_name))
|
||||
row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(folder.first))
|
||||
row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun queryChildDocuments(parentDocumentId: String, projection: Array<String>?,
|
||||
sortOrder: String?): Cursor {
|
||||
Log.d(Tag, "queryChildDocuments($parentDocumentId, $projection, $sortOrder)")
|
||||
val result = MatrixCursor(projection ?: DefaultDocumentProjection)
|
||||
getIndexBrowser(getFolderIdForDocId(parentDocumentId))
|
||||
.listFiles(getPathForDocId(parentDocumentId))
|
||||
.forEach { fileInfo ->
|
||||
includeFile(result, fileInfo)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun queryDocument(documentId: String, projection: Array<String>?): Cursor {
|
||||
Log.d(Tag, "queryDocument($documentId, $projection)")
|
||||
val result = MatrixCursor(projection ?: DefaultDocumentProjection)
|
||||
val fileInfo = getIndexBrowser(getFolderIdForDocId(documentId))
|
||||
.getFileInfoByAbsolutePath(getPathForDocId(documentId))
|
||||
includeFile(result, fileInfo)
|
||||
return result
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
override fun openDocument(documentId: String, mode: String, signal: CancellationSignal?):
|
||||
ParcelFileDescriptor {
|
||||
Log.d(Tag, "openDocument($documentId, $mode, $signal)")
|
||||
val fileInfo = FileInfo(folder = getFolderIdForDocId(documentId),
|
||||
path = getPathForDocId(documentId), type = FileInfo.FileType.FILE)
|
||||
val accessMode = ParcelFileDescriptor.parseMode(mode)
|
||||
if (accessMode != ParcelFileDescriptor.MODE_READ_ONLY) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
val latch = CountDownLatch(1)
|
||||
var outputFile: File? = null
|
||||
getLibraryHandler().syncthingClient { syncthingClient ->
|
||||
DownloadFileTask(context, syncthingClient, fileInfo,
|
||||
{ t, _ -> if (signal?.isCanceled == true) t.cancel() }, {
|
||||
outputFile = it
|
||||
latch.countDown()
|
||||
}, {})
|
||||
}
|
||||
latch.await()
|
||||
return ParcelFileDescriptor.open(outputFile, ParcelFileDescriptor.MODE_READ_ONLY)
|
||||
}
|
||||
|
||||
private fun includeFile(result: MatrixCursor, fileInfo: FileInfo) {
|
||||
val row = result.newRow()
|
||||
row.add(Document.COLUMN_DOCUMENT_ID, getDocIdForFile(fileInfo))
|
||||
row.add(Document.COLUMN_DISPLAY_NAME, fileInfo.fileName)
|
||||
row.add(Document.COLUMN_SIZE, fileInfo.size)
|
||||
val mime = if (fileInfo.isDirectory()) Document.MIME_TYPE_DIR
|
||||
else URLConnection.guessContentTypeFromName(fileInfo.fileName)
|
||||
row.add(Document.COLUMN_MIME_TYPE, mime)
|
||||
row.add(Document.COLUMN_LAST_MODIFIED, fileInfo.lastModified)
|
||||
row.add(Document.COLUMN_FLAGS, 0)
|
||||
}
|
||||
|
||||
private fun getFolderIdForDocId(docId: String) = docId.split(":")[0]
|
||||
|
||||
private fun getPathForDocId(docId: String) = docId.split(":")[1]
|
||||
|
||||
private fun getDocIdForFile(folderInfo: FolderInfo) = folderInfo.folderId + ":"
|
||||
|
||||
private fun getDocIdForFile(fileInfo: FileInfo) = fileInfo.folder + ":" + fileInfo.path
|
||||
|
||||
private fun getIndexBrowser(folderId: String): IndexBrowser {
|
||||
val latch = CountDownLatch(1)
|
||||
var indexBrowser: IndexBrowser? = null
|
||||
getLibraryHandler().syncthingClient {
|
||||
indexBrowser = it.indexHandler.newIndexBrowser(folderId)
|
||||
latch.countDown()
|
||||
}
|
||||
latch.await()
|
||||
return indexBrowser!!
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package net.syncthing.lite.library
|
||||
|
||||
import android.content.Context
|
||||
import android.preference.PreferenceManager
|
||||
import net.syncthing.java.client.SyncthingClient
|
||||
import net.syncthing.lite.R
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.util.*
|
||||
|
||||
class UpdateIndexTask(private val androidContext: Context, private val syncthingClient: SyncthingClient) {
|
||||
private val mPreferences = PreferenceManager.getDefaultSharedPreferences(androidContext)
|
||||
|
||||
fun updateIndex() {
|
||||
if (sIndexUpdateInProgress)
|
||||
return
|
||||
|
||||
sIndexUpdateInProgress = true
|
||||
syncthingClient.updateIndexFromPeers { _, failures ->
|
||||
sIndexUpdateInProgress = false
|
||||
if (failures.isEmpty()) {
|
||||
showToast(androidContext.getString(R.string.toast_index_update_successful))
|
||||
} else {
|
||||
showToast(androidContext.getString(R.string.toast_index_update_failed, failures.size))
|
||||
}
|
||||
mPreferences.edit()
|
||||
.putLong(LAST_INDEX_UPDATE_TS_PREF, Date().time)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showToast(message: String) {
|
||||
doAsync {
|
||||
uiThread {
|
||||
androidContext.toast(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val LAST_INDEX_UPDATE_TS_PREF = "LAST_INDEX_UPDATE_TS"
|
||||
|
||||
private var sIndexUpdateInProgress: Boolean = false
|
||||
}
|
||||
}
|
||||
@@ -1,103 +1,48 @@
|
||||
package net.syncthing.lite.library
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import net.syncthing.java.bep.BlockPusher
|
||||
import net.syncthing.java.client.SyncthingClient
|
||||
import net.syncthing.java.core.utils.PathUtils
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.utils.Util
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.io.IOException
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
// TODO: this should be an IntentService with notification
|
||||
class UploadFileTask(private val context: Context, private val syncthingClient: SyncthingClient,
|
||||
private val localFile: Uri, private val syncthingFolder: String,
|
||||
class UploadFileTask(context: Context, syncthingClient: SyncthingClient,
|
||||
localFile: Uri, private val syncthingFolder: String,
|
||||
syncthingSubFolder: String,
|
||||
private val onUploadCompleteListener: () -> Unit) {
|
||||
private val onProgress: (BlockPusher.FileUploadObserver) -> Unit,
|
||||
private val onComplete: () -> Unit,
|
||||
private val onError: () -> Unit) {
|
||||
|
||||
companion object {
|
||||
private val TAG = "UploadFileTask"
|
||||
}
|
||||
private val TAG = "UploadFileTask"
|
||||
|
||||
private val fileName = Util.getContentFileName(context, localFile)
|
||||
private val syncthingPath = PathUtils.buildPath(syncthingSubFolder, fileName)
|
||||
private val syncthingPath = PathUtils.buildPath(syncthingSubFolder, Util.getContentFileName(context, localFile))
|
||||
private val uploadStream = context.contentResolver.openInputStream(localFile)
|
||||
|
||||
private lateinit var mProgressDialog: ProgressDialog
|
||||
private var mCancelled = false
|
||||
private var isCancelled = false
|
||||
|
||||
fun uploadFile() {
|
||||
createDialog()
|
||||
init {
|
||||
Log.i(TAG, "Uploading file $localFile to folder $syncthingFolder:$syncthingPath")
|
||||
try {
|
||||
val uploadStream = context.contentResolver.openInputStream(localFile)
|
||||
syncthingClient.pushFile(uploadStream, syncthingFolder, syncthingPath, { observer ->
|
||||
onProgress(observer)
|
||||
try {
|
||||
while (!observer.isCompleted) {
|
||||
if (mCancelled)
|
||||
return@pushFile
|
||||
syncthingClient.getBlockPusher(syncthingFolder, { blockPusher ->
|
||||
val observer = blockPusher.pushFile(uploadStream, syncthingFolder, syncthingPath)
|
||||
onProgress(observer)
|
||||
while (!observer.isCompleted()) {
|
||||
if (isCancelled)
|
||||
return@getBlockPusher
|
||||
|
||||
observer.waitForProgressUpdate()
|
||||
Log.i(TAG, "upload progress = " + observer.progressMessage)
|
||||
onProgress(observer)
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
onError()
|
||||
observer.waitForProgressUpdate()
|
||||
Log.i(TAG, "upload progress = ${observer.progressPercentage()}%")
|
||||
onProgress(observer)
|
||||
}
|
||||
|
||||
IOUtils.closeQuietly(uploadStream)
|
||||
onComplete()
|
||||
}, { this.onError() })
|
||||
} catch (e: IOException) {
|
||||
onError()
|
||||
}
|
||||
|
||||
}, { onError() })
|
||||
}
|
||||
|
||||
private fun createDialog() {
|
||||
mProgressDialog = ProgressDialog(context)
|
||||
mProgressDialog.setMessage(context.getString(R.string.dialog_uploading_file, fileName))
|
||||
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
mProgressDialog.setCancelable(true)
|
||||
mProgressDialog.setOnCancelListener { mCancelled = true }
|
||||
mProgressDialog.isIndeterminate = true
|
||||
mProgressDialog.show()
|
||||
fun cancel() {
|
||||
isCancelled = true
|
||||
}
|
||||
|
||||
private fun onProgress(observer: BlockPusher.FileUploadObserver) {
|
||||
doAsync {
|
||||
uiThread {
|
||||
mProgressDialog.isIndeterminate = false
|
||||
mProgressDialog.max = observer.dataSource.size.toInt()
|
||||
mProgressDialog.progress = (observer.progress * observer.dataSource.size).toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onComplete() {
|
||||
if (mCancelled)
|
||||
return
|
||||
|
||||
Log.i(TAG, "Uploaded file $fileName to folder $syncthingFolder:$syncthingPath")
|
||||
doAsync {
|
||||
uiThread {
|
||||
mProgressDialog.dismiss()
|
||||
context.toast(R.string.toast_upload_complete)
|
||||
onUploadCompleteListener()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError() {
|
||||
doAsync {
|
||||
uiThread {
|
||||
mProgressDialog.dismiss()
|
||||
context.toast(R.string.toast_file_upload_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package net.syncthing.lite.utils
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.app.ProgressDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.support.v4.content.FileProvider
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import net.syncthing.java.bep.BlockPuller
|
||||
import net.syncthing.java.client.SyncthingClient
|
||||
import net.syncthing.java.core.beans.FileInfo
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.library.DownloadFileTask
|
||||
import org.apache.commons.io.FilenameUtils
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.newTask
|
||||
import org.jetbrains.anko.toast
|
||||
import java.io.File
|
||||
|
||||
class FileDownloadDialog(context: Context, syncthingClient: SyncthingClient,
|
||||
private val fileInfo: FileInfo) : AlertDialog(context) {
|
||||
|
||||
private val Tag = "FileDownloadDialog"
|
||||
private lateinit var progressDialog: ProgressDialog
|
||||
private var downloadFileTask: DownloadFileTask? = null
|
||||
|
||||
init {
|
||||
showDialog()
|
||||
doAsync {
|
||||
downloadFileTask = DownloadFileTask(context, syncthingClient, fileInfo,
|
||||
this@FileDownloadDialog::onProgress, this@FileDownloadDialog::onComplete,
|
||||
this@FileDownloadDialog::onError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog() {
|
||||
progressDialog = ProgressDialog(context)
|
||||
progressDialog.setMessage(context.getString(R.string.dialog_downloading_file, fileInfo.fileName))
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
progressDialog.setCancelable(true)
|
||||
progressDialog.setOnCancelListener { downloadFileTask?.cancel() }
|
||||
progressDialog.isIndeterminate = true
|
||||
progressDialog.show()
|
||||
}
|
||||
|
||||
private fun onProgress(downloadFileTask: DownloadFileTask, fileDownloadObserver: BlockPuller.FileDownloadObserver) {
|
||||
async(UI) {
|
||||
progressDialog.isIndeterminate = false
|
||||
progressDialog.max = (fileInfo.size as Long).toInt()
|
||||
progressDialog.progress = (fileDownloadObserver.progress() * fileInfo.size!!).toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onComplete(file: File) {
|
||||
async(UI) {
|
||||
progressDialog.dismiss()
|
||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FilenameUtils.getExtension(file.name))
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
val uri = FileProvider.getUriForFile(this@FileDownloadDialog.context, "net.syncthing.lite.fileprovider", file)
|
||||
intent.setDataAndType(uri, mimeType)
|
||||
intent.newTask()
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
try {
|
||||
this@FileDownloadDialog.context.startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
this@FileDownloadDialog.context.toast(R.string.toast_open_file_failed)
|
||||
Log.w(Tag, "No handler found for file " + file.name, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError() {
|
||||
async(UI) {
|
||||
progressDialog.cancel()
|
||||
this@FileDownloadDialog.context.toast(R.string.toast_file_download_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package net.syncthing.lite.utils
|
||||
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import net.syncthing.java.bep.BlockPusher
|
||||
import net.syncthing.java.client.SyncthingClient
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.library.UploadFileTask
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
|
||||
class FileUploadDialog(private val context: Context, private val syncthingClient: SyncthingClient,
|
||||
private val localFile: Uri, private val syncthingFolder: String,
|
||||
syncthingSubFolder: String,
|
||||
private val onUploadCompleteListener: () -> Unit) {
|
||||
|
||||
private lateinit var progressDialog: ProgressDialog
|
||||
private var uploadFileTask: UploadFileTask? = null
|
||||
|
||||
init {
|
||||
showDialog()
|
||||
doAsync {
|
||||
uploadFileTask = UploadFileTask(context, syncthingClient, localFile, syncthingFolder,
|
||||
syncthingSubFolder, this@FileUploadDialog::onProgress,
|
||||
this@FileUploadDialog::onComplete, this@FileUploadDialog::onError)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDialog() {
|
||||
progressDialog = ProgressDialog(context)
|
||||
progressDialog.setMessage(context.getString(R.string.dialog_uploading_file, Util.getContentFileName(context, localFile)))
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
progressDialog.setCancelable(true)
|
||||
progressDialog.setOnCancelListener { uploadFileTask?.cancel() }
|
||||
progressDialog.isIndeterminate = true
|
||||
progressDialog.show()
|
||||
}
|
||||
|
||||
private fun onProgress(observer: BlockPusher.FileUploadObserver) {
|
||||
async(UI) {
|
||||
progressDialog.isIndeterminate = false
|
||||
progressDialog.progress = observer.progressPercentage()
|
||||
progressDialog.max = 100
|
||||
}
|
||||
}
|
||||
|
||||
private fun onComplete() {
|
||||
async(UI) {
|
||||
progressDialog.dismiss()
|
||||
this@FileUploadDialog.context.toast(R.string.toast_upload_complete)
|
||||
onUploadCompleteListener()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError() {
|
||||
async(UI) {
|
||||
progressDialog.dismiss()
|
||||
this@FileUploadDialog.context.toast(R.string.toast_file_upload_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,15 @@ package net.syncthing.lite.utils
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import com.google.common.base.Objects.equal
|
||||
import com.google.common.base.Strings.nullToEmpty
|
||||
import android.provider.OpenableColumns
|
||||
import org.apache.commons.lang3.StringUtils.capitalize
|
||||
import java.io.File
|
||||
import java.security.InvalidParameterException
|
||||
|
||||
object Util {
|
||||
|
||||
fun getDeviceName(): String {
|
||||
val manufacturer = nullToEmpty(Build.MANUFACTURER)
|
||||
val model = nullToEmpty(Build.MODEL)
|
||||
val manufacturer = Build.MANUFACTURER ?: ""
|
||||
val model = Build.MODEL ?: ""
|
||||
val deviceName =
|
||||
if (model.startsWith(manufacturer)) {
|
||||
capitalize(model)
|
||||
@@ -24,17 +21,12 @@ object Util {
|
||||
return deviceName ?: "android"
|
||||
}
|
||||
|
||||
fun getContentFileName(context: Context, contentUri: Uri): String {
|
||||
var fileName = File(contentUri.lastPathSegment).name
|
||||
if (equal(contentUri.scheme, "content")) {
|
||||
context.contentResolver.query(contentUri, arrayOf(MediaStore.Images.Media.DATA), null, null, null)!!.use { cursor ->
|
||||
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||
cursor.moveToFirst()
|
||||
val path = cursor.getString(columnIndex)
|
||||
Log.d("Main", "recovered 'content' uri real path = " + path)
|
||||
fileName = File(Uri.parse(path).lastPathSegment).name
|
||||
fun getContentFileName(context: Context, uri: Uri): String {
|
||||
context.contentResolver.query(uri, null, null, null, null, null).use { cursor ->
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
throw InvalidParameterException("Cursor is null or empty")
|
||||
}
|
||||
return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
||||
}
|
||||
return fileName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#7D000000"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||
</vector>
|
||||
@@ -6,7 +6,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!--center content BEGIN-->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -14,36 +13,6 @@
|
||||
android:divider="?android:listDivider"
|
||||
android:showDividers="middle">
|
||||
|
||||
<!--index loading progress BEGIN-->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:padding="8dp"
|
||||
android:id="@+id/index_update"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/primary"
|
||||
android:visibility="gone">
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:indeterminate="true"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"/>
|
||||
<TextView
|
||||
android:id="@+id/index_update_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/white_on_primary"
|
||||
android:text="@string/index_update_progress_message"
|
||||
android:layout_gravity="start"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<!--index loading progress END-->
|
||||
|
||||
<!--main list view BEGIN-->
|
||||
<ListView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -69,12 +38,9 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"/>
|
||||
<!--main list view END-->
|
||||
|
||||
</LinearLayout>
|
||||
<!--center content END-->
|
||||
|
||||
<!--upload here overlay button BEGIN-->
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/main_list_view_upload_here_button"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -86,7 +52,6 @@
|
||||
app:elevation="6dp"
|
||||
app:pressedTranslationZ="12dp"
|
||||
android:src="@drawable/ic_file_upload_white_24dp"/>
|
||||
<!--upload here overlay button END-->
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ListView
|
||||
android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/empty"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/directory_empty" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -5,40 +5,12 @@
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<!-- The main content view -->
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:padding="8dp"
|
||||
android:id="@+id/index_update"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/primary"
|
||||
android:visibility="gone">
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:indeterminate="true"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"/>
|
||||
<TextView
|
||||
android:id="@+id/index_update_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/white_on_primary"
|
||||
android:text="@string/index_update_progress_message"
|
||||
android:layout_gravity="start"
|
||||
android:textAlignment="gravity"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/content_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
<!-- The navigation drawer -->
|
||||
|
||||
<android.support.design.widget.NavigationView
|
||||
android:id="@+id/navigation"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -46,6 +18,7 @@
|
||||
android:layout_gravity="start"
|
||||
android:background="@android:color/white"
|
||||
app:menu="@menu/drawer_view" />
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
||||
|
||||
</layout>
|
||||
@@ -4,27 +4,20 @@
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/abc_action_bar_content_inset_material"
|
||||
android:padding="24dp"
|
||||
android:theme="?alertDialogTheme"
|
||||
android:orientation="vertical">
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
android:layout_marginEnd="24dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/abc_action_bar_content_inset_material" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loading_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/loading_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingRight="@dimen/abc_action_bar_content_inset_material"
|
||||
android:minHeight="48dp" />
|
||||
|
||||
</layout>
|
||||
@@ -5,8 +5,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingRight="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<ImageView
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingRight="@dimen/abc_action_bar_content_inset_material"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -13,12 +13,6 @@
|
||||
android:icon="@drawable/ic_laptop_gray_24dp"
|
||||
android:title="@string/devices_label" />
|
||||
|
||||
<item
|
||||
android:id="@+id/update_index"
|
||||
android:icon="@drawable/ic_refresh_gray_24dp"
|
||||
android:title="@string/update_remote_index_label"
|
||||
android:checkable="false"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/clear_index"
|
||||
android:icon="@drawable/ic_delete_gray_24dp"
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="index_update_progress_message">Index wird aktualisiert...</string>
|
||||
<string name="app_name">Syncthing Lite</string>
|
||||
<string name="folder_list_empty_message">Keine Ordner verfügbar</string>
|
||||
<string name="clear_local_cache_index_label">Lokalen Index und Cache löschen</string>
|
||||
<string name="update_remote_index_label">Index aktualisieren</string>
|
||||
<string name="devices_list_view_empty_message">Keine Geräte verfügbar</string>
|
||||
<string name="toast_write_storage_permission_required">Schreibrechte werden für diese Funktion benötigt</string>
|
||||
<string name="scan_qr_code">QR code scannen</string>
|
||||
<string name="enter_device_id">Geräte ID eingeben</string>
|
||||
<string name="invalid_device_id">Ungültige Geräte ID</string>
|
||||
<string name="device_id_dialog_title">Geräte ID eingeben</string>
|
||||
<string name="toast_index_update_successful">Index erfolgreich aktualisiert</string>
|
||||
<string name="toast_index_update_failed">Index update für %1$d Geräte fehlgeschlagen</string>
|
||||
<string name="dialog_downloading_file">Datei %1$s wird heruntergeladen</string>
|
||||
<string name="toast_file_download_failed">Datei konnte nicht heruntergeladen werden</string>
|
||||
<string name="toast_open_file_failed">Keine kompatible app gefunden</string>
|
||||
<string name="toast_file_upload_failed">Hochladen gescheitert</string>
|
||||
<string name="toast_upload_complete">Hochladen erfolgreich</string>
|
||||
<string name="dialog_uploading_file">Datei %1$s wird hochgeladen</string>
|
||||
<string name="directory_empty">Ordner ist leer</string>
|
||||
<string name="clear_cache_and_index_title">Lokalen Cache und Index löschen?</string>
|
||||
<string name="clear_cache_and_index_body">Gesamten lokalen Cache und Index löschen?</string>
|
||||
<string name="index_update_folder">Index aktualisierung, Ordner</string>
|
||||
<string name="index_update_percent_synchronized">% synchronisiert</string>
|
||||
<string name="loading_config_starting_syncthing_client">Konfiguartion wird geladen, Syncthing wird gestartet</string>
|
||||
<string name="last_modified">- zuletzt modifiziert</string>
|
||||
<string name="last_modified_unknown">zuletzt modifiziert: unbekannt</string>
|
||||
<string name="last_modified_known">zuletzt modifiziert:</string>
|
||||
<string name="last_modified_time">Zuletzt modifiziert: %1$s</string>
|
||||
<string name="remove_device_title">Gerät entfernen:</string>
|
||||
<string name="remove_device_body_1">Gerät</string>
|
||||
<string name="remove_device_body_2">von den bekannten Geräten entfernen?</string>
|
||||
<string name="device_import_success">Gerät erfolgreich importiert:</string>
|
||||
<string name="device_already_known">Gerät ist bereits bekannt:</string>
|
||||
<string name="remove_device_message">Gerät %1$s entfernen?</string>
|
||||
<string name="device_import_success">Gerät %1$s erfolgreich importiert</string>
|
||||
<string name="device_already_known">Gerät ist bereits bekannt $1%s</string>
|
||||
<string name="folders_label">Ordner</string>
|
||||
<string name="devices_label">Geräte</string>
|
||||
</resources>
|
||||
<string name="folder_label_format">%1$s (%2$s)</string>
|
||||
<string name="folder_content_info">%1$s, %2$d Dateien, %3$d Ordner</string>
|
||||
<string name="file_info">%1$s, zuletzt modifiziert %2$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<resources>
|
||||
<string name="app_name">Syncthing Lite</string>
|
||||
<string name="folder_list_empty_message">Aucun dossier disponible</string>
|
||||
<string name="clear_local_cache_index_label">Effacer le cache et l\'index local</string>
|
||||
<string name="devices_list_view_empty_message">Aucun appareil disponible</string>
|
||||
<string name="scan_qr_code">Scanner le QR Code</string>
|
||||
<string name="enter_device_id">Entrer l\'ID de l\'appareil</string>
|
||||
<string name="invalid_device_id">ID de l\'appareil invalide</string>
|
||||
<string name="device_id_dialog_title">Entrer l\'ID de l\'appareil</string>
|
||||
<string name="dialog_downloading_file">Téléchargement du fichier %1$s</string>
|
||||
<string name="toast_file_download_failed">Le téléchargement du fichier a échoué</string>
|
||||
<string name="toast_open_file_failed">Aucune appli compatible trouvée</string>
|
||||
<string name="toast_file_upload_failed">Échec de l\'upload</string>
|
||||
<string name="toast_upload_complete">Upload du fichier terminé</string>
|
||||
<string name="dialog_uploading_file">Upload du fichier %1$s</string>
|
||||
<string name="clear_cache_and_index_title">Effacer le cache local et l\'index?</string>
|
||||
<string name="clear_cache_and_index_body">Effacer toutes les données du cache local et de l\'index ?</string>
|
||||
<string name="loading_config_starting_syncthing_client">Chargement de la configuration, démarrage du client Syncthing</string>
|
||||
<string name="last_modified_time">Dernière modification : %1$s</string>
|
||||
<string name="remove_device_title">Supprimer l\'appareil %1$s\?</string>
|
||||
<string name="remove_device_message">Supprimer l\'appareil %1$s de la liste des appareil connus ?</string>
|
||||
<string name="device_import_success">Appareil %1$s importé avec succès</string>
|
||||
<string name="device_already_known">Appareil déjà connu %1$s</string>
|
||||
<string name="folders_label">Dossiers</string>
|
||||
<string name="devices_label">Appareils</string>
|
||||
<string name="folder_label_format">%1$s (%2$s)</string>
|
||||
<string name="folder_content_info">%1$s, %2$d fichiers, %3$d dossiers</string>
|
||||
<string name="file_info">%1$s, dernière modification %2$s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,29 @@
|
||||
<resources>
|
||||
<string name="app_name">Syncthing Lite</string>
|
||||
<string name="folder_list_empty_message">Nessuna cartella disponibile</string>
|
||||
<string name="clear_local_cache_index_label">Cancella cache/indice</string>
|
||||
<string name="devices_list_view_empty_message">Nessun dispositivo disponibile</string>
|
||||
<string name="scan_qr_code">Scansiona codice QR</string>
|
||||
<string name="enter_device_id">Inserisci ID dispositivo</string>
|
||||
<string name="invalid_device_id">ID dispositivo non valido</string>
|
||||
<string name="device_id_dialog_title">Inserisci ID Dispositivo</string>
|
||||
<string name="dialog_downloading_file">Download del file %1$s</string>
|
||||
<string name="toast_file_download_failed">Impossibile scaricare il file</string>
|
||||
<string name="toast_open_file_failed">Nessuna applicazione compatibile trovata</string>
|
||||
<string name="toast_file_upload_failed">Caricamento file fallito</string>
|
||||
<string name="toast_upload_complete">Caricamento file completato</string>
|
||||
<string name="dialog_uploading_file">Caricamento del file %1$s</string>
|
||||
<string name="clear_cache_and_index_title">Cancellare la cache locale e l\'indice?</string>
|
||||
<string name="clear_cache_and_index_body">Cancellare tutti i dati della cache locale e i dati dell\'indice?</string>
|
||||
<string name="loading_config_starting_syncthing_client">Caricamento configurazione, avvio del client syncthing</string>
|
||||
<string name="last_modified_time">Ultima modifica: %1$s</string>
|
||||
<string name="remove_device_title">Rimuovere il dispositivo %1$s\?</string>
|
||||
<string name="remove_device_message">Rimuovere il dispositivo %1$s dalla lista dei dispositivi noti?</string>
|
||||
<string name="device_import_success">Dispositivo %1$s importato con successo</string>
|
||||
<string name="device_already_known">Dispositivo %1$s già presente</string>
|
||||
<string name="folders_label">Cartelle</string>
|
||||
<string name="devices_label">Dispositivi</string>
|
||||
<string name="folder_label_format">%1$s (%2$s)</string>
|
||||
<string name="folder_content_info">%1$s, %2$d file, %3$d cartelle</string>
|
||||
<string name="file_info">%1$s, ultima modifica %2$s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,29 @@
|
||||
<resources>
|
||||
<string name="app_name">Syncthing Lite</string>
|
||||
<string name="folder_list_empty_message">フォルダーがありません</string>
|
||||
<string name="clear_local_cache_index_label">ローカルキャッシュ/索引をクリア</string>
|
||||
<string name="devices_list_view_empty_message">デバイスがありません</string>
|
||||
<string name="scan_qr_code">QR コードをスキャン</string>
|
||||
<string name="enter_device_id">デバイス ID を入力</string>
|
||||
<string name="invalid_device_id">デバイス ID が無効です</string>
|
||||
<string name="device_id_dialog_title">デバイス ID を入力</string>
|
||||
<string name="dialog_downloading_file">ファイル %1$s のダウンロード中</string>
|
||||
<string name="toast_file_download_failed">ファイルのダウンロードに失敗しました</string>
|
||||
<string name="toast_open_file_failed">利用できるアプリが見つかりません</string>
|
||||
<string name="toast_file_upload_failed">ファイルのアップロードに失敗しました</string>
|
||||
<string name="toast_upload_complete">ファイルのアップロードが完了しました</string>
|
||||
<string name="dialog_uploading_file">ファイル %1$s のアップロード中</string>
|
||||
<string name="clear_cache_and_index_title">ローカルキャッシュと索引をクリアしますか?</string>
|
||||
<string name="clear_cache_and_index_body">すべてのローカルキャッシュデータと索引データをクリアしますか?</string>
|
||||
<string name="loading_config_starting_syncthing_client">設定の読み込み中、syncthing クライアントの開始中</string>
|
||||
<string name="last_modified_time">最終更新: %1$s</string>
|
||||
<string name="remove_device_title">デバイス %1$sを削除しますか?</string>
|
||||
<string name="remove_device_message">既存のデバイスリストからデバイス %1$s を削除しますか?</string>
|
||||
<string name="device_import_success">デバイス %1$s のインポートに成功しました</string>
|
||||
<string name="device_already_known">デバイスは既に存在します %1$s</string>
|
||||
<string name="folders_label">フォルダー</string>
|
||||
<string name="devices_label">デバイス</string>
|
||||
<string name="folder_label_format">%1$s (%2$s)</string>
|
||||
<string name="folder_content_info">%1$s, %2$d ファイル, %3$d ディレクトリー</string>
|
||||
<string name="file_info">%1$s, 最終更新 %2$s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,29 @@
|
||||
<resources>
|
||||
<string name="app_name">Syncthing Lite</string>
|
||||
<string name="folder_list_empty_message">Nici un director disponibil</string>
|
||||
<string name="clear_local_cache_index_label">Curăță indexul/memoria locală</string>
|
||||
<string name="devices_list_view_empty_message">Nici un dispozitiv disponibil</string>
|
||||
<string name="scan_qr_code">Scanează cod QR</string>
|
||||
<string name="enter_device_id">Introduceți ID dispozitiv</string>
|
||||
<string name="invalid_device_id">ID dispozitiv invalid</string>
|
||||
<string name="device_id_dialog_title">Introduceți ID dispozitiv</string>
|
||||
<string name="dialog_downloading_file">Se descarcă fișierul %1$s</string>
|
||||
<string name="toast_file_download_failed">Descărcarea fișierului a eșuat</string>
|
||||
<string name="toast_open_file_failed">Nu a fost găsită nici o aplicație compatibilă</string>
|
||||
<string name="toast_file_upload_failed">Încărcarea fișierului a eșuat</string>
|
||||
<string name="toast_upload_complete">Încărcarea fișierelor finalizată</string>
|
||||
<string name="dialog_uploading_file">Se încarcă fișierul %1$s</string>
|
||||
<string name="clear_cache_and_index_title">Se curăță memoria locală și indexul?</string>
|
||||
<string name="clear_cache_and_index_body">Se curăță datele din memoria locală și datele indexului?</string>
|
||||
<string name="loading_config_starting_syncthing_client">Încărcare setări, pornire client syncthing…</string>
|
||||
<string name="last_modified_time">Modificat ultima dată pe: %1$s</string>
|
||||
<string name="remove_device_title">Ștergere dispozitiv %1$s\?</string>
|
||||
<string name="remove_device_message">Se va șterge %1$s din lista dispozitivelor cunoscute?</string>
|
||||
<string name="device_import_success">Dispozitiv importat cu succes %1$s</string>
|
||||
<string name="device_already_known">Dispozitiv deja prezent %1$s</string>
|
||||
<string name="folders_label">Directoare</string>
|
||||
<string name="devices_label">Dispozitive</string>
|
||||
<string name="folder_label_format">%1$s (%2$s)</string>
|
||||
<string name="folder_content_info">%1$s, %2$d fișier(e), %3$d director(oare)</string>
|
||||
<string name="file_info">%1$s, modificat ultima dată pe %2$s</string>
|
||||
</resources>
|
||||
@@ -2,11 +2,7 @@
|
||||
<resources>
|
||||
<color name="primary">#f43703</color>
|
||||
<color name="primary_dark">#d13602</color>
|
||||
<color name="white_on_primary">#fefefe</color>
|
||||
<color name="accent">#FFC107</color>
|
||||
<color name="divider">#1F000000</color>
|
||||
|
||||
<color name="device_online_active">#ff99cc00</color>
|
||||
<color name="device_online_inactive">#f43703</color>
|
||||
<color name="device_offline">#aaaaaa</color>
|
||||
</resources>
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">Syncthing Lite</string>
|
||||
<string name="index_update_progress_message">Index update…</string>
|
||||
<string name="app_name">Syncthing Lite</string>
|
||||
<string name="folder_list_empty_message">No folder available</string>
|
||||
<string name="clear_local_cache_index_label">Clear local cache/index</string>
|
||||
<string name="update_remote_index_label">Update remote index</string>
|
||||
<string name="devices_list_view_empty_message">No devices available</string>
|
||||
<string name="toast_write_storage_permission_required">Write storage permission is required for this functionality</string>
|
||||
<string name="scan_qr_code">Scan QR code</string>
|
||||
<string name="enter_device_id">Enter device ID</string>
|
||||
<string name="invalid_device_id">Invalid device ID</string>
|
||||
<string name="device_id_dialog_title">Enter Device ID</string>
|
||||
<string name="toast_index_update_successful">Index update successful</string>
|
||||
<string name="toast_index_update_failed">Index update failed for %1$d devices</string>
|
||||
<string name="dialog_downloading_file">Downloading file %1$s</string>
|
||||
<string name="toast_file_download_failed">Failed to download file</string>
|
||||
<string name="toast_open_file_failed">No compatible app found</string>
|
||||
<string name="toast_file_upload_failed">File upload failed</string>
|
||||
<string name="toast_upload_complete">File upload complete</string>
|
||||
<string name="dialog_uploading_file">Uploading file %1$s</string>
|
||||
<string name="directory_empty">Directory is empty</string>
|
||||
<string name="clear_cache_and_index_title">Clear local cache and index?</string>
|
||||
<string name="clear_cache_and_index_body">Clear all local cache data and index data?</string>
|
||||
<string name="index_update_folder">Index update, folder</string>
|
||||
<string name="index_update_percent_synchronized">% synchronized</string>
|
||||
<string name="index_update_progress_label">Index update for folder %1$s, %2$d%% synchronized</string>
|
||||
<string name="loading_config_starting_syncthing_client">Loading config, starting syncthing client</string>
|
||||
<string name="last_modified">- last modified</string>
|
||||
<string name="last_modified_unknown">last modified: unknown</string>
|
||||
<string name="last_modified_known">last modified:</string>
|
||||
<string name="remove_device_title">Remove device:</string>
|
||||
<string name="remove_device_body_1">Remove device</string>
|
||||
<string name="remove_device_body_2">from list of known devices?</string>
|
||||
<string name="device_import_success">Successfully imported device:</string>
|
||||
<string name="device_already_known">Device already present:</string>
|
||||
<string name="last_modified_time">Last modified: %1$s</string>
|
||||
<string name="remove_device_title">Remove device %1$s\?</string>
|
||||
<string name="remove_device_message">Remove device %1$s from list of known devices?</string>
|
||||
<string name="device_import_success">Successfully imported device %1$s</string>
|
||||
<string name="device_already_known">Device already present %1$s</string>
|
||||
<string name="folders_label">Folders</string>
|
||||
<string name="devices_label">Devices</string>
|
||||
<string name="folder_label_format">%1$s (%2$s)</string>
|
||||
<string name="folder_content_info">%1$s, %2$d files, %3$d directories</string>
|
||||
<string name="file_info">%1$s, last modified %2$s</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-cache-path name="files" path="/" />
|
||||
</paths>
|
||||
+3
-4
@@ -1,9 +1,10 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.2.0'
|
||||
ext.kotlin_version = '1.2.20'
|
||||
ext.support_version = '27.0.2'
|
||||
ext.build_tools_version = '3.0.1'
|
||||
ext.anko_version = '0.10.4'
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
@@ -13,9 +14,7 @@ buildscript {
|
||||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:$build_tools_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
NEW_VERSION_NAME=$1
|
||||
OLD_VERSION_NAME=$(grep "versionName" "app/build.gradle" | awk '{print $2}' | tr -d "\"")
|
||||
if [[ -z ${NEW_VERSION_NAME} ]]
|
||||
then
|
||||
echo "New version name is empty. Please set a new version. Current version: $OLD_VERSION_NAME"
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "
|
||||
|
||||
Updating Translations
|
||||
-----------------------------
|
||||
"
|
||||
tx push -s
|
||||
# Force push/pull to make sure this is executed. Apparently tx only compares timestamps, not file
|
||||
# contents. So if a file was `touch`ed, it won't be updated by default.
|
||||
tx pull -a -f
|
||||
git add -A "app/src/main/res/values-*/strings.xml"
|
||||
if ! git diff --cached --exit-code;
|
||||
then
|
||||
git commit -m "Imported translations"
|
||||
fi
|
||||
|
||||
echo "
|
||||
|
||||
Updating Version
|
||||
-----------------------------
|
||||
"
|
||||
OLD_VERSION_CODE=$(grep "versionCode" "app/build.gradle" -m 1 | awk '{print $2}')
|
||||
NEW_VERSION_CODE=$(($OLD_VERSION_CODE + 1))
|
||||
sed -i "s/versionCode $OLD_VERSION_CODE/versionCode $NEW_VERSION_CODE/" "app/build.gradle"
|
||||
sed -i "s/versionName \"$OLD_VERSION_NAME\"/versionName \"$NEW_VERSION_NAME\"/" "app/build.gradle"
|
||||
|
||||
LIBRARY_NAME="com.github.Nutomic:syncthing-java"
|
||||
sed -i "s/$LIBRARY_NAME:$OLD_VERSION_NAME/$LIBRARY_NAME:$NEW_VERSION_NAME/" "app/build.gradle"
|
||||
|
||||
git add "app/build.gradle"
|
||||
git commit -m "Version $NEW_VERSION_NAME"
|
||||
git tag ${NEW_VERSION_NAME}
|
||||
|
||||
echo "
|
||||
|
||||
Running Lint
|
||||
-----------------------------
|
||||
"
|
||||
./gradlew clean lintVitalRelease
|
||||
|
||||
echo "
|
||||
Update ready.
|
||||
"
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
version=$(git describe --tags)
|
||||
regex='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
if [[ ! ${version} =~ $regex ]]
|
||||
then
|
||||
echo "Current commit is not a release"
|
||||
exit;
|
||||
fi
|
||||
|
||||
echo "
|
||||
|
||||
Pushing to Github
|
||||
-----------------------------
|
||||
"
|
||||
git push
|
||||
git push --tags
|
||||
|
||||
echo "
|
||||
|
||||
Push to Google Play
|
||||
-----------------------------
|
||||
"
|
||||
|
||||
read -s -p "Enter signing password: " password
|
||||
|
||||
SIGNING_PASSWORD=${password} ./gradlew assembleRelease
|
||||
|
||||
# Upload apk and listing to Google Play
|
||||
SIGNING_PASSWORD=${password} ./gradlew publishRelease
|
||||
|
||||
echo "
|
||||
|
||||
Release published!
|
||||
"
|
||||
Reference in New Issue
Block a user