commit fc78c8f84f8131140b53944cdbd5c42255748a19 Author: DebuggerX Date: Sun Jul 12 03:39:51 2026 +0800 first commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ebe5e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +*.iml +build/ + diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf5f517 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# 设计概要 + +- 设置项目: 服务器地址\Client名称 +- UI设计: 添加项目按钮\TITLE\云端总空间占用\刷新列表按钮 +- 列表条目: 项目名\本地修改时间\云端修改时间\上传按钮\下载按钮(可长按) +- 长按下载按钮出现多个时间点的记录 +- 长按\右键项目名删除 +- pc端自动由列表变为grid diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..cc31076 --- /dev/null +++ b/build.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ -d "build" ]; then + echo "build dir exists" +else + mkdir "build" +fi + +cd server + +dart compile exe bin/server.dart -o ../build/cloud_archiver_server + +cd ../client + +flutter build linux + +mv build/linux/x64/release/bundle/ build/linux/x64/release/cloud_archiver/ +zip -r ../build/cloud_archiver.zip build/linux/x64/release/cloud_archiver/ + +flutter build apk + diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/client/.metadata b/client/.metadata new file mode 100644 index 0000000..6653127 --- /dev/null +++ b/client/.metadata @@ -0,0 +1,33 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ad70ec4617166f1c38e5d2bfd388af71fda14f06" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06 + base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06 + - platform: android + create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06 + base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06 + - platform: linux + create_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06 + base_revision: ad70ec4617166f1c38e5d2bfd388af71fda14f06 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/client/analysis_options.yaml b/client/analysis_options.yaml new file mode 100644 index 0000000..3d4906e --- /dev/null +++ b/client/analysis_options.yaml @@ -0,0 +1,32 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options + +formatter: + page_width: 160 + trailing_commas: preserve diff --git a/client/android/.gitignore b/client/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/client/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts new file mode 100644 index 0000000..dfed388 --- /dev/null +++ b/client/android/app/build.gradle.kts @@ -0,0 +1,45 @@ +plugins { + id("com.android.application") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.debuggerx.cloud_archiver" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.debuggerx.cloud_archiver" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + +flutter { + source = "../.." +} diff --git a/client/android/app/src/debug/AndroidManifest.xml b/client/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/client/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8cecaff --- /dev/null +++ b/client/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/client/android/app/src/main/kotlin/com/debuggerx/cloud_archiver/MainActivity.kt b/client/android/app/src/main/kotlin/com/debuggerx/cloud_archiver/MainActivity.kt new file mode 100644 index 0000000..f88167f --- /dev/null +++ b/client/android/app/src/main/kotlin/com/debuggerx/cloud_archiver/MainActivity.kt @@ -0,0 +1,5 @@ +package com.debuggerx.cloud_archiver + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/client/android/app/src/main/res/drawable-v21/launch_background.xml b/client/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/client/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/client/android/app/src/main/res/drawable/launch_background.xml b/client/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/client/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/client/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/client/android/app/src/main/res/values-night/styles.xml b/client/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/client/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/client/android/app/src/main/res/values/styles.xml b/client/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/client/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/client/android/app/src/profile/AndroidManifest.xml b/client/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/client/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/client/android/build.gradle.kts b/client/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/client/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/client/android/gradle.properties b/client/android/gradle.properties new file mode 100644 index 0000000..e96108c --- /dev/null +++ b/client/android/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +# This newDsl flag was added by the Flutter template +android.newDsl=false +# This builtInKotlin flag was added by the Flutter template +android.builtInKotlin=false diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2d428bf --- /dev/null +++ b/client/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip diff --git a/client/android/settings.gradle.kts b/client/android/settings.gradle.kts new file mode 100644 index 0000000..c21f0c5 --- /dev/null +++ b/client/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/client/lib/config.dart b/client/lib/config.dart new file mode 100644 index 0000000..f785ace --- /dev/null +++ b/client/lib/config.dart @@ -0,0 +1,128 @@ +import 'dart:io'; + +import 'package:cloud_archiver/utils.dart'; +import 'dart:convert' show json; + +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' show join; +import 'package:uuid/uuid.dart'; + +late File _configFile; + +AppConfig? appConfig; + +class AppConfig { + final String client; + final String server; + final String user; + final String passwd; + + final List syncItems; + + static Future init() async { + var directory = await getApplicationSupportDirectory(); + _configFile = File(join(directory.path, 'config.json')); + if (_configFile.existsSync()) { + try { + appConfig = AppConfig.fromJson(json.decode(_configFile.readAsStringSync())); + } catch (e) { + print(e); + } + } + } + + static void updateConfig({ + required String client, + required String server, + required String user, + required String passwd, + }) { + appConfig = AppConfig( + client: client, + server: server, + user: user, + passwd: passwd, + syncItems: appConfig?.syncItems ?? [], + ); + _save(); + } + + static void addSyncItem(SyncItemConfig item) { + var items = (appConfig?.syncItems ?? []).where((ele) => ele.uuid != item.uuid); + + appConfig = AppConfig( + client: appConfig?.client ?? '', + server: appConfig?.server ?? '', + user: appConfig?.user ?? '', + passwd: appConfig?.passwd ?? '', + syncItems: [ + item, + ...items, + ], + ); + _save(); + loadLocalStatus(); + } + + static void removeSyncItem(SyncItemConfig item) { + var items = (appConfig?.syncItems ?? []).where((ele) => ele.uuid != item.uuid); + + appConfig = AppConfig( + client: appConfig?.client ?? '', + server: appConfig?.server ?? '', + user: appConfig?.user ?? '', + passwd: appConfig?.passwd ?? '', + syncItems: items.toList(), + ); + _save(); + loadLocalStatus(); + } + + AppConfig({ + required this.client, + required this.server, + required this.user, + required this.passwd, + required this.syncItems, + }); + + factory AppConfig.fromJson(Map json) => AppConfig( + client: json['client'], + server: json['server'], + user: json['user'], + passwd: json['passwd'], + syncItems: (json['syncItems'] as List? ?? []).map((ele) => SyncItemConfig.fromJson(ele)).toList(), + ); + + Map toJson() => { + 'client': client, + 'server': server, + 'user': user, + 'passwd': passwd, + 'syncItems': syncItems, + }; + + static void _save() { + _configFile.writeAsStringSync(json.encode(appConfig)); + } +} + +class SyncItemConfig { + final String uuid; + final String name; + final String path; + + SyncItemConfig({required this.name, required this.path, String? uuid}) : uuid = uuid ?? Uuid().v4(); + + factory SyncItemConfig.fromJson(Map json) => SyncItemConfig( + uuid: json['uuid'], + name: json['name'], + path: json['path'], + ); + + Map toJson() => { + 'uuid': uuid, + 'name': name, + 'path': path, + }; +} diff --git a/client/lib/dialog.dart b/client/lib/dialog.dart new file mode 100644 index 0000000..f1183b3 --- /dev/null +++ b/client/lib/dialog.dart @@ -0,0 +1,277 @@ +import 'package:cloud_archiver/config.dart'; +import 'package:cloud_archiver/utils.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; + +class AppConfigDialog extends StatefulWidget { + const AppConfigDialog({super.key}); + + static void show(BuildContext context) { + showDialog( + context: context, + builder: (context) => Dialog( + child: AppConfigDialog(), + ), + ); + } + + @override + State createState() => _AppConfigDialogState(); +} + +class _AppConfigDialogState extends State { + final TextEditingController clientCtl = TextEditingController(); + final TextEditingController serverCtl = TextEditingController(); + final TextEditingController userCtl = TextEditingController(); + final TextEditingController passwdCtl = TextEditingController(); + + @override + void initState() { + super.initState(); + if (appConfig != null) { + clientCtl.text = appConfig!.client; + serverCtl.text = appConfig!.server; + userCtl.text = appConfig!.user; + } + } + + @override + Widget build(BuildContext context) { + return Container( + width: 400, + height: 300, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black26, + offset: Offset(4, 4), + spreadRadius: 8, + blurRadius: 16, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + '配置DAV服务', + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + children: [ + SizedBox(width: 70, child: Text('标识名: ')), + Flexible( + child: TextField( + controller: clientCtl, + ), + ), + ], + ), + Row( + children: [ + SizedBox(width: 70, child: Text('服务器: ')), + Flexible( + child: TextField( + controller: serverCtl, + ), + ), + ], + ), + Row( + children: [ + SizedBox(width: 70, child: Text('用户名: ')), + Flexible( + child: TextField( + controller: userCtl, + ), + ), + ], + ), + Row( + children: [ + SizedBox(width: 70, child: Text('密码: ')), + Flexible( + child: TextField( + controller: passwdCtl, + ), + ), + ], + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('取消'), + ), + ElevatedButton( + onPressed: () { + var client = clientCtl.text.trim(); + var server = serverCtl.text.trim(); + var user = userCtl.text.trim(); + var passwd = passwdCtl.text.trim(); + connect(server, user, passwd).then( + (connected) { + if (connected) { + AppConfig.updateConfig( + client: client, + server: server, + user: user, + passwd: passwd, + ); + if (context.mounted) { + Navigator.of(context).pop(); + } + } + }, + ); + }, + child: Text('确认'), + ), + ], + ), + ], + ), + ); + } +} + +class AddSyncItemDialog extends StatefulWidget { + final SyncItemConfig? editItemConfig; + + const AddSyncItemDialog({super.key, this.editItemConfig}); + + static void show(BuildContext context) { + showDialog( + context: context, + builder: (context) => Dialog( + child: AddSyncItemDialog(), + ), + ); + } + + @override + State createState() => _AddSyncItemDialogState(); +} + +class _AddSyncItemDialogState extends State { + final TextEditingController nameCtl = TextEditingController(); + final TextEditingController pathCtl = TextEditingController(); + + @override + Widget build(BuildContext context) { + return Container( + width: 400, + height: 300, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black26, + offset: Offset(4, 4), + spreadRadius: 8, + blurRadius: 16, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + widget.editItemConfig == null ? '添加同步项' : '修改同步项', + style: Theme.of(context).textTheme.titleMedium, + ), + Row( + children: [ + SizedBox(width: 80, child: Text('项目名称: ')), + Flexible( + child: TextField( + controller: nameCtl, + ), + ), + ], + ), + Row( + children: [ + SizedBox(width: 80, child: Text('项目路径: ')), + Flexible( + child: TextField( + controller: pathCtl, + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () { + FilePicker.pickFiles( + allowMultiple: false, + ).then( + (res) { + if (res?.files.firstOrNull?.path?.isNotEmpty == true) { + pathCtl.text = res!.files.first.path!; + } + }, + ); + }, + icon: Icon(Icons.file_open_rounded), + ), + IconButton( + onPressed: () { + FilePicker.getDirectoryPath().then( + (path) { + if (path?.isNotEmpty == true) { + pathCtl.text = path!; + } + }, + ); + }, + icon: Icon(Icons.folder_open_rounded), + ), + ], + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FilledButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('取消'), + ), + ElevatedButton( + onPressed: () { + var name = nameCtl.text.trim(); + var path = pathCtl.text.trim(); + + if (name.isNotEmpty && path.isNotEmpty) { + AppConfig.addSyncItem( + SyncItemConfig( + name: name, + path: path, + uuid: widget.editItemConfig?.uuid, + ), + ); + Navigator.of(context).pop(); + } + }, + child: Text('确认'), + ), + ], + ), + ], + ), + ); + } +} diff --git a/client/lib/main.dart b/client/lib/main.dart new file mode 100644 index 0000000..e6b0ec9 --- /dev/null +++ b/client/lib/main.dart @@ -0,0 +1,262 @@ +import 'package:cloud_archiver/config.dart'; +import 'package:cloud_archiver/dialog.dart'; +import 'package:cloud_archiver/state.dart'; +import 'package:cloud_archiver/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; +import 'package:path/path.dart'; +import 'package:webdav_client/webdav_client.dart' as dav; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await AppConfig.init(); + if (appConfig?.server.isNotEmpty == true) { + connect(appConfig!.server, appConfig!.user, appConfig!.passwd).then( + (success) { + if (success) { + loadCloudArchives(); + } + }, + ); + } + if (appConfig?.syncItems.isNotEmpty == true) { + loadLocalStatus(); + } + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Cloud Archiver', + theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + var width = MediaQuery.sizeOf(context).width; + + int rows = (width ~/ 800) + 1; + + double itemWidth = width / rows; + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + leading: IconButton( + onPressed: () { + AddSyncItemDialog.show(context); + }, + icon: Icon(Icons.add), + ), + title: ValueListenableBuilder( + valueListenable: davStatus, + builder: (context, value, child) => Center( + child: switch (value) { + DavStatus.connected => Text('总占用: ${totalSize()}'), + DavStatus.loading => Text('加载中...'), + DavStatus.notConnected => Text('未连接服务器'), + }, + ), + ), + actions: [ + IconButton( + onPressed: () { + loadCloudArchives(); + }, + icon: Icon(Icons.refresh_outlined), + ), + IconButton( + onPressed: () { + AppConfigDialog.show(context); + }, + icon: Icon(Icons.settings), + ), + ], + ), + body: ValueListenableBuilder( + valueListenable: localSyncItems, + builder: (context, localSyncItems, child) { + return ValueListenableBuilder( + valueListenable: cloudArchives, + builder: (context, cloudArchives, child) { + var itemList = genItemList(localSyncItems, {...cloudArchives}); + + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: rows, + childAspectRatio: itemWidth / 80, + ), + itemBuilder: (context, index) { + var (name, syncItemConfig, localModifiedTime, cloudArchives) = itemList[index]; + var cloudModifiedTimeTs = cloudArchives.map((archive) => archive.mTime?.millisecondsSinceEpoch ?? 0).maxOrNull; + return Container( + margin: EdgeInsets.all(8), + padding: EdgeInsets.only(left: 10, right: 10, top: 6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black26, + offset: Offset(1, 1), + spreadRadius: 2, + blurRadius: 5, + ), + ], + ), + height: 80, + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Text( + name, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, height: 1), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.videogame_asset_outlined, size: 18), + SizedBox(width: 4), + Flexible(child: Text(localModifiedTime?.strHm ?? '未同步')), + ], + ), + ), + Flexible( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.cloud_outlined, size: 18), + SizedBox(width: 4), + Flexible( + child: Text( + cloudModifiedTimeTs == null ? '未上传' : DateTime.fromMillisecondsSinceEpoch(cloudModifiedTimeTs).strHm, + ), + ), + ], + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: localModifiedTime == null + ? null + : () { + if (syncItemConfig != null) { + upload(syncItemConfig, cloudModifiedTimeTs == null); + } + }, + icon: Icon(Icons.upload_rounded, size: 22), + ), + Builder( + builder: (ctx) { + return IconButton( + onPressed: cloudModifiedTimeTs == null + ? null + : () { + if (syncItemConfig != null) { + download(syncItemConfig, cloudArchives.sorted.first); + } + }, + onLongPress: cloudModifiedTimeTs == null + ? null + : () { + var btn = (ctx.findRenderObject() as RenderBox); + var offset = btn.localToGlobal(Offset(btn.size.width, btn.size.height)); + showMenu( + context: ctx, + position: RelativeRect.fromLTRB(offset.dx - 300, offset.dy, width - offset.dx, offset.dy + 200), + items: cloudArchives.sorted.map( + (file) { + var parts = basenameWithoutExtension(file.name ?? '').split('-'); + parts.removeLast(); + var client = parts.join('-'); + return PopupMenuItem( + value: file, + onTap: () { + if (syncItemConfig != null) { + download(syncItemConfig, file); + } + }, + child: SizedBox( + width: 300, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + client, + overflow: TextOverflow.ellipsis, + ), + ), + Text((file.mTime ?? file.cTime ?? DateTime(0)).strHms), + ], + ), + ), + ); + }, + ).toList(), + ); + }, + icon: Icon(Icons.download_rounded, size: 22), + ); + }, + ), + ], + ), + ], + ), + ], + ), + ); + }, + itemCount: itemList.length, + ); + }, + ); + }, + ), + ); + } + + List<(String, SyncItemConfig?, DateTime?, List)> genItemList( + Map localSyncItems, + Map> cloudArchives, + ) { + var result = <(String, SyncItemConfig?, DateTime?, List)>[]; + var items = localSyncItems.entries.toList()..sort((a, b) => (a.value?.millisecondsSinceEpoch ?? 0).compareTo(b.value?.millisecondsSinceEpoch ?? 0)); + + for (var item in items) { + var archive = cloudArchives.remove(item.key.name); + result.add((item.key.name, item.key, item.value, archive ?? [])); + } + + for (var archive in cloudArchives.entries) { + result.add((archive.key, null, null, archive.value)); + } + + return result; + } +} diff --git a/client/lib/state.dart b/client/lib/state.dart new file mode 100644 index 0000000..c92bd71 --- /dev/null +++ b/client/lib/state.dart @@ -0,0 +1,23 @@ +import 'package:cloud_archiver/config.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:webdav_client/webdav_client.dart' as dav; + +enum DavStatus { + notConnected, + connected, + loading, +} + +extension CloudArchivesSortExt on List { + List get sorted => sortedByCompare( + (file) => file.name ?? '', + (a, b) => b.split('-').last.compareTo(a.split('-').last), + ); +} + +final ValueNotifier davStatus = ValueNotifier(DavStatus.notConnected); + +final ValueNotifier>> cloudArchives = ValueNotifier({}); + +final ValueNotifier> localSyncItems = ValueNotifier({}); diff --git a/client/lib/utils.dart b/client/lib/utils.dart new file mode 100644 index 0000000..583bf08 --- /dev/null +++ b/client/lib/utils.dart @@ -0,0 +1,174 @@ +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:cloud_archiver/config.dart'; +import 'package:cloud_archiver/state.dart'; +import 'package:filesize/filesize.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:webdav_client/webdav_client.dart' as dav; + +late dav.Client _client; + +String totalSize() { + if (cloudArchives.value.isEmpty) return '0 MB'; + return filesize( + cloudArchives.value.values + .map((archives) => archives.map((archive) => archive.size ?? 0).reduce((total, size) => total + size)) + .reduce((total, size) => total + size), + 1, + ); +} + +Future connect(String server, String user, String auth) async { + var client = dav.newClient( + server, + debug: true, + user: user, + password: auth, + ); + client.setHeaders({'accept-charset': 'utf-8'}); + + client.setConnectTimeout(5 * 1000); + + // Set send data timeout time in milliseconds. + client.setSendTimeout(60 * 1000); + + // Set transfer data time in milliseconds. + client.setReceiveTimeout(60 * 1000); + + try { + davStatus.value = DavStatus.loading; + await client.ping(); + davStatus.value = DavStatus.connected; + _client = client; + return true; + } catch (e) { + print('$e'); + davStatus.value = DavStatus.notConnected; + } + return false; +} + +void loadLocalStatus() { + if (appConfig?.syncItems.isNotEmpty != true) return; + var result = {}; + for (var syncItem in appConfig!.syncItems) { + var file = File(syncItem.path); + var stat = file.statSync(); + switch (stat.type) { + case FileSystemEntityType.file: + result[syncItem] = stat.modified; + case FileSystemEntityType.directory: + result[syncItem] = dirModifiedTime(syncItem.path); + default: + result[syncItem] = null; + } + } + + localSyncItems.value = { + ...localSyncItems.value, + ...result, + }; +} + +DateTime dirModifiedTime(String dir) { + var directory = Directory(dir); + var modified = directory.statSync().modified; + for (var entry in Directory(dir).listSync(recursive: true)) { + var stat = entry.statSync(); + if (stat.type == FileSystemEntityType.file) { + if (stat.modified.isAfter(modified)) { + modified = stat.modified; + } + } + } + return modified; +} + +void loadCloudArchives() async { + if (davStatus.value != DavStatus.connected) return; + davStatus.value = DavStatus.loading; + List list = []; + try { + list = await _client.readDir('/'); + } catch (e) { + print(e); + davStatus.value = DavStatus.notConnected; + return; + } + var result = >{}; + for (var item in list) { + if (item.isDir == true && item.path?.isNotEmpty == true) { + var files = await _client.readDir(item.path!); + result[basename(item.path!)] = files; + } + } + + cloudArchives.value = result; + davStatus.value = DavStatus.connected; +} + +Future upload(SyncItemConfig item, bool needCreateDir) async { + if (needCreateDir) { + await _client.mkdir(item.name); + } + var file = File(item.path); + + var cacheDir = await getApplicationCacheDirectory(); + + var zipFileEncoder = ZipFileEncoder(); + var zipFileName = '${appConfig?.client ?? 'DEBUG'}-${DateTime.now().strFileName}.zip'; + var zipTempFilePath = join(cacheDir.path, zipFileName); + zipFileEncoder.create(zipTempFilePath); + if (file.statSync().type == FileSystemEntityType.directory) { + await zipFileEncoder.addDirectory(Directory(item.path)); + } else { + await zipFileEncoder.addFile(file); + } + + await zipFileEncoder.close(); + + await _client.writeFromFile(zipTempFilePath, join(item.name, zipFileName)); + + File(zipTempFilePath).delete(); + + loadCloudArchives(); +} + +Future download(SyncItemConfig item, dav.File cloudArchive) async { + var cacheDir = await getApplicationCacheDirectory(); + var zipPath = join(cacheDir.path, '${item.name}_${cloudArchive.name}'); + await _client.read2File(cloudArchive.path!, zipPath); + await extractFileToDisk(zipPath, cacheDir.path); + File(zipPath).delete(); + + var temp = File(join(cacheDir.path, basename(item.path))); + + switch (temp.statSync().type) { + case FileSystemEntityType.directory: + var directory = Directory(item.path); + if (directory.existsSync()) { + directory.deleteSync(recursive: true); + } + Directory(temp.path); + Directory(temp.path).renameSync(item.path); + case FileSystemEntityType.file: + var file = File(item.path); + if (file.existsSync()) { + file.deleteSync(); + } + temp.renameSync(item.path); + default: + } +} + +extension DateTimeExt on DateTime { + String get strHm => '$year/${padZero(month)}/${padZero(day)} ${padZero(hour)}:${padZero(minute)}'; + + String get strHms => '$strHm:${padZero(second)}'; + + String get strFileName => '${year}_${padZero(month)}_${padZero(day)}_${padZero(hour)}_${padZero(minute)}_${padZero(second)}'; + + String padZero(int num, [fix = 2]) => num.toString().padLeft(fix, '0'); +} diff --git a/client/linux/.gitignore b/client/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/client/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/client/linux/CMakeLists.txt b/client/linux/CMakeLists.txt new file mode 100644 index 0000000..d098188 --- /dev/null +++ b/client/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "cloud_archiver") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.debuggerx.cloud_archiver") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/client/linux/flutter/CMakeLists.txt b/client/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/client/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/client/linux/flutter/generated_plugin_registrant.cc b/client/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..e71a16d --- /dev/null +++ b/client/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/client/linux/flutter/generated_plugin_registrant.h b/client/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/client/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/client/linux/flutter/generated_plugins.cmake b/client/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..be1ee3e --- /dev/null +++ b/client/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + jni +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/client/linux/runner/CMakeLists.txt b/client/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/client/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/client/linux/runner/main.cc b/client/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/client/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/client/linux/runner/my_application.cc b/client/linux/runner/my_application.cc new file mode 100644 index 0000000..1157397 --- /dev/null +++ b/client/linux/runner/my_application.cc @@ -0,0 +1,148 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView* view) { + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "cloud_archiver"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "cloud_archiver"); + } + + gtk_window_set_default_size(window, 1280, 720); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); + gtk_widget_realize(GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + // MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/client/linux/runner/my_application.h b/client/linux/runner/my_application.h new file mode 100644 index 0000000..db16367 --- /dev/null +++ b/client/linux/runner/my_application.h @@ -0,0 +1,21 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/client/pubspec.lock b/client/pubspec.lock new file mode 100644 index 0000000..3fca0d4 --- /dev/null +++ b/client/pubspec.lock @@ -0,0 +1,554 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + archive: + dependency: "direct main" + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + collection: + dependency: "direct main" + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" + url: "https://pub.dev" + source: hosted + version: "0.3.5+4" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.dev" + source: hosted + version: "1.0.9" + dbus: + dependency: transitive + description: + name: dbus + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" + url: "https://pub.dev" + source: hosted + version: "0.7.14" + dio: + dependency: transitive + description: + name: dio + sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 + url: "https://pub.dev" + source: hosted + version: "5.10.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: f13a03000d942e476bc1ff0a736d2e9de711d2f89a95cd4c1d88f861c3348387 + url: "https://pub.dev" + source: hosted + version: "11.0.2" + filesize: + dependency: "direct main" + description: + name: filesize + sha256: f53df1f27ff60e466eefcd9df239e02d4722d5e2debee92a87dfd99ac66de2af + url: "https://pub.dev" + source: hosted + version: "2.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" + url: "https://pub.dev" + source: hosted + version: "2.0.35" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" + source: hosted + version: "1.18.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + webdav_client: + dependency: "direct main" + description: + name: webdav_client + sha256: "682fffc50b61dc0e8f46717171db03bf9caaa17347be41c0c91e297553bf86b2" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.12.2 <4.0.0" + flutter: ">=3.38.4" diff --git a/client/pubspec.yaml b/client/pubspec.yaml new file mode 100644 index 0000000..606add8 --- /dev/null +++ b/client/pubspec.yaml @@ -0,0 +1,98 @@ +name: cloud_archiver +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.12.2 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + crypto: ^3.0.7 + path_provider: ^2.1.6 + path: ^1.9.1 + webdav_client: ^1.2.2 + filesize: ^2.0.1 + file_picker: ^11.0.2 + uuid: ^4.5.3 + collection: ^1.19.1 + archive: ^4.0.9 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/client/test/widget_test.dart b/client/test/widget_test.dart new file mode 100644 index 0000000..e8a368c --- /dev/null +++ b/client/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cloud_archiver/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/server/.dockerignore b/server/.dockerignore new file mode 100644 index 0000000..21504f8 --- /dev/null +++ b/server/.dockerignore @@ -0,0 +1,9 @@ +.dockerignore +Dockerfile +build/ +.dart_tool/ +.git/ +.github/ +.gitignore +.idea/ +.packages diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..e4ce6a1 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,3 @@ +# https://dart.dev/tools/private-files +# Created by `dart pub` +.dart_tool/ diff --git a/server/CHANGELOG.md b/server/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/server/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..42e2d7c --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,21 @@ +# Use latest stable channel SDK. +FROM dart:stable AS build + +# Resolve app dependencies. +WORKDIR /app +COPY pubspec.* ./ +RUN dart pub get + +# Copy app source code (except anything in .dockerignore) and AOT compile app. +COPY . . +RUN dart build cli --target bin/server.dart -o output + +# Build minimal serving image from AOT-compiled `/server` +# and the pre-built AOT-runtime in the `/runtime/` directory of the base image. +FROM scratch +COPY --from=build /runtime/ / +COPY --from=build /app/output/bundle/ /app/ + +# Start server. +EXPOSE 8080 +CMD ["/app/bin/server"] diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..e695d9d --- /dev/null +++ b/server/README.md @@ -0,0 +1,49 @@ +A server app built using [Shelf](https://pub.dev/packages/shelf), +configured to enable running with [Docker](https://www.docker.com/). + +This sample code handles HTTP GET requests to `/` and `/echo/` + +# Running the sample + +## Running with the Dart SDK + +You can run the example with the [Dart SDK](https://dart.dev/get-dart) +like this: + +``` +$ dart run bin/server.dart +Server listening on port 8080 +``` + +And then from a second terminal: +``` +$ curl http://0.0.0.0:8080 +Hello, World! +$ curl http://0.0.0.0:8080/echo/I_love_Dart +I_love_Dart +``` + +## Running with Docker + +If you have [Docker Desktop](https://www.docker.com/get-started) installed, you +can build and run with the `docker` command: + +``` +$ docker build . -t myserver +$ docker run -it -p 8080:8080 myserver +Server listening on port 8080 +``` + +And then from a second terminal: +``` +$ curl http://0.0.0.0:8080 +Hello, World! +$ curl http://0.0.0.0:8080/echo/I_love_Dart +I_love_Dart +``` + +You should see the logging printed in the first terminal: +``` +2021-05-06T15:47:04.620417 0:00:00.000158 GET [200] / +2021-05-06T15:47:08.392928 0:00:00.001216 GET [200] /echo/I_love_Dart +``` diff --git a/server/analysis_options.yaml b/server/analysis_options.yaml new file mode 100644 index 0000000..169d660 --- /dev/null +++ b/server/analysis_options.yaml @@ -0,0 +1,30 @@ +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/tools/analysis diff --git a/server/bin/server.dart b/server/bin/server.dart new file mode 100644 index 0000000..dd91087 --- /dev/null +++ b/server/bin/server.dart @@ -0,0 +1,47 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:file/local.dart'; +import 'package:shelf/shelf_io.dart'; +import 'package:shelf_dav/shelf_dav.dart'; + +void main(List args) async { + var parser = ArgParser(); + parser.addOption('port', abbr: 'p', defaultsTo: '8800', help: '服务运行端口号'); + parser.addOption('dir', abbr: 'd', defaultsTo: './repo/', help: '文件存储路径'); + parser.addOption('user', abbr: 'u', help: 'DAV服务用户名', mandatory: true); + parser.addOption('auth', abbr: 'a', help: 'DAV服务密码', mandatory: true); + + var argResults = parser.parse(args); + + if (args.isEmpty) { + stdout.writeln(parser.usage); + return; + } + + final fs = const LocalFileSystem(); + final rootDir = fs.directory(argResults.option('dir') ?? './repo/'); + if (!rootDir.existsSync()) { + rootDir.createSync(recursive: true); + } + final config = DAVConfig( + root: rootDir, + prefix: '/dav', + allowAnonymous: false, + // 1G + maxUploadSize: 1024 * 1024 * 1024, + readOnly: false, + propertyStorageType: PropertyStorageType.file, + authenticationProvider: BasicAuthenticationProvider.plaintext( + users: {argResults.option('user')!: argResults.option('auth')!}, + ), + ); + + final dav = ShelfDAV.withConfig(config); + + final ip = InternetAddress.anyIPv4; + + final port = int.parse(argResults.option('port') ?? '8800'); + final server = await serve(dav.handler, ip, port); + print('WebDAV server listening on port ${server.port}'); +} diff --git a/server/pubspec.lock b/server/pubspec.lock new file mode 100644 index 0000000..05e6834 --- /dev/null +++ b/server/pubspec.lock @@ -0,0 +1,461 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: f6526c100095fd63a916824e3da344bbbd50c25c8f56bcd52d13c59d35bbe422 + url: "https://pub.dev" + source: hosted + version: "104.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "6c6d751533496152e78f71c46ad001260c5e74e0f9ec1f1cbc165a0467c086d6" + url: "https://pub.dev" + source: hosted + version: "14.0.0" + args: + dependency: "direct main" + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + file: + dependency: "direct main" + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http: + dependency: "direct dev" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_methods: + dependency: transitive + description: + name: http_methods + sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + libdbm: + dependency: transitive + description: + name: libdbm + sha256: f3e038d4138ff95839525094aefb72aa1dd34b18c14e9322b1135ebe6ae1f1ea + url: "https://pub.dev" + source: hosted + version: "0.2.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" + url: "https://pub.dev" + source: hosted + version: "0.12.20" + meta: + dependency: transitive + description: + name: meta + sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + url: "https://pub.dev" + source: hosted + version: "1.18.3" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mime_type: + dependency: transitive + description: + name: mime_type + sha256: d652b613e84dac1af28030a9fba82c0999be05b98163f9e18a0849c6e63838bb + url: "https://pub.dev" + source: hosted + version: "1.0.1" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d + url: "https://pub.dev" + source: hosted + version: "3.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shelf: + dependency: "direct main" + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_dav: + dependency: "direct main" + description: + name: shelf_dav + sha256: "3864921fe8b17e5c66385a4cd3f55902c5107eac352af6650fc9e0f9d04f19bb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_router: + dependency: transitive + description: + name: shelf_router + sha256: f5e5d492440a7fb165fe1e2e1a623f31f734d3370900070b2b1e0d0428d59864 + url: "https://pub.dev" + source: hosted + version: "1.1.4" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "93b153dcb6a26dcddee6ca087dd634b53e38c10b5aa163e8e49501a776456153" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "0d5ba5602ec3baa28c8ce365e1efc5575969c765f45c554a3e167dc7945b9c30" + url: "https://pub.dev" + source: hosted + version: "1.31.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "475610b2aa23c19687cce2961e44b0cc57cafe220f67c2b80201231b2a07fbe7" + url: "https://pub.dev" + source: hosted + version: "0.7.13" + test_core: + dependency: transitive + description: + name: test_core + sha256: a39c204a4fc7a7ccb04a2b985e359fda3cc37e45e0b8ac61c3fb1a05aa832132 + url: "https://pub.dev" + source: hosted + version: "0.6.19" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.12.2 <4.0.0" diff --git a/server/pubspec.yaml b/server/pubspec.yaml new file mode 100644 index 0000000..12c56a3 --- /dev/null +++ b/server/pubspec.yaml @@ -0,0 +1,17 @@ +name: server +description: A server app using the shelf package and Docker. +version: 1.0.0 +# repository: https://github.com/my_org/my_repo + +environment: + sdk: ^3.12.2 + +dependencies: + args: ^2.7.0 + file: ^7.0.1 + shelf: ^1.4.2 + shelf_dav: ^1.0.0 +dev_dependencies: + http: ^1.2.2 + lints: ^6.0.0 + test: ^1.25.6 diff --git a/server/repo/Save/flutter-2026_07_10_23_04_23.zip b/server/repo/Save/flutter-2026_07_10_23_04_23.zip new file mode 100644 index 0000000..4bbfaf9 Binary files /dev/null and b/server/repo/Save/flutter-2026_07_10_23_04_23.zip differ diff --git a/server/repo/Save/flutter-2026_07_10_23_16_45.zip b/server/repo/Save/flutter-2026_07_10_23_16_45.zip new file mode 100644 index 0000000..2e12079 Binary files /dev/null and b/server/repo/Save/flutter-2026_07_10_23_16_45.zip differ diff --git a/server/repo/Test/flutter-2026_07_10_22_45_33.zip b/server/repo/Test/flutter-2026_07_10_22_45_33.zip new file mode 100644 index 0000000..6827e97 Binary files /dev/null and b/server/repo/Test/flutter-2026_07_10_22_45_33.zip differ diff --git a/server/repo/Test/flutter-2026_07_10_23_17_01.zip b/server/repo/Test/flutter-2026_07_10_23_17_01.zip new file mode 100644 index 0000000..a8105e6 Binary files /dev/null and b/server/repo/Test/flutter-2026_07_10_23_17_01.zip differ