Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b9ba7d33b | |||
| c6950493e6 | |||
| b71740d044 | |||
| dafe262c1c | |||
| acb1f75c5c | |||
| 03cc4f931d | |||
| 967d65b3f9 | |||
| ce6e7e2130 | |||
| 809eff7354 | |||
| 944cecce1f | |||
| 31abff58c1 |
@@ -13,6 +13,8 @@ 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
|
||||
|
||||
+3
-3
@@ -9,10 +9,10 @@ android {
|
||||
dataBinding.enabled = true
|
||||
defaultConfig {
|
||||
applicationId "net.syncthing.lite"
|
||||
minSdkVersion 19
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 7
|
||||
versionName "0.1.5"
|
||||
versionName "0.2.0"
|
||||
multiDexEnabled true
|
||||
}
|
||||
sourceSets {
|
||||
@@ -51,7 +51,7 @@ dependencies {
|
||||
implementation "com.android.support:appcompat-v7:$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.5") {
|
||||
implementation ("com.github.Nutomic:syncthing-java:0.2.0") {
|
||||
exclude group: 'commons-logging', module:'commons-logging'
|
||||
exclude group: 'org.apache.httpcomponents', module:'httpclient'
|
||||
exclude group: 'org.slf4j'
|
||||
|
||||
@@ -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>
|
||||
@@ -28,7 +28,16 @@
|
||||
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>
|
||||
|
||||
@@ -8,19 +8,19 @@ import android.util.Log
|
||||
import android.view.View
|
||||
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.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 net.syncthing.lite.utils.FileDownloadDialog
|
||||
import net.syncthing.lite.utils.FileUploadDialog
|
||||
|
||||
class FolderBrowserActivity : SyncthingActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "FolderBrowserActivity"
|
||||
private const val REQUEST_WRITE_STORAGE = 142
|
||||
private const val REQUEST_SELECT_UPLOAD_FILE = 171
|
||||
|
||||
const val EXTRA_FOLDER_NAME = "folder_name"
|
||||
@@ -64,9 +64,9 @@ class FolderBrowserActivity : SyncthingActivity() {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
|
||||
if (requestCode == REQUEST_SELECT_UPLOAD_FILE && resultCode == Activity.RESULT_OK) {
|
||||
libraryHandler?.syncthingClient { syncthingClient ->
|
||||
UploadFileTask(this@FolderBrowserActivity, syncthingClient, intent!!.data,
|
||||
FileUploadDialog(this@FolderBrowserActivity, syncthingClient, intent!!.data,
|
||||
indexBrowser.folder, indexBrowser.currentPath,
|
||||
{ showFolderListView(indexBrowser.currentPath) } ).uploadFile()
|
||||
{ showFolderListView(indexBrowser.currentPath) } )
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class FolderBrowserActivity : SyncthingActivity() {
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
} else {
|
||||
Log.i(TAG, "pulling file = " + fileInfo)
|
||||
libraryHandler?.syncthingClient { DownloadFileTask(this, it, fileInfo).downloadFile() }
|
||||
libraryHandler?.syncthingClient { FileDownloadDialog(this, it, fileInfo) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,14 +125,8 @@ class FolderBrowserActivity : SyncthingActivity() {
|
||||
startActivityForResult(intent, REQUEST_SELECT_UPLOAD_FILE)
|
||||
}
|
||||
|
||||
override fun onIndexUpdateProgress(folder: String, percentage: Int) {
|
||||
binding.indexUpdate.visibility = View.VISIBLE
|
||||
binding.indexUpdateLabel.text = getString(R.string.index_update_progress_label, folder, percentage)
|
||||
updateFolderListView()
|
||||
}
|
||||
|
||||
override fun onIndexUpdateComplete(folder: String) {
|
||||
binding.indexUpdate.visibility = View.GONE
|
||||
override fun onIndexUpdateComplete(folderInfo: FolderInfo) {
|
||||
super.onIndexUpdateComplete(folderInfo)
|
||||
updateFolderListView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.os.Bundle
|
||||
import android.support.v7.app.ActionBarDrawerToggle
|
||||
import android.view.Gravity
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import net.syncthing.lite.R
|
||||
@@ -15,7 +14,6 @@ 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() {
|
||||
|
||||
@@ -69,7 +67,6 @@ class MainActivity : SyncthingActivity() {
|
||||
when (menuItem.itemId) {
|
||||
R.id.folders -> setContentFragment(FoldersFragment())
|
||||
R.id.devices -> setContentFragment(DevicesFragment())
|
||||
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))
|
||||
@@ -96,13 +93,4 @@ class MainActivity : SyncthingActivity() {
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIndexUpdateProgress(folder: String, percentage: Int) {
|
||||
binding.indexUpdate.visibility = View.VISIBLE
|
||||
binding.indexUpdateLabel.text = getString(R.string.index_update_progress_label, folder, percentage)
|
||||
}
|
||||
|
||||
override fun onIndexUpdateComplete(folder: String) {
|
||||
binding.indexUpdate.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ 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.core.beans.FolderInfo
|
||||
import net.syncthing.lite.BuildConfig
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.databinding.DialogLoadingBinding
|
||||
import net.syncthing.lite.library.LibraryHandler
|
||||
import org.jetbrains.anko.contentView
|
||||
import org.slf4j.impl.HandroidLoggerAdapter
|
||||
|
||||
abstract class SyncthingActivity : AppCompatActivity() {
|
||||
@@ -16,6 +19,7 @@ abstract class SyncthingActivity : AppCompatActivity() {
|
||||
var libraryHandler: LibraryHandler? = null
|
||||
private set
|
||||
private var loadingDialog: AlertDialog? = null
|
||||
private var snackBar: Snackbar? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -46,9 +50,18 @@ abstract class SyncthingActivity : AppCompatActivity() {
|
||||
onLibraryLoaded()
|
||||
}
|
||||
|
||||
open fun onIndexUpdateProgress(folder: String, 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(folder: String) {}
|
||||
open fun onIndexUpdateComplete(folderInfo: FolderInfo) {
|
||||
snackBar?.dismiss()
|
||||
snackBar = null
|
||||
}
|
||||
|
||||
open fun onLibraryLoaded() {}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
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, mutableListOf()) {
|
||||
ArrayAdapter<DeviceInfo>(context, R.layout.listview_device, mutableListOf()) {
|
||||
|
||||
override fun getView(position: Int, v: View?, parent: ViewGroup): View {
|
||||
val binding: ListviewDeviceBinding
|
||||
@@ -23,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
|
||||
|
||||
@@ -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,9 +22,9 @@ 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 = context.getString(R.string.folder_label_format, folderInfo.label, folderInfo.folder)
|
||||
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))
|
||||
|
||||
@@ -16,11 +16,9 @@ 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.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
|
||||
@@ -41,6 +39,20 @@ class DevicesFragment : SyncthingFragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -50,13 +62,15 @@ class DevicesFragment : SyncthingFragment() {
|
||||
adapter = DevicesAdapter(context!!)
|
||||
binding.list.adapter = adapter
|
||||
binding.list.setOnItemLongClickListener { _, _, position, _ ->
|
||||
val device = (binding.list.getItemAtPosition(position) as DeviceStats)
|
||||
val device = adapter.getItem(position)
|
||||
AlertDialog.Builder(context)
|
||||
.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) { _, _ ->
|
||||
libraryHandler?.configuration { config ->
|
||||
config.peers = config.peers.filterNot { config.localDeviceId == device.deviceId }.toSet()
|
||||
config.peers = config.peers.filterNot { it.deviceId == device.deviceId }.toSet()
|
||||
config.persistLater()
|
||||
updateDeviceList()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
@@ -66,10 +80,12 @@ class DevicesFragment : SyncthingFragment() {
|
||||
}
|
||||
|
||||
private fun updateDeviceList() {
|
||||
libraryHandler?.syncthingClient { syncthingClient ->
|
||||
adapter.clear()
|
||||
adapter.addAll(syncthingClient.devicesHandler.getDeviceStatsList())
|
||||
adapter.notifyDataSetChanged()
|
||||
async(UI) {
|
||||
libraryHandler?.syncthingClient { syncthingClient ->
|
||||
adapter.clear()
|
||||
adapter.addAll(syncthingClient.getPeerStatus())
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +101,7 @@ class DevicesFragment : SyncthingFragment() {
|
||||
}
|
||||
|
||||
private fun importDeviceId(deviceIdString: String) {
|
||||
libraryHandler?.library { configuration, syncthingClient, _ ->
|
||||
libraryHandler?.configuration { configuration ->
|
||||
async(UI) {
|
||||
val deviceId =
|
||||
try {
|
||||
@@ -100,7 +116,6 @@ class DevicesFragment : SyncthingFragment() {
|
||||
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)
|
||||
UpdateIndexTask(this@DevicesFragment.context!!, syncthingClient).updateIndex()
|
||||
} else {
|
||||
Toast.makeText(this@DevicesFragment.context, getString(R.string.device_already_known, deviceId), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.lite.R
|
||||
import net.syncthing.lite.activities.FolderBrowserActivity
|
||||
import net.syncthing.lite.adapters.FoldersListAdapter
|
||||
@@ -31,15 +32,20 @@ class FoldersFragment : SyncthingFragment() {
|
||||
|
||||
private fun showAllFoldersListView() {
|
||||
libraryHandler?.folderBrowser { folderBrowser ->
|
||||
val list = folderBrowser.folderInfoAndStatsList().sortedBy { it.left.label }
|
||||
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)!!.left.folder
|
||||
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,6 +2,7 @@ package net.syncthing.lite.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import net.syncthing.java.core.beans.FolderInfo
|
||||
import net.syncthing.lite.library.LibraryHandler
|
||||
|
||||
abstract class SyncthingFragment : Fragment() {
|
||||
@@ -27,7 +28,7 @@ abstract class SyncthingFragment : Fragment() {
|
||||
|
||||
open fun onLibraryLoaded() {}
|
||||
|
||||
open fun onIndexUpdateProgress(folder: String, percentage: Int) {}
|
||||
open fun onIndexUpdateProgress(folderInfo: FolderInfo, percentage: Int) {}
|
||||
|
||||
open fun onIndexUpdateComplete(folder: String) {}
|
||||
open fun onIndexUpdateComplete(folderInfo: FolderInfo) {}
|
||||
}
|
||||
@@ -1,106 +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.support.annotation.StringRes
|
||||
import android.support.v4.content.FileProvider
|
||||
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
|
||||
if (isCancelled)
|
||||
return@getBlockPuller
|
||||
|
||||
observer.waitForProgressUpdate()
|
||||
Log.i("pullFile", "download progress = " + observer.progressMessage())
|
||||
onProgress(observer)
|
||||
onProgress(this, observer)
|
||||
}
|
||||
|
||||
val outputFile = File("${mContext.externalCacheDir}/${mFileInfo.folder}/${mFileInfo.path}")
|
||||
val outputFile = File("${context.externalCacheDir}/${fileInfo.folder}/${fileInfo.path}")
|
||||
FileUtils.copyInputStreamToFile(observer.inputStream(), outputFile)
|
||||
Log.i(TAG, "downloaded file = " + mFileInfo.path)
|
||||
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)
|
||||
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) {
|
||||
doAsync {
|
||||
uiThread {
|
||||
progressDialog.dismiss()
|
||||
mContext.toast(error)
|
||||
}
|
||||
}
|
||||
fun cancel() {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,22 @@ package net.syncthing.lite.library
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import net.syncthing.java.bep.FolderBrowser
|
||||
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.beans.IndexInfo
|
||||
import net.syncthing.java.core.configuration.Configuration
|
||||
import net.syncthing.lite.utils.Util
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.util.*
|
||||
|
||||
class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit,
|
||||
private val onIndexUpdateProgressListener: (String, Int) -> Unit,
|
||||
private val onIndexUpdateCompleteListener: (String) -> Unit) {
|
||||
private val onIndexUpdateProgressListener: (FolderInfo, Int) -> Unit,
|
||||
private val onIndexUpdateCompleteListener: (FolderInfo) -> Unit) {
|
||||
|
||||
companion object {
|
||||
private var instanceCount = 0
|
||||
@@ -29,57 +28,46 @@ class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit
|
||||
private var isLoading = false
|
||||
}
|
||||
|
||||
private val TAG = "LibConnectionHandler"
|
||||
|
||||
private val onIndexUpdateListener: Any
|
||||
private val TAG = "LibraryHandler"
|
||||
|
||||
init {
|
||||
instanceCount++
|
||||
if (configuration == null && !isLoading) {
|
||||
isLoading = true
|
||||
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 {
|
||||
async(UI) {
|
||||
onLibraryLoaded(this@LibraryHandler)
|
||||
}
|
||||
isLoading = false
|
||||
}
|
||||
} else {
|
||||
onLibraryLoaded(this)
|
||||
}
|
||||
|
||||
onIndexUpdateListener = object : Any() {
|
||||
}
|
||||
syncthingClient {
|
||||
it.indexHandler.registerOnIndexRecordAcquiredListener(this::onIndexRecordAcquired)
|
||||
it.indexHandler.registerOnFullIndexAcquiredListenersListener(this::onRemoteIndexAcquired)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onIndexRecordAcquired(folderId: String, newRecords: List<FileInfo>, indexInfo: IndexInfo) {
|
||||
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(folderId, (indexInfo.getCompleted() * 100).toInt())
|
||||
onIndexUpdateProgressListener(folderInfo, (indexInfo.getCompleted() * 100).toInt())
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRemoteIndexAcquired(folderId: String) {
|
||||
private fun onRemoteIndexAcquired(folderInfo: FolderInfo) {
|
||||
Log.i(TAG, "handleIndexAcquiredEvent trigger folder list update from index acquired")
|
||||
|
||||
async(UI) {
|
||||
onIndexUpdateCompleteListener(folderId)
|
||||
onIndexUpdateCompleteListener(folderInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun init(context: Context) {
|
||||
isLoading = true
|
||||
val configuration = Configuration(configFolder = context.filesDir, cacheFolder = context.externalCacheDir)
|
||||
configuration.localDeviceName = Util.getDeviceName()
|
||||
configuration.persistLater()
|
||||
@@ -99,10 +87,9 @@ class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit
|
||||
LibraryHandler.configuration = configuration
|
||||
LibraryHandler.syncthingClient = syncthingClient
|
||||
LibraryHandler.folderBrowser = folderBrowser
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
fun library(callback: (Configuration, SyncthingClient, FolderBrowser) -> Unit) {
|
||||
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" })
|
||||
|
||||
@@ -158,7 +145,7 @@ class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit
|
||||
configuration = null
|
||||
}
|
||||
}.start()
|
||||
}, 1000)
|
||||
}, 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,100 +1,48 @@
|
||||
package net.syncthing.lite.library
|
||||
|
||||
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.apache.commons.io.IOUtils
|
||||
import org.jetbrains.anko.toast
|
||||
import java.io.IOException
|
||||
|
||||
// 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 {
|
||||
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.progressPercentage()}%")
|
||||
onProgress(observer)
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
onError()
|
||||
observer.waitForProgressUpdate()
|
||||
Log.i(TAG, "upload progress = ${observer.progressPercentage()}%")
|
||||
onProgress(observer)
|
||||
}
|
||||
|
||||
IOUtils.closeQuietly(uploadStream)
|
||||
onComplete()
|
||||
}, { onError() })
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
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()
|
||||
}
|
||||
|
||||
private fun onProgress(observer: BlockPusher.FileUploadObserver) {
|
||||
async(UI) {
|
||||
mProgressDialog.isIndeterminate = false
|
||||
mProgressDialog.progress = observer.progressPercentage()
|
||||
mProgressDialog.max = 100
|
||||
}
|
||||
}
|
||||
|
||||
private fun onComplete() {
|
||||
IOUtils.closeQuietly(uploadStream)
|
||||
if (mCancelled)
|
||||
return
|
||||
|
||||
Log.i(TAG, "Uploaded file $fileName to folder $syncthingFolder:$syncthingPath")
|
||||
async(UI) {
|
||||
mProgressDialog.dismiss()
|
||||
this@UploadFileTask.context.toast(R.string.toast_upload_complete)
|
||||
onUploadCompleteListener()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onError() {
|
||||
IOUtils.closeQuietly(uploadStream)
|
||||
async(UI) {
|
||||
mProgressDialog.dismiss()
|
||||
this@UploadFileTask.context.toast(R.string.toast_file_upload_failed)
|
||||
}
|
||||
fun cancel() {
|
||||
isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ import java.security.InvalidParameterException
|
||||
|
||||
object Util {
|
||||
|
||||
private val Tag = "Util"
|
||||
|
||||
fun getDeviceName(): String {
|
||||
val manufacturer = Build.MANUFACTURER ?: ""
|
||||
val model = Build.MODEL ?: ""
|
||||
@@ -28,8 +26,7 @@ object Util {
|
||||
if (cursor == null || !cursor.moveToFirst()) {
|
||||
throw InvalidParameterException("Cursor is null or empty")
|
||||
}
|
||||
return cursor.getString(
|
||||
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,27 +1,20 @@
|
||||
<resources>
|
||||
<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>
|
||||
<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_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_time">Zuletzt modifiziert: %1$s</string>
|
||||
<string name="remove_device_title">Gerät entfernen:</string>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,27 +1,20 @@
|
||||
<resources>
|
||||
<string name="app_name">Syncthing Lite</string>
|
||||
<string name="index_update_progress_message">Aggiornamento indice ...</string>
|
||||
<string name="folder_list_empty_message">Nessuna cartella disponibile</string>
|
||||
<string name="clear_local_cache_index_label">Cancella cache/indice</string>
|
||||
<string name="update_remote_index_label">Aggiorna indice remoto</string>
|
||||
<string name="devices_list_view_empty_message">Nessun dispositivo disponibile</string>
|
||||
<string name="toast_write_storage_permission_required">Per questa funzionalità è richiesto il permesso di scrittura</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="toast_index_update_successful">Aggiornamento indice riuscito</string>
|
||||
<string name="toast_index_update_failed">Aggiornamento indice non riuscito per %1$d dispositivi</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="directory_empty">La cartella è vuota</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="index_update_progress_label">Aggiornamento indice per la cartella %1$s, %2$d% sincronizzato</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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,2 +1,29 @@
|
||||
<resources>
|
||||
</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,7 +2,6 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
<resources>
|
||||
<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>
|
||||
<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_progress_label">Index update for folder %1$s, %2$d% 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_time">Last modified: %1$s</string>
|
||||
<string name="remove_device_title">Remove device %1$s\?</string>
|
||||
|
||||
Reference in New Issue
Block a user