From: Hamatoma Date: Wed, 25 Aug 2021 21:57:54 +0000 (+0200) Subject: Daily work: app runs until first page X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=0391e90c44c61b34892bb8eb2329a248301d9a2e;p=exhibition.git Daily work: app runs until first page --- diff --git a/.gitignore b/.gitignore index d3472d3..5bc5626 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ data/i18n/exhibition.lokalize data/i18n/main.lqa *~ data/i18n/lokalize-scripts/ +rest_server/tools/rest_server diff --git a/dart_tools/bin/i18n_text_parser.dart b/dart_tools/bin/i18n_text_parser.dart index a816ed2..48928ca 100644 --- a/dart_tools/bin/i18n_text_parser.dart +++ b/dart_tools/bin/i18n_text_parser.dart @@ -106,10 +106,10 @@ class I18nTextParser { RegExp? regExpExcluded; // ...........................1.............1.........2 final regExpModule = - RegExp(r'''(\w+) = (I18N\(\)|i18N)\.module\(["'](.*?)['"]\);'''); + RegExp(r'''(\w+) = (I18N\(\)|i18n)\.module\(["'](.*?)['"]\);'''); final regExpDelimiter = RegExp(r'''["']'''); // ..........1.............1..2..............................2 - final regExpText = RegExp(r'(i18N|I18N\(\))\.(tr|ntr|trMulti|trArgs)\('); + final regExpText = RegExp(r'(i18n|I18N\(\))\.(tr|ntr|trMulti|trArgs)\('); final regExpStringConstant = RegExp(r'''^\s*(r?)(["'])(.*?)\2'''); final regExpVariable = RegExp(r'^\w+'); final regExpEmptyString = RegExp(r'^\s*$'); diff --git a/exhibition.json b/exhibition.json new file mode 100644 index 0000000..3048aaf --- /dev/null +++ b/exhibition.json @@ -0,0 +1,20 @@ +{ + "form.card.padding": "16.0", + "form.gap.field_button.height": "16.0", + "productive": { + "version": "1.0", + "host": "localhost", + "port": 6000, + "scheme": "http", + "timeout": 10 + }, + "development": { + "version": "1.0", + "host": "pollsam.hamatoma.de", + "port": 443, + "scheme": "https", + "timeout": 10 + }, + "configDirectory": ".", + "dataDirectory": "." +} diff --git a/lib/base/defines.dart b/lib/base/defines.dart index 0b8ecc6..37a569a 100644 --- a/lib/base/defines.dart +++ b/lib/base/defines.dart @@ -1,3 +1,4 @@ +enum ServerEnvironment { productive, development } typedef YamlMap = Map; typedef YamlList = List; diff --git a/lib/base/i18n.dart b/lib/base/i18n.dart index ebb313d..0188106 100644 --- a/lib/base/i18n.dart +++ b/lib/base/i18n.dart @@ -1,8 +1,8 @@ import 'package:dart_bones/dart_bones.dart'; typedef MapModule = Map; typedef MapPlural = Map; -class I18n { - static I18n? instance; +class I18N { + static I18N? instance; final BaseLogger logger; final globalModule = '!global'; Map mapModules = {}; @@ -10,11 +10,11 @@ class I18n { String locale = 'de_DE'; String loadedLocale = ''; final regExpTag = RegExp(r'<#\d+>$'); - factory I18n() { + factory I18N() { return instance!; } - I18n.internal(this.logger) { + I18N.internal(this.logger) { instance = this; } diff --git a/lib/base/i18n_io.dart b/lib/base/i18n_io.dart index 0f39dd2..e2633a2 100644 --- a/lib/base/i18n_io.dart +++ b/lib/base/i18n_io.dart @@ -5,7 +5,7 @@ import 'package:path/path.dart'; import 'i18n.dart'; -class I18nIo extends I18n { +class I18nIo extends I18N { String dataDirectory; final regExpId = RegExp(r'^msgid "(.+?)"$'); final regExpTranslation = RegExp(r'^msgstr "(.+?)"$'); diff --git a/lib/exhibition_app.dart b/lib/exhibition_app.dart new file mode 100644 index 0000000..d3957cc --- /dev/null +++ b/lib/exhibition_app.dart @@ -0,0 +1,60 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:exhibition/page/start_page.dart'; +import 'package:flutter/material.dart'; +import 'page/users/users_list_page.dart'; +import 'page/info_page.dart'; +import 'page/log_page.dart'; + +import 'setting/global_data.dart'; + +class ExhibitionApp extends StatefulWidget { + static BaseLogger logger = globalLogger; + + @override + ExhibitionAppState createState() { + return ExhibitionAppState(); + } +} + +class ExhibitionAppState extends State { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: true, + title: 'Exhibition', + theme: ThemeData( + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + initialRoute: '/start', + onGenerateRoute: _getRoute, + ); + } +} + +Route? _getRoute(RouteSettings settings) { + MaterialPageRoute? route; + StatefulWidget? page; + switch (settings.name) { + case '/users/list': + page = UsersListPage(GlobalData()); + break; + case '/start': + page = StartPage(globalLogger); + break; + case '/info': + page = InfoPage(GlobalData()); + break; + case '/log': + page = LogPage(GlobalData()); + break; + } + if (page != null) { + route = MaterialPageRoute( + settings: settings, + builder: (BuildContext context) => page!, + fullscreenDialog: false, + ); + } + return route; +} diff --git a/lib/main.dart b/lib/main.dart index 202509b..5ba9748 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,115 +1,27 @@ +import 'package:dart_bones/dart_bones.dart'; +import 'package:exhibition/base/i18n.dart'; +import 'package:exhibition/setting/global_data.dart'; import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } +import 'exhibition_app.dart'; +import 'persistence/rest_persistence.dart'; + +void main() async { + final logger = MemoryLogger(LEVEL_FINE); + ExhibitionApp.logger = logger; + I18N.internal(logger); + final configuration = BaseConfiguration({}, logger); + // We create a simplified instance. The real instance will be created in StartPage + GlobalData.internal( + configuration, + (p0) => null, + (p0) => null, + () => null, + RestPersistence( + version: '0.0.1', + host: '', + port: 0, + sessionTimeout: 30, + logger: logger), + logger); + runApp(ExhibitionApp()); } diff --git a/lib/meta/roles_meta.dart b/lib/meta/roles_meta.dart index 8023c45..209a139 100644 --- a/lib/meta/roles_meta.dart +++ b/lib/meta/roles_meta.dart @@ -2,8 +2,8 @@ import '../base/defines.dart'; import 'module_meta_data.dart'; import '../base/i18n.dart'; -final i18N = I18n(); -final M = i18N.module("Roles"); +final i18n = I18N(); +final M = i18n.module("Roles"); class RoleMeta extends ModuleMetaData { static RoleMeta instance = RoleMeta.internal(); @@ -12,19 +12,19 @@ class RoleMeta extends ModuleMetaData { 'Roles', [ PropertyMetaData('id', DataType.reference, ':primary:', 'combo', - i18N.tr('Id')), + i18n.tr('Id')), PropertyMetaData( - 'name', DataType.string, ':notnull:', '', i18N.tr('Name'), + 'name', DataType.string, ':notnull:', '', i18n.tr('Name'), size: 64), PropertyMetaData('created', DataType.datetime, ':hidden:', '', - i18N.tr('Created')), + i18n.tr('Created')), PropertyMetaData('createdBy', DataType.string, ':hidden:', '', - i18N.tr('Created by'), + i18n.tr('Created by'), size: 32), PropertyMetaData('changed', DataType.datetime, ':hidden:', '', - i18N.tr('Changed')), + i18n.tr('Changed')), PropertyMetaData('changedBy', DataType.string, ':hidden:', '', - i18N.tr('Changed by'), + i18n.tr('Changed by'), size: 32), ], ); diff --git a/lib/meta/users_meta.dart b/lib/meta/users_meta.dart index daec632..095fc44 100644 --- a/lib/meta/users_meta.dart +++ b/lib/meta/users_meta.dart @@ -1,8 +1,8 @@ import '../base/defines.dart'; import 'module_meta_data.dart'; import '../base/i18n.dart'; -final i18N = I18n(); -final M = i18N.module("Users"); +final i18n = I18N(); +final M = i18n.module("Users"); class UserMeta extends ModuleMetaData { static UserMeta instance = UserMeta.internal(); @@ -11,27 +11,27 @@ class UserMeta extends ModuleMetaData { 'Users', [ PropertyMetaData('id', DataType.reference, ':primary:', 'combo', - i18N.tr('Id')), + i18n.tr('Id')), PropertyMetaData('name', DataType.string, ':notnull:', '', - i18N.tr('Name'), + i18n.tr('Name'), size: 64), PropertyMetaData('displayName', DataType.string, ':unique:notnull:', - '', i18N.tr('Display name', M), + '', i18n.tr('Display name', M), size: 32), PropertyMetaData('email', DataType.string, ':unique:notnull:', '', - i18N.tr('EMail', M), + i18n.tr('EMail', M), size: 255), PropertyMetaData('role', DataType.reference, ':notnull:', '', - i18N.tr('Role'), reference: 'roles.role_id'), + i18n.tr('Role'), reference: 'roles.role_id'), PropertyMetaData('created', DataType.datetime, ':hidden:', '', - i18N.tr('Created')), + i18n.tr('Created')), PropertyMetaData('createdBy', DataType.string, ':hidden:', '', - i18N.tr('Created by'), + i18n.tr('Created by'), size: 32), PropertyMetaData('changed', DataType.datetime, ':hidden:', '', - i18N.tr('Changed')), + i18n.tr('Changed')), PropertyMetaData('changedBy', DataType.string, ':hidden:', '', - i18N.tr('Changed by'), + i18n.tr('Changed by'), size: 32), ], ); diff --git a/lib/page/start_page.dart b/lib/page/start_page.dart new file mode 100644 index 0000000..fbec21d --- /dev/null +++ b/lib/page/start_page.dart @@ -0,0 +1,114 @@ +import 'dart:convert' as convert; + +import 'package:dart_bones/dart_bones.dart'; +import 'package:exhibition/persistence/rest_persistence.dart'; +import 'package:exhibition/setting/app_bar_exhibition.dart'; +import 'package:exhibition/setting/drawer_exhibition.dart'; +import 'package:exhibition/setting/footer_exhibition.dart'; +import 'package:flutter/material.dart'; + +//import '../helper/exhibition_defines.dart'; +import '../setting/installation.dart'; +import '../setting/global_data.dart'; + +class StartPage extends StatefulWidget { + final BaseLogger logger; + + StartPage(this.logger, {Key? key}) : super(key: key); + + @override + StartPageState createState() { + // UserPageState.setPageData(pageData); + final rc = StartPageState(logger); + logger.log('Start'); + return rc; + } +} + +class StartPageState extends State { + final BaseLogger logger; + final GlobalKey _formKey = + GlobalKey(debugLabel: 'Start'); + + String logName = ''; + + StartPageState(this.logger); + + @override + Widget build(BuildContext context) { + final padding = 16.0; + final listItems = [ + Text( + 'Herzlich Willkommen', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + SizedBox(height: padding), + ...(logger as MemoryLogger) + .messages + .map((line) => Text( + line, + style: TextStyle( + color: line.startsWith('+++') ? Colors.red : Colors.black), + )) + .toList(), + SizedBox(height: padding), + ElevatedButton( + onPressed: () => setState(() => 1), child: Text('Aktualisieren')), + ]; + return Scaffold( + //appBar: applicationData.appBarBuilder('Start'), + //drawer: applicationData.drawerBuilder(context), + body: Form( + key: _formKey, + child: Card( + margin: EdgeInsets.symmetric(vertical: padding, horizontal: padding), + child: Padding( + padding: + EdgeInsets.symmetric(vertical: padding, horizontal: padding), + child: ListView( + children: listItems, + ))), + )); + } + + @override + void initState() { + super.initState(); + Installation.initialize(logger); + // Note: Installation.kind must be set before. + final installation = Installation(); + installation.checkPermissions().then((success) { + setState(() { + if (success) { + setState(() { + installation.checkConfiguration().then((success) { + if (success) { + logger.log(i18n.tr('Starting application')); + final content = FileSync() + .fileAsString(installation.configurationFile.path); + final map = convert.jsonDecode(content); + final configuration = BaseConfiguration(map, logger); + final globalData = GlobalData.internal( + configuration, + AppBarExhibition.builder(), + DrawerExhibition.builder(), + FooterExhibition.builder(), + RestPersistence.fromConfig(configuration, logger), + logger); + globalData.initializeAsync().then((value) { + Navigator.pushNamed(context, '/users/list'); + }); + } + }); + }); + }; + }); + }); + } + + void store(context) async { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + } + } +} diff --git a/lib/page/users/user_list.dart b/lib/page/users/user_list.dart deleted file mode 100644 index 1675056..0000000 --- a/lib/page/users/user_list.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../base/defines.dart'; -import 'user_state.dart'; -import 'package:equatable/equatable.dart'; - -class User extends StatefulWidget { - @override - _UserState createState() => _UserState(); -} - -class _UserState extends State { - double? width; - - @override - void initState() { - //context.read().add(UserEvent(EventSource.undef, EventCardinality.undef)); - super.initState(); - } - - @override - void didChangeDependencies() { - width = MediaQuery.of(context).size.width; - super.didChangeDependencies(); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, UserState state) { - return Column( - children: [ - //ResultDisplay( - // text: _getDisplayText(state.calculationModel), - //), - Row( - children: [ - _getButton(text: '7', onTap: () => numberPressed(7)), - _getButton(text: '8', onTap: () => numberPressed(8)), - _getButton(text: '9', onTap: () => numberPressed(9)), - _getButton( - text: 'x', - onTap: () => operatorPressed('*'), - backgroundColor: Color.fromRGBO(220, 220, 220, 1)), - ], - ), - Row( - children: [ - _getButton(text: '4', onTap: () => numberPressed(4)), - _getButton(text: '5', onTap: () => numberPressed(5)), - _getButton(text: '6', onTap: () => numberPressed(6)), - _getButton( - text: '/', - onTap: () => operatorPressed('/'), - backgroundColor: Color.fromRGBO(220, 220, 220, 1)), - ], - ), - Row( - children: [ - _getButton(text: '1', onTap: () => numberPressed(1)), - _getButton(text: '2', onTap: () => numberPressed(2)), - _getButton(text: '3', onTap: () => numberPressed(3)), - _getButton( - text: '+', - onTap: () => operatorPressed('+'), - backgroundColor: Color.fromRGBO(220, 220, 220, 1)) - ], - ), - Row( - children: [ - _getButton( - text: '=', - onTap: calculateResult, - backgroundColor: Colors.orange, - textColor: Colors.white), - _getButton(text: '0', onTap: () => numberPressed(0)), - _getButton( - text: 'C', - onTap: clear, - backgroundColor: Color.fromRGBO(220, 220, 220, 1)), - _getButton( - text: '-', - onTap: () => operatorPressed('-'), - backgroundColor: Color.fromRGBO(220, 220, 220, 1)), - ], - ), - Spacer(), - //UserHistoryContainer( - // calculations: state.history.reversed.toList()) - ], - ); - }, - ); - } - - Widget _getButton( - {required String text, - required void Function() onTap, - Color backgroundColor = Colors.white, - Color textColor = Colors.black}) { - /* - return CalculatorButton( - label: text, - onTap: onTap, - // @ToDo: width always != null? - size: width! / 4 - 12, - backgroundColor: backgroundColor, - labelColor: textColor, - ); - */ - return SizedBox(width: 10); - } - - numberPressed(int number) { - //context.read().add(NumberPressed(number: number)); - } - - operatorPressed(String operator) { - //context.read().add(OperatorPressed(operator: operator)); - } - - calculateResult() { - //context.read().add(CalculateResult()); - } - - clear() { - //context.read().add(ClearUser()); - } - /* - String _getDisplayText(UserModel model) { - if (model.result != null) { - return '${model.result}'; - } - - if (model.secondOperand != null) { - return '${model.firstOperand}${model.operator}${model.secondOperand}'; - } - - if (model.operator != null) { - return '${model.firstOperand}${model.operator}'; - } - - if (model.firstOperand != null) { - return '${model.firstOperand}'; - } - - return "${model.result ?? 0}"; - } - */ -} diff --git a/lib/page/users/users_list_page.dart b/lib/page/users/users_list_page.dart new file mode 100644 index 0000000..505c897 --- /dev/null +++ b/lib/page/users/users_list_page.dart @@ -0,0 +1,54 @@ +import 'package:exhibition/setting/global_data.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../base/defines.dart'; +import 'user_state.dart'; +import 'package:equatable/equatable.dart'; +import '../../base/i18n.dart'; +final i18n = I18N(); +class UsersListPage extends StatefulWidget { + final GlobalData globalData; + UsersListPage(this.globalData): super(); + @override + _UsersListPageState createState() => _UsersListPageState(); +} + +class _UsersListPageState extends State { + double? width; + + @override + void initState() { + //context.read().add(UserEvent(EventSource.undef, EventCardinality.undef)); + super.initState(); + } + + @override + void didChangeDependencies() { + width = MediaQuery.of(context).size.width; + super.didChangeDependencies(); + } + final nameController = TextEditingController(); + @override + Widget build(BuildContext context) { + final padding = 16.0; + return BlocBuilder( + builder: (context, UserState state) { + return Column( + children: [ + TextFormField( + controller: nameController, + readOnly: true, + decoration: InputDecoration(labelText: i18n.tr('Name')), + ), + SizedBox(height: padding), + Text('Liste'), + Spacer(), + //UserHistoryContainer( + // calculations: state.history.reversed.toList()) + ], + ); + }, + ); + } + +} diff --git a/lib/setting/app_bar_exhibition.dart b/lib/setting/app_bar_exhibition.dart index d0ed168..1ec7c6a 100644 --- a/lib/setting/app_bar_exhibition.dart +++ b/lib/setting/app_bar_exhibition.dart @@ -1,9 +1,10 @@ +import 'package:exhibition/setting/global_data.dart'; import 'package:flutter/material.dart'; class AppBarExhibition extends AppBar { AppBarExhibition({required String title, Key? key}) : super(title: Text(title), key: key); - static AppBarExhibition builder(String title) => - AppBarExhibition(title: title); + static AppBarBuilder builder() => + (String title) => AppBarExhibition(title: title); } diff --git a/lib/setting/drawer_exhibition.dart b/lib/setting/drawer_exhibition.dart index e0a8f44..a8f7d1b 100644 --- a/lib/setting/drawer_exhibition.dart +++ b/lib/setting/drawer_exhibition.dart @@ -10,7 +10,7 @@ class DrawerExhibition extends Drawer { : super(child: buildGrid(context), key: key); /// Returns a method creating a drawer. - static DrawerExhibition builder(dynamic context) => DrawerExhibition(context); + static DrawerBuilder builder() => (context) => DrawerExhibition(context); static Widget buildGrid(context) { final converter = MenuConverter(); diff --git a/lib/setting/footer_exhibition.dart b/lib/setting/footer_exhibition.dart index 2627bf0..5488cce 100644 --- a/lib/setting/footer_exhibition.dart +++ b/lib/setting/footer_exhibition.dart @@ -34,5 +34,5 @@ class FooterExhibition implements FooterInterface { } /// Returns a method creating a footer. - static FooterExhibition builder() => FooterExhibition(); + static FooterBuilder builder() => () => FooterExhibition(); } diff --git a/lib/setting/global_data.dart b/lib/setting/global_data.dart index 8ec1694..de67da3 100644 --- a/lib/setting/global_data.dart +++ b/lib/setting/global_data.dart @@ -2,6 +2,7 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:exhibition/persistence/rest_persistence.dart'; import 'package:flutter/material.dart'; +import '../base/defines.dart'; import '../page/page_controller_exhibition.dart'; typedef AppBarBuilder = Function(String); @@ -24,10 +25,13 @@ abstract class FooterInterface { Widget widget(PageControllerExhibition controller); } -/// Storage for globale resources. This is a singleton. +/// Storage for global resources. This is a singleton. class GlobalData { - static const version = '2021.06.027.00'; + static const version = '2021.08.24.00'; static GlobalData? _instance; + static String baseDirectory = ''; + static var serverEnvironment = ServerEnvironment.productive; + final BaseLogger logger; final AppBarBuilder appBarBuilder; final DrawerBuilder drawerBuilder; @@ -47,5 +51,10 @@ class GlobalData { this.drawerBuilder, this.footerBuilder, this.restPersistence, this.logger) { logger.log('Start'); + _instance = this; + } + Future initializeAsync() async{ + // Do customize! + return; } } diff --git a/lib/setting/installation.dart b/lib/setting/installation.dart new file mode 100644 index 0000000..68ae7d4 --- /dev/null +++ b/lib/setting/installation.dart @@ -0,0 +1,233 @@ +import 'dart:async'; +import 'dart:convert' as convert; +import 'dart:io'; + +import 'package:dart_bones/dart_bones.dart'; +import 'package:path/path.dart' as path; +import 'package:permission_handler/permission_handler.dart'; + +import 'global_data.dart'; +import '../base/defines.dart'; +import '../base/i18n.dart'; + +final i18n = I18N(); +final M = i18n.module('Example'); + +/// Initializes the program if needed. +class Installation { + static String osType = Platform.operatingSystem; + static Installation? instance; + static ServerEnvironment serverEnvironment = ServerEnvironment.productive; + static int retries = 1; + final neededPermissions = [ + Permission.camera, + Permission.location, + Permission.storage, + Permission.manageExternalStorage, + ]; + bool _success = true; + Directory home = Directory('.'); + Directory dataDirectory = Directory('.'); + File configurationFile = File(''); + File hivesFile = File(''); + File dataFile = File(''); + String user = '?'; + final BaseLogger logger; + + factory Installation() => instance ?? Installation.internal(globalLogger); + + /// The private constructor. + /// [logger] does the logging. + Installation.internal(this.logger) { + logger.log('Version: ${GlobalData.version}'); + } + + bool get success => _success; + + /// Checks whether there is a valid configuration file. + /// If not we try to create one. + /// Returns true: a valid configuration exists. + Future checkConfiguration() async { + bool forceInit = false; + if (GlobalData.baseDirectory.isEmpty) { + GlobalData.baseDirectory = home.path; + } + configurationFile = File(path.join(home.path, 'exhibition.json')); + if (!(forceInit || + !configurationFile.existsSync() || + !hasValidConfiguration())) { + logger.log(i18n.tr('Configuration OK'), LEVEL_DETAIL); + } else { + final content = '''{ + "form.card.padding": "16.0", + "form.gap.field_button.height": "16.0", + "productive": { + "version": "1.0", + "host": "localhost", + "port": 6000, + "scheme": "http", + "timeout": 10 + }, + "development": { + "version": "1.0", + "host": "pollsam.hamatoma.de", + "port": 443, + "scheme": "https", + "timeout": 10 + }, + "configDirectory": "${home.path}", + "dataDirectory": "${dataDirectory.path}" +} +'''; + logger.log( + i18n.tr('creating configuration:') + ' ${configurationFile.path}'); + configurationFile.writeAsStringSync(content, mode: FileMode.writeOnly); + } + var rc = hasValidConfiguration(); + if (rc) { + // do needed things! + } + if (rc) { + logger.log(i18n.tr('Configuration is OK'), LEVEL_DETAIL); + } + return rc; + } + + /// Checks the needed access rights. If missing we request that. + /// Returns true: all rights are available + Future checkPermissions() async { + var rc = true; + //logger.log(i18n.tr('We do not check access rights')); + if (Platform.isAndroid) { + for (var permission in neededPermissions) { + logger.log(nameOfPermission(permission), LEVEL_FINE); + var status = await permission.status; + if (status == PermissionStatus.granted) { + logger.log('${nameOfPermission(permission)}: ${nameOfStatus(status)}', + LEVEL_DETAIL); + } else { + logger.log(i18n.tr('right') + + ' ${nameOfPermission(permission)}: ${nameOfStatus(status)}'); + await permission.request(); + status = await permission.status; + if (status != PermissionStatus.granted) { + logger.error(i18n.trArgs('missing right %1: %2', M, + [nameOfPermission(permission), nameOfStatus(status)])); + rc = _success = false; + } + } + } + } + if (rc) { + logger.log(i18n.tr('access rights OK'), LEVEL_DETAIL); + } + return rc; + } + + /// Checks whether a valid configuration exists. + /// Returns true: a valid configuration exists. + bool hasValidConfiguration() { + final fileContent = configurationFile.readAsStringSync(); + var rc = true; + try { + final map = convert.jsonDecode(fileContent); + final config = BaseConfiguration(map, logger); + final section = enumToString(serverEnvironment); + if (config.asString('host', section: section) == null || + config.asInt('port', section: section) == null) { + rc = logger + .error('${configurationFile.path}: ' + i18n.tr('wrong: host/port')); + } else if (config.asString('configDirectory') == null || + config.asString('dataDirectory') == null) { + rc = logger.error('${configurationFile.path}: ' + + i18n.tr('wrong: configDirectory/dataDirectory')); + } + } on FormatException catch (exc) { + rc = logger + .error('${configurationFile.path}: ' + i18n.tr('failure:' + ' $exc')); + } + return rc; + } + + /// Prüft, ob eine gültige Datei mit den Bienenstockdaten existiert. + /// Liefert true, wenn ja. + bool hasValidHives() { + final fileContent = hivesFile.readAsStringSync(); + final filename = hivesFile.path; + var rc = false; + try { + final list = convert.jsonDecode(fileContent); + rc = list is Iterable; + if (!rc) { + logger.error('$filename: "hives" ist kein Array'); + } else { + var ix = -1; + for (var item in list) { + ix++; + rc = item is Map; + if (!rc) { + logger.error('$filename: hives[$ix] ist keine Map'); + } else { + final map2 = item; + rc = (map2.containsKey('hiveid') && + map2.containsKey('name') && + map2.containsKey('lat') && + map2.containsKey('long')); + if (!rc) { + logger.error('$filename: Fehler in hives[$ix]'); + break; + } + } + } + } + } on FormatException catch (exc) { + logger.error('$filename: kein JSon: $exc'); + } + return rc; + } + + /// Liefert den Namen des Rechts [permission]. + static String nameOfPermission(Permission permission) { + var rc; + if (permission == Permission.storage) { + rc = 'Speicher'; + } else if (permission == Permission.camera) { + rc = 'Kamera'; + } else { + if (permission == Permission.location) { + rc = 'Geo-Position'; + } else { + rc = 'Permission-${permission.value}'; + } + } + return rc; + } + + /// Liefert den Namen des [status]. + static String nameOfStatus(PermissionStatus status) { + String rc; + switch (status) { + case PermissionStatus.granted: + rc = 'erlaubt'; + break; + case PermissionStatus.denied: + rc = 'verweigert'; + break; + case PermissionStatus.restricted: + rc = 'eingeschränkt'; + break; + case PermissionStatus.limited: + rc = 'begrenzt'; + break; + case PermissionStatus.permanentlyDenied: + rc = 'gesperrt'; + break; + } + return rc; + } + + /// Initialisiert die einzige Instanz (Singleton). + static Future initialize(BaseLogger logger) async { + instance = Installation.internal(logger); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index a4d9bf1..1de4d83 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,7 @@ dependencies: flutter_markdown: ^0.6.1 flutter_bloc: ^7.1.0 equatable: ^2.0.3 + permission_handler: ^7.0.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 diff --git a/rest_server/lib/rest_server.dart b/rest_server/lib/rest_server.dart index cacd227..1db9672 100644 --- a/rest_server/lib/rest_server.dart +++ b/rest_server/lib/rest_server.dart @@ -8,9 +8,9 @@ import 'package:crypto/crypto.dart'; import 'package:dart_bones/dart_bones.dart'; import 'package:http/http.dart' as http; import 'package:path/path.dart' as path; -import 'sql_storage.dart'; import 'package:mysql1/mysql1.dart'; +import 'sql_storage.dart'; const forbidden = 'forbidden'; const wrongData = 'wrong data'; const wrongParameters = 'wrong parameters'; @@ -46,7 +46,7 @@ void runIsolate(SendPort isolateToMainStream) { /// That is a HTTP server serving only the method POST (for queries and storages). /// The server is multi threaded by isolates. class RestServer { - static const _version = 'V2021.06.29.00'; + static const _version = 'V2021.08.19.00'; static BaseLogger? unittestLogger; BaseLogger logger = MemoryLogger(LEVEL_FINE); BaseConfiguration configuration = BaseConfiguration({}, globalLogger); @@ -177,7 +177,7 @@ service: address: 0.0.0.0 port: 58021 dataDirectory: /var/cache/exhibition/data - sqlDirectory: /usr/share/exhibition/rest_server/data/sql + sqlDirectory: /usr/share/exhibition/data/sql threads: 2 watchDogPause: 60 # logFile: /var/log/local/exhibition.log @@ -185,7 +185,7 @@ trace: answerLength: 200 db: db: appexhibition - user: jonny + user: exhibition code: "Top Secret" host: localhost port: 3306 @@ -240,7 +240,28 @@ clientSessionTimeout: 900 service.uninstallService(appName, user: appName, group: appName); } + /// Returns the program version. static String version() => _version; + static Future sql(List args, ArgResults results) async { + final logger = MemoryLogger(LEVEL_FINE); + final rc = StringBuffer(); + var filename = results['configuration']; + if (!File(filename).existsSync()) { + print('+++ missing $filename'); + unittestLogger?.error('missing configuration: $filename'); + } else { + final configuration = Configuration.fromFile(filename, logger); + MySqlDb db = MySqlDb.fromConfiguration(configuration, logger); + await db.connect(); + if (db.hasConnection){ + for (var filename in args){ + logger.log('=== script $filename:', LEVEL_DETAIL); + rc.writeln(await db.executeScriptFile(filename)); + } + } + } + return rc.toString(); + } } /// Implements an isolate instance. @@ -282,7 +303,7 @@ class ServiceWorker { buffer.write('['); String? separator; for (var item in list) { - if (separator == null){ + if (separator == null) { separator = ','; } else { buffer.write(separator); @@ -292,7 +313,7 @@ class ServiceWorker { } else if (item is Map) { rowToJson(item, buffer); } else if (item is Iterable) { - arrayToJson(item, buffer); + arrayToJson(item, buffer); } } buffer.write(']'); @@ -681,8 +702,7 @@ class ServiceWorker { } break; case SqlStatementType.update: - final count = - await db!.updateRaw(sql, params: positionalParameters); + final count = await db!.updateRaw(sql, params: positionalParameters); rc = 'rows:$count'; break; default: diff --git a/rest_server/lib/services.dart b/rest_server/lib/services.dart index 88dff1b..0052deb 100644 --- a/rest_server/lib/services.dart +++ b/rest_server/lib/services.dart @@ -19,7 +19,7 @@ Future run(List args) async { if (results['help']) { try { try { - final serviceName = 'exhibition-sv'; + final serviceName = 'exhibition.sv'; print('''Usage $serviceName [mode] [options] Starts a REST server answering the client database requests. : @@ -31,6 +31,8 @@ Future run(List args) async { Uninstalls the program. version Displays the version. + sql [ ...] + Executes a SQL script