From 84fe935fcd25285b87cdee841149e29d24295b15 Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Tue, 18 Oct 2022 11:37:00 +0800 Subject: [PATCH] Bug Fix --- lib/chat/cubit/chat_cubit.dart | 4 +- .../view/input_box/cubit/input_cubit.dart | 4 +- lib/chat/view/input_box/input_box.dart | 36 ++++++++--- lib/home/cubit/home_cubit.dart | 31 ++++++++- lib/home/view/contact_page/contact_page.dart | 64 +++++++++---------- .../view/contact_page/view/contact_tile.dart | 4 +- .../message_page/models/message_info.dart | 3 +- .../view/message_page/view/message_tile.dart | 7 +- .../profile_page/cubit/log_out_cubit.dart | 3 +- .../cubit/initialization_cubit.dart | 8 ++- .../local_service_repository.dart | 34 ++++++++-- .../tcp_repository/tcp_repository.dart | 37 +++++++---- lib/search/view/history_tile.dart | 5 +- lib/search/view/user_tile.dart | 5 +- linux/CMakeLists.txt | 2 +- windows/CMakeLists.txt | 2 +- windows/runner/Runner.rc | 12 ++-- 17 files changed, 168 insertions(+), 93 deletions(-) diff --git a/lib/chat/cubit/chat_cubit.dart b/lib/chat/cubit/chat_cubit.dart index ee65cfd..306ea1f 100644 --- a/lib/chat/cubit/chat_cubit.dart +++ b/lib/chat/cubit/chat_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:03:56 - * @LastEditTime : 2022-10-17 17:43:55 + * @LastEditTime : 2022-10-18 11:25:04 * @Description : */ @@ -9,7 +9,6 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:open_file/open_file.dart'; -import 'package:path/path.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tcp_client/chat/cubit/chat_state.dart'; import 'package:tcp_client/chat/model/chat_history.dart'; @@ -158,7 +157,6 @@ class ChatCubit extends Cubit { response as ForwardMessageResponse; if(response.message.senderID == userID || response.message.recieverID == userID) { // Message storage will be handled by home bloc listener - // addMessage(response.message); //Emit new state var newHistory = ChatHistory( message: response.message, diff --git a/lib/chat/view/input_box/cubit/input_cubit.dart b/lib/chat/view/input_box/cubit/input_cubit.dart index a19b162..bfd99f1 100644 --- a/lib/chat/view/input_box/cubit/input_cubit.dart +++ b/lib/chat/view/input_box/cubit/input_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 21:57:05 - * @LastEditTime : 2022-10-14 22:55:16 + * @LastEditTime : 2022-10-18 11:25:09 * @Description : */ @@ -12,8 +12,6 @@ import 'package:tcp_client/chat/cubit/chat_cubit.dart'; import 'package:tcp_client/chat/view/input_box/cubit/input_state.dart'; import 'package:tcp_client/chat/view/input_box/model/input.dart'; import 'package:tcp_client/repositories/common_models/message.dart'; -import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart'; -import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart'; class MessageInputCubit extends Cubit { MessageInputCubit({ diff --git a/lib/chat/view/input_box/input_box.dart b/lib/chat/view/input_box/input_box.dart index 711138f..a20c8c8 100644 --- a/lib/chat/view/input_box/input_box.dart +++ b/lib/chat/view/input_box/input_box.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 17:54:30 - * @LastEditTime : 2022-10-15 00:27:39 + * @LastEditTime : 2022-10-18 11:25:12 * @Description : */ @@ -16,7 +16,6 @@ import 'package:tcp_client/chat/view/input_box/cubit/input_cubit.dart'; import 'package:tcp_client/chat/view/input_box/cubit/input_state.dart'; import 'package:tcp_client/chat/view/input_box/model/input.dart'; import 'package:tcp_client/repositories/common_models/message.dart'; -import 'package:tcp_client/repositories/tcp_repository/models/tcp_request.dart'; class InputBox extends StatelessWidget { InputBox({super.key}); @@ -29,9 +28,11 @@ class InputBox extends StatelessWidget { create:(context) => MessageInputCubit( chatCubit: context.read() ), - child: SizedBox( - height: 64, + child: Container( + // height: 64, + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), child: Row( + crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: BlocListener( @@ -39,16 +40,29 @@ class InputBox extends StatelessWidget { listener: (context, state) { _controller.clear(); }, - child: Builder( + child: Container( + constraints: const BoxConstraints(maxHeight: 120), + child: Builder( builder: (context) => TextField( - controller: _controller, - onChanged: (value) { - context.read().onInputChange(MessageInput.dirty(value)); - }, + controller: _controller, + onChanged: (value) { + context.read().onInputChange(MessageInput.dirty(value)); + }, + maxLines: null, + decoration: const InputDecoration( + border: UnderlineInputBorder( + borderSide: BorderSide( + width: 1.0 + ) + ), + hintText: 'Input message here' + ), + ), ), ) ), ), + const SizedBox(width: 8.0,), IconButton( onPressed: () { var chatCubit = context.read(); @@ -66,8 +80,9 @@ class InputBox extends StatelessWidget { } }); }, - icon: const Icon(Icons.attach_file_rounded) + icon: Icon(Icons.attach_file_rounded, color: Colors.grey[700],) ), + const SizedBox(width: 8.0,), BlocBuilder( builder:(context, state) { return IconButton( @@ -75,6 +90,7 @@ class InputBox extends StatelessWidget { context.read().onSubmission(); } : null, icon: const Icon(Icons.send_rounded), + color: state.status == FormzStatus.valid ? Colors.blue : Colors.grey[400], ); }, ) diff --git a/lib/home/cubit/home_cubit.dart b/lib/home/cubit/home_cubit.dart index 38f57d2..d24e67e 100644 --- a/lib/home/cubit/home_cubit.dart +++ b/lib/home/cubit/home_cubit.dart @@ -1,14 +1,17 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:02:28 - * @LastEditTime : 2022-10-17 17:00:45 + * @LastEditTime : 2022-10-17 19:26:13 * @Description : */ +import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:flutter/cupertino.dart'; import 'package:tcp_client/home/cubit/home_state.dart'; import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart'; +import 'package:tcp_client/repositories/tcp_repository/models/tcp_response.dart'; import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart'; class HomeCubit extends Cubit { @@ -20,11 +23,13 @@ class HomeCubit extends Cubit { pageController.addListener(() { emit(state.copyWith(page: HomePagePosition.fromValue((pageController.page ?? 0).round()))); }); + subscription = tcpRepository.responseStreamBroadcast.listen(_onTCPResponse); } final LocalServiceRepository localServiceRepository; final TCPRepository tcpRepository; final PageController pageController; + late final StreamSubscription subscription; void switchPage(HomePagePosition newPage) { pageController.animateToPage( @@ -33,4 +38,28 @@ class HomeCubit extends Cubit { curve: Curves.easeInOutCubicEmphasized ); } + + void _onTCPResponse(TCPResponse response) { + switch(response.type) { + case TCPResponseType.forwardMessage: { + response as ForwardMessageResponse; + localServiceRepository.storeMessages([response.message]); + break; + } + case TCPResponseType.fetchMessage: { + response as FetchMessageResponse; + localServiceRepository.storeMessages(response.messages); + break; + } + default: { + break; + } + } + } + + @override + Future close() { + subscription.cancel(); + return super.close(); + } } diff --git a/lib/home/view/contact_page/contact_page.dart b/lib/home/view/contact_page/contact_page.dart index 67a081c..71d81c3 100644 --- a/lib/home/view/contact_page/contact_page.dart +++ b/lib/home/view/contact_page/contact_page.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-12 23:36:07 - * @LastEditTime : 2022-10-17 17:35:05 + * @LastEditTime : 2022-10-18 11:25:18 * @Description : */ @@ -18,39 +18,37 @@ class ContactPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - child: BlocBuilder( - builder: (context, state) { - var indexedData = state.indexedData; - return AzListView( - data: indexedData, - itemCount: indexedData.length, - itemBuilder: (context, index) { - return ContactTile( - userInfo: (indexedData[index] as ContactModel).userInfo, - ); - }, - physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), - susItemBuilder: (context, index) { - return Container( - height: 40, - width: MediaQuery.of(context).size.width, - padding: const EdgeInsets.only(left: 16.0), - color: Colors.grey[200], - alignment: Alignment.centerLeft, - child: Text( - indexedData[index].getSuspensionTag(), - softWrap: false, - style: TextStyle( - fontSize: 14.0, - color: Colors.grey[700], - ), + return BlocBuilder( + builder: (context, state) { + var indexedData = state.indexedData; + return AzListView( + data: indexedData, + itemCount: indexedData.length, + itemBuilder: (context, index) { + return ContactTile( + userInfo: (indexedData[index] as ContactModel).userInfo, + ); + }, + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + susItemBuilder: (context, index) { + return Container( + height: 40, + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.only(left: 16.0), + color: Colors.grey[200], + alignment: Alignment.centerLeft, + child: Text( + indexedData[index].getSuspensionTag(), + softWrap: false, + style: TextStyle( + fontSize: 14.0, + color: Colors.grey[700], ), - ); - }, - ); - }, - ), + ), + ); + }, + ); + }, ); } } diff --git a/lib/home/view/contact_page/view/contact_tile.dart b/lib/home/view/contact_page/view/contact_tile.dart index 09eb6b1..7bb638b 100644 --- a/lib/home/view/contact_page/view/contact_tile.dart +++ b/lib/home/view/contact_page/view/contact_tile.dart @@ -1,12 +1,10 @@ /* * @Author : Linloir * @Date : 2022-10-13 14:02:00 - * @LastEditTime : 2022-10-17 17:19:50 + * @LastEditTime : 2022-10-18 11:25:32 * @Description : */ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tcp_client/chat/chat_page.dart'; diff --git a/lib/home/view/message_page/models/message_info.dart b/lib/home/view/message_page/models/message_info.dart index a58e645..9167d3f 100644 --- a/lib/home/view/message_page/models/message_info.dart +++ b/lib/home/view/message_page/models/message_info.dart @@ -1,13 +1,12 @@ /* * @Author : Linloir * @Date : 2022-10-12 23:48:54 - * @LastEditTime : 2022-10-15 01:01:48 + * @LastEditTime : 2022-10-18 11:25:36 * @Description : */ import 'package:equatable/equatable.dart'; import 'package:tcp_client/repositories/common_models/message.dart'; -import 'package:tcp_client/repositories/common_models/userinfo.dart'; class MessageInfo extends Equatable { final Message? message; diff --git a/lib/home/view/message_page/view/message_tile.dart b/lib/home/view/message_page/view/message_tile.dart index 7d2d58c..e84b616 100644 --- a/lib/home/view/message_page/view/message_tile.dart +++ b/lib/home/view/message_page/view/message_tile.dart @@ -1,19 +1,16 @@ /* * @Author : Linloir * @Date : 2022-10-13 13:17:52 - * @LastEditTime : 2022-10-17 17:18:22 + * @LastEditTime : 2022-10-18 11:25:40 * @Description : */ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tcp_client/chat/chat_page.dart'; import 'package:tcp_client/common/avatar/avatar.dart'; import 'package:tcp_client/common/username/username.dart'; import 'package:tcp_client/repositories/common_models/message.dart'; -import 'package:tcp_client/repositories/common_models/userinfo.dart'; import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart'; import 'package:tcp_client/repositories/tcp_repository/tcp_repository.dart'; import 'package:tcp_client/repositories/user_repository/user_repository.dart'; @@ -110,6 +107,8 @@ class MessageTile extends StatelessWidget { child: IgnorePointer( child: Text( message?.contentDecoded ?? '', + maxLines: 1, + overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 16, ), diff --git a/lib/home/view/profile_page/cubit/log_out_cubit.dart b/lib/home/view/profile_page/cubit/log_out_cubit.dart index 81e700e..ba3a93c 100644 --- a/lib/home/view/profile_page/cubit/log_out_cubit.dart +++ b/lib/home/view/profile_page/cubit/log_out_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-14 10:54:57 - * @LastEditTime : 2022-10-14 11:44:12 + * @LastEditTime : 2022-10-17 20:51:45 * @Description : */ @@ -36,6 +36,7 @@ class LogOutCubit extends Cubit { } void onLogout() async { + emit(LogOutStatus.processing); tcpRepository.pushRequest(LogoutRequest(token: (await SharedPreferences.getInstance()).getInt('token'))); } diff --git a/lib/initialization/cubit/initialization_cubit.dart b/lib/initialization/cubit/initialization_cubit.dart index ec57075..fe48ebb 100644 --- a/lib/initialization/cubit/initialization_cubit.dart +++ b/lib/initialization/cubit/initialization_cubit.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-12 09:56:04 - * @LastEditTime : 2022-10-14 16:47:04 + * @LastEditTime : 2022-10-17 21:11:30 * @Description : */ @@ -22,7 +22,11 @@ class InitializationCubit extends Cubit { TCPRepository? tcpRepository; LocalServiceRepository? localServiceRepository; Future(() async { - localServiceRepository = await LocalServiceRepository.create(databaseFilePath: '${(await getApplicationDocumentsDirectory()).path}/.data/database.db'); + localServiceRepository = await LocalServiceRepository.create( + databaseFilePath: '${ + (await getApplicationDocumentsDirectory()).path + }/LChatClient/.data/database.db' + ); }).then((_) { emit(state.copyWith( databaseStatus: InitializationStatus.done, diff --git a/lib/repositories/local_service_repository/local_service_repository.dart b/lib/repositories/local_service_repository/local_service_repository.dart index 7080f8f..9cc9842 100644 --- a/lib/repositories/local_service_repository/local_service_repository.dart +++ b/lib/repositories/local_service_repository/local_service_repository.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-11 10:56:02 - * @LastEditTime : 2022-10-17 13:01:35 + * @LastEditTime : 2022-10-18 11:27:15 * @Description : Local Service Repository */ @@ -133,20 +133,30 @@ class LocalServiceRepository { } Future> findMessages({required String pattern}) async { + if(pattern.isEmpty) { + return []; + } // Obtain shared preferences. final pref = await SharedPreferences.getInstance(); // Get user info from preferences var currentUserID = pref.getInt('userid'); - var alikeMessages = await _database.query( + var rawMessages = await _database.query( 'msgs', - where: 'userid = ? or targetid = ?', + where: '(userid = ? or targetid = ?)', whereArgs: [ currentUserID, currentUserID ], orderBy: 'timestamp desc', limit: 100 ); - return alikeMessages.map((e) => Message.fromJSONObject(jsonObject: e)).toList(); + List alikeMessages = []; + for(var rawMessage in rawMessages) { + var message = Message.fromJSONObject(jsonObject: rawMessage); + if(message.contentDecoded.contains(pattern)) { + alikeMessages.add(message); + } + } + return alikeMessages; } //Find the most recent message of given users @@ -266,7 +276,7 @@ class LocalServiceRepository { conflictAlgorithm: ConflictAlgorithm.replace ); } catch (err) { - print(err); + //TODO: Log the err } //Clear temp file tempFile.file.delete(); @@ -318,7 +328,19 @@ class LocalServiceRepository { } Future fetchUserInfoViaUsername({required String username}) async { - + var result = await _database.query( + 'users', + where: 'username = ?', + whereArgs: [ + username + ] + ); + if(result.isEmpty) { + return null; + } + else { + return UserInfo.fromJSONObject(jsonObject: result[0]); + } } Future fetchMessage({required String msgmd5}) async { diff --git a/lib/repositories/tcp_repository/tcp_repository.dart b/lib/repositories/tcp_repository/tcp_repository.dart index b4e57eb..2706dc7 100644 --- a/lib/repositories/tcp_repository/tcp_repository.dart +++ b/lib/repositories/tcp_repository/tcp_repository.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-11 09:42:05 - * @LastEditTime : 2022-10-15 16:50:16 + * @LastEditTime : 2022-10-18 11:27:46 * @Description : TCP repository */ @@ -25,13 +25,26 @@ class TCPRepository { required String remoteAddress, required int remotePort }): _socket = socket, _remoteAddress = remoteAddress, _remotePort = remotePort { - _socket.listen(_pullResponse); + Future(() async { + try{ + await for(var response in _socket) { + _pullResponse(response); + } + } catch(e) { + _socket.close(); + } + // _responseRawStreamController.close(); + // _payloadPullStreamController.close(); + // _payloadRawStreamController.close(); + // _responseStreamController.close(); + // _requestStreamController.close(); + }); //This future never ends, would that be bothersome? Future(() async { await for(var request in _requestStreamController.stream) { await _socket.addStream(request.stream); } - }); + }).onError((error, stackTrace) {_socket.close();}); Future(() async { var responseQueue = StreamQueue(_responseRawStreamController.stream); var payloadQueue = StreamQueue(_payloadRawStreamController.stream); @@ -42,7 +55,7 @@ class TCPRepository { } responseQueue.cancel(); payloadQueue.cancel(); - }); + }).onError((error, stackTrace) {_socket.close();}); } static Future create({ @@ -75,6 +88,9 @@ class TCPRepository { //Byte length for subsequent data of the json object int payloadLength = 0; + //Temp filename counter + int _fileCounter = 0; + //Construct a stream which emits events on intact requests final StreamController> _responseRawStreamController = StreamController(); final StreamController _payloadRawStreamController = StreamController(); @@ -147,9 +163,11 @@ class TCPRepository { Future(() async { var documentDirectory = await getApplicationDocumentsDirectory(); //Create temp file to read payload (might be huge) - Directory('${documentDirectory.path}/ChatClient').createSync(); - Directory('${documentDirectory.path}/ChatClient/.tmp').createSync(); - var tempFile = File('${documentDirectory.path}/ChatClient/.tmp/${DateTime.now().microsecondsSinceEpoch}')..createSync(); + Directory('${documentDirectory.path}/LChatClient').createSync(); + Directory('${documentDirectory.path}/LChatClient/.tmp').createSync(); + var tempFile = File('${documentDirectory.path}/LChatClient/.tmp/${DateTime.now().microsecondsSinceEpoch}$_fileCounter')..createSync(); + _fileCounter += 1; + _fileCounter %= 10; await for(var data in payloadPullStream) { await tempFile.writeAsBytes(data, mode: FileMode.append, flush: true); } @@ -342,10 +360,5 @@ class TCPRepository { void dispose() async { await _socket.flush(); await _socket.close(); - _responseRawStreamController.close(); - _payloadPullStreamController.close(); - _payloadRawStreamController.close(); - _responseStreamController.close(); - _requestStreamController.close(); } } \ No newline at end of file diff --git a/lib/search/view/history_tile.dart b/lib/search/view/history_tile.dart index 54e784a..174acf9 100644 --- a/lib/search/view/history_tile.dart +++ b/lib/search/view/history_tile.dart @@ -1,7 +1,7 @@ /* * @Author : Linloir * @Date : 2022-10-13 21:41:49 - * @LastEditTime : 2022-10-14 10:38:31 + * @LastEditTime : 2022-10-17 22:23:46 * @Description : */ @@ -36,6 +36,7 @@ class HistoryTile extends StatelessWidget { const SizedBox(width: 12,), Expanded( child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric( @@ -51,6 +52,8 @@ class HistoryTile extends StatelessWidget { ), child: Text( message.contentDecoded, + maxLines: 1, + overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 16, ), diff --git a/lib/search/view/user_tile.dart b/lib/search/view/user_tile.dart index d2fa21f..5a9dd92 100644 --- a/lib/search/view/user_tile.dart +++ b/lib/search/view/user_tile.dart @@ -1,19 +1,16 @@ /* * @Author : Linloir * @Date : 2022-10-13 21:41:41 - * @LastEditTime : 2022-10-14 12:04:26 + * @LastEditTime : 2022-10-18 11:28:17 * @Description : */ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:tcp_client/common/avatar/avatar.dart'; import 'package:tcp_client/common/username/username.dart'; import 'package:tcp_client/profile/user_profile_page.dart'; import 'package:tcp_client/repositories/common_models/userinfo.dart'; -import 'package:tcp_client/repositories/local_service_repository/local_service_repository.dart'; import 'package:tcp_client/repositories/user_repository/user_repository.dart'; import 'package:tcp_client/search/cubit/search_cubit.dart'; diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 4e66ec3..bb24585 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX) set(BINARY_NAME "tcp_client") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.tcp_client") +set(APPLICATION_ID "com.linloir.tcp_client") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index c4822c1..8723c84 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -4,7 +4,7 @@ project(tcp_client 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 "tcp_client") +set(BINARY_NAME "LChatClient") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 6bfb00a..637643d 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -89,13 +89,13 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "tcp_client" "\0" + VALUE "CompanyName", "com.linloir" "\0" + VALUE "FileDescription", "LChatClient" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "tcp_client" "\0" - VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "tcp_client.exe" "\0" - VALUE "ProductName", "tcp_client" "\0" + VALUE "InternalName", "LChatClient" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.linloir. All rights reserved." "\0" + VALUE "OriginalFilename", "LCatClient.exe" "\0" + VALUE "ProductName", "LCatClient" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END