17 Commits

Author SHA1 Message Date
Felix Ableitner 1b7dbd91e6 Version 0.1.4 2018-01-27 17:31:27 +09:00
Felix Ableitner cb7a2d362f Imported translations 2018-01-27 17:31:27 +09:00
Felix Ableitner ef2d7fe9d7 Updated release script 2018-01-27 17:30:51 +09:00
Felix Ableitner 0bd96302e0 Adjust for library changes 2018-01-27 17:10:31 +09:00
Felix Ableitner e0a95a0314 Added transifex link to readme 2018-01-27 05:36:48 +09:00
Felix Ableitner c2e7f7cbc2 Add Transifex integration 2018-01-27 00:09:38 +09:00
Felix Ableitner 631a2a4fe3 Fixed some crashes during index update 2018-01-25 17:23:29 +09:00
Felix Ableitner 36e54f5d24 Implement proper string formatting 2018-01-23 16:40:11 +09:00
Felix Ableitner 3506df6b22 Code cleanup 2018-01-22 22:06:47 +09:00
Felix Ableitner 2e1369d9a8 Update dependencies 2018-01-22 01:52:13 +09:00
Felix Ableitner a3495784f7 Version 0.1.3 2018-01-18 01:36:17 +09:00
Felix Ableitner 82c92c9031 Use FileProvider to share downloaded files (fixes #13) 2018-01-18 01:15:59 +09:00
Felix Ableitner bd5d89c158 Adjust to library changes 2018-01-18 00:38:20 +09:00
Felix Ableitner 9cf96d86dd Improve LibraryHandler to prevent various crashes (fixes #8) 2018-01-09 14:51:15 +09:00
Felix Ableitner f4c1e6a0f0 Fixed crash related to loading dialog (ref #11) 2018-01-09 03:32:57 +09:00
Felix Ableitner 036d3846bc Added release scripts 2018-01-04 16:39:52 +09:00
Felix Ableitner 6696f0ff88 Version 0.1.2 2018-01-04 16:28:10 +09:00
25 changed files with 483 additions and 343 deletions
+9
View File
@@ -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
+4
View File
@@ -15,6 +15,10 @@ This project is based on [syncthing-java][3], a java implementation of Syncthing
[<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
+27 -8
View File
@@ -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
@@ -10,8 +11,8 @@ android {
applicationId "net.syncthing.lite"
minSdkVersion 19
targetSdkVersion 25
versionCode 3
versionName "0.1.2"
versionCode 6
versionName "0.1.4"
multiDexEnabled true
}
sourceSets {
@@ -21,26 +22,44 @@ 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
}
}
}
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.1.4") {
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"
}
}
+11 -1
View File
@@ -22,6 +22,16 @@
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>
</application>
</manifest>
</manifest>
@@ -5,18 +5,14 @@ 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
@@ -52,14 +48,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,22 +70,25 @@ 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 ->
UploadFileTask(this@FolderBrowserActivity, syncthingClient, intent!!.data,
indexBrowser.folder, indexBrowser.currentPath,
this@FolderBrowserActivity::showUploadHereDialog).uploadFile()
}
}
}
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
@@ -101,7 +96,7 @@ class FolderBrowserActivity : SyncthingActivity() {
} else {
Log.i(TAG, "pulling file = " + fileInfo)
executeWithPermissions(
Runnable { DownloadFileTask(this, syncthingClient(), fileInfo).downloadFile() })
Runnable { libraryHandler?.syncthingClient { DownloadFileTask(this, it, fileInfo).downloadFile() } })
}
}
}
@@ -113,17 +108,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
}
}
@@ -137,10 +132,9 @@ class FolderBrowserActivity : SyncthingActivity() {
})
}
override fun onIndexUpdateProgress(folder: FolderInfo, percentage: Int) {
override fun onIndexUpdateProgress(folder: String, 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))
binding.indexUpdateLabel.text = getString(R.string.index_update_progress_label, folder, percentage)
updateFolderListView()
}
@@ -8,7 +8,8 @@ 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
@@ -50,10 +51,6 @@ class MainActivity : SyncthingActivity() {
onNavigationItemSelectedListener(selection)
}
override fun onLibraryLoaded() {
currentFragment?.onLibraryLoaded()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
drawerToggle!!.onConfigurationChanged(newConfig)
@@ -72,7 +69,7 @@ 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.update_index -> libraryHandler?.syncthingClient { UpdateIndexTask(this@MainActivity, it).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 +91,22 @@ class MainActivity : SyncthingActivity() {
}
private fun cleanCacheAndIndex() {
syncthingClient().clearCacheAndIndex()
recreate()
async(UI) {
libraryHandler?.syncthingClient { it.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 onIndexUpdateProgress(folder: String, percentage: Int) {
async(UI) {
binding.indexUpdate.visibility = View.VISIBLE
binding.indexUpdateLabel.text = getString(R.string.index_update_progress_label, folder, percentage)
}
}
override fun onIndexUpdateComplete() {
binding.indexUpdate.visibility = View.GONE
async(UI) {
binding.indexUpdate.visibility = View.GONE
}
}
}
@@ -5,81 +5,48 @@ import android.databinding.DataBindingUtil
import android.os.Bundle
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.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
}
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(folder: String, percentage: Int) {}
open fun onIndexUpdateComplete() {}
@@ -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.lite.R
import net.syncthing.lite.databinding.ListviewDeviceBinding
class DevicesAdapter(context: Context) :
ArrayAdapter<DeviceStats>(context, R.layout.listview_device, Lists.newArrayList()) {
ArrayAdapter<DeviceStats>(context, R.layout.listview_device, mutableListOf()) {
override fun getView(position: Int, v: View?, parent: ViewGroup): View {
val binding: ListviewDeviceBinding
@@ -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
}
@@ -25,13 +25,14 @@ class FoldersListAdapter(context: Context?, list: List<Pair<FolderInfo, FolderSt
}
val folderInfo = getItem(position)!!.left
val folderStats = getItem(position)!!.right
binding.folderName.text = "${folderInfo.label} (${folderInfo.folder})"
binding.folderName.text = context.getString(R.string.folder_label_format, 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"
else 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,9 +12,11 @@ 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
@@ -24,14 +25,11 @@ 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 +41,7 @@ class DevicesFragment : SyncthingFragment() {
return binding.root
}
override fun onLibraryLoadedAndActivityCreated() {
override fun onLibraryLoaded() {
initDeviceList()
updateDeviceList()
}
@@ -52,23 +50,25 @@ 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 = (binding.list.getItemAtPosition(position) as DeviceStats)
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 { it.Editor().removePeer(device.deviceId).persistLater() }
}
.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()
libraryHandler?.syncthingClient { syncthingClient ->
adapter.clear()
adapter.addAll(syncthingClient.devicesHandler.getDeviceStatsList())
adapter.notifyDataSetChanged()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
@@ -82,22 +82,27 @@ 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?.library { configuration, syncthingClient, _ ->
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()
val modified = configuration.Editor().addPeers(DeviceInfo(deviceId, null))
if (modified) {
configuration.Editor().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)
UpdateIndexTask(this@DevicesFragment.context!!, syncthingClient).updateIndex()
} else {
Toast.makeText(this@DevicesFragment.context, getString(R.string.device_already_known, deviceId), Toast.LENGTH_SHORT).show()
}
}
}
}
@@ -6,23 +6,15 @@ 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 +25,21 @@ 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().sortedBy { it.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)
}
}
}
}
@@ -2,30 +2,32 @@ package net.syncthing.lite.fragments
import android.os.Bundle
import android.support.v4.app.Fragment
import net.syncthing.lite.activities.SyncthingActivity
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(folder: String, percentage: Int) {}
open fun onIndexUpdateComplete() {}
}
@@ -4,9 +4,8 @@ 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.support.v4.content.FileProvider
import android.util.Log
import android.webkit.MimeTypeMap
import net.syncthing.java.bep.BlockPuller
@@ -34,18 +33,17 @@ class DownloadFileTask(private val mContext: Context, private val mSyncthingClie
mSyncthingClient.pullFile(mFileInfo, { observer ->
onProgress(observer)
try {
while (!observer.isCompleted) {
while (!observer.isCompleted()) {
if (cancelled)
return@pullFile
observer.waitForProgressUpdate()
Log.i("pullFile", "download progress = " + observer.progressMessage)
Log.i("pullFile", "download progress = " + observer.progressMessage())
onProgress(observer)
}
val outputDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val outputFile = File(outputDir, mFileInfo.fileName)
FileUtils.copyInputStreamToFile(observer.inputStream, outputFile)
val outputFile = File("${mContext.externalCacheDir}/${mFileInfo.folder}/${mFileInfo.path}")
FileUtils.copyInputStreamToFile(observer.inputStream(), outputFile)
Log.i(TAG, "downloaded file = " + mFileInfo.path)
onComplete(outputFile)
} catch (e: IOException) {
@@ -73,7 +71,7 @@ class DownloadFileTask(private val mContext: Context, private val mSyncthingClie
uiThread {
progressDialog.isIndeterminate = false
progressDialog.max = (mFileInfo.size as Long).toInt()
progressDialog.progress = (fileDownloadObserver.progress * mFileInfo.size!!).toInt()
progressDialog.progress = (fileDownloadObserver.progress() * mFileInfo.size!!).toInt()
}
}
}
@@ -85,15 +83,16 @@ class DownloadFileTask(private val mContext: Context, private val mSyncthingClie
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(FilenameUtils.getExtension(file.name))
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(Uri.fromFile(file), mimeType)
val uri = FileProvider.getUriForFile(mContext, "net.syncthing.lite.fileprovider", file)
intent.setDataAndType(uri, mimeType)
intent.newTask()
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
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) {
@@ -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,178 @@
package net.syncthing.lite.library
import android.content.Context
import android.os.Handler
import android.preference.PreferenceManager
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.FolderInfo
import net.syncthing.java.core.beans.FileInfo
import net.syncthing.java.core.beans.IndexInfo
import net.syncthing.java.core.configuration.ConfigurationService
import net.syncthing.java.core.security.KeystoreHandler
import net.syncthing.lite.utils.Util
import org.apache.commons.io.FileUtils
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.io.File
import java.io.IOException
import java.util.*
class LibraryHandler {
class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit,
private val onIndexUpdateProgressListener: (String, Int) -> Unit,
private val onIndexUpdateCompleteListener: () -> Unit) {
companion object {
private var instanceCount = 0
private var configuration: ConfigurationService? = null
private var syncthingClient: SyncthingClient? = null
private var folderBrowser: FolderBrowser? = null
private val callbacks = ArrayList<(ConfigurationService, SyncthingClient, FolderBrowser) -> Unit>()
private var isLoading = false
}
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
private val onIndexUpdateListener: Any
interface OnIndexUpdatedListener {
fun onIndexUpdateProgress(folder: FolderInfo, percentage: Int)
fun onIndexUpdateComplete()
}
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()
init {
instanceCount++
if (configuration == null && !isLoading) {
doAsync {
init(context)
//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))
syncthingClient { UpdateIndexTask(context, it).updateIndex() }
}
uiThread {
onLibraryLoaded(this@LibraryHandler)
}
}
} 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!!)
onIndexUpdateListener = object : Any() {
}
syncthingClient {
it.indexHandler.registerOnIndexRecordAcquiredListener(this::onIndexRecordAcquired)
it.indexHandler.registerOnFullIndexAcquiredListenersListener(this::onRemoteIndexAcquired)
}
}
private fun onIndexRecordAcquired(folderId: String, newRecords: List<FileInfo>, indexInfo: IndexInfo) {
newRecords.size
Log.i(TAG, "handleIndexRecordEvent trigger folder list update from index record acquired")
onIndexUpdateProgressListener(folderId, (indexInfo.getCompleted() * 100).toInt())
}
private fun onRemoteIndexAcquired(folderId: String) {
Log.i(TAG, "handleIndexAcquiredEvent trigger folder list update from index acquired")
onIndexUpdateCompleteListener()
}
private fun init(context: Context) {
isLoading = true
val configuration = ConfigurationService.Loader()
.setCache(File(context.externalCacheDir, ".cache"))
.setDatabase(File(context.getExternalFilesDir(null), "database"))
.loadFrom(File(context.getExternalFilesDir(null), "config.properties"))
configuration.Editor().setDeviceName(Util.getDeviceName())
try {
FileUtils.cleanDirectory(configuration.temp)
} catch (e: IOException) {
Log.e(TAG, "Failed to delete temporary files", e)
close()
}
KeystoreHandler.Loader().loadAndStore(configuration)
configuration.Editor().persistLater()
Log.i(TAG, "loaded mConfiguration = " + configuration.Writer().dumpToString())
Log.i(TAG, "storage space = " + configuration.getStorageInfo().dumpAvailableSpace())
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")
configuration.close()
syncthingClient.close()
folderBrowser.close()
}
async(UI) {
callbacks.forEach { it(configuration, syncthingClient, folderBrowser) }
}
LibraryHandler.configuration = configuration
LibraryHandler.syncthingClient = syncthingClient
LibraryHandler.folderBrowser = folderBrowser
isLoading = false
}
fun setOnIndexUpdatedListener(onIndexUpdatedListener: OnIndexUpdatedListener) {
mOnIndexUpdatedListener = onIndexUpdatedListener
syncthingClient!!.indexHandler.eventBus.register(object : Any() {
fun library(callback: (ConfigurationService, 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: (ConfigurationService) -> 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?.close()
configuration = null
}
}.start()
}, 1000)
}
}
@@ -4,14 +4,14 @@ import android.app.ProgressDialog
import android.content.Context
import android.net.Uri
import android.util.Log
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.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
// TODO: this should be an IntentService with notification
@@ -38,12 +38,12 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
syncthingClient.pushFile(uploadStream, syncthingFolder, syncthingPath, { observer ->
onProgress(observer)
try {
while (!observer.isCompleted) {
while (!observer.isCompleted()) {
if (mCancelled)
return@pushFile
observer.waitForProgressUpdate()
Log.i(TAG, "upload progress = " + observer.progressMessage)
Log.i(TAG, "upload progress = " + observer.progressMessage())
onProgress(observer)
}
} catch (e: InterruptedException) {
@@ -69,12 +69,10 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
}
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()
}
async(UI) {
mProgressDialog.isIndeterminate = false
mProgressDialog.max = observer.dataSource().getSize().toInt()
mProgressDialog.progress = (observer.progress() * observer.dataSource().getSize()).toInt()
}
}
@@ -83,21 +81,17 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
return
Log.i(TAG, "Uploaded file $fileName to folder $syncthingFolder:$syncthingPath")
doAsync {
uiThread {
mProgressDialog.dismiss()
context.toast(R.string.toast_upload_complete)
onUploadCompleteListener()
}
async(UI) {
mProgressDialog.dismiss()
this@UploadFileTask.context.toast(R.string.toast_upload_complete)
onUploadCompleteListener()
}
}
private fun onError() {
doAsync {
uiThread {
mProgressDialog.dismiss()
context.toast(R.string.toast_file_upload_failed)
}
async(UI) {
mProgressDialog.dismiss()
this@UploadFileTask.context.toast(R.string.toast_file_upload_failed)
}
}
}
}
@@ -5,16 +5,16 @@ 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 org.apache.commons.lang3.StringUtils.capitalize
import java.io.File
object Util {
private val Tag = "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)
@@ -26,12 +26,12 @@ object Util {
fun getContentFileName(context: Context, contentUri: Uri): String {
var fileName = File(contentUri.lastPathSegment).name
if (equal(contentUri.scheme, "content")) {
if (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)
Log.d(Tag, "recovered 'content' uri real path = " + path)
fileName = File(Uri.parse(path).lastPathSegment).name
}
}
+11 -11
View File
@@ -1,6 +1,6 @@
<?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="index_update_progress_message">Index wird aktualisiert…</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>
@@ -21,17 +21,17 @@
<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="index_update_progress_label">Index aktualisierung für Ordner %1$s, %2$d\\% 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>
-3
View File
@@ -6,7 +6,4 @@
<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>
+11 -11
View File
@@ -1,5 +1,5 @@
<resources>
<string name="app_name" translatable="false">Syncthing Lite</string>
<string name="app_name">Syncthing Lite</string>
<string name="index_update_progress_message">Index update…</string>
<string name="folder_list_empty_message">No folder available</string>
<string name="clear_local_cache_index_label">Clear local cache/index</string>
@@ -21,17 +21,17 @@
<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_unknown">Last modified: unknown</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>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-cache-path name="files" path="/" />
</paths>
+3 -4
View File
@@ -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'
}
}
+54
View File
@@ -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.
"
+37
View File
@@ -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!
"