100行 python实现Android与windows局域网文件夹同步

news/2024/5/28 4:08:59/文章来源:https://blog.csdn.net/jiulang9/article/details/133252519

编程解决一切烦恼

Obsidian搭建个人笔记

最近在使用Obsidian搭建个人云笔记

请添加图片描述

尽管我使用腾讯云COS图床+gitee实现了云备份,但是在Android上使的Obsidian备份有点麻烦。还好我主要是在电脑端做笔记,手机只是作为阅读工具。

所以,我写一个局域网文件夹同步工具,来解决这个问题。

传输速度很快

在这里插入图片描述

局域网文件互传

Windows和Android之间实现局域网内文件互传有以下几种协议

HTTP 协议

优点:

  • 实现简单,客户端和服务器都有成熟的库
  • 安全性较好,支持HTTPS加密
  • 可以传输不同类型的数据,包括文件、文本等

缺点:

  • 传输效率比Socket等协议低
  • 需要自行处理大文件分片上传和下载

Socket 协议

优点:

  • 传输效率高,特别适合传输大文件
  • 建立连接简单快速

缺点:

  • 需要处理粘包问题,协议较为复杂
  • 没有加密,安全性差
  • 需要处理网络状态变化等异常

SFTP 协议

优点:

  • 安全性好,基于SSH通道传输
  • 支持直接映射为本地磁盘访问

缺点:

  • 实现较复杂,需要找到可用的SFTP库
  • 传输效率比Socket低

WebSocket 协议

优点:

  • 传输效率高,支持双向通信
  • 接口简单统一

缺点:

  • 需要处理连接状态,实现较为复杂
  • 没有加密,安全性较差

综合来说,使用HTTPSocket都是不错的选择

WebSocket

但是最后我选择了WebSocket,原因是Socket在处理接收数据的时候需要考虑缓冲区的大小和计算json结尾标识,实现起来较为繁琐,而WebSocketSocket在实现这个简单的功能时的性能差别几乎可以忽略不计,而且WebSocket可以轻松实现按行读取数据,有效避免数据污染和丢失的问题。最关键的一点是,WebSocket还可以轻松实现剪贴板同步功能。

我一开始尝试使用Socket来实现这个功能,但很快就发现实现起来相当麻烦,于是换用了WebSocket,两者在速度上没有任何差别,用WebSocket起来舒服多了!

我最近开发了一个笔录加密共享App 也是使用了WebSocket
下载地址:https://www.wordsfairy.cloud/introduce/

请添加图片描述

思路

使用Python将Windows目标文件夹压缩成zip格式,然后将其发送到Android设备。在Android设备上,接收压缩文件后,通过MD5校验确保文件的完整性。一旦确认无误,将zip文件解压到当前目录,最后删除压缩文件。整个过程既有趣又实用!

MD5校验没写,一直用着也没发现有压缩包损坏的情况(超小声)

定义json格式和功能标识码

为每个功能定义标识码

enum class SocketType(val type: String, val msg: String) {FILE_SYNC("FILE_SYNC", "文件同步"),FOLDER_SYNC("FOLDER_SYNC", "文件夹同步"),CLIPBOARD_SYNC("CLIPBOARD_SYNC", "剪贴板同步"),HEARTBEAT("HEARTBEAT", "心跳"),FILE_SENDING("FILE_SENDING", "发送中"),FOLDER_SYNCING("FOLDER_SYNCING", "文件夹同步中"),FILE_SENDEND("FILE_SENDEND", "发送完成");
}

用于文件传输过程中表示文件发送进度的模型类

data class FileSendingDot(val fileName: String,val bufferSize: Int,val total: Long,val sent: Long,val data: String
)

Python服务器端实现

创建websocket服务端

使用Pythonasynciowebsockets模块实现了一个异步的WebSocket服务器,通过异步事件循环来处理客户端的连接和通信。

import asyncio
import websocketsstart_server = websockets.serve(handle_client, "", 9999)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
解析同步请求,操作本地文件夹
json_obj = json.loads(data)type_value = json_obj["type"]data_value = json_obj["data"]if type_value == "FILE_SYNC":await send_file(websocket,"FILE_SENDING", file_path)
利用循环分块读取文件并通过WebSocket发送每个数据块,同时构造消息对象封装文件信息
file_data = f.read(buffer_size)sent_size += len(file_data)# 发送数据块,包含序号和数据send_file_data = base64.b64encode(file_data).decode()file_seading_data = {"fileName": filename,"bufferSize":buffer_size,"total": total_size,"sent": sent_size,"data": send_file_data,}msg = {"type": type,"msg": "发送中","data": json.dumps(file_seading_data),}await ws.send(json.dumps(msg))

安卓客户端 Jetpack ComposeUI 实现

请求所有文件访问权限


va launcher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) { result ->
// 权限已授权 or 权限被拒绝
}private fun checkAndRequestAllFilePermissions() {//检查权限if (!Environment.isExternalStorageManager()) {val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)intent.setData(Uri.parse("package:$packageName"))launcher.launch(intent)}
}

自定义保存路径

选择文件夹

rememberLauncherForActivityResult() 创建一个ActivityResultLauncher,用于启动并获取文件夹选择的回调结果。

val selectFolderResult = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { data ->val uri = data.data?.dataif (uri != null) {intentChannel.trySend(ViewIntent.SelectFolder(uri))} else {ToastModel("选择困难! ƪ(˘⌣˘)ʃ", ToastModel.Type.Info).showToast()}}

Uri的path

fun Uri.toFilePath(): String {val uriPath = this.path ?: return ""val path = uriPath.split(":")[1]return Environment.getExternalStorageDirectory().path + "/" + path
}

请添加图片描述

okhttp实现websocket


private val client = OkHttpClient.Builder().build()//通过callbackFlow封装,实现流式API
fun connect() =createSocketFlow().onEach {LogX.i("WebSocket", "收到消息 $it")}.retry(reconnectInterval)private fun createSocketFlow(): Flow<String> = callbackFlow {val request = Request.Builder().url("ws://192.168.0.102:9999").build()val listener = object : WebSocketListener() {...接收消息的回调}socket = client.newWebSocket(request, listener)//心跳机制launchHeartbeat()awaitClose { socket?.cancel() }
}.flowOn(Dispatchers.IO)//服务端发送数据
fun send(message: String) {socket?.send(message)
}

接收文件

使用 Base64.decode() 方法将 base64 数据解码成字节数组 fileData

val fileName = dot.fileName
val file = File(AppSystemSetManage.fileSavePath, fileName)
val fileData = Base64.decode(dot.data, Base64.DEFAULT)
  • 接着就是使用IO数据流 OutputStream 加上自定义的路径 一顿操作 就得到zip文件了
  • 最后解压zip到当前文件夹

接收文件

显示发送进度

从FileSendingDot对象中取出已发送数据量sent和总数据量total。
可以实时获取文件传输的进度

drawBehind在后面绘制矩形实现进度条占位。根据进度计算矩形宽度,实现进度填充效果。不会遮挡子组件,很简洁地实现自定义进度条。

Box(modifier = Modifier.fillMaxWidth().drawBehind {val fraction = progress * size.widthdrawRoundRect(color = progressColor,size = Size(width = fraction, height = size.height),cornerRadius = CornerRadius(12.dp.toPx()),alpha = 0.9f,)}
@Composable
fun ProgressCard(modifier: Modifier = Modifier,title: String,progress: Float,onClick: () -> Unit = {}
) {val progressColor = WordsFairyTheme.colors.themeAccent//通过判断progress的值来决定是否显示加载val load = progress > 0Fval textColor = if (load) WordsFairyTheme.colors.themeUi else WordsFairyTheme.colors.textPrimaryOutlinedCard(modifier = modifier,onClick = onClick,colors =CardDefaults.cardColors(WordsFairyTheme.colors.itemBackground),border = BorderStroke(1.dp, textColor)) {Box(modifier = Modifier.fillMaxWidth().drawBehind {val fraction = progress * size.widthdrawRoundRect(color = progressColor,size = Size(width = fraction, height = size.height),cornerRadius = CornerRadius(12.dp.toPx()),alpha = 0.9f,)},content = {Row {Title(title = title, Modifier.padding(16.dp),color = textColor)Spacer(Modifier.weight(1f))if (load)Title(title = "${(progress * 100).toInt()}%", Modifier.padding(16.dp),color = textColor)}})}
}

效果图

请添加图片描述

python代码

import asyncio
import websockets
import os
from pathlib import Path
import pyperclip
import json
import base64
import zipfile
import mathFILE_BUFFER_MIN = 1024
FILE_BUFFER_MAX = 1024 * 1024 # 1MBfile_path = "E:\\xy\\FruitSugarContentDetection.zip"
folder_path = "E:\\Note\\Obsidian"
zip_path = "E:\\Note\\Obsidian.zip"async def send_file(ws,type, filepath):# 获取文件名filename = os.path.basename(filepath)total_size = os.path.getsize(filepath)sent_size = 0if total_size < FILE_BUFFER_MAX * 10:buffer_size = math.ceil(total_size / 100) else:buffer_size = FILE_BUFFER_MAXwith open(filepath, "rb") as f:while sent_size < total_size:file_data = f.read(buffer_size)sent_size += len(file_data)# 发送数据块,包含序号和数据send_file_data = base64.b64encode(file_data).decode()file_seading_data = {"fileName": filename,"bufferSize":buffer_size,"total": total_size,"sent": sent_size,"data": send_file_data,}msg = {"type": type,"msg": "发送中","data": json.dumps(file_seading_data),}await ws.send(json.dumps(msg))print((sent_size / total_size) * 100)# 发送结束标志endmsg = {"type": "FILE_SENDEND", "msg": "发送完成", "data": "发送完成"}await ws.send(json.dumps(endmsg))async def handle_client(websocket, path):# 用户连接时打印日志print("用户连接")async for data in websocket:print(data)json_obj = json.loads(data)type_value = json_obj["type"]data_value = json_obj["data"]if type_value == "FILE_SYNC":await send_file(websocket,"FILE_SENDING", file_path)if type_value == "FOLDER_SYNC":zip_folder(folder_path, zip_path)await send_file(websocket,"FOLDER_SYNCING", zip_path)   if type_value == "CLIPBOARD_SYNC":pyperclip.copy(data_value)print(data_value)if type_value == "HEARTBEAT":dictionary_data = {"type": "HEARTBEAT","msg": "hi","data": "",}await websocket.send(json.dumps(dictionary_data))# 用户断开时打印日志print("用户断开")def zip_folder(folder_path, zip_path):with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:for root, _, files in os.walk(folder_path):for file in files:file_path = os.path.join(root, file)zipf.write(file_path, arcname=os.path.relpath(file_path, folder_path))start_server = websockets.serve(handle_client, "", 9999)asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

源码

github: https://github.com/JIULANG9/FileSync

gitee: https://gitee.com/JIULANG9/FileSync

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ldbm.cn/p/138276.html

如若内容造成侵权/违法违规/事实不符,请联系编程新知网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 技术架构图(InsCode AI 创作助手辅助)

Spring Boot 技术架构是一种用于构建现代应用程序的框架&#xff0c;它可以与各种前端、代理、网关、业务服务、中间件、存储、持续集成和容器服务集成在一起&#xff0c;以创建功能强大的应用程序。 源文件下载链接&#xff01;&#xff01;&#xff01;&#xff01;&#xff…

02-Zookeeper实战

上一篇&#xff1a;01-Zookeeper特性与节点数据类型详解 1. zookeeper安装 Step1&#xff1a; 配置JAVA环境&#xff0c;检验环境&#xff1a; java -versionStep2: 下载解压 zookeeper wget https://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.5.8/apache-zookeepe…

Android Studio 的aapt2.exe在哪个目录下

一般在&#xff1a;C:\Users\admin\AppData\Local\Android\Sdk\build-tools\30.0.2&#xff08;不一定是30.0.2&#xff0c;这个得看你的版本&#xff09; 怎么找&#xff1a; 1.打开Android studio

unity lua开发体系搭建

在前面的文章里面我们已经介绍了怎么样在unity里面配置lua的开发环境&#xff0c;我们可以通过C#代码装载lua的脚本并执行相应的处理&#xff0c;这次我们一步步搭建下lua的开发体系。 1.基于c#体系所有的类都继承MonoBehaviour在这里lua环境下我们也需要创建一个类似于这个类的…

9月的一些琐碎,但值得记录的事情!

明显感觉时间过得好快&#xff0c;上个月还在写8月的小结&#xff0c;马上就是9月的了。 9月份比较忙&#xff0c;但是很充实&#xff0c;可能自己创业做事情&#xff0c;多少更有动力一些。 1. 要说&#xff19;月份最大的事情&#xff0c;就是女儿开始上幼儿园了&#xff0c;…

功率放大器有哪些要求和标准参数

功率放大器是一种常见的电子设备&#xff0c;用于将输入信号增强到更高的功率级别。为了满足不同应用需求&#xff0c;功率放大器需要符合一些特定的要求和标准参数。 在现代电子设备中&#xff0c;功率放大器广泛应用于各种领域&#xff0c;如通信、音频放大、射频放大等。它们…

APP自动化之weditor工具

由于最近事情颇多&#xff0c;许久未更新文章。大家在做APP自动化测试过程中&#xff0c;可能使用的是Appium官方提供的inspect进行元素定位&#xff0c;但此工具调试不方便&#xff0c;于是今天给大家分享一款更好用的APP定位元素工具&#xff1a;weditor weditor基于web网页…

复杂度分析

文章目录 如何分析、统计算法的执行效率和资源消耗&#xff1f;为什么需要复杂度分析&#xff1f;测试结果非常依赖测试环境测试结果受数据规模的影响很大 大O复杂度表示法时间复杂度分析只关注循环次数最多的一段代码加法法则&#xff1a;总复杂度等于量级最大的那段代码的复杂…

分布式网络在移动医疗场景中的应用

随着医疗信息化建设实践的深入&#xff0c;越来越多的医疗机构开始借助网络信息技术改善其运营及管理模式&#xff0c;为患者提供更高质量、更高效率、更加安全体贴的医疗服务。移动医疗便是在此背景下产生的新业务需求。 常见的移动医疗场景 住院部&#xff1a;移动查房、智…

力扣 -- 44. 通配符匹配

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:bool isMatch(string s, string p) {int ms.size();int np.size();//为了调整映射关系s s;p p;//多开一行多开一列vector<vector<bool>> dp(m1,vector<bool>(n1,false));//初始化//dp[0]…

【GDB】使用 GDB 自动画红黑树

阅读本文前需要的基础知识 用 python 扩展 gdb python 绘制 graphviz 使用 GDB 画红黑树 前面几节中介绍了 gdb 的 python 扩展&#xff0c;参考 用 python 扩展 gdb 并且 python 有 graphviz 模块&#xff0c;那么可以用 gdb 调用 python&#xff0c;在 python 中使用 grap…

数据结构--双链表

今天我们来用数组来模拟双链表 为什么要数组模拟呢&#xff1f; 因为用数组模拟的双链表&#xff0c;运行速度更快&#xff0c;做算法题更加舒服 用数组模拟双链表的内容 1、同样也有首尾结点 2、相邻的两个节点是相互指向的 3、可以看成两个方向相反的单链表相互连接在一起 …

【项目】Http服务器

【项目】Http服务器 项目简介 背景&#xff1a; http协议被广泛使用&#xff0c;从移动端&#xff0c;pc端浏览器&#xff0c;http协议无疑是打开互联网应用窗口的重要协议&#xff0c;http在网络应用层中的地位不可撼动&#xff0c;是能准确区分前后台的重要协议。 描述&a…

Android Studio插件版本与Gradle 版本对应关系

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、Gradle各版本对应关系3.1 Gradle 版…

26663-2011 大型液压安全联轴器 课堂随笔

声明 本文是学习GB-T 26663-2011 大型液压安全联轴器. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了大型液压安全联轴器的分类、技术要求、试验方法及检验规则等。 本标准适用于联接两同轴线的传动轴系&#xff0c;可起到限制…

【AntDesign】封装全局异常处理-全局拦截器

[toc] 场景 本文前端用的是阿里的Ant-Design框架&#xff0c;其他框架也有全局拦截器&#xff0c;思路是相同&#xff0c;具体实现自行百度下吧 因为每次都需要调接口&#xff0c;都需要单独处理异常情况&#xff08;code !0&#xff09;&#xff0c;因此前端需要对后端返回的…

问题记录 springboot 事务方法中使用this调用其它方法

原因: 因为代理对象中调用了原始对象的toString()方法,所以两个不同的对象打印出的引用是相同的

解决Linux服务器中docker访问报127.0.0.1:2375拒绝连接 (Connection refused)的问题

解决问题&#xff1a; org.apache.hc.client5.http.HttpHostConnectException: Connect to http://127.0.0.1:2375 [/127.0.0.1] failed: 拒绝连接 (Connection refused) 解决思路&#xff1a; 在Linux服务器中&#xff0c;Docker是远程访问的&#xff0c;因此需要开放2375端口…

Java 基于 SpringBoot 的在线学习平台

1 简介 基于SpringBoot的Java学习平台&#xff0c;通过这个系统能够满足学习信息的管理及学生和教师的学习管理功能。系统的主要功能包括首页&#xff0c;个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;课程信息管理&#xff0c;类型管理&#xff0c;作业信息…

时序分解 | Matlab实现SSA-VMD麻雀算法优化变分模态分解时间序列信号分解

时序分解 | Matlab实现SSA-VMD麻雀算法优化变分模态分解时间序列信号分解 目录 时序分解 | Matlab实现SSA-VMD麻雀算法优化变分模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SSA-VMD麻雀搜索算法SSA优化VMD变分模态分解 可直接运行 分解效果好…