From df0f2d651cd9231873ed5354ecdaddbd24cee4da Mon Sep 17 00:00:00 2001 From: ShellWen Chen Date: Mon, 5 Aug 2024 18:44:49 +0800 Subject: [PATCH] feat(sftp): notify MediaStore on file changes This commit adds functionality to notify the MediaStore when files are modified via SFTP. This ensures that changes made through SFTP are reflected in the Android media library. Specifically, the MediaStore is notified after file creation, deletion, copying, renaming, and link creation. Additionally, it is notified after closing a file that was opened for writing. This ensures that the MediaStore is kept up-to-date with any changes made to files through SFTP. --- .../Plugins/SftpPlugin/SimpleSftpServer.kt | 74 ++++++++++++++++++- .../SftpPlugin/saf/SafFileSystemProvider.kt | 6 -- .../Plugins/SftpPlugin/saf/SafPath.kt | 6 +- 3 files changed, 78 insertions(+), 8 deletions(-) diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.kt b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.kt index aa2259f8..c1c18a00 100644 --- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.kt +++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.kt @@ -7,6 +7,7 @@ package org.kde.kdeconnect.Plugins.SftpPlugin import android.content.Context +import android.net.Uri import android.os.Build import android.util.Log import org.apache.sshd.common.file.nativefs.NativeFileSystemFactory @@ -26,6 +27,7 @@ import org.apache.sshd.sftp.server.SftpFileSystemAccessor import org.apache.sshd.sftp.server.SftpSubsystemFactory import org.apache.sshd.sftp.server.SftpSubsystemProxy import org.kde.kdeconnect.Device +import org.kde.kdeconnect.Helpers.MediaStoreHelper import org.kde.kdeconnect.Helpers.RandomHelper import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper import org.kde.kdeconnect.Helpers.SecurityHelpers.constantTimeCompare @@ -33,10 +35,13 @@ import org.kde.kdeconnect.Plugins.SftpPlugin.saf.SafFileSystemFactory import org.kde.kdeconnect.Plugins.SftpPlugin.saf.SafPath import org.slf4j.impl.HandroidLoggerAdapter import java.io.IOException +import java.nio.channels.Channel import java.nio.channels.SeekableByteChannel import java.nio.charset.StandardCharsets +import java.nio.file.CopyOption import java.nio.file.OpenOption import java.nio.file.Path +import java.nio.file.StandardOpenOption import java.nio.file.attribute.FileAttribute import java.security.GeneralSecurityException import java.security.KeyPair @@ -103,6 +108,17 @@ internal class SimpleSftpServer { sshd.subsystemFactories = listOf(SftpSubsystemFactory.Builder().apply { withFileSystemAccessor(object : SftpFileSystemAccessor { + fun notifyMediaStore(path: Path) { + kotlin.runCatching { + val uri = Uri.parse(path.toUri().toString()) + MediaStoreHelper.indexFile(context, uri) + uri + }.fold( + onSuccess = { Log.i(TAG, "Notified media store: $path, $it") }, + onFailure = { Log.w(TAG, "Failed to notify media store: $path", it) } + ) + } + override fun openFile( subsystem: SftpSubsystemProxy?, fileHandle: FileHandle?, @@ -116,6 +132,61 @@ internal class SimpleSftpServer { } return super.openFile(subsystem, fileHandle, file, handle, options, *attrs) } + + override fun removeFile( + subsystem: SftpSubsystemProxy?, + path: Path?, + isDirectory: Boolean + ) { + super.removeFile(subsystem, path, isDirectory) + path?.let { notifyMediaStore(it) } + } + + override fun copyFile( + subsystem: SftpSubsystemProxy?, + src: Path?, + dst: Path?, + opts: MutableCollection? + ) { + super.copyFile(subsystem, src, dst, opts) + dst?.let { notifyMediaStore(it) } + } + + override fun renameFile( + subsystem: SftpSubsystemProxy?, + oldPath: Path?, + newPath: Path?, + opts: MutableCollection? + ) { + super.renameFile(subsystem, oldPath, newPath, opts) + oldPath?.let { notifyMediaStore(it) } + newPath?.let { notifyMediaStore(it) } + } + + override fun createLink( + subsystem: SftpSubsystemProxy?, + link: Path?, + existing: Path?, + symLink: Boolean + ) { + super.createLink(subsystem, link, existing, symLink) + link?.let { notifyMediaStore(it) } + existing?.let { notifyMediaStore(it) } + } + + override fun closeFile( + subsystem: SftpSubsystemProxy?, + fileHandle: FileHandle?, + file: Path?, + handle: String?, + channel: Channel?, + options: MutableSet? + ) { + super.closeFile(subsystem, fileHandle, file, handle, channel, options) + if (options?.contains(StandardOpenOption.WRITE) == true) { + file?.let { notifyMediaStore(it) } + } + } }) }.build()) @@ -189,8 +260,9 @@ internal class SimpleSftpServer { } companion object { - private val PORT_RANGE = 1739..1764 + private const val TAG = "SimpleSftpServer" + private val PORT_RANGE = 1739..1764 const val USER: String = "kdeconnect" init { diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafFileSystemProvider.kt b/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafFileSystemProvider.kt index 452481fd..a92e0b52 100644 --- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafFileSystemProvider.kt +++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafFileSystemProvider.kt @@ -98,7 +98,6 @@ class SafFileSystemProvider( val docFile = parent.createFile(Files.probeContentType(path), path.names.last()) ?: throw IOException("Failed to create $path") val uri = docFile.uri - MediaStoreHelper.indexFile(context, uri) path.safUri = uri return uri } @@ -226,7 +225,6 @@ class SafFileSystemProvider( if (!docFile.delete()) { throw IOException("Failed to delete $path") } - MediaStoreHelper.indexFile(context, docFile.uri) } override fun copy(source: Path, target: Path, vararg options: CopyOption) { @@ -272,8 +270,6 @@ class SafFileSystemProvider( if (newUri == null) { // renameDocument returns null on failure return@firstStep } - MediaStoreHelper.indexFile(context, sourceUri) - MediaStoreHelper.indexFile(context, newUri) source.safUri = newUri return } catch (ignored: FileNotFoundException) { @@ -295,8 +291,6 @@ class SafFileSystemProvider( parentUri, destParentUri ) - MediaStoreHelper.indexFile(context, sourceUri) - MediaStoreHelper.indexFile(context, newUri) source.safUri = newUri!! return } diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafPath.kt b/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafPath.kt index 317f638f..bc38d4f8 100644 --- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafPath.kt +++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/saf/SafPath.kt @@ -7,9 +7,9 @@ package org.kde.kdeconnect.Plugins.SftpPlugin.saf import android.content.Context import android.net.Uri -import android.util.Log import androidx.documentfile.provider.DocumentFile import org.apache.sshd.common.file.util.BasePath +import java.net.URI import java.nio.file.LinkOption class SafPath( @@ -21,6 +21,10 @@ class SafPath( return this.normalize() } + override fun toUri(): URI { + return URI.create(safUri.toString()) ?: throw IllegalStateException("SafUri is null") + } + fun getDocumentFile(ctx: Context): DocumentFile? { if (safUri == null) return null return DocumentFile.fromTreeUri(ctx, safUri!!)