4 Commits

Author SHA1 Message Date
Felix Ableitner ef401378e2 Version 0.1.5 2018-01-29 23:08:08 +09:00
Felix Ableitner 2c0be54e61 Imported translations 2018-01-29 23:08:08 +09:00
Felix Ableitner cb4b838082 Rewrite configuration 2018-01-29 21:53:54 +09:00
Felix Ableitner 6a6b40a89d Fix file uploads, use system chooser for uploads 2018-01-28 21:30:11 +09:00
16 changed files with 102 additions and 224 deletions
+6 -3
View File
@@ -11,8 +11,8 @@ android {
applicationId "net.syncthing.lite"
minSdkVersion 19
targetSdkVersion 25
versionCode 6
versionName "0.1.4"
versionCode 7
versionName "0.1.5"
multiDexEnabled true
}
sourceSets {
@@ -38,6 +38,9 @@ android {
signingConfig signingConfigs.release
}
}
packagingOptions {
exclude 'META-INF/*'
}
}
dependencies {
@@ -48,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.4") {
implementation ("com.github.Nutomic:syncthing-java:0.1.5") {
exclude group: 'commons-logging', module:'commons-logging'
exclude group: 'org.apache.httpcomponents', module:'httpclient'
exclude group: 'org.slf4j'
-3
View File
@@ -2,7 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.syncthing.lite">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
@@ -20,8 +19,6 @@
</activity>
<activity android:name=".activities.FolderBrowserActivity"
android:parentActivityName=".activities.MainActivity"/>
<activity android:name=".activities.FilePickerActivity"
android:parentActivityName=".activities.FolderBrowserActivity" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="net.syncthing.lite.fileprovider"
@@ -1,101 +0,0 @@
package net.syncthing.lite.activities
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.TextView
import net.syncthing.lite.R
import java.io.File
import java.util.*
/**
* Activity that allows selecting a directory in the local file system.
*/
class FilePickerActivity : SyncthingActivity(), AdapterView.OnItemClickListener {
private lateinit var mListView: ListView
private lateinit var mFilesAdapter: FileAdapter
private lateinit var mLocation: File
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_folder_picker)
mListView = findViewById(android.R.id.list)
mListView.onItemClickListener = this
mListView.emptyView = findViewById(android.R.id.empty)
mFilesAdapter = FileAdapter(this)
mListView.adapter = mFilesAdapter
displayFolder(Environment.getExternalStorageDirectory())
}
/**
* Refreshes the ListView to show the contents of the location in ``mLocation.peek()}.
*/
private fun displayFolder(location: File) {
mLocation = location
mFilesAdapter.clear()
// In case we don't have read access to the location, just display nothing.
val contents = location.listFiles() ?: arrayOf()
Arrays.sort(contents) { f1, f2 ->
if (f1.isDirectory && f2.isFile)
return@sort -1
if (f1.isFile && f2.isDirectory)
return@sort 1
f1.name.compareTo(f2.name)
}
for (f in contents) {
mFilesAdapter.add(f)
}
mListView.adapter = mFilesAdapter
}
override fun onItemClick(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
val f = mFilesAdapter.getItem(i)
if (f!!.isDirectory) {
displayFolder(f)
} else {
val intent = Intent()
intent.data = Uri.fromFile(f)
setResult(Activity.RESULT_OK, intent)
finish()
}
}
private inner class FileAdapter(context: Context) : ArrayAdapter<File>(context, R.layout.item_folder_picker) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = super.getView(position, convertView, parent)
val title = view.findViewById<TextView>(android.R.id.text1)
val f = getItem(position)!!
title.text = f.name
val icon =
if (f.isDirectory) R.drawable.ic_folder_black_24dp
else R.drawable.ic_image_black_24dp
title.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0)
return view
}
}
override fun onBackPressed() {
if (mLocation != Environment.getExternalStorageDirectory()) {
displayFolder(mLocation.parentFile)
} else {
setResult(Activity.RESULT_CANCELED)
finish()
}
}
}
@@ -1,16 +1,11 @@
package net.syncthing.lite.activities
import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.databinding.DataBindingUtil
import android.os.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 net.syncthing.java.bep.IndexBrowser
import net.syncthing.java.core.beans.FileInfo
import net.syncthing.java.core.utils.PathUtils
@@ -19,23 +14,21 @@ import net.syncthing.lite.adapters.FolderContentsAdapter
import net.syncthing.lite.databinding.ActivityFolderBrowserBinding
import net.syncthing.lite.library.DownloadFileTask
import net.syncthing.lite.library.UploadFileTask
import org.jetbrains.anko.intentFor
class FolderBrowserActivity : SyncthingActivity() {
companion object {
private val TAG = "FolderBrowserActivity"
private val REQUEST_WRITE_STORAGE = 142
private val REQUEST_SELECT_UPLOAD_FILE = 171
private const val TAG = "FolderBrowserActivity"
private const val REQUEST_WRITE_STORAGE = 142
private const val REQUEST_SELECT_UPLOAD_FILE = 171
val EXTRA_FOLDER_NAME = "folder_name"
const val EXTRA_FOLDER_NAME = "folder_name"
}
private lateinit var binding: ActivityFolderBrowserBinding
private lateinit var indexBrowser: IndexBrowser
private lateinit var adapter: FolderContentsAdapter
private var runWhenPermissionsReceived: Runnable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -73,7 +66,7 @@ class FolderBrowserActivity : SyncthingActivity() {
libraryHandler?.syncthingClient { syncthingClient ->
UploadFileTask(this@FolderBrowserActivity, syncthingClient, intent!!.data,
indexBrowser.folder, indexBrowser.currentPath,
this@FolderBrowserActivity::showUploadHereDialog).uploadFile()
{ showFolderListView(indexBrowser.currentPath) } ).uploadFile()
}
}
}
@@ -95,8 +88,7 @@ class FolderBrowserActivity : SyncthingActivity() {
binding.progressBar.visibility = View.VISIBLE
} else {
Log.i(TAG, "pulling file = " + fileInfo)
executeWithPermissions(
Runnable { libraryHandler?.syncthingClient { DownloadFileTask(this, it, fileInfo).downloadFile() } })
libraryHandler?.syncthingClient { DownloadFileTask(this, it, fileInfo).downloadFile() }
}
}
}
@@ -127,9 +119,10 @@ class FolderBrowserActivity : SyncthingActivity() {
}
private fun showUploadHereDialog() {
executeWithPermissions(Runnable {
startActivityForResult(intentFor<FilePickerActivity>(), REQUEST_SELECT_UPLOAD_FILE)
})
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
startActivityForResult(intent, REQUEST_SELECT_UPLOAD_FILE)
}
override fun onIndexUpdateProgress(folder: String, percentage: Int) {
@@ -138,37 +131,8 @@ class FolderBrowserActivity : SyncthingActivity() {
updateFolderListView()
}
override fun onIndexUpdateComplete() {
override fun onIndexUpdateComplete(folder: String) {
binding.indexUpdate.visibility = View.GONE
updateFolderListView()
}
private fun executeWithPermissions(runnable: Runnable) {
val permissionState = ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (permissionState != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_WRITE_STORAGE)
runWhenPermissionsReceived = runnable
} else {
runnable.run()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,
grantResults: IntArray) {
when (requestCode) {
REQUEST_WRITE_STORAGE -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, R.string.toast_write_storage_permission_required,
Toast.LENGTH_LONG).show()
} else {
runWhenPermissionsReceived!!.run()
}
runWhenPermissionsReceived = null
}
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
}
@@ -98,15 +98,11 @@ class MainActivity : SyncthingActivity() {
}
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)
}
binding.indexUpdate.visibility = View.VISIBLE
binding.indexUpdateLabel.text = getString(R.string.index_update_progress_label, folder, percentage)
}
override fun onIndexUpdateComplete() {
async(UI) {
binding.indexUpdate.visibility = View.GONE
}
override fun onIndexUpdateComplete(folder: String) {
binding.indexUpdate.visibility = View.GONE
}
}
@@ -48,7 +48,7 @@ abstract class SyncthingActivity : AppCompatActivity() {
open fun onIndexUpdateProgress(folder: String, percentage: Int) {}
open fun onIndexUpdateComplete() {}
open fun onIndexUpdateComplete(folder: String) {}
open fun onLibraryLoaded() {}
}
@@ -27,10 +27,7 @@ class FoldersListAdapter(context: Context?, list: List<Pair<FolderInfo, FolderSt
val folderStats = getItem(position)!!.right
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_time,
binding.folderLastmodInfo.text = context.getString(R.string.last_modified_time,
DateUtils.getRelativeDateTimeString(context, folderStats.lastUpdate.time, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0))
binding.folderContentInfo.text = context.getString(R.string.folder_content_info, folderStats.describeSize(), folderStats.fileCount, folderStats.dirCount)
return binding.root
@@ -55,7 +55,9 @@ class DevicesFragment : SyncthingFragment() {
.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 { it.Editor().removePeer(device.deviceId).persistLater() }
libraryHandler?.configuration { config ->
config.peers = config.peers.filterNot { config.localDeviceId == device.deviceId }.toSet()
}
}
.setNegativeButton(android.R.string.no, null)
.show()
@@ -93,9 +95,9 @@ class DevicesFragment : SyncthingFragment() {
return@async
}
val modified = configuration.Editor().addPeers(DeviceInfo(deviceId, null))
if (modified) {
configuration.Editor().persistLater()
if (!configuration.peerIds.contains(deviceId)) {
configuration.peers = configuration.peers + DeviceInfo(deviceId, null)
configuration.persistLater()
Toast.makeText(this@DevicesFragment.context, getString(R.string.device_import_success, deviceId), Toast.LENGTH_SHORT).show()
updateDeviceList()//TODO remove this if event triggered (and handler trigger update)
UpdateIndexTask(this@DevicesFragment.context!!, syncthingClient).updateIndex()
@@ -29,5 +29,5 @@ abstract class SyncthingFragment : Fragment() {
open fun onIndexUpdateProgress(folder: String, percentage: Int) {}
open fun onIndexUpdateComplete() {}
open fun onIndexUpdateComplete(folder: String) {}
}
@@ -10,26 +10,22 @@ 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.IndexInfo
import net.syncthing.java.core.configuration.ConfigurationService
import net.syncthing.java.core.security.KeystoreHandler
import net.syncthing.java.core.configuration.Configuration
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(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit,
private val onIndexUpdateProgressListener: (String, Int) -> Unit,
private val onIndexUpdateCompleteListener: () -> Unit) {
private val onIndexUpdateCompleteListener: (String) -> Unit) {
companion object {
private var instanceCount = 0
private var configuration: ConfigurationService? = null
private var configuration: Configuration? = null
private var syncthingClient: SyncthingClient? = null
private var folderBrowser: FolderBrowser? = null
private val callbacks = ArrayList<(ConfigurationService, SyncthingClient, FolderBrowser) -> Unit>()
private val callbacks = ArrayList<(Configuration, SyncthingClient, FolderBrowser) -> Unit>()
private var isLoading = false
}
@@ -67,41 +63,32 @@ class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit
}
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())
async(UI) {
onIndexUpdateProgressListener(folderId, (indexInfo.getCompleted() * 100).toInt())
}
}
private fun onRemoteIndexAcquired(folderId: String) {
Log.i(TAG, "handleIndexAcquiredEvent trigger folder list update from index acquired")
onIndexUpdateCompleteListener()
async(UI) {
onIndexUpdateCompleteListener(folderId)
}
}
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 configuration = Configuration(configFolder = context.filesDir, cacheFolder = context.externalCacheDir)
configuration.localDeviceName = Util.getDeviceName()
configuration.persistLater()
val syncthingClient = SyncthingClient(configuration)
//TODO listen for device events, update device list
val folderBrowser = syncthingClient.indexHandler.newFolderBrowser()
if (instanceCount == 0) {
Log.d(TAG, "All LibraryHandler instances were closed during init")
configuration.close()
syncthingClient.close()
folderBrowser.close()
}
@@ -115,7 +102,7 @@ class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit
isLoading = false
}
fun library(callback: (ConfigurationService, SyncthingClient, FolderBrowser) -> Unit) {
fun library(callback: (Configuration, SyncthingClient, FolderBrowser) -> Unit) {
val nullCount = listOf(configuration, syncthingClient, folderBrowser).count { it == null }
assert(nullCount == 0 || nullCount == 3, { "Inconsistent library state" })
@@ -136,7 +123,7 @@ class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit
library { _, s, _ -> callback(s) }
}
fun configuration(callback: (ConfigurationService) -> Unit) {
fun configuration(callback: (Configuration) -> Unit) {
library { c, _, _ -> callback(c) }
}
@@ -168,7 +155,6 @@ class LibraryHandler(context: Context, onLibraryLoaded: (LibraryHandler) -> Unit
folderBrowser = null
syncthingClient?.close()
syncthingClient = null
configuration?.close()
configuration = null
}
}.start()
@@ -11,6 +11,7 @@ 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
@@ -26,6 +27,7 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
private val fileName = Util.getContentFileName(context, localFile)
private val syncthingPath = PathUtils.buildPath(syncthingSubFolder, fileName)
private val uploadStream = context.contentResolver.openInputStream(localFile)
private lateinit var mProgressDialog: ProgressDialog
private var mCancelled = false
@@ -34,7 +36,6 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
createDialog()
Log.i(TAG, "Uploading file $localFile to folder $syncthingFolder:$syncthingPath")
try {
val uploadStream = context.contentResolver.openInputStream(localFile)
syncthingClient.pushFile(uploadStream, syncthingFolder, syncthingPath, { observer ->
onProgress(observer)
try {
@@ -43,7 +44,7 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
return@pushFile
observer.waitForProgressUpdate()
Log.i(TAG, "upload progress = " + observer.progressMessage())
Log.i(TAG, "upload progress = ${observer.progressPercentage()}%")
onProgress(observer)
}
} catch (e: InterruptedException) {
@@ -51,11 +52,11 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
}
onComplete()
}, { this.onError() })
}, { onError() })
} catch (e: IOException) {
Log.w(TAG, e)
onError()
}
}
private fun createDialog() {
@@ -71,12 +72,13 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
private fun onProgress(observer: BlockPusher.FileUploadObserver) {
async(UI) {
mProgressDialog.isIndeterminate = false
mProgressDialog.max = observer.dataSource().getSize().toInt()
mProgressDialog.progress = (observer.progress() * observer.dataSource().getSize()).toInt()
mProgressDialog.progress = observer.progressPercentage()
mProgressDialog.max = 100
}
}
private fun onComplete() {
IOUtils.closeQuietly(uploadStream)
if (mCancelled)
return
@@ -89,6 +91,7 @@ class UploadFileTask(private val context: Context, private val syncthingClient:
}
private fun onError() {
IOUtils.closeQuietly(uploadStream)
async(UI) {
mProgressDialog.dismiss()
this@UploadFileTask.context.toast(R.string.toast_file_upload_failed)
@@ -3,10 +3,9 @@ package net.syncthing.lite.utils
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.util.Log
import android.provider.OpenableColumns
import org.apache.commons.lang3.StringUtils.capitalize
import java.io.File
import java.security.InvalidParameterException
object Util {
@@ -24,17 +23,13 @@ object Util {
return deviceName ?: "android"
}
fun getContentFileName(context: Context, contentUri: Uri): String {
var fileName = File(contentUri.lastPathSegment).name
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(Tag, "recovered 'content' uri real path = " + path)
fileName = File(Uri.parse(path).lastPathSegment).name
fun getContentFileName(context: Context, uri: Uri): String {
context.contentResolver.query(uri, null, null, null, null, null).use { cursor ->
if (cursor == null || !cursor.moveToFirst()) {
throw InvalidParameterException("Cursor is null or empty")
}
return cursor.getString(
cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
return fileName
}
}
-1
View File
@@ -23,7 +23,6 @@
<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_unknown">zuletzt modifiziert: unbekannt</string>
<string name="last_modified_time">Zuletzt modifiziert: %1$s</string>
<string name="remove_device_title">Gerät entfernen:</string>
<string name="remove_device_message">Gerät %1$s entfernen?</string>
+36
View File
@@ -0,0 +1,36 @@
<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>
<string name="remove_device_message">Rimuovere il dispositivo %1$s dalla lista dei dispositivi noti?</string>
<string name="device_import_success">Dispositivo %1$s importato con successo</string>
<string name="device_already_known">Dispositivo %1$s già presente</string>
<string name="folders_label">Cartelle</string>
<string name="devices_label">Dispositivi</string>
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d file, %3$d cartelle</string>
<string name="file_info">%1$s, ultima modifica %2$s</string>
</resources>
+2
View File
@@ -0,0 +1,2 @@
<resources>
</resources>
-1
View File
@@ -23,7 +23,6 @@
<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="loading_config_starting_syncthing_client">Loading config, starting syncthing client</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>