diff --git a/app/src/main/java/com/chiller3/basicsync/Notifications.kt b/app/src/main/java/com/chiller3/basicsync/Notifications.kt
index 4952bdc..77cdb39 100644
--- a/app/src/main/java/com/chiller3/basicsync/Notifications.kt
+++ b/app/src/main/java/com/chiller3/basicsync/Notifications.kt
@@ -109,12 +109,14 @@ class Notifications(private val context: Context) {
if (runState.showFolderStates) {
setContentText(buildString {
append(context.resources.getQuantityString(
- R.plurals.connected_devices,
- state.connectedDevices,
- state.connectedDevices,
+ R.plurals.device_state_connected,
+ state.deviceStates.connected,
+ state.deviceStates.connected,
))
for ((resId, count) in arrayOf(
+ R.plurals.device_state_syncing to state.deviceStates.syncing,
+ R.plurals.device_state_pending to state.deviceStates.pending,
R.plurals.folder_state_idle to state.folderStates.idle,
R.plurals.folder_state_scanning to state.folderStates.scanning,
R.plurals.folder_state_syncing to state.folderStates.syncing,
diff --git a/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt b/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt
index 8e7ecef..ae7d57b 100644
--- a/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt
+++ b/app/src/main/java/com/chiller3/basicsync/settings/SettingsScreen.kt
@@ -926,7 +926,7 @@ private fun PreviewSettingsScreen() {
preRunAction = null,
showExit = false,
folderStates = SyncthingService.FolderStates(),
- connectedDevices = 0,
+ deviceStates = SyncthingService.DeviceStates(),
)
AppTheme {
diff --git a/app/src/main/java/com/chiller3/basicsync/syncthing/DeviceState.kt b/app/src/main/java/com/chiller3/basicsync/syncthing/DeviceState.kt
index 3c0def7..4df36fb 100644
--- a/app/src/main/java/com/chiller3/basicsync/syncthing/DeviceState.kt
+++ b/app/src/main/java/com/chiller3/basicsync/syncthing/DeviceState.kt
@@ -535,7 +535,9 @@ class DeviceStateTracker(private val context: Context) :
}
}
- fun updateConnectedDevices(count: Int) {
+ fun updateConnectedDevices(deviceStates: SyncthingService.DeviceStates) {
+ val count = deviceStates.connected
+
handler.post {
if (state.connectedDevices != count) {
val connectedBefore = state.connectedOnce
diff --git a/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt b/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt
index 5e2a7eb..80d4dac 100644
--- a/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt
+++ b/app/src/main/java/com/chiller3/basicsync/syncthing/SyncthingService.kt
@@ -89,7 +89,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
private val preRunAction: PreRunAction?,
private val showExit: Boolean,
val folderStates: FolderStates,
- val connectedDevices: Int,
+ val deviceStates: DeviceStates,
) {
private val shouldResume: Boolean
get() = blockedReasons.isEmpty()
@@ -202,6 +202,18 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
)
}
+ data class DeviceStates(
+ val connected: Int,
+ val syncing: Int,
+ val pending: Int,
+ ) {
+ constructor() : this(
+ connected = 0,
+ syncing = 0,
+ pending = 0,
+ )
+ }
+
private lateinit var prefs: Preferences
private lateinit var notifications: Notifications
private val runnerThread = Thread(::runner)
@@ -266,7 +278,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
@GuardedBy("stateLock")
private var syncthingFolderStates = FolderStates()
@GuardedBy("stateLock")
- private var syncthingConnectedDevices = 0
+ private var syncthingDeviceStates = DeviceStates()
private val isResumed: Boolean
@GuardedBy("stateLock")
@@ -471,7 +483,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
preRunAction = currentPreRunAction,
showExit = prefs.showExit,
folderStates = syncthingFolderStates,
- connectedDevices = syncthingConnectedDevices,
+ deviceStates = syncthingDeviceStates,
)
val wasChanged = notificationState != lastServiceState
@@ -479,7 +491,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
if (wasChanged || forceShowNotification) {
if (wasChanged) {
deviceStateTracker.updateBusyFolders(notificationState.folderStates)
- deviceStateTracker.updateConnectedDevices(notificationState.connectedDevices)
+ deviceStateTracker.updateConnectedDevices(notificationState.deviceStates)
val guiInfo = guiInfo
@@ -627,7 +639,7 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
syncthingConflicts = emptyList()
syncthingAlerts = 0
syncthingFolderStates = FolderStates()
- syncthingConnectedDevices = 0
+ syncthingDeviceStates = DeviceStates()
syncthingApp = null
stateChanged()
@@ -680,9 +692,13 @@ class SyncthingService : Service(), SyncthingStatusReceiver, DeviceStateListener
}
@WorkerThread
- override fun onConnectedDevicesUpdated(count: Int) {
+ override fun onDeviceStatesUpdated(connected: Int, syncing: Int, pending: Int) {
synchronized(stateLock) {
- syncthingConnectedDevices = count
+ syncthingDeviceStates = DeviceStates(
+ connected = connected,
+ syncing = syncing,
+ pending = pending,
+ )
stateChanged()
}
}
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index e434737..4751fb1 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -213,7 +213,7 @@
- • %d folderów uruchamianych
- • %d folderów uruchamianych
-
+
- • %d podłączone urządzenie
- • %d podłączone urządzenia
- • %d podłączonych urządzeń
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 8a426c7..e6d7723 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -189,7 +189,7 @@
- • %d klasör başlatılıyor
- • %d klasör başlatılıyor
-
+
- • %d cihaz bağlandı
- • %d cihaz bağlandı
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 85f14ec..a901f0e 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -177,7 +177,7 @@
- • %d 个文件夹正在启动
-
+
- • %d 台设备已连接
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 682fc1d..071221d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -337,10 +337,20 @@
- • %d folders starting
-
+
- • %d device connected
- • %d devices connected
+
+
+ - • %d device syncing
+ - • %d devices syncing
+
+
+
+ - • %d device pending sync
+ - • %d devices pending sync
+
Auto mode
diff --git a/stbridge/stbridge.go b/stbridge/stbridge.go
index 3230fd1..115d2ad 100644
--- a/stbridge/stbridge.go
+++ b/stbridge/stbridge.go
@@ -306,7 +306,11 @@ type SyncthingStatusReceiver interface {
starting int32,
)
- OnConnectedDevicesUpdated(count int32)
+ OnDeviceStatesUpdated(
+ connected int32,
+ syncing int32,
+ pending int32,
+ )
}
// db.DB is internal, so it's unnameable and we can't create a function that
@@ -436,11 +440,28 @@ func dispatchFolderStates(
)
}
-func dispatchConnectedDevices(
- devicesConnected map[string]struct{},
+type deviceStates struct {
+ connected map[string]struct{}
+ dirty map[string]map[string]struct{}
+}
+
+func dispatchDeviceStates(
+ deviceStates *deviceStates,
receiver SyncthingStatusReceiver,
) {
- receiver.OnConnectedDevicesUpdated(int32(len(devicesConnected)))
+ connected := int32(len(deviceStates.connected))
+ syncing := int32(0)
+ pending := int32(0)
+
+ for deviceID, _ := range deviceStates.dirty {
+ if _, ok := deviceStates.connected[deviceID]; ok {
+ syncing += 1
+ } else {
+ pending += 1
+ }
+ }
+
+ receiver.OnDeviceStatesUpdated(connected, syncing, pending)
}
func eventLoop(
@@ -463,12 +484,16 @@ func eventLoop(
events.StateChanged |
events.DeviceConnected |
events.DeviceDisconnected |
+ events.FolderCompletion |
events.ConfigSaved,
)
defer sub.Unsubscribe()
- devicesConnected := map[string]struct{}{}
folderStates := map[string]string{}
+ deviceStates := deviceStates{
+ connected: map[string]struct{}{},
+ dirty: map[string]map[string]struct{}{},
+ }
for {
select {
@@ -580,17 +605,46 @@ func eventLoop(
data := evt.Data.(map[string]string)
deviceID := data["id"]
- devicesConnected[deviceID] = struct{}{}
+ deviceStates.connected[deviceID] = struct{}{}
- dispatchConnectedDevices(devicesConnected, receiver)
+ dispatchDeviceStates(&deviceStates, receiver)
case events.DeviceDisconnected:
data := evt.Data.(map[string]string)
deviceID := data["id"]
- delete(devicesConnected, deviceID)
+ delete(deviceStates.connected, deviceID)
+
+ dispatchDeviceStates(&deviceStates, receiver)
+
+ case events.FolderCompletion:
+ data := evt.Data.(map[string]interface{})
+ folderID := data["folder"].(string)
+ deviceID := data["device"].(string)
+
+ needBytes := data["needBytes"].(int64)
+ needItems := data["needItems"].(int)
+ needDeletes := data["needDeletes"].(int)
+
+ syncing := needBytes != 0 || needItems != 0 || needDeletes != 0
- dispatchConnectedDevices(devicesConnected, receiver)
+ if syncing {
+ if _, ok := deviceStates.dirty[deviceID]; !ok {
+ deviceStates.dirty[deviceID] = map[string]struct{}{}
+ }
+ if _, ok := deviceStates.dirty[deviceID][folderID]; !ok {
+ deviceStates.dirty[deviceID][folderID] = struct{}{}
+ }
+ } else {
+ if folders, ok := deviceStates.dirty[deviceID]; ok {
+ delete(folders, folderID)
+ if len(folders) == 0 {
+ delete(deviceStates.dirty, deviceID)
+ }
+ }
+ }
+
+ dispatchDeviceStates(&deviceStates, receiver)
// When a folder is deleted, we need to manually clear out the
// corresponding conflicts because we will not receive deletion
@@ -605,23 +659,44 @@ func eventLoop(
conflictsInfo.folderPaths[folder.ID], _ = fs.ExpandTilde(folder.Path)
}
- for key := range conflictsInfo.byFolder {
- if _, ok := conflictsInfo.folderPaths[key]; !ok {
- delete(conflictsInfo.byFolder, key)
+ for folderID := range conflictsInfo.byFolder {
+ if _, ok := conflictsInfo.folderPaths[folderID]; !ok {
+ delete(conflictsInfo.byFolder, folderID)
}
}
- for key := range folderStates {
- if _, ok := conflictsInfo.folderPaths[key]; !ok {
- delete(folderStates, key)
+ dispatchConflicts(conflictsInfo, receiver)
+
+ for folderID := range folderStates {
+ if _, ok := conflictsInfo.folderPaths[folderID]; !ok {
+ delete(folderStates, folderID)
}
}
- dispatchConflicts(conflictsInfo, receiver)
dispatchFolderStates(folderStates, receiver)
- // Unlike folders, we don't need to remove deleted devices from
- // devicesConnected. We'll always receive a disconnection event
- // when connections are closed during deletion.
+
+ devices := map[string]struct{}{}
+
+ for _, device := range cfg.Devices {
+ devices[device.DeviceID.String()] = struct{}{}
+ }
+
+ // We don't need to clean up deviceStates.connected because
+ // we'll always receive a disconnection event when connections
+ // are closed during deletion.
+
+ for deviceID, folders := range deviceStates.dirty {
+ for folderID := range folders {
+ if _, ok := conflictsInfo.folderPaths[folderID]; !ok {
+ delete(folders, folderID)
+ }
+ }
+ if _, ok := devices[deviceID]; !ok || len(folders) == 0 {
+ delete(deviceStates.dirty, deviceID)
+ }
+ }
+
+ dispatchDeviceStates(&deviceStates, receiver)
// A config.Wrapper is needed to determine if a restart is
// required. config.Configuration does not contain enough info.