├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── .metadata ├── README.md ├── _script │ └── extract_tr.dart ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── honmaple │ │ │ │ │ └── maple_file │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-hdpi │ │ │ │ ├── android12splash.png │ │ │ │ └── splash.png │ │ │ │ ├── drawable-mdpi │ │ │ │ ├── android12splash.png │ │ │ │ └── splash.png │ │ │ │ ├── drawable-night-hdpi │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-mdpi │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-v21 │ │ │ │ ├── background.png │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-night-xhdpi │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-xxhdpi │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night-xxxhdpi │ │ │ │ └── android12splash.png │ │ │ │ ├── drawable-night │ │ │ │ ├── background.png │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ ├── background.png │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-xhdpi │ │ │ │ ├── android12splash.png │ │ │ │ └── splash.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── android12splash.png │ │ │ │ └── splash.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── android12splash.png │ │ │ │ └── splash.png │ │ │ │ ├── drawable │ │ │ │ ├── background.png │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launcher_icon.png │ │ │ │ ├── values-night-v31 │ │ │ │ └── styles.xml │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ ├── values-v31 │ │ │ │ └── styles.xml │ │ │ │ ├── values-zh │ │ │ │ └── strings.xml │ │ │ │ └── values │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ └── icon │ │ ├── icon-macos.png │ │ └── icon.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-50x50@1x.png │ │ │ │ ├── Icon-App-50x50@2x.png │ │ │ │ ├── Icon-App-57x57@1x.png │ │ │ │ ├── Icon-App-57x57@2x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-72x72@1x.png │ │ │ │ ├── Icon-App-72x72@2x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ ├── LaunchBackground.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── background.png │ │ │ │ └── darkbackground.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ ├── Runner-Bridging-Header.h │ │ ├── en.lproj │ │ │ └── InfoPlist.strings │ │ └── zh-Hans.lproj │ │ │ └── InfoPlist.strings │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ ├── api │ │ ├── file │ │ │ ├── pages │ │ │ │ ├── file_list.dart │ │ │ │ ├── file_preview.dart │ │ │ │ ├── file_select.dart │ │ │ │ ├── repo_edit.dart │ │ │ │ ├── repo_list.dart │ │ │ │ ├── setting_download.dart │ │ │ │ ├── setting_theme.dart │ │ │ │ └── setting_upload.dart │ │ │ ├── providers │ │ │ │ ├── file.dart │ │ │ │ ├── file_setting.dart │ │ │ │ ├── repo.dart │ │ │ │ └── service.dart │ │ │ ├── route.dart │ │ │ └── widgets │ │ │ │ ├── file_action.dart │ │ │ │ ├── file_breadcrumb.dart │ │ │ │ ├── file_drag.dart │ │ │ │ ├── file_tree.dart │ │ │ │ ├── file_view.dart │ │ │ │ ├── preview │ │ │ │ ├── audio.dart │ │ │ │ ├── image.dart │ │ │ │ ├── source.dart │ │ │ │ ├── text.dart │ │ │ │ └── video.dart │ │ │ │ └── repo │ │ │ │ ├── alist.dart │ │ │ │ ├── base.dart │ │ │ │ ├── form.dart │ │ │ │ ├── ftp.dart │ │ │ │ ├── github.dart │ │ │ │ ├── github_release.dart │ │ │ │ ├── local.dart │ │ │ │ ├── mirror.dart │ │ │ │ ├── s3.dart │ │ │ │ ├── sftp.dart │ │ │ │ ├── smb.dart │ │ │ │ ├── upyun.dart │ │ │ │ └── webdav.dart │ │ ├── home │ │ │ ├── pages │ │ │ │ ├── about.dart │ │ │ │ ├── help.dart │ │ │ │ ├── index.dart │ │ │ │ └── not_found.dart │ │ │ └── route.dart │ │ ├── setting │ │ │ ├── pages │ │ │ │ ├── setting.dart │ │ │ │ ├── setting_backup.dart │ │ │ │ ├── setting_locale.dart │ │ │ │ └── setting_theme.dart │ │ │ ├── providers │ │ │ │ ├── info.dart │ │ │ │ ├── service.dart │ │ │ │ ├── setting.dart │ │ │ │ └── setting_appearance.dart │ │ │ └── route.dart │ │ └── task │ │ │ ├── pages │ │ │ └── task_list.dart │ │ │ ├── providers │ │ │ ├── persist.dart │ │ │ ├── service.dart │ │ │ └── task.dart │ │ │ ├── route.dart │ │ │ └── widgets │ │ │ └── task_action.dart │ ├── app │ │ ├── app.dart │ │ ├── extensions │ │ │ └── provider.dart │ │ ├── grpc.dart │ │ ├── grpc_io.dart │ │ ├── grpc_web.dart │ │ ├── i18n.dart │ │ ├── i18n │ │ │ ├── en_us.dart │ │ │ └── zh_cn.dart │ │ └── router.dart │ ├── common │ │ ├── providers │ │ │ ├── pagination.dart │ │ │ ├── selection.dart │ │ │ └── value.dart │ │ ├── utils │ │ │ ├── color.dart │ │ │ ├── navigator.dart │ │ │ ├── path.dart │ │ │ ├── time.dart │ │ │ └── util.dart │ │ ├── watchers │ │ │ └── lifecycle.dart │ │ └── widgets │ │ │ ├── breadcrumb.dart │ │ │ ├── custom.dart │ │ │ ├── dialog.dart │ │ │ ├── form.dart │ │ │ ├── keyboard.dart │ │ │ ├── resize.dart │ │ │ ├── responsive.dart │ │ │ ├── statsmap.dart │ │ │ ├── tree.dart │ │ │ ├── tree1.dart │ │ │ └── tree2.dart │ ├── generated │ │ ├── ffi │ │ │ └── libserver.dart │ │ └── proto │ │ │ ├── api │ │ │ ├── file │ │ │ │ ├── file.pb.dart │ │ │ │ ├── file.pbenum.dart │ │ │ │ ├── file.pbjson.dart │ │ │ │ ├── repo.pb.dart │ │ │ │ ├── repo.pbenum.dart │ │ │ │ ├── repo.pbjson.dart │ │ │ │ ├── service.pb.dart │ │ │ │ ├── service.pbenum.dart │ │ │ │ ├── service.pbgrpc.dart │ │ │ │ └── service.pbjson.dart │ │ │ ├── setting │ │ │ │ ├── info.pb.dart │ │ │ │ ├── info.pbenum.dart │ │ │ │ ├── info.pbjson.dart │ │ │ │ ├── service.pb.dart │ │ │ │ ├── service.pbenum.dart │ │ │ │ ├── service.pbgrpc.dart │ │ │ │ ├── service.pbjson.dart │ │ │ │ ├── setting.pb.dart │ │ │ │ ├── setting.pbenum.dart │ │ │ │ └── setting.pbjson.dart │ │ │ └── task │ │ │ │ ├── persist.pb.dart │ │ │ │ ├── persist.pbenum.dart │ │ │ │ ├── persist.pbjson.dart │ │ │ │ ├── service.pb.dart │ │ │ │ ├── service.pbenum.dart │ │ │ │ ├── service.pbgrpc.dart │ │ │ │ ├── service.pbjson.dart │ │ │ │ ├── task.pb.dart │ │ │ │ ├── task.pbenum.dart │ │ │ │ └── task.pbjson.dart │ │ │ └── google │ │ │ ├── api │ │ │ ├── annotations.pb.dart │ │ │ ├── annotations.pbenum.dart │ │ │ ├── annotations.pbjson.dart │ │ │ ├── http.pb.dart │ │ │ ├── http.pbenum.dart │ │ │ └── http.pbjson.dart │ │ │ └── protobuf │ │ │ ├── descriptor.pb.dart │ │ │ ├── descriptor.pbenum.dart │ │ │ ├── descriptor.pbjson.dart │ │ │ ├── timestamp.pb.dart │ │ │ ├── timestamp.pbenum.dart │ │ │ └── timestamp.pbjson.dart │ └── main.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Configs │ │ │ ├── AppInfo.xcconfig │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ ├── Release.entitlements │ │ ├── en.lproj │ │ │ └── InfoPlist.strings │ │ └── zh-Hans.lproj │ │ │ ├── InfoPlist.strings │ │ │ └── MainMenu.strings │ └── RunnerTests │ │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── example └── screenshot │ ├── 01.png │ ├── 02.png │ ├── 03.png │ ├── 04.png │ ├── 05.png │ ├── 06.png │ ├── desktop-01.png │ └── desktop-02.png ├── proto ├── api │ ├── file │ │ ├── file.proto │ │ ├── repo.proto │ │ └── service.proto │ ├── setting │ │ ├── info.proto │ │ ├── service.proto │ │ └── setting.proto │ └── task │ │ ├── persist.proto │ │ ├── service.proto │ │ └── task.proto ├── buf.gen.yaml ├── buf.lock ├── buf.yaml └── gen.sh └── server ├── README.md ├── cmd ├── desktop │ └── main.go ├── fileserver │ └── main.go └── mobile │ └── main.go ├── go.mod ├── go.sum ├── internal ├── api │ ├── api.go │ ├── file │ │ ├── fs │ │ │ ├── copy.go │ │ │ ├── download.go │ │ │ ├── fs.go │ │ │ ├── move.go │ │ │ ├── remove.go │ │ │ └── upload.go │ │ ├── init.go │ │ └── service │ │ │ ├── base.go │ │ │ ├── file.go │ │ │ ├── repo.go │ │ │ └── util.go │ ├── setting │ │ ├── init.go │ │ └── service │ │ │ ├── base.go │ │ │ ├── info.go │ │ │ └── setting.go │ └── task │ │ ├── init.go │ │ └── service │ │ ├── base.go │ │ ├── persist.go │ │ └── task.go ├── app │ ├── app.go │ ├── config │ │ ├── config.go │ │ ├── const.go │ │ ├── logger.go │ │ └── model.go │ ├── grpc.go │ ├── serializer │ │ ├── any.go │ │ └── time.go │ └── service.go └── proto │ ├── api │ ├── file │ │ ├── file.pb.go │ │ ├── file.swagger.json │ │ ├── repo.pb.go │ │ ├── repo.swagger.json │ │ ├── service.pb.go │ │ ├── service.pb.gw.go │ │ ├── service.swagger.json │ │ └── service_grpc.pb.go │ ├── setting │ │ ├── info.pb.go │ │ ├── info.swagger.json │ │ ├── service.pb.go │ │ ├── service.pb.gw.go │ │ ├── service.swagger.json │ │ ├── service_grpc.pb.go │ │ ├── setting.pb.go │ │ └── setting.swagger.json │ └── task │ │ ├── persist.pb.go │ │ ├── persist.swagger.json │ │ ├── service.pb.go │ │ ├── service.pb.gw.go │ │ ├── service.swagger.json │ │ ├── service_grpc.pb.go │ │ ├── task.pb.go │ │ └── task.swagger.json │ └── google │ ├── api │ ├── annotations.pb.go │ ├── annotations.swagger.json │ ├── http.pb.go │ └── http.swagger.json │ └── protobuf │ ├── descriptor.pb.go │ ├── descriptor.swagger.json │ ├── timestamp.pb.go │ └── timestamp.swagger.json └── pkg ├── driver ├── alist │ ├── alist.go │ └── util.go ├── base │ ├── base.go │ ├── cache.go │ ├── compress.go │ ├── encrypt.go │ ├── hook.go │ ├── recycle.go │ ├── util.go │ └── wrap.go ├── errors.go ├── file.go ├── fs.go ├── ftp │ ├── ftp.go │ └── util.go ├── github │ ├── github.go │ └── release.go ├── local │ └── local.go ├── memory │ ├── memory.go │ └── memory_test.go ├── meta.go ├── mirror │ ├── mirror.go │ └── util.go ├── op.go ├── root.go ├── s3 │ ├── s3.go │ └── util.go ├── sftp │ └── sftp.go ├── smb │ └── smb.go ├── upyun │ ├── upyun.go │ └── util.go └── webdav │ ├── util.go │ └── webdav.go ├── runner ├── cache.go ├── runner.go ├── task.go ├── task_logger.go └── task_option.go └── util ├── cache.go ├── check.go ├── http └── http.go ├── io.go ├── path.go ├── str.go └── struct.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | *.db 13 | 14 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 15 | .glide/ 16 | *.bak 17 | _tmp 18 | 19 | .DS_Store 20 | docs/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |

红枫云盘

4 |
5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | [主页][home] | [下载][download] | [帮助文档][document] 18 |
19 | 20 | [home]: https://fileapp.honmaple.com 21 | [document]: https://fileapp.honmaple.com/guide/introduction.html 22 | [download]: https://github.com/honmaple/maple-file/releases/tag/v1.0.8 23 | 24 | > 使用 **Flutter** 实现的无服务端多协议云盘文件上传和管理APP 25 | 26 | ## 支持的存储 27 | - [X] 本地文件 28 | - [X] FTP 29 | - [X] SFTP 30 | - [X] S3 31 | - [X] SMB 32 | - [X] Webdav 33 | - [X] Alist 34 | - [X] Github 35 | - [X] Github Release 36 | - [X] Mirror(镜像站,支持文件查看和下载,支持格式:清华源、阿里源或者其他 **NGINX** 文件列表源) 37 | - [X] 又拍云 38 | 39 | ## 功能 40 | - 支持文件列表查看/复制/移动/删除/重命名/上传/下载 41 | - 支持桌面端拖拽上传(文件或者文件夹) 42 | - 支持文件多选及操作 43 | - 支持文件列表信息缓存 44 | - 支持回收站 45 | - 支持视频、音频、图片和文本文件的预览 46 | - 支持文件加密和压缩 47 | - 支持各存储之间的备份和同步(**测试中**) 48 | - 支持多语言(中文、英文) 49 | - 支持 **Web**, **Android**, **MacOS** 和 **Windows** 50 | 51 | ## 截图 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 |
71 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | *.g.dart 42 | *.freezed.dart 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | **/ios/Frameworks/ 50 | **/macos/Frameworks/ 51 | **/android/app/libs/ -------------------------------------------------------------------------------- /app/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "603104015dd692ea3403755b55d07813d5cf8965" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 17 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 18 | - platform: android 19 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 20 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 21 | - platform: ios 22 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 23 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 24 | - platform: linux 25 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 26 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 27 | - platform: macos 28 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 29 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 30 | - platform: web 31 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 32 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 33 | - platform: windows 34 | create_revision: 603104015dd692ea3403755b55d07813d5cf8965 35 | base_revision: 603104015dd692ea3403755b55d07813d5cf8965 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # maple_file 2 | 3 | - ffi 4 | ``` 5 | dart run ffigen 6 | ``` 7 | 8 | - freezed 9 | ``` 10 | dart run build_runner build 11 | ``` 12 | 13 | - launcher icon 14 | ``` 15 | dart run flutter_launcher_icons && rm -r android/app/src/main/res/mipmap-anydpi-v26 16 | ``` 17 | 18 | - launcher screen 19 | ``` 20 | dart run flutter_native_splash:create 21 | ``` 22 | 23 | - build 24 | ``` 25 | flutter build apk --no-tree-shake-icons --split-per-abi 26 | ``` 27 | 28 | - i18n 29 | ``` 30 | dart run _script/extract_tr.dart 31 | ``` -------------------------------------------------------------------------------- /app/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | analyzer: 28 | exclude: 29 | - "**/*.g.dart" 30 | - "**/*.freezed.dart" 31 | errors: 32 | invalid_annotation_target: ignore 33 | 34 | # Additional information about this file can be found at 35 | # https://dart.dev/guides/language/analysis-options 36 | -------------------------------------------------------------------------------- /app/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/android/app/src/main/kotlin/com/honmaple/maple_file/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.honmaple.maple_file 2 | 3 | import androidx.annotation.NonNull 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugin.common.MethodChannel 7 | import com.honmaple.maple_file.server.Server 8 | 9 | class MainActivity: FlutterActivity() { 10 | private val CHANNEL = "honmaple.com/maple_file" 11 | 12 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 13 | super.configureFlutterEngine(flutterEngine) 14 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { 15 | call, 16 | result -> 17 | when (call.method) { 18 | "Start" -> { 19 | try { 20 | val addr = Server.start(call.arguments as String) 21 | result.success(addr); 22 | } catch (e: Exception) { 23 | result.error("GRPC", e.message, null); 24 | } 25 | } 26 | "Stop" -> { 27 | Server.stop() 28 | result.success(null) 29 | } 30 | else -> { 31 | result.notImplemented() 32 | } 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /app/android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 红枫云盘 4 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | MapleFile 4 | -------------------------------------------------------------------------------- /app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /app/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | maven { url "https://storage.googleapis.com/download.flutter.io" } 6 | } 7 | } 8 | 9 | rootProject.buildDir = "../build" 10 | subprojects { 11 | project.buildDir = "${rootProject.buildDir}/${project.name}" 12 | } 13 | subprojects { 14 | project.evaluationDependsOn(":app") 15 | } 16 | 17 | tasks.register("clean", Delete) { 18 | delete rootProject.buildDir 19 | } 20 | -------------------------------------------------------------------------------- /app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.3-all.zip 6 | -------------------------------------------------------------------------------- /app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.2.1" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /app/assets/icon/icon-macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/assets/icon/icon-macos.png -------------------------------------------------------------------------------- /app/assets/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/assets/icon/icon.png -------------------------------------------------------------------------------- /app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /app/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | import Libserver 4 | 5 | // https://docs.flutter.dev/add-to-app/ios/project-setup 6 | // https://docs.flutter.dev/platform-integration/platform-channels 7 | @main 8 | @objc class AppDelegate: FlutterAppDelegate { 9 | override func application( 10 | _ application: UIApplication, 11 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 12 | ) -> Bool { 13 | let controller : FlutterViewController = window?.rootViewController as! FlutterViewController 14 | let serverChannel = FlutterMethodChannel(name: "honmaple.com/maple_file", 15 | binaryMessenger: controller.binaryMessenger) 16 | serverChannel.setMethodCallHandler({ 17 | (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in 18 | switch call.method { 19 | case "Start": 20 | let args = call.arguments as? String 21 | var error: NSError? 22 | if let addr = ServerStart(args, &error) as? String { 23 | result(addr) 24 | } else { 25 | result(FlutterError(code: "GRPC", message: error.debugDescription, details: nil)) 26 | } 27 | case "Stop": 28 | ServerStop() 29 | result(nil); 30 | default: 31 | result(FlutterMethodNotImplemented) 32 | } 33 | }) 34 | 35 | GeneratedPluginRegistrant.register(with: self) 36 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "darkbackground.png", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "LaunchImage.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "filename" : "LaunchImage@2x.png", 10 | "idiom" : "universal", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "filename" : "LaunchImage@3x.png", 15 | "idiom" : "universal", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "author" : "xcode", 21 | "version" : 1 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /app/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /app/ios/Runner/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | "CFBundleDisplayName" = "MapleFile"; -------------------------------------------------------------------------------- /app/ios/Runner/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | "CFBundleDisplayName" = "红枫云盘"; 2 | "NSAppTransportSecurity" = "应用需要网络访问权限才能管理你的云盘文件"; 3 | "NSCameraUsageDescription" = "应用需要相机权限才能拍摄并上传的照片到云盘"; 4 | "NSPhotoLibraryUsageDescription" = "应用需要照片权限才能上传你的照片到云盘"; 5 | -------------------------------------------------------------------------------- /app/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/lib/api/file/providers/repo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | import "package:macos_secure_bookmarks/macos_secure_bookmarks.dart"; 6 | 7 | import "package:maple_file/app/app.dart"; 8 | import "package:maple_file/common/utils/util.dart"; 9 | import 'package:maple_file/generated/proto/api/file/repo.pb.dart'; 10 | 11 | import 'service.dart'; 12 | 13 | class RepoNotifier extends AsyncNotifier> { 14 | @override 15 | FutureOr> build() async { 16 | return await FileService.instance.listRepos(); 17 | } 18 | } 19 | 20 | Future loadBookmark( 21 | String bookmark, { 22 | SecureBookmarks? secureBookmarks, 23 | }) async { 24 | if (!Util.isMacOS) { 25 | return; 26 | } 27 | if (bookmark == "") { 28 | return; 29 | } 30 | 31 | try { 32 | secureBookmarks ??= SecureBookmarks(); 33 | 34 | final resolvedFile = await secureBookmarks.resolveBookmark( 35 | bookmark, 36 | isDirectory: true, 37 | ); 38 | await secureBookmarks.startAccessingSecurityScopedResource(resolvedFile); 39 | } catch (e) { 40 | App.logger.warning(e.toString()); 41 | } 42 | } 43 | 44 | Future loadBookmarks(ProviderContainer container) async { 45 | if (!Util.isMacOS) { 46 | return; 47 | } 48 | final secureBookmarks = SecureBookmarks(); 49 | 50 | final repos = await container.refresh(repoProvider.future); 51 | for (final repo in repos) { 52 | if (repo.driver != "local") { 53 | continue; 54 | } 55 | final option = jsonDecode(repo.option) as Map; 56 | await loadBookmark( 57 | option["bookmark"] ?? "", 58 | secureBookmarks: secureBookmarks, 59 | ); 60 | } 61 | } 62 | 63 | final repoProvider = AsyncNotifierProvider>(() { 64 | return RepoNotifier(); 65 | }); 66 | -------------------------------------------------------------------------------- /app/lib/api/file/route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:maple_file/app/router.dart'; 4 | 5 | import "pages/file_list.dart"; 6 | import "pages/file_select.dart"; 7 | import "pages/file_preview.dart"; 8 | 9 | import "pages/repo_list.dart"; 10 | import "pages/repo_edit.dart"; 11 | 12 | import "pages/setting_theme.dart"; 13 | import "pages/setting_upload.dart"; 14 | import "pages/setting_download.dart"; 15 | 16 | Future init(CustomRouter router) async { 17 | router.registerMany({ 18 | '/file/list': (context) { 19 | return FileList.fromRoute(ModalRoute.of(context)); 20 | }, 21 | '/file/select': (context) { 22 | return FileSelect.fromRoute(ModalRoute.of(context)); 23 | }, 24 | '/file/preview': (context) { 25 | return FilePreview.fromRoute(ModalRoute.of(context)); 26 | }, 27 | '/file/setting/repo': (context) { 28 | return const RepoList(); 29 | }, 30 | '/file/setting/repo/edit': (context) { 31 | return RepoEdit.fromRoute(ModalRoute.of(context)); 32 | }, 33 | '/file/setting/theme': (context) { 34 | return const FileSettingTheme(); 35 | }, 36 | '/file/setting/upload': (context) { 37 | return const FileSettingUpload(); 38 | }, 39 | '/file/setting/download': (context) { 40 | return const FileSettingDownload(); 41 | }, 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /app/lib/api/file/widgets/file_breadcrumb.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:maple_file/app/i18n.dart'; 4 | import 'package:maple_file/common/widgets/breadcrumb.dart'; 5 | 6 | class FileBreadcrumb extends StatelessWidget { 7 | final String path; 8 | 9 | const FileBreadcrumb({super.key, required this.path}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | List list = path.split('/'); 14 | return Breadcrumb( 15 | children: [ 16 | for (int index = 0; index < list.length; index++) 17 | index == 0 18 | ? BreadcrumbItem( 19 | child: Text("全部文件".tr()), 20 | onTap: () { 21 | int length = list.length - index; 22 | Navigator.popUntil(context, (route) { 23 | length--; 24 | return length <= 0; 25 | }); 26 | }, 27 | ) 28 | : BreadcrumbItem( 29 | onTap: () { 30 | int length = list.length - index; 31 | Navigator.popUntil(context, (route) { 32 | length--; 33 | return length <= 0; 34 | }); 35 | }, 36 | child: Text(list[index]), 37 | ), 38 | ], 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/lib/api/file/widgets/repo/alist.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:maple_file/app/i18n.dart'; 6 | import 'package:maple_file/common/widgets/form.dart'; 7 | import 'package:maple_file/generated/proto/api/file/repo.pb.dart'; 8 | 9 | class Alist extends StatefulWidget { 10 | const Alist({super.key, required this.form}); 11 | 12 | final Repo form; 13 | 14 | @override 15 | State createState() => _AlistState(); 16 | } 17 | 18 | class _AlistState extends State { 19 | late Map _option; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | 25 | _option = widget.form.option == "" ? {} : jsonDecode(widget.form.option); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Card( 31 | child: Column( 32 | children: [ 33 | CustomFormField( 34 | label: "接入点".tr(), 35 | value: _option["endpoint"], 36 | isRequired: true, 37 | onTap: (result) { 38 | setState(() { 39 | _option["endpoint"] = result; 40 | }); 41 | 42 | widget.form.option = jsonEncode(_option); 43 | }, 44 | ), 45 | CustomFormField( 46 | label: "用户".tr(), 47 | value: _option["username"], 48 | isRequired: true, 49 | onTap: (result) { 50 | setState(() { 51 | _option["username"] = result; 52 | }); 53 | 54 | widget.form.option = jsonEncode(_option); 55 | }, 56 | ), 57 | CustomFormField( 58 | type: CustomFormFieldType.password, 59 | label: "密码".tr(), 60 | value: _option["password"], 61 | isRequired: true, 62 | onTap: (result) { 63 | setState(() { 64 | _option["password"] = result; 65 | }); 66 | 67 | widget.form.option = jsonEncode(_option); 68 | }, 69 | ), 70 | ], 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/lib/api/file/widgets/repo/mirror.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:maple_file/app/i18n.dart'; 6 | import 'package:maple_file/common/widgets/form.dart'; 7 | import 'package:maple_file/common/widgets/dialog.dart'; 8 | import 'package:maple_file/generated/proto/api/file/repo.pb.dart'; 9 | 10 | class Mirror extends StatefulWidget { 11 | const Mirror({super.key, required this.form}); 12 | 13 | final Repo form; 14 | 15 | @override 16 | State createState() => _MirrorState(); 17 | } 18 | 19 | class _MirrorState extends State { 20 | late Map _option; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | 26 | _option = widget.form.option == "" ? {} : jsonDecode(widget.form.option); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Card( 32 | child: Column( 33 | children: [ 34 | CustomFormField( 35 | label: "源站".tr(), 36 | value: _option["endpoint"], 37 | isRequired: true, 38 | onTap: (result) { 39 | setState(() { 40 | _option["endpoint"] = result; 41 | }); 42 | 43 | widget.form.option = jsonEncode(_option); 44 | }, 45 | ), 46 | ListTile( 47 | title: Text('源站格式'.tr()), 48 | trailing: Wrap( 49 | crossAxisAlignment: WrapCrossAlignment.center, 50 | children: [ 51 | Text(_option["format"] ?? "未选择".tr()), 52 | const Icon(Icons.chevron_right), 53 | ], 54 | ), 55 | onTap: () async { 56 | final result = await showListDialog(context, items: [ 57 | ListDialogItem(value: "nginx"), 58 | ListDialogItem(value: "tuna"), 59 | ListDialogItem(value: "aliyun"), 60 | ]); 61 | if (result != null) { 62 | setState(() { 63 | _option["format"] = result; 64 | }); 65 | widget.form.option = jsonEncode(_option); 66 | } 67 | }, 68 | ), 69 | ], 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/lib/api/home/pages/not_found.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:maple_file/app/i18n.dart'; 4 | 5 | class NotFound extends StatelessWidget { 6 | const NotFound({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | appBar: AppBar( 12 | title: const Text("404", style: TextStyle(fontSize: 16)), 13 | ), 14 | body: Center( 15 | child: Text("未找到页面".tr()), 16 | ), 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/lib/api/home/route.dart: -------------------------------------------------------------------------------- 1 | import 'package:maple_file/app/router.dart'; 2 | import 'package:maple_file/common/widgets/responsive.dart'; 3 | 4 | import "pages/help.dart"; 5 | import "pages/about.dart"; 6 | import "pages/index.dart"; 7 | import "pages/not_found.dart"; 8 | 9 | Future init(CustomRouter router) async { 10 | router.registerMany({ 11 | "/": (context) { 12 | if (Breakpoint.isSmall(context)) { 13 | return const Index(); 14 | } 15 | return DesktopIndex( 16 | initialRoute: "/file/list", 17 | onGenerateRoute: router.replaceRoute(replace: { 18 | "/": null, 19 | }), 20 | ); 21 | }, 22 | '/help': (context) { 23 | return const Help(); 24 | }, 25 | '/about': (context) { 26 | return const About(); 27 | }, 28 | '/404': (context) { 29 | return const NotFound(); 30 | }, 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /app/lib/api/setting/pages/setting_locale.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | 4 | import 'package:maple_file/app/i18n.dart'; 5 | 6 | import '../providers/setting_appearance.dart'; 7 | 8 | class SettingLocale extends ConsumerStatefulWidget { 9 | const SettingLocale({super.key}); 10 | 11 | @override 12 | ConsumerState createState() => _SettingLocaleState(); 13 | } 14 | 15 | class _SettingLocaleState extends ConsumerState { 16 | @override 17 | Widget build(BuildContext context) { 18 | final currentLocale = 19 | ref.watch(appearanceProvider.select((state) => state.locale)); 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Text('语言'.tr()), 23 | ), 24 | body: Container( 25 | // margin: const EdgeInsets.fromLTRB(16, 0, 16, 0), 26 | child: ListView( 27 | children: [ 28 | RadioListTile( 29 | title: const Text("中文"), 30 | groupValue: currentLocale, 31 | value: "zh", 32 | onChanged: (val) { 33 | if (val != null) { 34 | ref.read(appearanceProvider.notifier).update((state) { 35 | return state.copyWith(locale: val); 36 | }); 37 | } 38 | }, 39 | ), 40 | RadioListTile( 41 | title: const Text("English"), 42 | groupValue: currentLocale, 43 | value: "en", 44 | onChanged: (val) { 45 | if (val != null) { 46 | ref.read(appearanceProvider.notifier).update((state) { 47 | return state.copyWith(locale: val); 48 | }); 49 | } 50 | }, 51 | ), 52 | ], 53 | ), 54 | ), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/lib/api/setting/providers/info.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | import 'package:maple_file/generated/proto/api/setting/info.pb.dart'; 5 | 6 | import 'service.dart'; 7 | 8 | class InfoNotifier extends AsyncNotifier { 9 | final _service = SystemService(); 10 | 11 | @override 12 | FutureOr build() async { 13 | return await _service.info(); 14 | } 15 | } 16 | 17 | final infoProvider = AsyncNotifierProvider(() { 18 | return InfoNotifier(); 19 | }); 20 | -------------------------------------------------------------------------------- /app/lib/api/setting/providers/service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:maple_file/app/grpc.dart'; 4 | import 'package:maple_file/generated/proto/api/setting/info.pb.dart'; 5 | import 'package:maple_file/generated/proto/api/setting/setting.pb.dart'; 6 | import 'package:maple_file/generated/proto/api/setting/service.pbgrpc.dart'; 7 | 8 | class SystemService { 9 | static SystemService get instance => _instance; 10 | static final SystemService _instance = SystemService._internal(); 11 | factory SystemService() => _instance; 12 | 13 | late SystemServiceClient _client; 14 | late DateTime _clientTime; 15 | 16 | SystemService._internal() { 17 | _setClient(); 18 | } 19 | 20 | SystemServiceClient get client { 21 | if (GRPC.instance.connectTime.isAfter(_clientTime)) { 22 | _setClient(); 23 | } 24 | return _client; 25 | } 26 | 27 | void _setClient() { 28 | _client = SystemServiceClient( 29 | GRPC.instance.client, 30 | ); 31 | _clientTime = GRPC.instance.connectTime; 32 | } 33 | 34 | Future info() async { 35 | final result = await doFuture(() async { 36 | InfoRequest request = InfoRequest(); 37 | InfoResponse response = await client.info(request); 38 | return response.result; 39 | }); 40 | return result.data ?? 41 | Info( 42 | os: "unknown", 43 | arch: "unknown", 44 | runtime: "unknown", 45 | version: "unknown", 46 | ); 47 | } 48 | 49 | Future getSetting(String key) async { 50 | GetSettingRequest request = GetSettingRequest(key: key); 51 | GetSettingResponse response = await client.getSetting(request); 52 | return response.result.value; 53 | } 54 | 55 | Future updateSetting(String key, Object? value) { 56 | return doFuture(() async { 57 | UpdateSettingRequest request = UpdateSettingRequest( 58 | key: key, 59 | value: jsonEncode(value), 60 | ); 61 | await client.updateSetting(request); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/lib/api/setting/providers/setting.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | 6 | import 'service.dart'; 7 | 8 | class SettingAsyncNotifier extends FamilyAsyncNotifier { 9 | final _service = SystemService(); 10 | 11 | @override 12 | FutureOr build(String arg) { 13 | return _service.getSetting(arg); 14 | } 15 | } 16 | 17 | class SettingNotifier extends Notifier { 18 | final String key; 19 | final T value; 20 | final T Function(Map json) fromJson; 21 | 22 | final _service = SystemService(); 23 | 24 | SettingNotifier({ 25 | required this.key, 26 | required this.value, 27 | required this.fromJson, 28 | }); 29 | 30 | @override 31 | T build() { 32 | final result = ref.watch(settingProvider(key)).valueOrNull; 33 | if (result == null) { 34 | return value; 35 | } 36 | return fromJson(jsonDecode(result)); 37 | } 38 | 39 | T update(T Function(T state) cb) { 40 | final value = cb(state); 41 | 42 | _service.updateSetting(key, value).then((_) { 43 | state = value; 44 | }); 45 | return state; 46 | } 47 | 48 | Future init() async { 49 | await ref.read(settingProvider(key).future); 50 | } 51 | } 52 | 53 | final settingProvider = 54 | AsyncNotifierProvider.family( 55 | SettingAsyncNotifier.new); 56 | 57 | NotifierProvider, T> newSettingNotifier( 58 | String key, 59 | T value, 60 | T Function(Map json) fromJson, 61 | ) { 62 | return NotifierProvider, T>(() { 63 | return SettingNotifier(key: key, value: value, fromJson: fromJson); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /app/lib/api/setting/providers/setting_appearance.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:freezed_annotation/freezed_annotation.dart'; 3 | import 'package:flex_color_scheme/flex_color_scheme.dart'; 4 | 5 | import 'setting.dart'; 6 | 7 | part 'setting_appearance.freezed.dart'; 8 | part 'setting_appearance.g.dart'; 9 | 10 | const defaultFlexScheme = FlexScheme.redM3; 11 | 12 | extension FlexSchemeExtension on FlexScheme { 13 | Color primaryColor(BuildContext context) { 14 | switch (MediaQuery.of(context).platformBrightness) { 15 | case Brightness.light: 16 | return colors(Brightness.light).primary; 17 | case Brightness.dark: 18 | return colors(Brightness.dark).primary; 19 | } 20 | } 21 | } 22 | 23 | @freezed 24 | class AppearanceSetting with _$AppearanceSetting { 25 | const AppearanceSetting._(); 26 | 27 | const factory AppearanceSetting({ 28 | @Default(ThemeMode.system) @JsonKey(name: 'theme.mode') ThemeMode themeMode, 29 | @JsonKey(name: 'theme.color') String? themeColor, 30 | @Default("zh") @JsonKey(name: 'locale') String locale, 31 | }) = _AppearanceSetting; 32 | 33 | factory AppearanceSetting.fromJson(Map json) => 34 | _$AppearanceSettingFromJson(json); 35 | 36 | FlexScheme get scheme => FlexScheme.values.firstWhere( 37 | (item) => item.name == themeColor, 38 | orElse: () => defaultFlexScheme, 39 | ); 40 | } 41 | 42 | final appearanceProvider = newSettingNotifier( 43 | "app.appearance", 44 | const AppearanceSetting(), 45 | AppearanceSetting.fromJson, 46 | ); 47 | -------------------------------------------------------------------------------- /app/lib/api/setting/route.dart: -------------------------------------------------------------------------------- 1 | import 'package:maple_file/app/router.dart'; 2 | 3 | import "pages/setting.dart"; 4 | import "pages/setting_backup.dart"; 5 | import "pages/setting_locale.dart"; 6 | import "pages/setting_theme.dart"; 7 | 8 | Future init(CustomRouter router) async { 9 | router.registerMany({ 10 | '/setting': (context) { 11 | return const Setting(); 12 | }, 13 | '/setting/theme': (context) { 14 | return const SettingTheme(); 15 | }, 16 | '/setting/locale': (context) { 17 | return const SettingLocale(); 18 | }, 19 | '/setting/backup': (context) { 20 | return const SettingBackup(); 21 | }, 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /app/lib/api/task/providers/persist.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 4 | 5 | import 'package:maple_file/generated/proto/api/task/persist.pb.dart'; 6 | 7 | import 'service.dart'; 8 | 9 | class PersistTaskNotifier extends AsyncNotifier> { 10 | final _service = TaskService(); 11 | 12 | @override 13 | FutureOr> build() async { 14 | return await _service.listPersistTasks(); 15 | } 16 | } 17 | 18 | final persistTaskProvider = 19 | AsyncNotifierProvider>(() { 20 | return PersistTaskNotifier(); 21 | }); 22 | -------------------------------------------------------------------------------- /app/lib/api/task/route.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:maple_file/app/router.dart'; 4 | 5 | import "pages/task_list.dart"; 6 | 7 | Future init(CustomRouter router) async { 8 | router.registerMany({ 9 | '/task/list': (context) { 10 | return TaskList.fromRoute(ModalRoute.of(context)); 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /app/lib/app/grpc_web.dart: -------------------------------------------------------------------------------- 1 | import 'package:grpc/service_api.dart' as grpcapi; 2 | import 'package:grpc/grpc_web.dart'; 3 | 4 | class GrpcService { 5 | Future start() async { 6 | _client = GrpcWebClientChannel.xhr(Uri.parse('http://127.0.0.1:8000')); 7 | } 8 | 9 | Future stop() async {} 10 | 11 | Future restart() async { 12 | await stop(); 13 | await start(); 14 | } 15 | 16 | String get addr { 17 | return "127.0.0.1:8000"; 18 | } 19 | 20 | late grpcapi.ClientChannel _client; 21 | 22 | grpcapi.ClientChannel get client { 23 | return _client; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/lib/common/providers/pagination.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:easy_refresh/easy_refresh.dart'; 4 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 5 | 6 | abstract mixin class PaginationNotifierMixin { 7 | AsyncNotifierProviderRef> get ref; 8 | Future> get future; 9 | AsyncValue> get state; 10 | set state(AsyncValue> newState); 11 | 12 | int _page = 1; 13 | bool _hasMore = true; 14 | 15 | FutureOr> fetch(int page); 16 | 17 | FutureOr load() async { 18 | if (_hasMore) { 19 | state = await AsyncValue.guard(() async { 20 | final old = await future; 21 | final results = await fetch(_page + 1); 22 | if (results.length < (old.length / _page)) { 23 | _hasMore = false; 24 | } 25 | return [...old, ...results]; 26 | }); 27 | _page++; 28 | if (state.hasError) { 29 | return IndicatorResult.fail; 30 | } 31 | return _hasMore ? IndicatorResult.success : IndicatorResult.noMore; 32 | } 33 | return IndicatorResult.noMore; 34 | } 35 | 36 | FutureOr refresh() async { 37 | _page = 1; 38 | _hasMore = true; 39 | ref.invalidateSelf(); 40 | await future; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/lib/common/providers/value.dart: -------------------------------------------------------------------------------- 1 | class FormNotifier { 2 | T _value; 3 | 4 | final Function()? notifier; 5 | 6 | FormNotifier(this._value, {this.notifier}); 7 | 8 | T get value => _value; 9 | 10 | set value(T newValue) { 11 | if (_value == newValue) { 12 | return; 13 | } 14 | _value = newValue; 15 | notifier?.call(); 16 | } 17 | 18 | T update(T Function(T newValue) cb) { 19 | value = cb(value); 20 | notifier?.call(); 21 | return value; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/lib/common/utils/color.dart: -------------------------------------------------------------------------------- 1 | import 'package:convert/convert.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ColorUtil { 5 | static Color backgroundColorWithString(String value) { 6 | final strHex = 7 | hex.encode('${value}color'.codeUnits.map((e) => e % 256).toList()); 8 | String colorStr = ''; 9 | const hexLength = 6; 10 | final spacing = strHex.length ~/ hexLength; 11 | for (int i = 0; i < hexLength; i++) { 12 | colorStr += String.fromCharCode(strHex.codeUnitAt(i * spacing + 1)); 13 | } 14 | return Color(int.parse('ff$colorStr', radix: 16)); 15 | } 16 | 17 | static Color foregroundColorWithString(String value) { 18 | final bgColor = backgroundColorWithString(value); 19 | return bgColor.red * 0.299 + bgColor.green * 0.587 + bgColor.blue * 0.114 > 20 | 186 21 | ? Colors.black 22 | : Colors.white; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/lib/common/utils/navigator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomNavigator extends StatelessWidget { 4 | final String id; 5 | final Navigator navigator; 6 | 7 | CustomNavigator({super.key, required this.id, required this.navigator}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return navigator; 12 | } 13 | 14 | static NavigatorState of(BuildContext context, 15 | {int id, ValueKey key}) { 16 | final NavigatorState state = Navigator.of( 17 | context, 18 | rootNavigator: id == null, 19 | ); 20 | if (state.widget is CustomNavigator) { 21 | if ((state.widget as CustomNavigator).id == id) { 22 | return state; 23 | } else { 24 | return of(state.context, id: id); 25 | } 26 | } 27 | return state; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/lib/common/utils/time.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart' as intl; 2 | 3 | import 'package:maple_file/generated/proto/google/protobuf/timestamp.pb.dart' 4 | as pb; 5 | 6 | class TimeUtil { 7 | static String formatDate(DateTime d, String format) { 8 | return intl.DateFormat(format).format(d); 9 | } 10 | 11 | static String dateToString(DateTime d) { 12 | return intl.DateFormat('yyyy-MM-dd').format(d); 13 | } 14 | 15 | static String timeToString(DateTime d) { 16 | return intl.DateFormat('HH:mm:ss').format(d); 17 | } 18 | 19 | static String datetimeToString(DateTime d) { 20 | return intl.DateFormat('yyyy-MM-dd HH:mm:ss').format(d); 21 | } 22 | 23 | static DateTime pbToTime(pb.Timestamp t) { 24 | return DateTime.fromMillisecondsSinceEpoch(t.seconds.toInt() * 1000); 25 | } 26 | 27 | static String pbToString(pb.Timestamp t, String format) { 28 | return formatDate(pbToTime(t), format); 29 | } 30 | 31 | static String timesince(DateTime d) { 32 | Duration diff = DateTime.now().difference(d); 33 | // if (diff.inDays >= 7) { 34 | // return intl.DateFormat('yyyy-MM-dd HH:mm:ss').format(d); 35 | // } 36 | // if (diff.inDays > 0) { 37 | // return "${diff.inDays} ${diff.inDays == 1 ? "day" : "days"} ago"; 38 | // } 39 | // if (diff.inHours > 0) { 40 | // return "${diff.inHours} ${diff.inHours == 1 ? "hour" : "hours"} ago"; 41 | // } 42 | if (diff.inHours > 0) { 43 | return intl.DateFormat('yyyy-MM-dd HH:mm:ss').format(d); 44 | } 45 | if (diff.inMinutes > 0) { 46 | return "${diff.inMinutes} ${diff.inMinutes == 1 ? "minute" : "minutes"} ago"; 47 | } 48 | if (diff.inSeconds > 10) { 49 | return "${diff.inSeconds} ${diff.inSeconds == 1 ? "second" : "seconds"} ago"; 50 | } 51 | return "刚刚"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/lib/common/utils/util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:mime/mime.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | 5 | class Util { 6 | static bool get isWeb { 7 | return kIsWeb; 8 | } 9 | 10 | static bool get isIOS { 11 | return !kIsWeb && Platform.isIOS; 12 | } 13 | 14 | static bool get isAndroid { 15 | return !kIsWeb && Platform.isAndroid; 16 | } 17 | 18 | static bool get isMacOS { 19 | return !kIsWeb && Platform.isMacOS; 20 | } 21 | 22 | static bool get isLinux { 23 | return !kIsWeb && Platform.isLinux; 24 | } 25 | 26 | static bool get isWindows { 27 | return !kIsWeb && Platform.isWindows; 28 | } 29 | 30 | static bool get isMobile { 31 | return !kIsWeb && (Platform.isAndroid || Platform.isIOS); 32 | } 33 | 34 | static bool get isDesktop { 35 | if (kIsWeb) { 36 | return false; 37 | } 38 | return Platform.isWindows || Platform.isLinux || Platform.isMacOS; 39 | } 40 | 41 | static String mimeType(String name) { 42 | return lookupMimeType(name) ?? ""; 43 | } 44 | 45 | static String formatSize(int size) { 46 | if (size == 0) { 47 | return "0"; 48 | } else if (size < 1024) { 49 | return '${size}B'; 50 | } else if (size < 1024 * 1024) { 51 | return '${(size / 1024).toStringAsFixed(2)}KB'; 52 | } else if (size < 1024 * 1024 * 1024) { 53 | return '${(size / 1024 / 1024).toStringAsFixed(2)}MB'; 54 | } else { 55 | return '${(size / 1024 / 1024 / 1024).toStringAsFixed(2)}GB'; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/lib/common/watchers/lifecycle.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LifeCycleWatcher extends StatefulWidget { 4 | final Widget child; 5 | final void Function(AppLifecycleState state) onChangedState; 6 | 7 | const LifeCycleWatcher({ 8 | super.key, 9 | required this.child, 10 | required this.onChangedState, 11 | }); 12 | 13 | @override 14 | State createState() => _LifeCycleWatcherState(); 15 | } 16 | 17 | class _LifeCycleWatcherState extends State 18 | with WidgetsBindingObserver { 19 | @override 20 | Widget build(BuildContext context) { 21 | return widget.child; 22 | } 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | WidgetsBinding.instance.addObserver(this); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | WidgetsBinding.instance.removeObserver(this); 33 | super.dispose(); 34 | } 35 | 36 | @override 37 | void didChangeAppLifecycleState(AppLifecycleState state) { 38 | widget.onChangedState(state); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/lib/common/widgets/breadcrumb.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BreadcrumbItem extends StatelessWidget { 4 | final Widget child; 5 | final GestureTapCallback? onTap; 6 | 7 | const BreadcrumbItem({ 8 | super.key, 9 | this.onTap, 10 | required this.child, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | if (onTap != null) { 16 | return GestureDetector( 17 | onTap: onTap, 18 | child: child, 19 | ); 20 | } 21 | return child; 22 | } 23 | } 24 | 25 | class Breadcrumb extends StatelessWidget { 26 | final Widget divider; 27 | final List children; 28 | 29 | const Breadcrumb({ 30 | super.key, 31 | this.divider = const Icon(Icons.chevron_right, size: 18), 32 | required this.children, 33 | }); 34 | 35 | Breadcrumb.builder({ 36 | super.key, 37 | this.divider = const Icon(Icons.chevron_right, size: 18), 38 | required int itemCount, 39 | required IndexedWidgetBuilder itemBuilder, 40 | }) : children = List.generate( 41 | itemCount, 42 | (i) => Builder(builder: (context) => itemBuilder(context, i)), 43 | ); 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | List items = []; 48 | 49 | for (final (index, child) in children.indexed) { 50 | items.add(child); 51 | if (index < children.length - 1) { 52 | items.add(divider); 53 | } 54 | } 55 | return SingleChildScrollView( 56 | scrollDirection: Axis.horizontal, 57 | child: Row( 58 | children: items, 59 | ), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/lib/common/widgets/keyboard.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:flutter/material.dart'; 3 | 4 | class KeyboardHeightBuilder extends StatefulWidget { 5 | const KeyboardHeightBuilder({ 6 | super.key, 7 | required this.builder, 8 | }); 9 | 10 | final Widget Function(BuildContext, double, double) builder; 11 | 12 | @override 13 | State createState() => _KeyboardHeightBuilderState(); 14 | } 15 | 16 | class _KeyboardHeightBuilderState extends State 17 | with WidgetsBindingObserver { 18 | double _height = 0; 19 | double _maxHeight = 0; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | WidgetsBinding.instance.addObserver(this); 25 | } 26 | 27 | @override 28 | void dispose() { 29 | WidgetsBinding.instance.removeObserver(this); 30 | super.dispose(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return widget.builder(context, _height, _maxHeight); 36 | } 37 | 38 | @override 39 | void didChangeMetrics() { 40 | super.didChangeMetrics(); 41 | if (mounted) { 42 | final bottom = EdgeInsets.fromViewPadding( 43 | View.of(context).viewInsets, 44 | View.of(context).devicePixelRatio, 45 | ).bottom; 46 | 47 | _maxHeight = math.max(_maxHeight, bottom); 48 | if (_height != bottom) { 49 | setState(() { 50 | _height = bottom; 51 | }); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/file/file.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/file/file.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/file/repo.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/file/repo.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/file/service.pb.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/file/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:core' as $core; 13 | 14 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/file/service.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/file/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/file/service.pbjson.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/file/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:convert' as $convert; 13 | import 'dart:core' as $core; 14 | import 'dart:typed_data' as $typed_data; 15 | 16 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/setting/info.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/setting/info.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/setting/info.pbjson.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/setting/info.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:convert' as $convert; 13 | import 'dart:core' as $core; 14 | import 'dart:typed_data' as $typed_data; 15 | 16 | @$core.Deprecated('Use infoDescriptor instead') 17 | const Info$json = { 18 | '1': 'Info', 19 | '2': [ 20 | {'1': 'os', '3': 1, '4': 1, '5': 9, '10': 'os'}, 21 | {'1': 'arch', '3': 2, '4': 1, '5': 9, '10': 'arch'}, 22 | {'1': 'runtime', '3': 3, '4': 1, '5': 9, '10': 'runtime'}, 23 | {'1': 'version', '3': 4, '4': 1, '5': 9, '10': 'version'}, 24 | {'1': 'description', '3': 5, '4': 1, '5': 9, '10': 'description'}, 25 | ], 26 | }; 27 | 28 | /// Descriptor for `Info`. Decode as a `google.protobuf.DescriptorProto`. 29 | final $typed_data.Uint8List infoDescriptor = $convert.base64Decode( 30 | 'CgRJbmZvEg4KAm9zGAEgASgJUgJvcxISCgRhcmNoGAIgASgJUgRhcmNoEhgKB3J1bnRpbWUYAy' 31 | 'ABKAlSB3J1bnRpbWUSGAoHdmVyc2lvbhgEIAEoCVIHdmVyc2lvbhIgCgtkZXNjcmlwdGlvbhgF' 32 | 'IAEoCVILZGVzY3JpcHRpb24='); 33 | 34 | @$core.Deprecated('Use infoRequestDescriptor instead') 35 | const InfoRequest$json = { 36 | '1': 'InfoRequest', 37 | }; 38 | 39 | /// Descriptor for `InfoRequest`. Decode as a `google.protobuf.DescriptorProto`. 40 | final $typed_data.Uint8List infoRequestDescriptor = $convert.base64Decode( 41 | 'CgtJbmZvUmVxdWVzdA=='); 42 | 43 | @$core.Deprecated('Use infoResponseDescriptor instead') 44 | const InfoResponse$json = { 45 | '1': 'InfoResponse', 46 | '2': [ 47 | {'1': 'result', '3': 1, '4': 1, '5': 11, '6': '.api.setting.Info', '10': 'result'}, 48 | ], 49 | }; 50 | 51 | /// Descriptor for `InfoResponse`. Decode as a `google.protobuf.DescriptorProto`. 52 | final $typed_data.Uint8List infoResponseDescriptor = $convert.base64Decode( 53 | 'CgxJbmZvUmVzcG9uc2USKQoGcmVzdWx0GAEgASgLMhEuYXBpLnNldHRpbmcuSW5mb1IGcmVzdW' 54 | 'x0'); 55 | 56 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/setting/service.pb.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/setting/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:core' as $core; 13 | 14 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/setting/service.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/setting/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/setting/service.pbjson.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/setting/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:convert' as $convert; 13 | import 'dart:core' as $core; 14 | import 'dart:typed_data' as $typed_data; 15 | 16 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/setting/setting.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/setting/setting.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/task/persist.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/task/persist.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/task/service.pb.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/task/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:core' as $core; 13 | 14 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/task/service.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/task/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/task/service.pbjson.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/task/service.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:convert' as $convert; 13 | import 'dart:core' as $core; 14 | import 'dart:typed_data' as $typed_data; 15 | 16 | -------------------------------------------------------------------------------- /app/lib/generated/proto/api/task/task.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: api/task/task.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:core' as $core; 13 | 14 | import 'package:protobuf/protobuf.dart' as $pb; 15 | 16 | class TaskState extends $pb.ProtobufEnum { 17 | static const TaskState TASK_STATE_UNSPECIFIED = TaskState._(0, _omitEnumNames ? '' : 'TASK_STATE_UNSPECIFIED'); 18 | static const TaskState TASK_STATE_RUNNING = TaskState._(1, _omitEnumNames ? '' : 'TASK_STATE_RUNNING'); 19 | static const TaskState TASK_STATE_SUCCEEDED = TaskState._(2, _omitEnumNames ? '' : 'TASK_STATE_SUCCEEDED'); 20 | static const TaskState TASK_STATE_CANCELING = TaskState._(3, _omitEnumNames ? '' : 'TASK_STATE_CANCELING'); 21 | static const TaskState TASK_STATE_CANCELED = TaskState._(4, _omitEnumNames ? '' : 'TASK_STATE_CANCELED'); 22 | static const TaskState TASK_STATE_FAILED = TaskState._(5, _omitEnumNames ? '' : 'TASK_STATE_FAILED'); 23 | 24 | static const TaskState TASK_STATE_PENDING = TASK_STATE_UNSPECIFIED; 25 | 26 | static const $core.List values = [ 27 | TASK_STATE_UNSPECIFIED, 28 | TASK_STATE_RUNNING, 29 | TASK_STATE_SUCCEEDED, 30 | TASK_STATE_CANCELING, 31 | TASK_STATE_CANCELED, 32 | TASK_STATE_FAILED, 33 | ]; 34 | 35 | static final $core.Map<$core.int, TaskState> _byValue = $pb.ProtobufEnum.initByValue(values); 36 | static TaskState? valueOf($core.int value) => _byValue[value]; 37 | 38 | const TaskState._($core.int v, $core.String n) : super(v, n); 39 | } 40 | 41 | 42 | const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names'); 43 | -------------------------------------------------------------------------------- /app/lib/generated/proto/google/api/annotations.pb.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: google/api/annotations.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:core' as $core; 13 | 14 | import 'package:protobuf/protobuf.dart' as $pb; 15 | 16 | import 'http.pb.dart' as $3; 17 | 18 | class Annotations { 19 | static final http = $pb.Extension<$3.HttpRule>(_omitMessageNames ? '' : 'google.protobuf.MethodOptions', _omitFieldNames ? '' : 'http', 72295728, $pb.PbFieldType.OM, defaultOrMaker: $3.HttpRule.getDefault, subBuilder: $3.HttpRule.create); 20 | static void registerAllExtensions($pb.ExtensionRegistry registry) { 21 | registry.add(http); 22 | } 23 | } 24 | 25 | 26 | const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); 27 | const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names'); 28 | -------------------------------------------------------------------------------- /app/lib/generated/proto/google/api/annotations.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: google/api/annotations.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/google/api/annotations.pbjson.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: google/api/annotations.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:convert' as $convert; 13 | import 'dart:core' as $core; 14 | import 'dart:typed_data' as $typed_data; 15 | 16 | -------------------------------------------------------------------------------- /app/lib/generated/proto/google/api/http.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: google/api/http.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/google/protobuf/timestamp.pbenum.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: google/protobuf/timestamp.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | -------------------------------------------------------------------------------- /app/lib/generated/proto/google/protobuf/timestamp.pbjson.dart: -------------------------------------------------------------------------------- 1 | // 2 | // Generated code. Do not modify. 3 | // source: google/protobuf/timestamp.proto 4 | // 5 | // @dart = 2.12 6 | 7 | // ignore_for_file: annotate_overrides, camel_case_types, comment_references 8 | // ignore_for_file: constant_identifier_names, library_prefixes 9 | // ignore_for_file: non_constant_identifier_names, prefer_final_fields 10 | // ignore_for_file: unnecessary_import, unnecessary_this, unused_import 11 | 12 | import 'dart:convert' as $convert; 13 | import 'dart:core' as $core; 14 | import 'dart:typed_data' as $typed_data; 15 | 16 | @$core.Deprecated('Use timestampDescriptor instead') 17 | const Timestamp$json = { 18 | '1': 'Timestamp', 19 | '2': [ 20 | {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'}, 21 | {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'}, 22 | ], 23 | }; 24 | 25 | /// Descriptor for `Timestamp`. Decode as a `google.protobuf.DescriptorProto`. 26 | final $typed_data.Uint8List timestampDescriptor = $convert.base64Decode( 27 | 'CglUaW1lc3RhbXASGAoHc2Vjb25kcxgBIAEoA1IHc2Vjb25kcxIUCgVuYW5vcxgCIAEoBVIFbm' 28 | 'Fub3M='); 29 | 30 | -------------------------------------------------------------------------------- /app/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /app/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | void fl_register_plugins(FlPluginRegistry* registry) { 18 | g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = 19 | fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); 20 | audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); 21 | g_autoptr(FlPluginRegistrar) desktop_drop_registrar = 22 | fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); 23 | desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); 24 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 25 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 26 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 27 | g_autoptr(FlPluginRegistrar) fvp_registrar = 28 | fl_plugin_registry_get_registrar_for_plugin(registry, "FvpPlugin"); 29 | fvp_plugin_register_with_registrar(fvp_registrar); 30 | g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = 31 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); 32 | screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); 33 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 34 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 35 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 36 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 37 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 38 | window_manager_plugin_register_with_registrar(window_manager_registrar); 39 | } 40 | -------------------------------------------------------------------------------- /app/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /app/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | audioplayers_linux 7 | desktop_drop 8 | file_selector_linux 9 | fvp 10 | screen_retriever_linux 11 | url_launcher_linux 12 | window_manager 13 | ) 14 | 15 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 16 | ) 17 | 18 | set(PLUGIN_BUNDLED_LIBRARIES) 19 | 20 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 22 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 25 | endforeach(plugin) 26 | 27 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 28 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 29 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 30 | endforeach(ffi_plugin) 31 | -------------------------------------------------------------------------------- /app/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /app/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /app/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /app/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /app/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import audioplayers_darwin 9 | import desktop_drop 10 | import file_picker 11 | import file_selector_macos 12 | import fvp 13 | import macos_secure_bookmarks 14 | import package_info_plus 15 | import path_provider_foundation 16 | import screen_retriever_macos 17 | import url_launcher_macos 18 | import video_player_avfoundation 19 | import wakelock_plus 20 | import window_manager 21 | 22 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 23 | AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) 24 | DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) 25 | FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) 26 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 27 | FvpPlugin.register(with: registry.registrar(forPlugin: "FvpPlugin")) 28 | SecureBookmarksPlugin.register(with: registry.registrar(forPlugin: "SecureBookmarksPlugin")) 29 | FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 30 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 31 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 32 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 33 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 34 | WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) 35 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 36 | } 37 | -------------------------------------------------------------------------------- /app/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.15' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "xcode" 5 | }, 6 | "images": [ 7 | { 8 | "size": "16x16", 9 | "idiom": "mac", 10 | "filename": "app_icon_16.png", 11 | "scale": "1x" 12 | }, 13 | { 14 | "size": "16x16", 15 | "idiom": "mac", 16 | "filename": "app_icon_32.png", 17 | "scale": "2x" 18 | }, 19 | { 20 | "size": "32x32", 21 | "idiom": "mac", 22 | "filename": "app_icon_32.png", 23 | "scale": "1x" 24 | }, 25 | { 26 | "size": "32x32", 27 | "idiom": "mac", 28 | "filename": "app_icon_64.png", 29 | "scale": "2x" 30 | }, 31 | { 32 | "size": "128x128", 33 | "idiom": "mac", 34 | "filename": "app_icon_128.png", 35 | "scale": "1x" 36 | }, 37 | { 38 | "size": "128x128", 39 | "idiom": "mac", 40 | "filename": "app_icon_256.png", 41 | "scale": "2x" 42 | }, 43 | { 44 | "size": "256x256", 45 | "idiom": "mac", 46 | "filename": "app_icon_256.png", 47 | "scale": "1x" 48 | }, 49 | { 50 | "size": "256x256", 51 | "idiom": "mac", 52 | "filename": "app_icon_512.png", 53 | "scale": "2x" 54 | }, 55 | { 56 | "size": "512x512", 57 | "idiom": "mac", 58 | "filename": "app_icon_512.png", 59 | "scale": "1x" 60 | }, 61 | { 62 | "size": "512x512", 63 | "idiom": "mac", 64 | "filename": "app_icon_1024.png", 65 | "scale": "2x" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /app/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = MapleFile 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.honmaple.mapleFile 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 com.honmaple. All rights reserved. 15 | -------------------------------------------------------------------------------- /app/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /app/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /app/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /app/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | com.apple.security.files.downloads.read-write 14 | 15 | com.apple.security.files.user-selected.read-only 16 | 17 | com.apple.security.files.bookmarks.app-scope 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundleDisplayName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | $(PRODUCT_COPYRIGHT) 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.server 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.files.downloads.read-write 12 | 13 | com.apple.security.files.user-selected.read-write 14 | 15 | com.apple.security.files.bookmarks.app-scope 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/macos/Runner/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by jianglin on 2025/3/12. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "MapleFile"; 10 | -------------------------------------------------------------------------------- /app/macos/Runner/zh-Hans.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* 2 | InfoPlist.strings 3 | Runner 4 | 5 | Created by jianglin on 2025/3/12. 6 | 7 | */ 8 | 9 | "CFBundleDisplayName" = "红枫云盘"; 10 | -------------------------------------------------------------------------------- /app/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:maple_file/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /app/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/web/favicon.png -------------------------------------------------------------------------------- /app/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/web/icons/Icon-192.png -------------------------------------------------------------------------------- /app/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/web/icons/Icon-512.png -------------------------------------------------------------------------------- /app/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /app/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /app/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | maple_file 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maple_file", 3 | "short_name": "maple_file", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /app/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /app/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | void RegisterPlugins(flutter::PluginRegistry* registry) { 19 | AudioplayersWindowsPluginRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); 21 | DesktopDropPluginRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("DesktopDropPlugin")); 23 | FileSelectorWindowsRegisterWithRegistrar( 24 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 25 | FvpPluginCApiRegisterWithRegistrar( 26 | registry->GetRegistrarForPlugin("FvpPluginCApi")); 27 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 28 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 29 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 30 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 31 | UrlLauncherWindowsRegisterWithRegistrar( 32 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 33 | WindowManagerPluginRegisterWithRegistrar( 34 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 35 | } 36 | -------------------------------------------------------------------------------- /app/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /app/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | audioplayers_windows 7 | desktop_drop 8 | file_selector_windows 9 | fvp 10 | permission_handler_windows 11 | screen_retriever_windows 12 | url_launcher_windows 13 | window_manager 14 | ) 15 | 16 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 17 | ) 18 | 19 | set(PLUGIN_BUNDLED_LIBRARIES) 20 | 21 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 23 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 26 | endforeach(plugin) 27 | 28 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 29 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 30 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 31 | endforeach(ffi_plugin) 32 | -------------------------------------------------------------------------------- /app/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /app/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /app/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"maple_file", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /app/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /app/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/app/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /app/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /app/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/screenshot/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/01.png -------------------------------------------------------------------------------- /example/screenshot/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/02.png -------------------------------------------------------------------------------- /example/screenshot/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/03.png -------------------------------------------------------------------------------- /example/screenshot/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/04.png -------------------------------------------------------------------------------- /example/screenshot/05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/05.png -------------------------------------------------------------------------------- /example/screenshot/06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/06.png -------------------------------------------------------------------------------- /example/screenshot/desktop-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/desktop-01.png -------------------------------------------------------------------------------- /example/screenshot/desktop-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/honmaple/maple-file/a0782fccc88cc993314c5c1c37d4500fc64636da/example/screenshot/desktop-02.png -------------------------------------------------------------------------------- /proto/api/file/repo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.file; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | option go_package = "api/file"; 8 | 9 | message Repo { 10 | // @gotags: gorm:"primary_key;auto_increment" 11 | int32 id = 1; 12 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 13 | google.protobuf.Timestamp created_at = 2; 14 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 15 | google.protobuf.Timestamp updated_at = 3; 16 | 17 | // @gotags: gorm:"not null;unique" 18 | string name = 4; 19 | string path = 5; 20 | bool status = 6; 21 | string driver = 7; 22 | string option = 8; 23 | } 24 | 25 | message ListReposRequest { 26 | map filter = 1; 27 | } 28 | 29 | message ListReposResponse { 30 | repeated Repo results = 1; 31 | } 32 | 33 | message CreateRepoRequest { 34 | Repo payload = 1; 35 | } 36 | 37 | message CreateRepoResponse { 38 | Repo result = 1; 39 | } 40 | 41 | message TestRepoRequest { 42 | Repo payload = 1; 43 | } 44 | 45 | message TestRepoResponse { 46 | bool success = 1; 47 | } 48 | 49 | message UpdateRepoRequest { 50 | Repo payload = 1; 51 | } 52 | 53 | message UpdateRepoResponse { 54 | Repo result = 1; 55 | } 56 | 57 | message DeleteRepoRequest { 58 | int32 id = 1; 59 | } 60 | 61 | message DeleteRepoResponse {} -------------------------------------------------------------------------------- /proto/api/setting/info.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.setting; 4 | 5 | option go_package = "api/setting"; 6 | 7 | message Info { 8 | string os = 1; 9 | string arch = 2; 10 | string runtime = 3; 11 | string version = 4; 12 | string description = 5; 13 | } 14 | 15 | message InfoRequest {} 16 | 17 | message InfoResponse { 18 | Info result = 1; 19 | } 20 | -------------------------------------------------------------------------------- /proto/api/setting/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.setting; 4 | 5 | import "google/api/annotations.proto"; 6 | import "api/setting/info.proto"; 7 | import "api/setting/setting.proto"; 8 | 9 | option go_package = "api/setting"; 10 | 11 | service SystemService { 12 | rpc Info (InfoRequest) returns (InfoResponse) { 13 | option (google.api.http) = { 14 | get: "/api/info" 15 | }; 16 | } 17 | 18 | rpc ResetSetting (ResetSettingRequest) returns (ResetSettingResponse) { 19 | option (google.api.http) = { 20 | post: "/api/settings/reset" 21 | }; 22 | } 23 | rpc UpdateSetting (UpdateSettingRequest) returns (UpdateSettingResponse) { 24 | option (google.api.http) = { 25 | post: "/api/settings" 26 | body: "*" 27 | }; 28 | } 29 | rpc GetSetting (GetSettingRequest) returns (GetSettingResponse) { 30 | option (google.api.http) = { 31 | get: "/api/setting/{key}" 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /proto/api/setting/setting.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.setting; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | option go_package = "api/setting"; 8 | 9 | message Setting { 10 | // @gotags: gorm:"primary_key;auto_increment" 11 | int32 id = 1; 12 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 13 | google.protobuf.Timestamp created_at = 2; 14 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 15 | google.protobuf.Timestamp updated_at = 3; 16 | 17 | // @gotags: gorm:"size:128;not null;unique" 18 | string key = 4; 19 | string value = 5; 20 | } 21 | 22 | message GetSettingRequest { 23 | string key = 1; 24 | } 25 | 26 | message GetSettingResponse { 27 | Setting result = 1; 28 | } 29 | 30 | message UpdateSettingRequest { 31 | string key = 1; 32 | string value = 2; 33 | } 34 | 35 | message UpdateSettingResponse { 36 | Setting result = 1; 37 | } 38 | 39 | message ResetSettingRequest { 40 | string key = 1; 41 | } 42 | 43 | message ResetSettingResponse {} -------------------------------------------------------------------------------- /proto/api/task/persist.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.task; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | option go_package = "api/task"; 8 | 9 | message PersistTask { 10 | // @gotags: gorm:"primary_key;auto_increment" 11 | int32 id = 1; 12 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 13 | google.protobuf.Timestamp created_at = 2; 14 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 15 | google.protobuf.Timestamp updated_at = 3; 16 | 17 | // @gotags: gorm:"not null;unique" 18 | string name = 4; 19 | bool status = 5; 20 | string type = 6; 21 | string option = 7; 22 | string cron_option = 8; 23 | } 24 | 25 | message ListPersistTasksRequest { 26 | map filter = 1; 27 | } 28 | 29 | message ListPersistTasksResponse { 30 | repeated PersistTask results = 1; 31 | } 32 | 33 | message CreatePersistTaskRequest { 34 | PersistTask payload = 1; 35 | } 36 | 37 | message CreatePersistTaskResponse { 38 | PersistTask result = 1; 39 | } 40 | 41 | message UpdatePersistTaskRequest { 42 | PersistTask payload = 1; 43 | } 44 | 45 | message UpdatePersistTaskResponse { 46 | PersistTask result = 1; 47 | } 48 | 49 | message DeletePersistTaskRequest { 50 | int32 id = 1; 51 | } 52 | 53 | message DeletePersistTaskResponse {} 54 | 55 | message TestPersistTaskRequest { 56 | PersistTask payload = 1; 57 | } 58 | 59 | message TestPersistTaskResponse {} 60 | 61 | message ExecutePersistTaskRequest { 62 | int32 id = 1; 63 | bool dry_run = 2; 64 | } 65 | 66 | message ExecutePersistTaskResponse {} -------------------------------------------------------------------------------- /proto/api/task/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.task; 4 | 5 | import "google/api/annotations.proto"; 6 | import "api/task/task.proto"; 7 | import "api/task/persist.proto"; 8 | 9 | option go_package = "api/task"; 10 | 11 | service TaskService { 12 | rpc ListTasks (ListTasksRequest) returns (ListTasksResponse) { 13 | option (google.api.http) = { 14 | post: "/api/task/list" 15 | body: "*" 16 | }; 17 | } 18 | rpc RetryTask (RetryTaskRequest) returns (RetryTaskResponse) { 19 | option (google.api.http) = { 20 | post: "/api/task/retry" 21 | body: "*" 22 | }; 23 | } 24 | rpc CancelTask (CancelTaskRequest) returns (CancelTaskResponse) { 25 | option (google.api.http) = { 26 | post: "/api/task/cancel" 27 | body: "*" 28 | }; 29 | } 30 | rpc RemoveTask (RemoveTaskRequest) returns (RemoveTaskResponse) { 31 | option (google.api.http) = { 32 | post: "/api/task/remove" 33 | body: "*" 34 | }; 35 | } 36 | 37 | rpc ListPersistTasks (ListPersistTasksRequest) returns (ListPersistTasksResponse) { 38 | option (google.api.http) = { 39 | get: "/api/task/persist/list" 40 | }; 41 | } 42 | rpc CreatePersistTask (CreatePersistTaskRequest) returns (CreatePersistTaskResponse) { 43 | option (google.api.http) = { 44 | post: "/api/task/persist" 45 | body: "*" 46 | }; 47 | } 48 | rpc UpdatePersistTask (UpdatePersistTaskRequest) returns (UpdatePersistTaskResponse) { 49 | option (google.api.http) = { 50 | put: "/api/task/persist/{payload.id}" 51 | body: "payload" 52 | }; 53 | } 54 | rpc DeletePersistTask (DeletePersistTaskRequest) returns (DeletePersistTaskResponse) { 55 | option (google.api.http) = { 56 | delete: "/api/task/persist/{id}" 57 | }; 58 | } 59 | rpc TestPersistTask (TestPersistTaskRequest) returns (TestPersistTaskResponse) {} 60 | rpc ExecutePersistTask (ExecutePersistTaskRequest) returns (ExecutePersistTaskResponse) {} 61 | } -------------------------------------------------------------------------------- /proto/api/task/task.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package api.task; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | option go_package = "api/task"; 8 | 9 | enum TaskState { 10 | option allow_alias = true; 11 | TASK_STATE_UNSPECIFIED = 0; 12 | 13 | TASK_STATE_PENDING = 0; 14 | TASK_STATE_RUNNING = 1; 15 | TASK_STATE_SUCCEEDED = 2; 16 | TASK_STATE_CANCELING = 3; 17 | TASK_STATE_CANCELED = 4; 18 | TASK_STATE_FAILED = 5; 19 | } 20 | 21 | message Task { 22 | // @gotags: gorm:"not null;unique" 23 | string id = 1; 24 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 25 | google.protobuf.Timestamp start_time = 2; 26 | // @gotags: gorm:"serializer:protobuf_timestamp;type:datetime" 27 | google.protobuf.Timestamp end_time = 3; 28 | 29 | // @gotags: gorm:"not null;" 30 | string name = 4; 31 | TaskState state = 5; 32 | double progress = 6; 33 | string progress_state = 7; 34 | string log = 8; 35 | string err = 9; 36 | } 37 | 38 | message ListTasksRequest { 39 | map filter = 1; 40 | } 41 | 42 | message ListTasksResponse { 43 | repeated Task results = 1; 44 | } 45 | 46 | message RetryTaskRequest { 47 | repeated string tasks = 1; 48 | } 49 | 50 | message RetryTaskResponse { 51 | } 52 | 53 | message CancelTaskRequest { 54 | repeated string tasks = 1; 55 | } 56 | 57 | message CancelTaskResponse { 58 | } 59 | 60 | message RemoveTaskRequest { 61 | repeated string tasks = 1; 62 | } 63 | 64 | message RemoveTaskResponse { 65 | } -------------------------------------------------------------------------------- /proto/buf.gen.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | managed: 3 | enabled: true 4 | go_package_prefix: 5 | default: github.com/honmaple/maple-file/server/internal/proto 6 | except: 7 | - buf.build/googleapis/googleapis 8 | 9 | # go install google.golang.org/protobuf/cmd/protoc-gen-go@latest 10 | # go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest 11 | # go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest 12 | # go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest 13 | # buf generate --include-imports --include-wkt 14 | plugins: 15 | - plugin: go 16 | opt: 17 | - paths=source_relative 18 | # out: gen/go 19 | out: ../server/internal/proto 20 | - plugin: go-grpc 21 | opt: 22 | - paths=source_relative 23 | # out: gen/go 24 | out: ../server/internal/proto 25 | - plugin: grpc-gateway 26 | opt: 27 | - paths=source_relative 28 | - generate_unbound_methods=false 29 | # out: gen/go 30 | out: ../server/internal/proto 31 | - plugin: openapiv2 32 | # out: gen/go 33 | out: ../server/internal/proto 34 | - plugin: dart 35 | opt: 36 | - grpc 37 | out: ../app/lib/generated/proto -------------------------------------------------------------------------------- /proto/buf.lock: -------------------------------------------------------------------------------- 1 | # Generated by buf. DO NOT EDIT. 2 | version: v1 3 | deps: 4 | - remote: buf.build 5 | owner: googleapis 6 | repository: googleapis 7 | commit: 09703837a2ed48dbbbb3fdfbe6a84f5c 8 | digest: shake256:de26a277fc28b8b411ecf58729d78d32fcf15090ffd998a4469225b17889bfb51442eaab04bb7a8d88d203ecdf0a9febd4ffd52c18ed1c2229160c7bd353ca95 9 | - remote: buf.build 10 | owner: grpc-ecosystem 11 | repository: grpc-gateway 12 | commit: cf1213ca7b09450999c79cd8da42a8e3 13 | digest: shake256:67b115260e12cb2d6c5d5ce8dbbf3a095c86f0e52b84f9dbd16dec9433b218f8694bc9aadb1d45eb6fd52f5a7029977d460e2d58afb3208ab6c680e7b21c80e4 14 | -------------------------------------------------------------------------------- /proto/buf.yaml: -------------------------------------------------------------------------------- 1 | version: v1 2 | breaking: 3 | use: 4 | - FILE 5 | lint: 6 | use: 7 | - DEFAULT 8 | except: 9 | - ENUM_VALUE_PREFIX 10 | - ENUM_NO_ALLOW_ALIAS 11 | - ENUM_ZERO_VALUE_SUFFIX 12 | - PACKAGE_VERSION_SUFFIX 13 | - PACKAGE_DIRECTORY_MATCH 14 | - RPC_REQUEST_STANDARD_NAME 15 | - RPC_RESPONSE_STANDARD_NAME 16 | - RPC_REQUEST_RESPONSE_UNIQUE 17 | deps: 18 | - buf.build/googleapis/googleapis 19 | - buf.build/grpc-ecosystem/grpc-gateway 20 | # - buf.build/protocolbuffers/wellknowntypes 21 | -------------------------------------------------------------------------------- /proto/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | buf generate --include-imports --include-wkt 4 | protoc-go-inject-tag -input="../server/internal/proto/api/file/*.pb.go" 5 | protoc-go-inject-tag -input="../server/internal/proto/api/task/*.pb.go" 6 | protoc-go-inject-tag -input="../server/internal/proto/api/setting/*.pb.go" -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Android 3 | ``` 4 | gomobile bind -ldflags="-w -s -X github.com/honmaple/maple-file/server/internal/app.VERSION=1.0.8" -o ../app/android/app/libs/libserver.aar -target=android -androidapi 21 -javapkg="com.honmaple.maple_file" github.com/honmaple/maple-file/server/cmd/mobile 5 | ``` 6 | 7 | ## IOS 8 | ``` 9 | gomobile bind -ldflags="-w -s -X github.com/honmaple/maple-file/server/internal/app.VERSION=1.0.8" -o ../app/ios/Frameworks/libserver.xcframework -target=ios github.com/honmaple/maple-file/server/cmd/mobile 10 | ``` 11 | 12 | ## MacOS 13 | ``` 14 | go build -ldflags="-w -s -X github.com/honmaple/maple-file/server/internal/app.VERSION=1.0.8" -buildmode=c-shared -o ../app/macos/Frameworks/libserver.dylib github.com/honmaple/maple-file/server/cmd/desktop 15 | ``` 16 | 17 | ## Windows 18 | ``` 19 | set CGO_ENABLED=1 20 | go build -ldflags="-w -s -X github.com/honmaple/maple-file/server/internal/app.VERSION=1.0.8" -buildmode=c-shared -o ../app/windows/libserver.dll github.com/honmaple/maple-file/server/cmd/desktop 21 | ``` 22 | 23 | ## Linux 24 | ``` 25 | go build -ldflags="-w -s -X github.com/honmaple/maple-file/server/internal/app.VERSION=1.0.8" -buildmode=c-shared -o ../app/linux/libserver.so github.com/honmaple/maple-file/server/cmd/desktop 26 | ``` 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /server/cmd/desktop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | import ( 5 | "github.com/honmaple/maple-file/server/internal/app" 6 | 7 | _ "github.com/honmaple/maple-file/server/internal/api" 8 | ) 9 | 10 | func main() {} 11 | 12 | var ( 13 | server *app.Server 14 | ) 15 | 16 | //export Start 17 | func Start(cfgPtr *C.char) (*C.char, *C.char) { 18 | if server != nil { 19 | return C.CString(server.Addr()), nil 20 | } 21 | 22 | if cfgPtr == nil { 23 | return nil, C.CString("cfg is required") 24 | } 25 | cfg := C.GoString(cfgPtr) 26 | 27 | var err error 28 | 29 | server, err = app.NewServer(cfg) 30 | if err != nil { 31 | return nil, C.CString(err.Error()) 32 | } 33 | go server.Start() 34 | 35 | return C.CString(server.Addr()), nil 36 | } 37 | 38 | //export Stop 39 | func Stop() { 40 | if server != nil { 41 | server.Shutdown() 42 | } 43 | server = nil 44 | } 45 | -------------------------------------------------------------------------------- /server/cmd/fileserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "fmt" 6 | "io/fs" 7 | "os" 8 | 9 | "github.com/honmaple/maple-file/server/internal/app" 10 | "github.com/honmaple/maple-file/server/internal/app/config" 11 | "github.com/urfave/cli/v2" 12 | 13 | _ "github.com/honmaple/maple-file/server/internal/api" 14 | ) 15 | 16 | var ( 17 | //go:embed web 18 | webFS embed.FS 19 | defaultApp = app.New() 20 | ) 21 | 22 | func before(clx *cli.Context) error { 23 | if err := defaultApp.Config.LoadFromFile(clx.String("config")); err != nil { 24 | return err 25 | } 26 | return defaultApp.Init() 27 | } 28 | 29 | func action(clx *cli.Context) error { 30 | if addr := clx.String("addr"); addr != "" { 31 | defaultApp.Config.Set(config.ServerAddr, addr) 32 | } 33 | if debug := clx.Bool("debug"); debug { 34 | defaultApp.Config.Set("server.mode", "dev") 35 | } 36 | 37 | var root fs.FS 38 | 39 | if ok := clx.Bool("fs"); ok { 40 | rootFS, err := fs.Sub(webFS, "web") 41 | if err != nil { 42 | return err 43 | } 44 | root = rootFS 45 | } 46 | 47 | listener, err := app.Listen(defaultApp.Config.GetString(config.ServerAddr)) 48 | if err != nil { 49 | return err 50 | } 51 | return defaultApp.Start(listener, root) 52 | } 53 | 54 | func main() { 55 | app := &cli.App{ 56 | Name: app.PROCESS, 57 | Usage: app.DESCRIPTION, 58 | Version: app.VERSION, 59 | Flags: []cli.Flag{ 60 | &cli.BoolFlag{ 61 | Name: "debug", 62 | Aliases: []string{"D"}, 63 | Usage: "debug mode", 64 | }, 65 | &cli.BoolFlag{ 66 | Name: "fs", 67 | Aliases: []string{"F"}, 68 | Usage: "serve static file", 69 | }, 70 | &cli.StringFlag{ 71 | Name: "addr", 72 | Aliases: []string{"a"}, 73 | Usage: "listen `ADDR`", 74 | Value: "127.0.0.1:8000", 75 | }, 76 | &cli.PathFlag{ 77 | Name: "config", 78 | Aliases: []string{"c"}, 79 | Usage: "load config from `FILE`", 80 | Value: "config.yaml", 81 | }, 82 | }, 83 | Before: before, 84 | Action: action, 85 | } 86 | if err := app.Run(os.Args); err != nil { 87 | fmt.Println(err.Error()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /server/cmd/mobile/main.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/honmaple/maple-file/server/internal/app" 5 | 6 | _ "github.com/honmaple/maple-file/server/internal/api" 7 | _ "golang.org/x/mobile/bind" 8 | ) 9 | 10 | var ( 11 | server *app.Server 12 | ) 13 | 14 | func Start(cfg string) (string, error) { 15 | if server != nil { 16 | return server.Addr(), nil 17 | } 18 | 19 | var err error 20 | 21 | server, err = app.NewServer(cfg) 22 | if err != nil { 23 | return "", err 24 | } 25 | go server.Start() 26 | 27 | return server.Addr(), nil 28 | } 29 | 30 | func Stop() { 31 | if server != nil { 32 | server.Shutdown() 33 | } 34 | server = nil 35 | } 36 | -------------------------------------------------------------------------------- /server/internal/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | _ "github.com/honmaple/maple-file/server/internal/api/file" 5 | _ "github.com/honmaple/maple-file/server/internal/api/setting" 6 | _ "github.com/honmaple/maple-file/server/internal/api/task" 7 | ) 8 | -------------------------------------------------------------------------------- /server/internal/api/file/fs/download.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/honmaple/maple-file/server/pkg/runner" 9 | "github.com/honmaple/maple-file/server/pkg/util" 10 | ) 11 | 12 | type DownloadTask struct { 13 | Path string `json:"path"` 14 | Writer io.Writer `json:"file"` 15 | } 16 | 17 | func (opt *DownloadTask) String() string { 18 | return fmt.Sprintf("下载 [%s]", opt.Path) 19 | } 20 | 21 | func (opt *DownloadTask) Execute(task runner.Task, fs FS) error { 22 | info, err := fs.Get(task.Context(), opt.Path) 23 | if err != nil { 24 | return err 25 | } 26 | if info.IsDir() { 27 | // TODO 打包后下载 28 | return errors.New("can't download dir") 29 | } 30 | 31 | src, err := fs.Open(opt.Path) 32 | if err != nil { 33 | return err 34 | } 35 | defer src.Close() 36 | 37 | fsize := util.PrettyByteSize(int(info.Size())) 38 | 39 | task.SetProgressState(fmt.Sprintf("0/%s", fsize)) 40 | 41 | _, err = util.Copy(task.Context(), opt.Writer, src, func(progress int64) { 42 | if size := info.Size(); size > 0 { 43 | task.SetProgress(float64(progress) / float64(size)) 44 | } 45 | task.SetProgressState(fmt.Sprintf("%s/%s", util.PrettyByteSize(int(progress)), fsize)) 46 | }) 47 | return err 48 | } 49 | -------------------------------------------------------------------------------- /server/internal/api/file/fs/remove.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/honmaple/maple-file/server/pkg/runner" 7 | ) 8 | 9 | type RemoveTaskOption struct { 10 | Path string `json:"path"` 11 | } 12 | 13 | func (opt *RemoveTaskOption) String() string { 14 | return fmt.Sprintf("删除 [%s]", opt.Path) 15 | } 16 | 17 | func (opt *RemoveTaskOption) Execute(task runner.Task, fs FS) error { 18 | return fs.Remove(task.Context(), opt.Path) 19 | } 20 | -------------------------------------------------------------------------------- /server/internal/api/file/fs/upload.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | filepath "path" 7 | 8 | "github.com/honmaple/maple-file/server/pkg/runner" 9 | "github.com/honmaple/maple-file/server/pkg/util" 10 | ) 11 | 12 | type UploadTask struct { 13 | Path string `json:"path"` 14 | Size int64 `json:"size"` 15 | Filename string `json:"filename"` 16 | Reader io.Reader `json:"reader"` 17 | } 18 | 19 | func (opt *UploadTask) String() string { 20 | return fmt.Sprintf("上传 [%s] 到 [%s]", opt.Filename, opt.Path) 21 | } 22 | 23 | func (opt *UploadTask) Execute(task runner.Task, fs FS) error { 24 | dst, err := fs.Create(filepath.Join(opt.Path, opt.Filename)) 25 | if err != nil { 26 | return err 27 | } 28 | defer dst.Close() 29 | 30 | fsize := util.PrettyByteSize(int(opt.Size)) 31 | 32 | task.SetProgressState(fmt.Sprintf("0/%s", fsize)) 33 | 34 | _, err = util.Copy(task.Context(), dst, opt.Reader, func(progress int64) { 35 | if size := opt.Size; size > 0 { 36 | task.SetProgress(float64(progress) / float64(size)) 37 | } 38 | task.SetProgressState(fmt.Sprintf("%s/%s", util.PrettyByteSize(int(progress)), fsize)) 39 | }) 40 | return err 41 | } 42 | -------------------------------------------------------------------------------- /server/internal/api/file/init.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "github.com/honmaple/maple-file/server/internal/api/file/service" 5 | "github.com/honmaple/maple-file/server/internal/app" 6 | 7 | pb "github.com/honmaple/maple-file/server/internal/proto/api/file" 8 | 9 | _ "github.com/honmaple/maple-file/server/pkg/driver/alist" 10 | _ "github.com/honmaple/maple-file/server/pkg/driver/ftp" 11 | _ "github.com/honmaple/maple-file/server/pkg/driver/github" 12 | _ "github.com/honmaple/maple-file/server/pkg/driver/local" 13 | _ "github.com/honmaple/maple-file/server/pkg/driver/mirror" 14 | _ "github.com/honmaple/maple-file/server/pkg/driver/s3" 15 | _ "github.com/honmaple/maple-file/server/pkg/driver/sftp" 16 | _ "github.com/honmaple/maple-file/server/pkg/driver/smb" 17 | _ "github.com/honmaple/maple-file/server/pkg/driver/upyun" 18 | _ "github.com/honmaple/maple-file/server/pkg/driver/webdav" 19 | ) 20 | 21 | func init() { 22 | app.Register("file", func(app *app.App) (app.Service, error) { 23 | if err := app.DB.AutoMigrate( 24 | new(pb.Repo), 25 | ); err != nil { 26 | return nil, err 27 | } 28 | return service.New(app), nil 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /server/internal/api/file/service/util.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | filepath "path" 5 | "time" 6 | 7 | pb "github.com/honmaple/maple-file/server/internal/proto/api/file" 8 | "github.com/honmaple/maple-file/server/pkg/driver" 9 | "github.com/honmaple/maple-file/server/pkg/util" 10 | "google.golang.org/protobuf/types/known/timestamppb" 11 | ) 12 | 13 | type readFunc func() (*pb.FileRequest, error) 14 | 15 | func (rf readFunc) Read(p []byte) (n int, err error) { 16 | req, err := rf() 17 | if err != nil { 18 | return 0, err 19 | } 20 | n = copy(p, req.GetChunk()) 21 | return n, nil 22 | } 23 | 24 | type chunkFunc func([]byte) error 25 | 26 | func (cf chunkFunc) Write(p []byte) (n int, err error) { 27 | if err := cf(p); err != nil { 28 | return 0, err 29 | } 30 | return len(p), nil 31 | } 32 | 33 | func renameFile(format, filename string) string { 34 | if format == "" { 35 | return filename 36 | } 37 | ext := filepath.Ext(filename) 38 | name := filename[:len(filename)-len(ext)] 39 | 40 | now := time.Now() 41 | return util.StrReplace(format, map[string]string{ 42 | "{extension}": ext, 43 | "{filename}": name, 44 | "{time:year}": now.Format("2006"), 45 | "{time:month}": now.Format("01"), 46 | "{time:day}": now.Format("02"), 47 | "{time:hour}": now.Format("15"), 48 | "{time:minute}": now.Format("04"), 49 | }) 50 | } 51 | 52 | func infoToFile(m driver.File) *pb.File { 53 | file := &pb.File{ 54 | Path: m.Path(), 55 | Name: m.Name(), 56 | Size: int32(m.Size()), 57 | Type: m.Type(), 58 | CreatedAt: timestamppb.New(m.ModTime()), 59 | UpdatedAt: timestamppb.New(m.ModTime()), 60 | } 61 | return file 62 | } 63 | -------------------------------------------------------------------------------- /server/internal/api/setting/init.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "github.com/honmaple/maple-file/server/internal/api/setting/service" 5 | "github.com/honmaple/maple-file/server/internal/app" 6 | pb "github.com/honmaple/maple-file/server/internal/proto/api/setting" 7 | ) 8 | 9 | func init() { 10 | app.Register("setting", func(app *app.App) (app.Service, error) { 11 | if err := app.DB.AutoMigrate( 12 | new(pb.Setting), 13 | ); err != nil { 14 | return nil, err 15 | } 16 | return service.New(app), nil 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /server/internal/api/setting/service/base.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 7 | "github.com/labstack/echo/v4" 8 | "google.golang.org/grpc" 9 | 10 | "github.com/honmaple/maple-file/server/internal/app" 11 | pb "github.com/honmaple/maple-file/server/internal/proto/api/setting" 12 | ) 13 | 14 | type Service struct { 15 | pb.UnimplementedSystemServiceServer 16 | app *app.App 17 | } 18 | 19 | func (srv *Service) Register(grpc *grpc.Server) { 20 | pb.RegisterSystemServiceServer(grpc, srv) 21 | } 22 | 23 | func (srv *Service) RegisterGateway(ctx context.Context, mux *runtime.ServeMux, e *echo.Echo) { 24 | pb.RegisterSystemServiceHandlerServer(ctx, mux, srv) 25 | } 26 | 27 | func New(app *app.App) *Service { 28 | // settings := make([]*pb.Setting, 0) 29 | // db.Model(pb.Setting{}).Find(&settings) 30 | // for _, set := range settings { 31 | // value := make(map[string]any) 32 | // if err := json.Unmarshal([]byte(set.GetValue()), &value); err != nil { 33 | // continue 34 | // } 35 | // conf.Set(set.GetKey(), value) 36 | // } 37 | return &Service{app: app} 38 | } 39 | -------------------------------------------------------------------------------- /server/internal/api/setting/service/info.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "runtime" 6 | 7 | "github.com/honmaple/maple-file/server/internal/app" 8 | pb "github.com/honmaple/maple-file/server/internal/proto/api/setting" 9 | ) 10 | 11 | func (srv *Service) Info(ctx context.Context, in *pb.InfoRequest) (*pb.InfoResponse, error) { 12 | result := &pb.Info{ 13 | Os: runtime.GOOS, 14 | Arch: runtime.GOARCH, 15 | Runtime: runtime.Version(), 16 | Version: app.VERSION, 17 | Description: app.DESCRIPTION, 18 | } 19 | return &pb.InfoResponse{Result: result}, nil 20 | } 21 | -------------------------------------------------------------------------------- /server/internal/api/setting/service/setting.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strings" 7 | 8 | pb "github.com/honmaple/maple-file/server/internal/proto/api/setting" 9 | ) 10 | 11 | func (srv *Service) GetSetting(ctx context.Context, req *pb.GetSettingRequest) (*pb.GetSettingResponse, error) { 12 | key := req.GetKey() 13 | 14 | if !strings.HasPrefix(key, "app.") { 15 | return nil, errors.New("未知的设置") 16 | } 17 | 18 | result := new(pb.Setting) 19 | 20 | q := srv.app.DB.WithContext(ctx).First(&result, "key = ?", key) 21 | if err := q.Error; err != nil { 22 | return nil, err 23 | } 24 | return &pb.GetSettingResponse{Result: result}, nil 25 | } 26 | 27 | func (srv *Service) UpdateSetting(ctx context.Context, req *pb.UpdateSettingRequest) (*pb.UpdateSettingResponse, error) { 28 | key := req.GetKey() 29 | 30 | if !strings.HasPrefix(key, "app.") { 31 | return nil, errors.New("未知的设置") 32 | } 33 | 34 | result := new(pb.Setting) 35 | 36 | q := srv.app.DB.WithContext(ctx).First(&result, "key = ?", key) 37 | if err := q.Error; err != nil { 38 | if q.RowsAffected > 0 { 39 | return nil, err 40 | } 41 | pre := &pb.Setting{ 42 | Key: req.GetKey(), 43 | Value: req.GetValue(), 44 | } 45 | 46 | if err := srv.app.DB.WithContext(ctx).Create(pre).Error; err != nil { 47 | return nil, err 48 | } 49 | result = pre 50 | } else { 51 | if value := req.GetValue(); value != result.GetValue() { 52 | result := srv.app.DB.WithContext(ctx).Model(&result).Update("value", value) 53 | if err := result.Error; err != nil { 54 | return nil, err 55 | } 56 | } 57 | } 58 | return &pb.UpdateSettingResponse{Result: result}, nil 59 | } 60 | 61 | func (srv *Service) ResetSetting(ctx context.Context, req *pb.ResetSettingRequest) (*pb.ResetSettingResponse, error) { 62 | return &pb.ResetSettingResponse{}, nil 63 | } 64 | -------------------------------------------------------------------------------- /server/internal/api/task/init.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "github.com/honmaple/maple-file/server/internal/api/task/service" 5 | "github.com/honmaple/maple-file/server/internal/app" 6 | pb "github.com/honmaple/maple-file/server/internal/proto/api/task" 7 | ) 8 | 9 | func init() { 10 | app.Register("task", func(app *app.App) (app.Service, error) { 11 | if err := app.DB.AutoMigrate( 12 | new(pb.PersistTask), 13 | ); err != nil { 14 | return nil, err 15 | } 16 | return service.New(app), nil 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /server/internal/api/task/service/base.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 7 | "github.com/labstack/echo/v4" 8 | "google.golang.org/grpc" 9 | 10 | "github.com/honmaple/maple-file/server/internal/app" 11 | 12 | pb "github.com/honmaple/maple-file/server/internal/proto/api/task" 13 | ) 14 | 15 | type Service struct { 16 | pb.UnimplementedTaskServiceServer 17 | app *app.App 18 | } 19 | 20 | func (srv *Service) Register(grpc *grpc.Server) { 21 | pb.RegisterTaskServiceServer(grpc, srv) 22 | } 23 | 24 | func (srv *Service) RegisterGateway(ctx context.Context, mux *runtime.ServeMux, e *echo.Echo) { 25 | pb.RegisterTaskServiceHandlerServer(ctx, mux, srv) 26 | } 27 | 28 | func New(app *app.App) *Service { 29 | srv := &Service{ 30 | app: app, 31 | } 32 | return srv 33 | } 34 | -------------------------------------------------------------------------------- /server/internal/app/config/const.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | ServerAddr = "server.addr" 5 | DatabaseDSN = "database.dsn" 6 | DatabaseEcho = "database.echo" 7 | 8 | LoggerLevel = "logger.level" 9 | LoggerOutput = "logger.output" 10 | LoggerTimestamp = "logger.timestamp" 11 | LoggerFile = "logger.file" 12 | LoggerFileMaxAge = "logger.file_max_age" 13 | LoggerFileMaxSize = "logger.file_max_size" 14 | LoggerFileMaxBackup = "logger.file_max_backup" 15 | ) 16 | 17 | var defaultConfig = map[string]any{ 18 | LoggerLevel: "debug", 19 | LoggerOutput: "stderr", 20 | LoggerTimestamp: true, 21 | LoggerFile: "server.log", 22 | LoggerFileMaxAge: 30, 23 | LoggerFileMaxSize: 100, 24 | LoggerFileMaxBackup: 3, 25 | 26 | ServerAddr: "unix://server.sock", 27 | DatabaseDSN: "sqlite://server.db", 28 | } 29 | -------------------------------------------------------------------------------- /server/internal/app/config/logger.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "strings" 7 | 8 | "github.com/sirupsen/logrus" 9 | "gopkg.in/natefinch/lumberjack.v2" 10 | ) 11 | 12 | var levels = map[string]logrus.Level{ 13 | "debug": logrus.DebugLevel, 14 | "info": logrus.InfoLevel, 15 | "warn": logrus.WarnLevel, 16 | "error": logrus.ErrorLevel, 17 | } 18 | 19 | type Logger struct { 20 | *logrus.Logger 21 | lu *lumberjack.Logger 22 | } 23 | 24 | func (log *Logger) Close() error { 25 | if log.lu == nil { 26 | return nil 27 | } 28 | return log.lu.Close() 29 | } 30 | 31 | func NewLogger(conf *Config) *Logger { 32 | log := &Logger{} 33 | 34 | level, ok := levels[conf.GetString(LoggerLevel)] 35 | if !ok { 36 | level = logrus.InfoLevel 37 | } 38 | 39 | outs := make(map[string]bool) 40 | for _, i := range strings.Split(conf.GetString(LoggerOutput), "|") { 41 | outs[strings.TrimSpace(i)] = true 42 | } 43 | 44 | outWriters := make([]io.Writer, 0) 45 | for k := range outs { 46 | switch k { 47 | case "file": 48 | out := &lumberjack.Logger{ 49 | Filename: conf.GetString(LoggerFile), 50 | MaxAge: conf.GetInt(LoggerFileMaxAge), 51 | MaxSize: conf.GetInt(LoggerFileMaxSize), 52 | MaxBackups: conf.GetInt(LoggerFileMaxBackup), 53 | } 54 | log.lu = out 55 | 56 | outWriters = append(outWriters, out) 57 | case "stdout": 58 | outWriters = append(outWriters, os.Stdout) 59 | case "stderr": 60 | outWriters = append(outWriters, os.Stderr) 61 | } 62 | } 63 | if len(outWriters) == 0 { 64 | outWriters = append(outWriters, os.Stdout) 65 | } 66 | timestamp := conf.GetBool(LoggerTimestamp) 67 | 68 | log.Logger = &logrus.Logger{ 69 | Out: io.MultiWriter(outWriters...), 70 | Formatter: &logrus.TextFormatter{ 71 | DisableTimestamp: !timestamp, 72 | FullTimestamp: timestamp, 73 | }, 74 | Level: level, 75 | } 76 | return log 77 | } 78 | -------------------------------------------------------------------------------- /server/internal/app/grpc.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "os" 7 | "strings" 8 | 9 | "google.golang.org/grpc" 10 | // "github.com/honmaple/maple-file/server/internal/app/config" 11 | ) 12 | 13 | type Grpc struct { 14 | app *App 15 | opts []grpc.ServerOption 16 | } 17 | 18 | func unaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 19 | // authentication (token verification) 20 | // md, ok := metadata.FromIncomingContext(ctx) 21 | // if !ok { 22 | // return nil, errMissingMetadata 23 | // } 24 | // if !valid(md["authorization"]) { 25 | // return nil, errInvalidToken 26 | // } 27 | return handler(ctx, req) 28 | } 29 | 30 | func streamInterceptor(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 31 | // authentication (token verification) 32 | // md, ok := metadata.FromIncomingContext(ss.Context()) 33 | // if !ok { 34 | // return errMissingMetadata 35 | // } 36 | // if !valid(md["authorization"]) { 37 | // return errInvalidToken 38 | // } 39 | 40 | // err := handler(srv, newWrappedStream(ss)) 41 | // if err != nil { 42 | // logger("RPC failed with error: %v", err) 43 | // } 44 | // return err 45 | return handler(srv, stream) 46 | } 47 | 48 | func (s *Grpc) listen(addr string) (net.Listener, error) { 49 | switch { 50 | case strings.HasPrefix(addr, "unix://"): 51 | sock := addr[7:] 52 | if _, err := os.Stat(sock); err == nil || os.IsExist(err) { 53 | if err := os.Remove(sock); err != nil { 54 | return nil, err 55 | } 56 | } 57 | return net.Listen("unix", sock) 58 | case strings.HasPrefix(addr, "tcp://"): 59 | return net.Listen("tcp", addr[6:]) 60 | default: 61 | return net.Listen("tcp", addr) 62 | } 63 | } 64 | 65 | func (s *Grpc) Use(opts ...grpc.ServerOption) { 66 | s.opts = append(s.opts, opts...) 67 | } 68 | -------------------------------------------------------------------------------- /server/internal/app/serializer/time.go: -------------------------------------------------------------------------------- 1 | package serializer 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "time" 8 | 9 | "google.golang.org/protobuf/types/known/timestamppb" 10 | "gorm.io/gorm/schema" 11 | ) 12 | 13 | type ProtobufTimestamp struct{} 14 | 15 | func (ProtobufTimestamp) Scan(ctx context.Context, field *schema.Field, dst reflect.Value, dbValue interface{}) (err error) { 16 | // fmt.Println(reflect.TypeOf(dbValue).Name()) 17 | if dbValue != nil { 18 | var t *timestamppb.Timestamp 19 | 20 | switch v := dbValue.(type) { 21 | case []byte: 22 | tm, err := time.Parse("2006-01-02 15:04:05+00:00", string(v)) 23 | if err != nil { 24 | return err 25 | } 26 | t = timestamppb.New(tm) 27 | case string: 28 | tm, err := time.Parse("2006-01-02 15:04:05+00:00", v) 29 | if err != nil { 30 | return err 31 | } 32 | t = timestamppb.New(tm) 33 | case int64: 34 | t = timestamppb.New(time.Unix(v, 0)) 35 | case time.Time: 36 | t = timestamppb.New(v) 37 | default: 38 | return fmt.Errorf("failed to scan TIMESTAMP value: %#v", dbValue) 39 | } 40 | field.Set(ctx, dst, t) 41 | } 42 | return 43 | } 44 | 45 | func (ProtobufTimestamp) Value(_ context.Context, _ *schema.Field, _ reflect.Value, fieldValue interface{}) (interface{}, error) { 46 | rv := reflect.ValueOf(fieldValue) 47 | if rv.IsNil() || rv.IsZero() { 48 | return time.Now(), nil 49 | } 50 | 51 | if t, ok := fieldValue.(time.Time); ok { 52 | return t, nil 53 | } 54 | 55 | if t, ok := fieldValue.(*timestamppb.Timestamp); ok { 56 | if err := t.CheckValid(); err != nil { 57 | return nil, err 58 | } 59 | return t.AsTime(), nil 60 | } 61 | return nil, fmt.Errorf("invalid field type %#v for TimestampSerializer", fieldValue) 62 | } 63 | -------------------------------------------------------------------------------- /server/internal/app/service.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 7 | "github.com/labstack/echo/v4" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | type Service interface { 12 | Register(*grpc.Server) 13 | RegisterGateway(context.Context, *runtime.ServeMux, *echo.Echo) 14 | } 15 | 16 | type Creator func(*App) (Service, error) 17 | 18 | var creators map[string]Creator 19 | 20 | func Register(name string, creator Creator) { 21 | creators[name] = creator 22 | } 23 | 24 | func init() { 25 | creators = make(map[string]Creator) 26 | } 27 | -------------------------------------------------------------------------------- /server/internal/proto/api/file/file.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "api/file/file.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/api/file/repo.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "api/file/repo.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/api/setting/info.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "api/setting/info.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/api/setting/setting.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "api/setting/setting.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/api/task/persist.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "api/task/persist.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/api/task/task.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "api/task/task.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/google/api/annotations.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "google/api/annotations.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/google/api/http.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "google/api/http.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/google/protobuf/descriptor.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "google/protobuf/descriptor.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/internal/proto/google/protobuf/timestamp.swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "google/protobuf/timestamp.proto", 5 | "version": "version not set" 6 | }, 7 | "consumes": [ 8 | "application/json" 9 | ], 10 | "produces": [ 11 | "application/json" 12 | ], 13 | "paths": {}, 14 | "definitions": { 15 | "protobufAny": { 16 | "type": "object", 17 | "properties": { 18 | "@type": { 19 | "type": "string" 20 | } 21 | }, 22 | "additionalProperties": {} 23 | }, 24 | "rpcStatus": { 25 | "type": "object", 26 | "properties": { 27 | "code": { 28 | "type": "integer", 29 | "format": "int32" 30 | }, 31 | "message": { 32 | "type": "string" 33 | }, 34 | "details": { 35 | "type": "array", 36 | "items": { 37 | "type": "object", 38 | "$ref": "#/definitions/protobufAny" 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /server/pkg/driver/alist/util.go: -------------------------------------------------------------------------------- 1 | package alist 2 | 3 | import ( 4 | "io/fs" 5 | "time" 6 | 7 | "github.com/tidwall/gjson" 8 | ) 9 | 10 | type fileinfo struct { 11 | info gjson.Result 12 | } 13 | 14 | func (d *fileinfo) Name() string { return d.info.Get("name").String() } 15 | func (d *fileinfo) Size() int64 { return d.info.Get("size").Int() } 16 | func (d *fileinfo) Mode() fs.FileMode { return 0 } 17 | func (d *fileinfo) ModTime() time.Time { return d.info.Get("modified").Time() } 18 | func (d *fileinfo) IsDir() bool { return d.info.Get("is_dir").Bool() } 19 | func (d *fileinfo) Sys() any { return nil } 20 | -------------------------------------------------------------------------------- /server/pkg/driver/base/base.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | filepath "path" 5 | "strings" 6 | 7 | "github.com/honmaple/maple-file/server/pkg/driver" 8 | "github.com/honmaple/maple-file/server/pkg/util" 9 | ) 10 | 11 | type Option struct { 12 | RootPath string `json:"root_path" validate:"omitempty,startswith=/"` 13 | HiddenFiles []string `json:"hidden_files"` 14 | Encrypt bool `json:"encrypt"` 15 | EncryptOption EncryptOption `json:"encrypt_option"` 16 | Compress bool `json:"compress"` 17 | CompressOption CompressOption `json:"compress_option"` 18 | Recycle bool `json:"recycle"` 19 | RecycleOption RecycleOption `json:"recycle_option"` 20 | Cache bool `json:"cache"` 21 | CacheOption CacheOption `json:"cache_option"` 22 | } 23 | 24 | func (opt *Option) getActualPath(path string) string { 25 | path = util.CleanPath(path) 26 | if opt.RootPath == "" { 27 | return path 28 | } 29 | return filepath.Join(opt.RootPath, path) 30 | } 31 | 32 | func (opt *Option) getActualFile(file driver.File) (driver.File, bool) { 33 | if opt.RootPath != "" { 34 | file = driver.NewFile(strings.TrimPrefix(file.Path(), opt.RootPath), file) 35 | } 36 | if len(opt.HiddenFiles) > 0 && Included(file, opt.HiddenFiles) { 37 | return file, false 38 | } 39 | return file, true 40 | } 41 | 42 | func (opt *Option) NewFS(fs driver.FS) (driver.FS, error) { 43 | return New(fs, opt) 44 | } 45 | 46 | func New(fs driver.FS, opt *Option) (driver.FS, error) { 47 | opts := make([]WrapOption, 0) 48 | if opt.Encrypt { 49 | opts = append(opts, &opt.EncryptOption) 50 | } 51 | if opt.Compress { 52 | opts = append(opts, &opt.CompressOption) 53 | } 54 | if opt.Recycle { 55 | opts = append(opts, &opt.RecycleOption) 56 | } 57 | if opt.Cache { 58 | opts = append(opts, &opt.CacheOption) 59 | } 60 | opts = append(opts, &HookOption{ 61 | PathFn: opt.getActualPath, 62 | FileFn: opt.getActualFile, 63 | }) 64 | return WrapFS(fs, opts...) 65 | } 66 | -------------------------------------------------------------------------------- /server/pkg/driver/base/cache.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/hashicorp/golang-lru/v2/expirable" 8 | "github.com/honmaple/maple-file/server/pkg/driver" 9 | ) 10 | 11 | type CacheOption struct { 12 | ExpireTime time.Duration `json:"expire_time"` 13 | } 14 | 15 | func (opt *CacheOption) NewFS(fs driver.FS) (driver.FS, error) { 16 | return CacheFS(fs, opt) 17 | } 18 | 19 | type cacheFS struct { 20 | driver.FS 21 | opt *CacheOption 22 | cache *expirable.LRU[string, []driver.File] 23 | } 24 | 25 | var _ driver.FS = (*cacheFS)(nil) 26 | 27 | func (d *cacheFS) List(ctx context.Context, path string, metas ...driver.Meta) ([]driver.File, error) { 28 | files, ok := d.cache.Get(path) 29 | if ok { 30 | return files, nil 31 | } 32 | 33 | files, err := d.FS.List(ctx, path, metas...) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | d.cache.Add(path, files) 39 | return files, nil 40 | } 41 | 42 | func CacheFS(fs driver.FS, opt *CacheOption) (driver.FS, error) { 43 | if opt.ExpireTime <= 0 { 44 | opt.ExpireTime = 60 45 | } 46 | 47 | return &cacheFS{ 48 | FS: fs, 49 | opt: opt, 50 | cache: expirable.NewLRU[string, []driver.File](0, nil, opt.ExpireTime*time.Second), 51 | }, nil 52 | } 53 | -------------------------------------------------------------------------------- /server/pkg/driver/base/compress.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | 7 | "github.com/honmaple/maple-file/server/pkg/driver" 8 | ) 9 | 10 | type CompressOption struct { 11 | Level int `json:"level"` 12 | } 13 | 14 | func (opt *CompressOption) NewFS(fs driver.FS) (driver.FS, error) { 15 | return CompressFS(fs, opt) 16 | } 17 | 18 | type compressFS struct { 19 | driver.FS 20 | opt *CompressOption 21 | } 22 | 23 | var _ driver.FS = (*compressFS)(nil) 24 | 25 | func (d *compressFS) compress(out io.Writer) (*gzip.Writer, error) { 26 | level := d.opt.Level 27 | if level == 0 { 28 | level = gzip.BestCompression 29 | } 30 | return gzip.NewWriterLevel(out, level) 31 | } 32 | 33 | func (d *compressFS) uncompress(in io.Reader) (*gzip.Reader, error) { 34 | return gzip.NewReader(in) 35 | } 36 | 37 | func (d *compressFS) Open(path string) (driver.FileReader, error) { 38 | r, err := d.FS.Open(path) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | nr, err := d.uncompress(r) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return &WrapReader{r, nr}, nil 48 | } 49 | 50 | func (d *compressFS) Create(path string) (driver.FileWriter, error) { 51 | w, err := d.FS.Create(path) 52 | if err != nil { 53 | return nil, err 54 | } 55 | nw, err := d.compress(w) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return &WrapWriter{w, nw}, nil 60 | } 61 | 62 | func CompressFS(fs driver.FS, opt *CompressOption) (driver.FS, error) { 63 | if err := driver.VerifyOption(opt); err != nil { 64 | return nil, err 65 | } 66 | return &compressFS{FS: fs, opt: opt}, nil 67 | } 68 | -------------------------------------------------------------------------------- /server/pkg/driver/base/util.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | filepath "path" 5 | "strings" 6 | 7 | "github.com/honmaple/maple-file/server/pkg/driver" 8 | ) 9 | 10 | func Included(file driver.File, types []string) bool { 11 | if len(types) == 0 { 12 | return false 13 | } 14 | ext := filepath.Ext(file.Name()) 15 | for _, typ := range types { 16 | exclude := false 17 | if strings.HasPrefix(typ, "-") { 18 | typ = typ[1:] 19 | exclude = true 20 | } 21 | if ext == typ { 22 | return !exclude 23 | } 24 | 25 | name := file.Name() 26 | if strings.Contains(typ, "/") { 27 | name = strings.TrimPrefix(filepath.Join(file.Path(), name), "/") 28 | } 29 | if m, _ := filepath.Match(typ, name); m { 30 | return !exclude 31 | } 32 | } 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /server/pkg/driver/base/wrap.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/honmaple/maple-file/server/pkg/driver" 7 | ) 8 | 9 | type WrapOption interface { 10 | NewFS(driver.FS) (driver.FS, error) 11 | } 12 | 13 | func WrapFS(fs driver.FS, opts ...WrapOption) (newFS driver.FS, err error) { 14 | newFS = fs 15 | for _, opt := range opts { 16 | newFS, err = opt.NewFS(newFS) 17 | if err != nil { 18 | return nil, err 19 | } 20 | } 21 | return 22 | } 23 | 24 | type WrapReader struct { 25 | driver.FileReader 26 | r io.Reader 27 | } 28 | 29 | func (r *WrapReader) Read(p []byte) (n int, err error) { return r.r.Read(p) } 30 | 31 | type WrapWriter struct { 32 | driver.FileWriter 33 | w io.WriteCloser 34 | } 35 | 36 | func (w *WrapWriter) Write(p []byte) (n int, err error) { return w.w.Write(p) } 37 | func (w *WrapWriter) Close() error { 38 | w.FileWriter.Close() 39 | w.w.Close() 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /server/pkg/driver/errors.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/honmaple/maple-file/server/pkg/util" 7 | ) 8 | 9 | var ( 10 | ErrOption = errors.New("driver's option err") 11 | ErrNotSupport = errors.New("operate not support") 12 | ErrSrcNotExist = errors.New("src is not exists") 13 | ErrDstNotExist = errors.New("dst is not exists") 14 | ErrDstIsExist = errors.New("dst already exists") 15 | ErrDriverNotExist = errors.New("driver is not exists") 16 | ErrOpenDirectory = errors.New("can't open a directory") 17 | 18 | VerifyOption = util.VerifyOption 19 | ) 20 | -------------------------------------------------------------------------------- /server/pkg/driver/ftp/util.go: -------------------------------------------------------------------------------- 1 | package ftp 2 | 3 | import ( 4 | "io/fs" 5 | "time" 6 | 7 | "github.com/jlaffaye/ftp" 8 | ) 9 | 10 | type fileinfo struct { 11 | info *ftp.Entry 12 | } 13 | 14 | func (d *fileinfo) Name() string { return d.info.Name } 15 | func (d *fileinfo) Size() int64 { return int64(d.info.Size) } 16 | func (d *fileinfo) Mode() fs.FileMode { return 0 } 17 | func (d *fileinfo) ModTime() time.Time { return d.info.Time } 18 | func (d *fileinfo) IsDir() bool { return d.info.Type == ftp.EntryTypeFolder } 19 | func (d *fileinfo) Sys() any { return nil } 20 | -------------------------------------------------------------------------------- /server/pkg/driver/s3/util.go: -------------------------------------------------------------------------------- 1 | package s3 2 | 3 | import ( 4 | "io/fs" 5 | filepath "path" 6 | "strings" 7 | "time" 8 | 9 | "github.com/aws/aws-sdk-go/service/s3" 10 | ) 11 | 12 | type headinfo struct { 13 | name string 14 | info *s3.HeadObjectOutput 15 | } 16 | 17 | func (d *headinfo) Name() string { return d.name } 18 | func (d *headinfo) Size() int64 { return *d.info.ContentLength } 19 | func (d *headinfo) Mode() fs.FileMode { return 0 } 20 | func (d *headinfo) ModTime() time.Time { return *d.info.LastModified } 21 | func (d *headinfo) IsDir() bool { return *d.info.ContentLength == 0 } 22 | func (d *headinfo) Sys() any { return nil } 23 | 24 | type fileinfo struct { 25 | info *s3.Object 26 | } 27 | 28 | func (d *fileinfo) Name() string { return filepath.Base(*d.info.Key) } 29 | func (d *fileinfo) Size() int64 { return *d.info.Size } 30 | func (d *fileinfo) Mode() fs.FileMode { return 0 } 31 | func (d *fileinfo) ModTime() time.Time { return *d.info.LastModified } 32 | func (d *fileinfo) IsDir() bool { return false } 33 | func (d *fileinfo) Sys() any { return nil } 34 | 35 | type dirinfo struct { 36 | info *s3.CommonPrefix 37 | } 38 | 39 | func (d *dirinfo) Name() string { return filepath.Base(strings.TrimSuffix(*d.info.Prefix, "/")) } 40 | func (d *dirinfo) Size() int64 { return 0 } 41 | func (d *dirinfo) Mode() fs.FileMode { return 0 } 42 | func (d *dirinfo) ModTime() time.Time { return time.Now() } 43 | func (d *dirinfo) IsDir() bool { return true } 44 | func (d *dirinfo) Sys() any { return nil } 45 | 46 | type emptyinfo struct { 47 | size int64 48 | name string 49 | mode fs.FileMode 50 | isDir bool 51 | modTime time.Time 52 | } 53 | 54 | func (f *emptyinfo) Name() string { return f.name } 55 | func (f *emptyinfo) Size() int64 { return f.size } 56 | func (f *emptyinfo) Mode() fs.FileMode { return f.mode } 57 | func (f *emptyinfo) IsDir() bool { return f.isDir } 58 | func (f *emptyinfo) ModTime() time.Time { return f.modTime } 59 | func (f *emptyinfo) Sys() any { return nil } 60 | -------------------------------------------------------------------------------- /server/pkg/driver/upyun/util.go: -------------------------------------------------------------------------------- 1 | package upyun 2 | 3 | import ( 4 | "io/fs" 5 | "time" 6 | 7 | "github.com/upyun/go-sdk/v3/upyun" 8 | ) 9 | 10 | type fileinfo struct { 11 | info *upyun.FileInfo 12 | } 13 | 14 | func (d *fileinfo) Name() string { return d.info.Name } 15 | func (d *fileinfo) Size() int64 { return d.info.Size } 16 | func (d *fileinfo) Mode() fs.FileMode { return 0 } 17 | func (d *fileinfo) ModTime() time.Time { return d.info.Time } 18 | func (d *fileinfo) IsDir() bool { return d.info.IsDir } 19 | func (d *fileinfo) Sys() any { return nil } 20 | -------------------------------------------------------------------------------- /server/pkg/driver/webdav/util.go: -------------------------------------------------------------------------------- 1 | package webdav 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | type fileinfo struct { 8 | os.FileInfo 9 | name string 10 | } 11 | 12 | func (d *fileinfo) Name() string { return d.name } 13 | 14 | -------------------------------------------------------------------------------- /server/pkg/runner/cache.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type Cache[K comparable, V any] interface { 8 | Load(K) (V, bool) 9 | Store(K, V) 10 | Delete(K) 11 | Reset() 12 | Range(func(K, V) bool) 13 | Len() int 14 | } 15 | 16 | type cache[K comparable, V any] struct { 17 | m *sync.Map 18 | } 19 | 20 | func (c *cache[K, V]) Load(key K) (value V, ok bool) { 21 | v, ok := c.m.Load(key) 22 | if !ok { 23 | return 24 | } 25 | value = v.(V) 26 | return 27 | } 28 | 29 | func (c *cache[K, V]) Store(key K, value V) { 30 | c.m.Store(key, value) 31 | } 32 | 33 | func (c *cache[K, V]) Delete(key K) { 34 | c.m.Delete(key) 35 | } 36 | 37 | func (c *cache[K, V]) Range(f func(K, V) bool) { 38 | c.m.Range(func(key, value any) bool { 39 | return f(key.(K), value.(V)) 40 | }) 41 | } 42 | 43 | func (c *cache[K, V]) Reset() { 44 | c.m.Range(func(key, value any) bool { 45 | c.m.Delete(key.(K)) 46 | return true 47 | }) 48 | } 49 | 50 | func (c *cache[K, V]) Len() int { 51 | len := 0 52 | c.m.Range(func(key, value any) bool { 53 | len++ 54 | return true 55 | }) 56 | return len 57 | } 58 | 59 | func NewCache[K comparable, V any]() Cache[K, V] { 60 | return &cache[K, V]{m: new(sync.Map)} 61 | } -------------------------------------------------------------------------------- /server/pkg/runner/task_logger.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | type Logger interface { 10 | Info(...interface{}) 11 | Infof(string, ...interface{}) 12 | Error(...interface{}) 13 | Errorf(string, ...interface{}) 14 | } 15 | 16 | func NewLogger(out io.Writer) Logger { 17 | return &logrus.Logger{ 18 | Out: out, 19 | Formatter: &logrus.TextFormatter{ 20 | DisableTimestamp: false, 21 | FullTimestamp: true, 22 | }, 23 | Level: logrus.InfoLevel, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/pkg/runner/task_option.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | 7 | "github.com/honmaple/maple-file/server/pkg/util" 8 | ) 9 | 10 | type ( 11 | Option interface { 12 | NewTask() Task 13 | } 14 | 15 | FuncOption interface { 16 | String() string 17 | Execute(Task) error 18 | } 19 | FuncOptionCreator func() FuncOption 20 | 21 | FuncOptionWithArg[T any] interface { 22 | String() string 23 | Execute(Task, T) error 24 | } 25 | ) 26 | 27 | type funcOptionWithArg[T any] struct { 28 | // `json:",inline"` 对于任意类型any无效,必须自定义解析 29 | FuncOptionWithArg[T] 30 | arg T 31 | } 32 | 33 | func (opt *funcOptionWithArg[T]) Execute(task Task) error { 34 | return opt.FuncOptionWithArg.Execute(task, opt.arg) 35 | } 36 | 37 | func (opt *funcOptionWithArg[T]) UnmarshalJSON(data []byte) error { 38 | return json.Unmarshal(data, opt.FuncOptionWithArg) 39 | } 40 | 41 | func (opt funcOptionWithArg[T]) MarshalJSON() ([]byte, error) { 42 | return json.Marshal(opt.FuncOptionWithArg) 43 | } 44 | 45 | func NewFuncOptionWithArg[T any](opt FuncOptionWithArg[T], arg T) *funcOptionWithArg[T] { 46 | return &funcOptionWithArg[T]{opt, arg} 47 | } 48 | 49 | var allFuncOptions map[string]FuncOptionCreator 50 | 51 | func NewFuncOption(typ string, option string) (FuncOption, error) { 52 | creator, ok := allFuncOptions[typ] 53 | if !ok { 54 | return nil, fmt.Errorf("The task type %s not found", typ) 55 | } 56 | opt := creator() 57 | if err := json.Unmarshal([]byte(option), opt); err != nil { 58 | return nil, err 59 | } 60 | return opt, nil 61 | } 62 | 63 | func Verify(typ string, option string) error { 64 | creator, ok := allFuncOptions[typ] 65 | if !ok { 66 | return fmt.Errorf("The task type %s not found", typ) 67 | } 68 | opt := creator() 69 | if err := json.Unmarshal([]byte(option), opt); err != nil { 70 | return err 71 | } 72 | return util.VerifyOption(opt) 73 | } 74 | 75 | func Register(typ string, creator FuncOptionCreator) { 76 | allFuncOptions[typ] = creator 77 | } 78 | 79 | func init() { 80 | allFuncOptions = make(map[string]FuncOptionCreator) 81 | } 82 | -------------------------------------------------------------------------------- /server/pkg/util/cache.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "iter" 5 | "sync" 6 | ) 7 | 8 | type Cache[K comparable, V any] interface { 9 | Load(K) (V, bool) 10 | Store(K, V) 11 | Delete(K) 12 | Reset() 13 | Range(func(K, V) bool) 14 | Len() int 15 | Iter() iter.Seq2[K, V] 16 | } 17 | 18 | type cache[K comparable, V any] struct { 19 | m *sync.Map 20 | } 21 | 22 | func (c *cache[K, V]) Load(key K) (value V, ok bool) { 23 | v, ok := c.m.Load(key) 24 | if !ok { 25 | return 26 | } 27 | value = v.(V) 28 | return 29 | } 30 | 31 | func (c *cache[K, V]) Store(key K, value V) { 32 | c.m.Store(key, value) 33 | } 34 | 35 | func (c *cache[K, V]) Delete(key K) { 36 | c.m.Delete(key) 37 | } 38 | 39 | func (c *cache[K, V]) Iter() iter.Seq2[K, V] { 40 | return func(yield func(key K, value V) bool) { 41 | c.Range(yield) 42 | } 43 | } 44 | 45 | func (c *cache[K, V]) Range(f func(K, V) bool) { 46 | c.m.Range(func(key, value any) bool { 47 | return f(key.(K), value.(V)) 48 | }) 49 | } 50 | 51 | func (c *cache[K, V]) Reset() { 52 | c.m.Range(func(key, value any) bool { 53 | c.m.Delete(key.(K)) 54 | return true 55 | }) 56 | } 57 | 58 | func (c *cache[K, V]) Len() int { 59 | len := 0 60 | c.m.Range(func(key, value any) bool { 61 | len++ 62 | return true 63 | }) 64 | return len 65 | } 66 | 67 | func NewCache[K comparable, V any]() Cache[K, V] { 68 | return &cache[K, V]{m: new(sync.Map)} 69 | } 70 | -------------------------------------------------------------------------------- /server/pkg/util/check.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | 7 | "github.com/go-playground/locales/en" 8 | "github.com/go-playground/locales/zh" 9 | "github.com/go-playground/universal-translator" 10 | "github.com/go-playground/validator/v10" 11 | zh_translations "github.com/go-playground/validator/v10/translations/zh" 12 | ) 13 | 14 | var ( 15 | uni *ut.UniversalTranslator 16 | validate *validator.Validate 17 | once sync.Once 18 | ) 19 | 20 | func VerifyOption(value interface{}) error { 21 | once.Do(func() { 22 | en := en.New() 23 | uni = ut.New(en, en, zh.New()) 24 | validate = validator.New(validator.WithRequiredStructEnabled()) 25 | 26 | trans, _ := uni.GetTranslator("zh") 27 | zh_translations.RegisterDefaultTranslations(validate, trans) 28 | }) 29 | 30 | trans, _ := uni.GetTranslator("zh") 31 | 32 | if err := validate.Struct(value); err != nil { 33 | if e, ok := err.(validator.ValidationErrors); ok && len(e) > 0 { 34 | return errors.New(e[0].Translate(trans)) 35 | } 36 | return err 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /server/pkg/util/path.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | filepath "path" 5 | "strings" 6 | ) 7 | 8 | func CleanPath(path string) string { 9 | // path = filepath.FromSlash(path) 10 | if !strings.HasPrefix(path, "/") { 11 | path = "/" + path 12 | } 13 | return filepath.Clean(path) 14 | } 15 | 16 | func IsSubPath(path string, subPath string) bool { 17 | path, subPath = CleanPath(path), CleanPath(subPath) 18 | if path == "/" { 19 | return true 20 | } 21 | return path == subPath || strings.HasPrefix(subPath, path+"/") 22 | } 23 | -------------------------------------------------------------------------------- /server/pkg/util/str.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func Ptr[T any](x T) *T { 8 | return &x 9 | } 10 | 11 | func StrReplace(s string, vars map[string]string) string { 12 | if vars == nil || s == "" { 13 | return s 14 | } 15 | args := make([]string, 0) 16 | for k, v := range vars { 17 | args = append(args, k) 18 | args = append(args, v) 19 | } 20 | r := strings.NewReplacer(args...) 21 | return r.Replace(s) 22 | } 23 | -------------------------------------------------------------------------------- /server/pkg/util/struct.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/viper" 6 | "reflect" 7 | "strings" 8 | ) 9 | 10 | type Filter struct { 11 | *viper.Viper 12 | } 13 | 14 | func NewFilter(m map[string]string) *Filter { 15 | cf := viper.New() 16 | for k, v := range m { 17 | cf.Set(k, v) 18 | } 19 | return &Filter{cf} 20 | } 21 | 22 | type DynamicModel[T any] struct { 23 | newModel any 24 | oldModel T 25 | isset bool 26 | values map[string]reflect.Value 27 | } 28 | 29 | func (m *DynamicModel[T]) Model() T { 30 | if m.isset { 31 | return m.oldModel 32 | } 33 | v := reflect.ValueOf(m.newModel) 34 | for v.Kind() == reflect.Ptr { 35 | v = v.Elem() 36 | } 37 | 38 | t := v.Type() 39 | for i := 0; i < t.NumField(); i++ { 40 | field := t.Field(i) 41 | if !field.IsExported() { 42 | continue 43 | } 44 | if oldVal, ok := m.values[field.Name]; ok { 45 | oldVal.Set(v.Field(i)) 46 | } 47 | } 48 | m.isset = true 49 | return m.oldModel 50 | } 51 | 52 | func (m *DynamicModel[T]) NewModel() any { 53 | return m.newModel 54 | } 55 | 56 | func NewDynamicModel[T any](oldModel T, tagName string, replacer map[string]string) *DynamicModel[T] { 57 | m := &DynamicModel[T]{oldModel: oldModel, values: make(map[string]reflect.Value)} 58 | 59 | v := reflect.ValueOf(oldModel) 60 | for v.Kind() == reflect.Ptr { 61 | v = v.Elem() 62 | } 63 | fields := make([]reflect.StructField, 0) 64 | 65 | t := v.Type() 66 | for i := 0; i < t.NumField(); i++ { 67 | field := t.Field(i) 68 | 69 | tag := field.Tag.Get(tagName) 70 | if tag == "-" { 71 | newTag := "" 72 | if replacer != nil { 73 | newTag = replacer[field.Name] 74 | } 75 | field.Tag = reflect.StructTag(strings.ReplaceAll(string(field.Tag), fmt.Sprintf(`%s:"-"`, tagName), newTag)) 76 | } 77 | fields = append(fields, field) 78 | 79 | m.values[field.Name] = v.Field(i) 80 | } 81 | m.newModel = reflect.New(reflect.StructOf(fields)).Interface() 82 | return m 83 | } 84 | --------------------------------------------------------------------------------