├── .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 |
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 |
--------------------------------------------------------------------------------