react-native-kookit 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ # FTP 文件列表功能增强
2
+
3
+ 本文档描述了对 FTP 客户端示例应用中文件列表功能的增强。
4
+
5
+ ## 新增功能
6
+
7
+ ### 🗂️ 增强的文件列表显示
8
+
9
+ **文件信息显示:**
10
+
11
+ - 📁 文件夹图标 / 📄 文件图标
12
+ - 文件/文件夹名称
13
+ - 文件大小(字节)
14
+ - 最后修改时间
15
+ - 文件权限信息
16
+
17
+ **交互功能:**
18
+
19
+ - 点击文件夹:直接进入该文件夹
20
+ - 点击文件:显示文件操作菜单
21
+ - 点击菜单按钮(⋮):显示详细操作选项
22
+
23
+ ### 📂 目录导航功能
24
+
25
+ **导航按钮:**
26
+
27
+ - **Refresh(刷新)**: 重新加载当前目录的文件列表
28
+ - **Up(向上)**: 返回父目录
29
+ - **Root(根目录)**: 直接跳转到根目录
30
+ - **New Folder(新建文件夹)**: 创建新文件夹
31
+
32
+ **当前路径显示:**
33
+
34
+ - 实时显示当前所在的目录路径
35
+ - 便于用户了解当前位置
36
+
37
+ ### 📄 文件操作菜单
38
+
39
+ **对于文件:**
40
+
41
+ - **Download(下载)**: 下载文件到本地临时目录
42
+ - **Delete(删除)**: 删除远程服务器上的文件
43
+ - **Cancel(取消)**: 关闭操作菜单
44
+
45
+ **对于文件夹:**
46
+
47
+ - **Open Directory(打开目录)**: 进入该文件夹
48
+ - **Delete(删除)**: 删除整个文件夹及其内容
49
+ - **Cancel(取消)**: 关闭操作菜单
50
+
51
+ ### 📁 新建文件夹功能
52
+
53
+ **操作流程:**
54
+
55
+ 1. 点击 "New Folder" 按钮
56
+ 2. 在弹出的对话框中输入文件夹名称
57
+ 3. 点击 "Create" 创建文件夹
58
+ 4. 自动刷新文件列表显示新创建的文件夹
59
+
60
+ ### 🔒 安全确认
61
+
62
+ **删除确认:**
63
+
64
+ - 删除任何文件或文件夹前都会弹出确认对话框
65
+ - 防止误删除重要数据
66
+ - 显示将要删除的项目名称
67
+
68
+ ## 用户界面改进
69
+
70
+ ### 📱 响应式设计
71
+
72
+ **文件列表项:**
73
+
74
+ - 更大的点击区域,便于触摸操作
75
+ - 清晰的视觉层次,信息一目了然
76
+ - 文件夹和文件使用不同的颜色和样式区分
77
+
78
+ **模态对话框:**
79
+
80
+ - 半透明背景,专注于当前操作
81
+ - 居中显示,适合各种屏幕尺寸
82
+ - 清晰的按钮布局,操作直观
83
+
84
+ ### 🎨 视觉优化
85
+
86
+ **图标系统:**
87
+
88
+ - 📁 文件夹图标:蓝色文本,便于识别
89
+ - 📄 文件图标:常规颜色
90
+ - ⋮ 菜单图标:灰色,表示更多操作
91
+
92
+ **信息层次:**
93
+
94
+ - 文件名:主要信息,字体较大
95
+ - 文件大小:次要信息,字体中等
96
+ - 修改时间:辅助信息,字体较小,灰色
97
+
98
+ ## 操作流程示例
99
+
100
+ ### 典型文件浏览流程
101
+
102
+ 1. **连接 FTP 服务器**
103
+
104
+ ```
105
+ 创建客户端 → 输入连接信息 → 点击连接
106
+ ```
107
+
108
+ 2. **浏览文件**
109
+
110
+ ```
111
+ 查看文件列表 → 点击文件夹进入 → 查看文件详情
112
+ ```
113
+
114
+ 3. **文件操作**
115
+
116
+ ```
117
+ 选择文件 → 选择操作(下载/删除) → 确认操作
118
+ ```
119
+
120
+ 4. **目录管理**
121
+ ```
122
+ 新建文件夹 → 输入名称 → 创建 → 查看结果
123
+ ```
124
+
125
+ ### 下载文件示例
126
+
127
+ ```typescript
128
+ // 用户点击文件 → 选择下载 → 自动执行下载
129
+ const downloadFile = async (file: any) => {
130
+ const localPath = `/tmp/${file.name}`;
131
+ await ftpClient.download(file.name, localPath);
132
+ // 显示成功消息和本地路径
133
+ };
134
+ ```
135
+
136
+ ### 创建文件夹示例
137
+
138
+ ```typescript
139
+ // 用户输入文件夹名 → 点击创建 → 自动刷新列表
140
+ const createFolder = async () => {
141
+ await ftpClient.createDirectory(newFolderName);
142
+ await listFiles(); // 刷新文件列表
143
+ };
144
+ ```
145
+
146
+ ## 错误处理
147
+
148
+ ### 网络错误
149
+
150
+ - 连接超时:显示友好的错误消息
151
+ - 认证失败:提示检查用户名和密码
152
+ - 权限不足:显示权限相关的错误信息
153
+
154
+ ### 文件操作错误
155
+
156
+ - 下载失败:显示具体的失败原因
157
+ - 删除失败:可能是权限问题或文件正在使用
158
+ - 创建文件夹失败:可能是名称冲突或权限不足
159
+
160
+ ### 用户输入错误
161
+
162
+ - 空文件夹名:禁用创建按钮
163
+ - 无效字符:提示输入有效的文件夹名称
164
+
165
+ ## 技术实现要点
166
+
167
+ ### 状态管理
168
+
169
+ - `files`: 当前目录的文件列表
170
+ - `selectedFile`: 当前选中的文件
171
+ - `showFileModal`: 文件操作模态框显示状态
172
+ - `showCreateFolder`: 创建文件夹模态框显示状态
173
+
174
+ ### 事件处理
175
+
176
+ - 文件点击:区分文件和文件夹的不同处理逻辑
177
+ - 模态框管理:正确的显示和隐藏逻辑
178
+ - 异步操作:proper async/await 处理和错误捕获
179
+
180
+ ### 用户体验优化
181
+
182
+ - 操作反馈:每个操作都有相应的日志记录
183
+ - 进度显示:下载和上传操作显示进度
184
+ - 状态保持:操作完成后自动刷新相关状态
185
+
186
+ 这些增强功能使得 FTP 客户端示例应用更加实用和用户友好,提供了完整的文件管理体验。
File without changes
@@ -39,6 +39,8 @@ class FtpClient {
39
39
  private var config: FtpConnectionConfig? = null
40
40
  private var useUtf8 = false
41
41
 
42
+ fun isConnected(): Boolean = isConnected
43
+
42
44
  @Throws(Exception::class)
43
45
  suspend fun connect(config: FtpConnectionConfig) = withContext(Dispatchers.IO) {
44
46
  this@FtpClient.config = config
@@ -17,6 +17,7 @@ class ReactNativeKookitModule : Module() {
17
17
  private var audioManager: AudioManager? = null
18
18
  private var originalStreamVolume: Int = 0
19
19
  private var ftpClient: FtpClient? = null
20
+ private val ftpClients = mutableMapOf<String, FtpClient>()
20
21
  private val moduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
21
22
 
22
23
  // Each module class must implement the definition function. The definition consists of components
@@ -82,6 +83,257 @@ class ReactNativeKookitModule : Module() {
82
83
  }
83
84
  }
84
85
 
86
+ // New FTP Client Management Functions
87
+ AsyncFunction("createFtpClient") { clientId: String, promise: Promise ->
88
+ moduleScope.launch {
89
+ try {
90
+ if (ftpClients.containsKey(clientId)) {
91
+ promise.reject("FTP_CLIENT_EXISTS", "FTP client with ID '$clientId' already exists", null)
92
+ return@launch
93
+ }
94
+
95
+ val client = FtpClient()
96
+ ftpClients[clientId] = client
97
+ promise.resolve(mapOf("clientId" to clientId))
98
+ } catch (e: Exception) {
99
+ promise.reject("FTP_CREATE_CLIENT_ERROR", e.message, e)
100
+ }
101
+ }
102
+ }
103
+
104
+ AsyncFunction("disposeFtpClient") { clientId: String, promise: Promise ->
105
+ moduleScope.launch {
106
+ try {
107
+ val client = ftpClients[clientId]
108
+ if (client != null) {
109
+ client.disconnect()
110
+ ftpClients.remove(clientId)
111
+ }
112
+ promise.resolve(null)
113
+ } catch (e: Exception) {
114
+ promise.reject("FTP_DISPOSE_CLIENT_ERROR", e.message, e)
115
+ }
116
+ }
117
+ }
118
+
119
+ AsyncFunction("getFtpClientStatus") { clientId: String, promise: Promise ->
120
+ moduleScope.launch {
121
+ try {
122
+ val client = ftpClients[clientId]
123
+ val status = mapOf(
124
+ "exists" to (client != null),
125
+ "connected" to (client?.isConnected() ?: false)
126
+ )
127
+ promise.resolve(status)
128
+ } catch (e: Exception) {
129
+ promise.reject("FTP_CLIENT_STATUS_ERROR", e.message, e)
130
+ }
131
+ }
132
+ }
133
+
134
+ AsyncFunction("listFtpClients") { promise: Promise ->
135
+ moduleScope.launch {
136
+ try {
137
+ val clients = mutableMapOf<String, Map<String, Any>>()
138
+ ftpClients.forEach { (id, client) ->
139
+ clients[id] = mapOf("connected" to client.isConnected())
140
+ }
141
+ val result = mapOf(
142
+ "clients" to clients,
143
+ "count" to ftpClients.size
144
+ )
145
+ promise.resolve(result)
146
+ } catch (e: Exception) {
147
+ promise.reject("FTP_LIST_CLIENTS_ERROR", e.message, e)
148
+ }
149
+ }
150
+ }
151
+
152
+ AsyncFunction("ftpClientConnect") { clientId: String, config: Map<String, Any>, promise: Promise ->
153
+ moduleScope.launch {
154
+ try {
155
+ val client = ftpClients[clientId]
156
+ ?: throw Exception("FTP client with ID '$clientId' not found")
157
+
158
+ val ftpConfig = FtpConnectionConfig(
159
+ host = config["host"] as String,
160
+ port = (config["port"] as? Double)?.toInt() ?: 21,
161
+ username = config["username"] as String,
162
+ password = config["password"] as String,
163
+ passive = config["passive"] as? Boolean ?: true,
164
+ timeout = (config["timeout"] as? Double)?.toInt() ?: 30000
165
+ )
166
+
167
+ client.connect(ftpConfig)
168
+ promise.resolve(null)
169
+ } catch (e: Exception) {
170
+ promise.reject("FTP_CLIENT_CONNECT_ERROR", e.message, e)
171
+ }
172
+ }
173
+ }
174
+
175
+ AsyncFunction("ftpClientDisconnect") { clientId: String, promise: Promise ->
176
+ moduleScope.launch {
177
+ try {
178
+ val client = ftpClients[clientId]
179
+ ?: throw Exception("FTP client with ID '$clientId' not found")
180
+ client.disconnect()
181
+ promise.resolve(null)
182
+ } catch (e: Exception) {
183
+ promise.reject("FTP_CLIENT_DISCONNECT_ERROR", e.message, e)
184
+ }
185
+ }
186
+ }
187
+
188
+ AsyncFunction("ftpClientList") { clientId: String, path: String?, promise: Promise ->
189
+ moduleScope.launch {
190
+ try {
191
+ val client = ftpClients[clientId]
192
+ ?: throw Exception("FTP client with ID '$clientId' not found")
193
+
194
+ val files = client.listFiles(path)
195
+ val result = files.map { file ->
196
+ mapOf(
197
+ "name" to file.name,
198
+ "isDirectory" to file.isDirectory,
199
+ "size" to file.size,
200
+ "lastModified" to file.lastModified,
201
+ "permissions" to file.permissions
202
+ )
203
+ }
204
+ promise.resolve(result)
205
+ } catch (e: Exception) {
206
+ promise.reject("FTP_CLIENT_LIST_ERROR", e.message, e)
207
+ }
208
+ }
209
+ }
210
+
211
+ AsyncFunction("ftpClientDownload") { clientId: String, remotePath: String, localPath: String, promise: Promise ->
212
+ moduleScope.launch {
213
+ try {
214
+ val client = ftpClients[clientId]
215
+ ?: throw Exception("FTP client with ID '$clientId' not found")
216
+
217
+ val listener = object : FtpProgressListener {
218
+ override fun onProgress(transferred: Long, total: Long) {
219
+ val percentage = if (total > 0) (transferred * 100 / total).toInt() else 0
220
+ sendEvent("onFtpProgress", mapOf(
221
+ "clientId" to clientId,
222
+ "transferred" to transferred,
223
+ "total" to total,
224
+ "percentage" to percentage
225
+ ))
226
+ }
227
+
228
+ override fun onComplete() {
229
+ sendEvent("onFtpComplete", mapOf("clientId" to clientId))
230
+ }
231
+
232
+ override fun onError(error: String) {
233
+ sendEvent("onFtpError", mapOf(
234
+ "clientId" to clientId,
235
+ "error" to error
236
+ ))
237
+ }
238
+ }
239
+
240
+ client.downloadFile(remotePath, localPath, listener)
241
+ promise.resolve(null)
242
+ } catch (e: Exception) {
243
+ promise.reject("FTP_CLIENT_DOWNLOAD_ERROR", e.message, e)
244
+ }
245
+ }
246
+ }
247
+
248
+ AsyncFunction("ftpClientUpload") { clientId: String, localPath: String, remotePath: String, promise: Promise ->
249
+ moduleScope.launch {
250
+ try {
251
+ val client = ftpClients[clientId]
252
+ ?: throw Exception("FTP client with ID '$clientId' not found")
253
+
254
+ val listener = object : FtpProgressListener {
255
+ override fun onProgress(transferred: Long, total: Long) {
256
+ val percentage = if (total > 0) (transferred * 100 / total).toInt() else 0
257
+ sendEvent("onFtpProgress", mapOf(
258
+ "clientId" to clientId,
259
+ "transferred" to transferred,
260
+ "total" to total,
261
+ "percentage" to percentage
262
+ ))
263
+ }
264
+
265
+ override fun onComplete() {
266
+ sendEvent("onFtpComplete", mapOf("clientId" to clientId))
267
+ }
268
+
269
+ override fun onError(error: String) {
270
+ sendEvent("onFtpError", mapOf(
271
+ "clientId" to clientId,
272
+ "error" to error
273
+ ))
274
+ }
275
+ }
276
+
277
+ client.uploadFile(localPath, remotePath, listener)
278
+ promise.resolve(null)
279
+ } catch (e: Exception) {
280
+ promise.reject("FTP_CLIENT_UPLOAD_ERROR", e.message, e)
281
+ }
282
+ }
283
+ }
284
+
285
+ AsyncFunction("ftpClientDelete") { clientId: String, remotePath: String, isDirectory: Boolean?, promise: Promise ->
286
+ moduleScope.launch {
287
+ try {
288
+ val client = ftpClients[clientId]
289
+ ?: throw Exception("FTP client with ID '$clientId' not found")
290
+ client.deleteFile(remotePath, isDirectory ?: false)
291
+ promise.resolve(null)
292
+ } catch (e: Exception) {
293
+ promise.reject("FTP_CLIENT_DELETE_ERROR", e.message, e)
294
+ }
295
+ }
296
+ }
297
+
298
+ AsyncFunction("ftpClientCreateDirectory") { clientId: String, remotePath: String, promise: Promise ->
299
+ moduleScope.launch {
300
+ try {
301
+ val client = ftpClients[clientId]
302
+ ?: throw Exception("FTP client with ID '$clientId' not found")
303
+ client.createDirectory(remotePath)
304
+ promise.resolve(null)
305
+ } catch (e: Exception) {
306
+ promise.reject("FTP_CLIENT_CREATE_DIR_ERROR", e.message, e)
307
+ }
308
+ }
309
+ }
310
+
311
+ AsyncFunction("ftpClientChangeDirectory") { clientId: String, remotePath: String, promise: Promise ->
312
+ moduleScope.launch {
313
+ try {
314
+ val client = ftpClients[clientId]
315
+ ?: throw Exception("FTP client with ID '$clientId' not found")
316
+ client.changeDirectory(remotePath)
317
+ promise.resolve(null)
318
+ } catch (e: Exception) {
319
+ promise.reject("FTP_CLIENT_CHANGE_DIR_ERROR", e.message, e)
320
+ }
321
+ }
322
+ }
323
+
324
+ AsyncFunction("ftpClientGetCurrentDirectory") { clientId: String, promise: Promise ->
325
+ moduleScope.launch {
326
+ try {
327
+ val client = ftpClients[clientId]
328
+ ?: throw Exception("FTP client with ID '$clientId' not found")
329
+ val currentDir = client.getCurrentDirectory()
330
+ promise.resolve(currentDir)
331
+ } catch (e: Exception) {
332
+ promise.reject("FTP_CLIENT_PWD_ERROR", e.message, e)
333
+ }
334
+ }
335
+ }
336
+
85
337
  AsyncFunction("ftpDisconnect") { promise: Promise ->
86
338
  moduleScope.launch {
87
339
  try {
@@ -0,0 +1,97 @@
1
+ import type { FtpConnectionConfig, FtpFileInfo } from "./ReactNativeKookit.types";
2
+ export interface FtpClientEventHandlers {
3
+ onProgress?: (progress: {
4
+ transferred: number;
5
+ total: number;
6
+ percentage: number;
7
+ }) => void;
8
+ onComplete?: () => void;
9
+ onError?: (error: Error) => void;
10
+ }
11
+ export declare class FtpClient {
12
+ private clientId;
13
+ private isDisposed;
14
+ private eventHandlers;
15
+ private eventSubscriptions;
16
+ constructor(clientId?: string);
17
+ private setupEventListeners;
18
+ /**
19
+ * Set event handlers
20
+ */
21
+ setEventHandlers(handlers: FtpClientEventHandlers): void;
22
+ /**
23
+ * Initialize the FTP client instance
24
+ */
25
+ initialize(): Promise<void>;
26
+ /**
27
+ * Connect to the FTP server
28
+ */
29
+ connect(config: FtpConnectionConfig): Promise<void>;
30
+ /**
31
+ * Disconnect from the FTP server
32
+ */
33
+ disconnect(): Promise<void>;
34
+ /**
35
+ * List files in the specified directory
36
+ */
37
+ list(path?: string): Promise<FtpFileInfo[]>;
38
+ /**
39
+ * Download a file from the remote server
40
+ */
41
+ download(remotePath: string, localPath: string): Promise<void>;
42
+ /**
43
+ * Upload a file to the remote server
44
+ */
45
+ upload(localPath: string, remotePath: string): Promise<void>;
46
+ /**
47
+ * Delete a file or directory on the remote server
48
+ */
49
+ delete(remotePath: string, isDirectory?: boolean): Promise<void>;
50
+ /**
51
+ * Create a directory on the remote server
52
+ */
53
+ createDirectory(remotePath: string): Promise<void>;
54
+ /**
55
+ * Change the current working directory on the remote server
56
+ */
57
+ changeDirectory(remotePath: string): Promise<void>;
58
+ /**
59
+ * Get the current working directory on the remote server
60
+ */
61
+ getCurrentDirectory(): Promise<string>;
62
+ /**
63
+ * Get client status information
64
+ * @returns Client status including existence and connection state
65
+ */
66
+ getStatus(): Promise<{
67
+ clientId: string;
68
+ exists: boolean;
69
+ connected: boolean;
70
+ }>;
71
+ /**
72
+ * Get the client ID
73
+ */
74
+ getClientId(): string;
75
+ /**
76
+ * Check if the client is connected
77
+ */
78
+ isConnected(): Promise<boolean>;
79
+ /**
80
+ * Dispose the FTP client and clean up resources
81
+ */
82
+ dispose(): Promise<void>;
83
+ /**
84
+ * Static method to list all FTP clients
85
+ */
86
+ static listClients(): Promise<{
87
+ clients: Record<string, {
88
+ connected: boolean;
89
+ }>;
90
+ count: number;
91
+ }>;
92
+ /**
93
+ * Static method to create and initialize a new FTP client
94
+ */
95
+ static create(clientId?: string): Promise<FtpClient>;
96
+ }
97
+ //# sourceMappingURL=FtpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FtpClient.d.ts","sourceRoot":"","sources":["../src/FtpClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,mBAAmB,EACnB,WAAW,EAIZ,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,KAAK,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,kBAAkB,CAAqC;gBAEnD,QAAQ,CAAC,EAAE,MAAM;IAO7B,OAAO,CAAC,mBAAmB;IAoC3B;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAIxD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IASzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;OAEG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAQjD;;OAEG;IACG,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpE;;OAEG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlE;;OAEG;IACG,MAAM,CACV,UAAU,EAAE,MAAM,EAClB,WAAW,GAAE,OAAe,GAC3B,OAAO,CAAC,IAAI,CAAC;IAYhB;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWxD;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAW5C;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;IAUF;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAarC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B;;OAEG;WACU,WAAW,IAAI,OAAO,CAAC;QAClC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,SAAS,EAAE,OAAO,CAAA;SAAE,CAAC,CAAC;QAChD,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IAIF;;OAEG;WACU,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;CAK3D"}