Skip to content

Commit

Permalink
Add file browser
Browse files Browse the repository at this point in the history
  • Loading branch information
pipe01 committed Mar 25, 2024
1 parent 602b9ae commit 8643fef
Show file tree
Hide file tree
Showing 7 changed files with 416 additions and 46 deletions.
17 changes: 17 additions & 0 deletions app/src/main/java/net/pipe01/pinepartner/NavFrame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@ import net.pipe01.pinepartner.pages.devices.AddDevicePage
import net.pipe01.pinepartner.pages.devices.DFUPage
import net.pipe01.pinepartner.pages.devices.DevicePage
import net.pipe01.pinepartner.pages.devices.DevicesPage
import net.pipe01.pinepartner.pages.devices.FileBrowserPage
import net.pipe01.pinepartner.pages.plugins.CodeViewerPage
import net.pipe01.pinepartner.pages.plugins.ImportPluginPage
import net.pipe01.pinepartner.pages.plugins.PluginPage
import net.pipe01.pinepartner.pages.plugins.PluginsPage
import net.pipe01.pinepartner.pages.settings.NotificationSettingsPage
import net.pipe01.pinepartner.pages.settings.SettingsPage
import net.pipe01.pinepartner.service.BackgroundService
import java.net.URLDecoder
import java.net.URLEncoder

object Route {
const val DEVICES = "devices"
Expand Down Expand Up @@ -223,6 +226,7 @@ fun NavFrame(
db = db,
deviceAddress = address!!,
onUploadFirmware = { navController.navigate("${Route.DEVICES}/$address/dfu") },
onBrowseFiles = { navController.navigate("${Route.DEVICES}/$address/files") },
)
}
composable("${Route.DEVICES}/{address}/dfu") {
Expand All @@ -238,6 +242,19 @@ fun NavFrame(
},
)
}
composable("${Route.DEVICES}/{address}/files?path={path}") {
val address = it.arguments?.getString("address")
val path = it.arguments?.getString("path")?.let { URLDecoder.decode(it, "utf8") }

FileBrowserPage(
backgroundService = backgroundService,
deviceAddress = address!!,
path = path ?: "",
onOpenFolder = { newPath ->
navController.navigate("${Route.DEVICES}/$address/files?path=${URLEncoder.encode(newPath, "utf8")}")
},
)
}
}
}
}
Expand Down
107 changes: 63 additions & 44 deletions app/src/main/java/net/pipe01/pinepartner/devices/blefs/FS.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.util.Log
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import net.pipe01.pinepartner.devices.Device
Expand All @@ -16,6 +17,33 @@ import java.time.Instant

private const val TAG = "BLEFS"

fun joinPaths(path1: String, path2: String): String {
return cleanPath(if (path1 == "") {
path2
} else {
"$path1/$path2"
})
}

fun cleanPath(path: String): String {
val parts = ArrayDeque(path.split("/"))

while (!parts.isEmpty()) {
when (parts.last()) {
"", "." -> parts.removeLast()
".." -> {
parts.removeLast()
if (!parts.isEmpty()) {
parts.removeLast()
}
}
else -> break
}
}

return parts.joinToString("/")
}

@SuppressLint("MissingPermission")
private suspend fun Device.doRequest(
coroutineScope: CoroutineScope,
Expand All @@ -42,6 +70,7 @@ private suspend fun Device.doRequest(

start.await()

delay(1000)
val request = onBuildRequest()
fileService.write(DataByteArray(request.array()))

Expand Down Expand Up @@ -109,49 +138,6 @@ suspend fun Device.readFile(path: String, coroutineScope: CoroutineScope): ByteA
}
}
)
//
// val job = coroutineScope.launch {
// fileService
// .getNotifications()
// .onStart { start.complete(Unit) }
// .collect {
// val buf = ByteBuffer.wrap(it.value).order(ByteOrder.LITTLE_ENDIAN)
//
// if (buf.get() != 0x11.toByte()) {
// Log.e(TAG, "Invalid file response")
// return@collect
// }
//
// val status = buf.get()
// buf.getShort() // Padding
// val offset = buf.getInt()
// val totalSize = buf.getInt()
// val chunkSize = buf.getInt()
//
// if (file == null) {
// file = ByteArray(totalSize)
// }
//
// it.value.copyInto(file!!, offset, buf.position(), buf.position() + chunkSize)
//
// Log.d(TAG, "Read response $offset/$totalSize bytes")
//
// val bytesRemaining = totalSize - offset - chunkSize
//
// if (bytesRemaining == 0) {
// done.complete(Unit)
// } else {
// val continueBuf = ByteBuffer.allocate(12).order(ByteOrder.LITTLE_ENDIAN)
// continueBuf.put(0x12)
// continueBuf.put(0x01)
// continueBuf.putShort(0) // Padding
// continueBuf.putInt(offset + chunkSize)
// continueBuf.putInt(wantChunkSize)
//
// fileService.write(DataByteArray(continueBuf.array()))
// }
// }
// }

Log.d(TAG, "File received, ${file?.size} bytes")
return file ?: throw IllegalStateException("No file received")
Expand Down Expand Up @@ -280,7 +266,8 @@ suspend fun Device.listFiles(path: String, coroutineScope: CoroutineScope): List

files.add(
File(
path = String(pathBuf),
name = String(pathBuf),
fullPath = joinPaths(path, String(pathBuf)),
isDirectory = flags and 0x01u != 0u,
modTime = Instant.ofEpochMilli(timestampNanos.toLong() / 1_000_000),
size = size
Expand All @@ -292,4 +279,36 @@ suspend fun Device.listFiles(path: String, coroutineScope: CoroutineScope): List
)

return files
}

@SuppressLint("MissingPermission")
suspend fun Device.createFolder(path: String, coroutineScope: CoroutineScope) {
doRequest(
coroutineScope = coroutineScope,
onBuildRequest = {
val pathBytes = path.toByteArray()

ByteBuffer
.allocate(16 + pathBytes.size)
.order(ByteOrder.LITTLE_ENDIAN)
.put(0x40)
.put(0) // Padding
.putShort(pathBytes.size.toShort())
.putInt(0) // Padding
.putLong(0) // Timestamp
.put(pathBytes)
},
onReceiveResponse = { resp, _ ->
if (resp.get() != 0x41.toByte()) {
Log.e(TAG, "Invalid create folder response")
return@doRequest true
}

val status = resp.get()

Log.d(TAG, "Create folder status $status")

true
}
)
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package net.pipe01.pinepartner.devices.blefs

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.time.Instant

@Parcelize
data class File(
val path: String,
val name: String,
val fullPath: String,
val isDirectory: Boolean,
val modTime: Instant,
val size: UInt,
)
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ fun DevicePage(
deviceAddress: String,
backgroundService: BackgroundService,
onUploadFirmware: () -> Unit,
onBrowseFiles: () -> Unit,
) {
val coroutineScope = rememberCoroutineScope()

Expand Down Expand Up @@ -64,6 +65,10 @@ fun DevicePage(
Text(text = "Upload firmware")
}

Button(onClick = onBrowseFiles) {
Text(text = "Browse files")
}

Spacer(modifier = Modifier.height(10.dp))

Button(onClick = {
Expand Down
Loading

0 comments on commit 8643fef

Please sign in to comment.