localsend二次开发自动监听文件夹实现自动发送 链接到标题
1. 项目需求 链接到标题
我想增加一个自动在局域网内互传文件的功能。核心功能是监听本地电脑中的某个文件夹,当文件夹中有新文件时自动广播发送给其他的客户端,所以需要进行以下逻辑的实现:
- 在localsend界面中增加两个配置选项,一个是监听文件夹路径,另一个是启用监听文件夹
- 在localsend软件中增加监听逻辑,当启用监听文件夹后,只要文件夹中有新增的文件就将他自动发送给局域网其他设备
2.项目架构基础与分层 链接到标题
2.1 什么是前端、后端? 链接到标题
- 前端:用户直接看到和操作的界面,比如App的设置页面、按钮、输入框等。
- 后端:在用户看不到的地方,负责处理数据、业务逻辑,比如监听文件夹、自动广播文件等。
2.2 项目常见的分层 链接到标题
以Flutter项目为例,通常分为以下几层:
层级 | 作用说明 | 你本次涉及的文件举例 |
---|---|---|
UI层(视图) | 展示界面,响应用户操作 | settings_tab.dart |
状态管理层 | 管理和同步App的各种设置、状态 | settings_provider.dart |
数据模型层 | 定义数据结构,描述App的各种状态和数据 | settings_state.dart |
持久化层 | 负责数据的本地存储和读取 | persistence_provider.dart |
业务逻辑层 | 处理核心功能,比如监听文件夹、广播文件等 | auto_broadcast_folder_provider.dart 、send_provider.dart |
工具/服务层 | 封装底层功能,比如文件夹监听服务 | directory_watcher_service.dart |
启动入口层 | 应用启动和初始化 | main.dart |
2.3 流程图 链接到标题
flowchart TD
A[用户在设置页面选择文件夹/开关监听] --> B[设置项保存到本地]
B --> C[状态管理层监听到设置变化]
C --> D[业务逻辑层决定是否启动监听服务]
D --> E[工具层开始监听文件夹]
E --> F[检测到新文件]
F --> G[业务逻辑层自动广播文件到所有设备]
G --> H[用户的其他设备收到文件]
2.4 文字版流程 链接到标题
- 用户在App设置页面选择监听文件夹路径,并打开“启用监听”开关;
- 这些设置通过状态管理层保存到本地;
- 业务逻辑层(Provider)监听到设置变化,决定是否启动监听服务;
- 工具层(服务类)开始监听指定文件夹;
- 一旦有新文件出现,业务逻辑层自动将文件广播到所有在线设备。
3.前端代码的实现 链接到标题
在标准的 Flutter 项目中。每加一个新设置项,都要让数据模型、持久化、Provider、UI四层都认识它,才能保证功能完整、数据一致、界面可用。
所以我们需要对settings_state.dart
、persistence_provider.dart
、settings_provider.dart
、settings_tab.dart
四个文件进行修改,以下是各个文件的职责与关系。
3.1 UI层:settings_tab.dart 链接到标题
- 作用:在设置页面中,新增了“监听文件夹路径”和“启用监听文件夹”两个设置项,允许用户选择文件夹并启用/关闭自动广播。
- 内容:展示所有设置项的控件(如开关、按钮、输入框),并通过 Provider 读写 SettingsState。
- 为什么要改:你要让用户能在界面上看到和操作“监听文件夹路径”和“启用监听”这两个新设置项。
// 监听文件夹路径设置项
_SettingsEntry(
label: '监听文件夹路径',
child: TextButton(
onPressed: () async {
// 弹出文件夹选择对话框
final directory = await pickDirectoryPath();
if (directory != null) {
// 保存用户选择的文件夹路径
await ref.notifier(settingsProvider).setAutoBroadcastFolderPath(directory);
}
},
child: Padding(
child: Text(
vm.settings.autoBroadcastFolderPath ?? '未设置', // 显示当前设置的路径
),
),
),
),
// 启用监听文件夹开关
_BooleanEntry(
label: '启用监听文件夹',
value: vm.settings.autoBroadcastEnabled,
onChanged: (b) async {
// 保存用户的开关选择
await ref.notifier(settingsProvider).setAutoBroadcastEnabled(b);
},
),
3.2 状态管理层:settings_provider.dart 链接到标题
- 作用:负责管理和持久化设置项,包括监听文件夹的开关和路径。
当设置项变化时,自动同步到本地存储,并通知相关Provider。 - 内容:用 Notifier/Provider 管理 SettingsState,提供各种 setter 方法,调用 persistence_provider 进行持久化,并通过 copyWith 更新状态。
- 为什么要改:你要让 UI 能通过 Provider 读写这两个新设置项,所以要加对应的 setter,并在初始化时从 persistence_provider 读取。
// 设置是否启用监听文件夹
Future<void> setAutoBroadcastEnabled(bool enabled) async {
await _persistence.setAutoBroadcastEnabled(enabled); // 持久化到本地
state = state.copyWith(autoBroadcastEnabled: enabled); // 更新内存状态
}
// 设置监听文件夹路径
Future<void> setAutoBroadcastFolderPath(String? path) async {
await _persistence.setAutoBroadcastFolderPath(path); // 持久化到本地
state = state.copyWith(autoBroadcastFolderPath: path); // 更新内存状态
}
3.3 数据模型层:settings_state.dart 链接到标题
- 作用:定义了
SettingsState
,新增了两个字段用于保存监听文件夹的开关和路径。 - 内容:包含所有设置项的字段(如主题、语言、端口、保存目录等),以及构造函数。
- 为什么要改:你要新增“监听文件夹路径”和“启用监听”这两个设置项,必须在 SettingsState 里加字段,否则整个设置系统都不会有这两个数据。
final bool autoBroadcastEnabled; // 是否启用监听文件夹
final String? autoBroadcastFolderPath; // 监听的文件夹路径
3.4 持久化层:persistence_provider.dart 链接到标题
- 作用:负责将设置项持久化到本地(如手机/电脑的本地文件),包括监听文件夹的开关和路径。
- 内容:定义了各种 get/set 方法,把 SettingsState 的每个字段和本地存储的 key 关联起来。
- 为什么要改:你新增了两个设置项,需要能保存和读取它们的值,所以要加对应的 get/set 方法和 key。
// 获取是否启用监听文件夹
bool getAutoBroadcastEnabled() {
return _prefs.getBool(_autoBroadcastEnabledKey) ?? false;
}
// 设置是否启用监听文件夹
Future<void> setAutoBroadcastEnabled(bool enabled) async {
await _prefs.setBool(_autoBroadcastEnabledKey, enabled);
}
// 获取监听文件夹路径
String? getAutoBroadcastFolderPath() {
return _prefs.getString(_autoBroadcastFolderPathKey);
}
// 设置监听文件夹路径
Future<void> setAutoBroadcastFolderPath(String? path) async {
if (path == null) {
await _prefs.remove(_autoBroadcastFolderPathKey);
} else {
await _prefs.setString(_autoBroadcastFolderPathKey, path);
}
}
异常报错处理:
代码添加后在persistence_provider.dart文件夹中存在报错提示。
解决办法:
根本原因是 SettingsState 的 copyWith 方法没有包含 autoBroadcastEnabled 和 autoBroadcastFolderPath 这两个新加的字段。
这是因为 SettingsState 使用了 dart_mappable 自动生成 copyWith,而新增字段后没有重新生成映射代码。
需要执行以下命令:
flutter pub run build_runner build --delete-conflicting-outputs
3.5 界面效果展示 链接到标题
添加完成后,运行项目可以看到已经增加了两个配置项
4.广播发送代码实现 链接到标题
4.1 业务逻辑层:auto_broadcast_folder_provider.dart 链接到标题
作用:根据设置项决定是否监听文件夹,并在检测到新文件时广播。
updateFromSettings
根据设置项决定是否监听文件夹。onFileDetected
回调会触发文件广播。
class AutoBroadcastFolderProvider {
final DirectoryWatcherService _watcherService = DirectoryWatcherService(); // 文件夹监听服务
void Function(String filePath)? onFileDetected; // 新文件回调
// 根据设置项更新监听状态
void updateFromSettings(bool enabled, String? folderPath) {
_watcherService.stopWatching(); // 先停止之前的监听
if (enabled && folderPath != null && onFileDetected != null) {
// 启用监听
_watcherService.startWatching(folderPath, (filePath) {
onFileDetected!(filePath); // 检测到新文件,调用回调
});
}
}
}
// Provider注册
final autoBroadcastFolderProvider = Provider<AutoBroadcastFolderProvider>((ref) {
final provider = AutoBroadcastFolderProvider();
provider.onFileDetected = (filePath) {
// 检测到新文件,调用发送Provider广播文件
ref.notifier(sendProvider).broadcastFileToAllDevices(filePath);
};
// 读取设置项,决定是否监听
final settings = ref.read(settingsProvider);
provider.updateFromSettings(settings.autoBroadcastEnabled, settings.autoBroadcastFolderPath);
return provider;
});
4.2工具/服务层:directory_watcher_service.dart 链接到标题
作用:封装了对文件夹的监听逻辑,利用DirectoryWatcher
库监听新文件。底层实现文件夹监听,发现新文件时通知上层。
class DirectoryWatcherService {
DirectoryWatcher? _watcher;
StreamSubscription<WatchEvent>? _subscription;
String? _currentPath;
// 开始监听文件夹
void startWatching(String folderPath, OnFileDetected onFileDetected) {
stopWatching(); // 先停止之前的监听
_currentPath = folderPath;
_watcher = DirectoryWatcher(folderPath); // 创建监听器
_subscription = _watcher!.events.listen((event) {
// 只处理新增文件事件
if (event.type == ChangeType.ADD && File(event.path).existsSync()) {
onFileDetected(event.path); // 回调通知新文件
}
});
}
// 停止监听
void stopWatching() {
_subscription?.cancel();
_subscription = null;
_watcher = null;
_currentPath = null;
}
}
4.3 业务逻辑层:send_provider.dart 链接到标题
作用:实现广播文件到所有设备的功能。
- 遍历所有在线设备,依次发送新文件。
// 广播文件到所有在线设备
Future<void> broadcastFileToAllDevices(String filePath) async {
final devices = ref.read(nearbyDevicesProvider).devices.values.toList(); // 获取所有在线设备
if (devices.isEmpty) return; // 没有设备则返回
final file = await CrossFile.fromPath(filePath); // 构造文件对象
for (final device in devices) {
// 依次发送文件到每个设备
await startSession(
target: device,
files: [file],
background: true, // 后台发送
);
}
}
4.4 启动入口层:main.dart 链接到标题
作用:应用启动时初始化Provider,确保监听服务随App启动。
Future<void> main(List<String> args) async {
// ...初始化代码...
runApp(RefenaScope.withContainer(
container: container,
child: TranslationProvider(
child: const LocalSendApp(),
),
));
}
class LocalSendApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ref = context.ref;
// 初始化时加载自动广播Provider,确保监听服务启动
ref.read(autoBroadcastFolderProvider);
// ...其他UI代码...
}
}
4.5 应用效果展示 链接到标题
全文完,请大家专注我们的B站视频号和微信公众号,如果对编译过程有疑问或者想与大家一起讨论开源软件的定制化,请大家加入我们的QQ群畅所欲言。