├── PrinterApp ├── main.ico ├── Images │ ├── splash.jpg │ ├── App.Background.png │ ├── snowflake1.png │ ├── snowflake2.png │ ├── snowflake3.png │ ├── app.profcomff.com.png │ └── profcomff_print.png ├── Fonts │ ├── Roboto-Bold.ttf │ └── Roboto-Regular.ttf ├── FileWithOptions.cs ├── NotifyPropertyChangeBase.cs ├── PrintOptions.cs ├── AssemblyInfo.cs ├── App.xaml ├── WebsocketReceiveOptions.cs ├── MarketingBody.cs ├── MouseOperations.cs ├── PrinterApp.csproj ├── ConfigFile.cs ├── MemoryMonitor.cs ├── PrinterViewModel.cs ├── Compliments.cs ├── App.xaml.cs ├── Marketing.cs ├── MainWindow.xaml.cs ├── MainWindow.xaml ├── AutoUpdater.cs ├── Styles.xaml └── PrinterModel.cs ├── .gitattributes ├── win-printer-app.sln ├── .github └── workflows │ └── deploy-printer-app.yml ├── LICENSE ├── README.md ├── CONTRIBUTING.md └── .gitignore /PrinterApp/main.ico: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bc97e74b232c0d409f51a5b1d7832863a738149750efab1b5cf6ba0f5d2691bc 3 | size 58616 4 | -------------------------------------------------------------------------------- /PrinterApp/Images/splash.jpg: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:199226f7ed436a6d1fa9c4fb7bd481b37ba17a8c62e28a4227e7d39d6b0fa8ed 3 | size 36020 4 | -------------------------------------------------------------------------------- /PrinterApp/Fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:ec685a46105296fe46c8744da4a11cf8118ba6c11271941766f7a546df6aa7c7 3 | size 167336 4 | -------------------------------------------------------------------------------- /PrinterApp/Fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4e147ab64b9fdf6d89d01f6b8c3ca0b3cddc59d608a8e2218f9a2504b5c98e14 3 | size 168260 4 | -------------------------------------------------------------------------------- /PrinterApp/Images/App.Background.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d457f05c1e8a8745106b941b24404379add131385bc65d5617abd02dd8753563 3 | size 36738 4 | -------------------------------------------------------------------------------- /PrinterApp/Images/snowflake1.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:b213f9c1b3d3f1b1f04e2084554f0644709a060e25d51754907cf38beba4f08a 3 | size 38669 4 | -------------------------------------------------------------------------------- /PrinterApp/Images/snowflake2.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:da82dab30d0cfc306aa5ce362501d86441df010dd23a4cbec8cb4f50941841b6 3 | size 33575 4 | -------------------------------------------------------------------------------- /PrinterApp/Images/snowflake3.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:679ff2f992f2500a9d4c374bb1abdb4390b638022bfe095faeb912f4253494e6 3 | size 32077 4 | -------------------------------------------------------------------------------- /PrinterApp/Images/app.profcomff.com.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f5e520ee449d49d29bb8688d0c3af82e13d1fe693d539ebeee51c8a4b633c46e 3 | size 11109 4 | -------------------------------------------------------------------------------- /PrinterApp/Images/profcomff_print.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:e237335d78d2c2fd81b2406c70185a3871107f4ff34e1165204781a247d589ae 3 | size 13226 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png filter=lfs diff=lfs merge=lfs -text 2 | *.jpg filter=lfs diff=lfs merge=lfs -text 3 | *.svg filter=lfs diff=lfs merge=lfs -text 4 | *.ttf filter=lfs diff=lfs merge=lfs -text 5 | *.ico filter=lfs diff=lfs merge=lfs -text 6 | -------------------------------------------------------------------------------- /PrinterApp/FileWithOptions.cs: -------------------------------------------------------------------------------- 1 | namespace PrinterApp; 2 | 3 | public class FileWithOptions 4 | { 5 | public FileWithOptions(string filename, PrintOptions options) 6 | { 7 | Filename = filename; 8 | Options = options; 9 | } 10 | 11 | public string Filename { get; } 12 | public PrintOptions Options { get; } 13 | } -------------------------------------------------------------------------------- /PrinterApp/NotifyPropertyChangeBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace PrinterApp; 5 | 6 | public class NotifyPropertyChangeBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler? PropertyChanged; 9 | 10 | protected void OnPropertyChanged([CallerMemberName] string prop = "") 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop)); 13 | } 14 | } -------------------------------------------------------------------------------- /PrinterApp/PrintOptions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PrinterApp; 4 | 5 | public class PrintOptions 6 | { 7 | public PrintOptions(string pages, int copies, bool twoSided, string format) 8 | { 9 | Pages = pages; 10 | Copies = copies; 11 | TwoSided = twoSided; 12 | Format = format; 13 | } 14 | 15 | public string Pages { get; } 16 | public int Copies { get; } 17 | [JsonProperty("two_sided")] public bool TwoSided { get; } 18 | public string Format { get; } 19 | } -------------------------------------------------------------------------------- /PrinterApp/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /PrinterApp/App.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PrinterApp/WebsocketReceiveOptions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PrinterApp; 4 | 5 | public class WebsocketReceiveOptions 6 | { 7 | public WebsocketReceiveOptions(string qrToken, FileWithOptions[] files, bool manualUpdate, 8 | bool reboot, string error) 9 | { 10 | QrToken = qrToken; 11 | Files = files; 12 | ManualUpdate = manualUpdate; 13 | Reboot = reboot; 14 | Error = error; 15 | } 16 | 17 | [JsonProperty("qr_token")] public string QrToken { get; } 18 | [JsonProperty("files")] public FileWithOptions[] Files { get; } 19 | [JsonProperty("manual_update")] public bool ManualUpdate { get; } 20 | [JsonProperty("reboot")] public bool Reboot { get; } 21 | [JsonProperty("error")] public string Error { get; } 22 | } -------------------------------------------------------------------------------- /win-printer-app.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32929.385 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PrinterApp", "PrinterApp\PrinterApp.csproj", "{B13A8300-776F-4B33-8F64-5350BDAF50E2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x86 = Debug|x86 11 | Release|x86 = Release|x86 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B13A8300-776F-4B33-8F64-5350BDAF50E2}.Debug|x86.ActiveCfg = Debug|x86 15 | {B13A8300-776F-4B33-8F64-5350BDAF50E2}.Debug|x86.Build.0 = Debug|x86 16 | {B13A8300-776F-4B33-8F64-5350BDAF50E2}.Release|x86.ActiveCfg = Release|x86 17 | {B13A8300-776F-4B33-8F64-5350BDAF50E2}.Release|x86.Build.0 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {6C13DEB1-7F87-4017-81C2-E7B0713DA032} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy-printer-app.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy PrinterApp x86" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | env: 9 | PROJECT_PATH: PrinterApp/PrinterApp.csproj 10 | 11 | jobs: 12 | build_release: 13 | runs-on: windows-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | lfs: true 18 | 19 | - uses: actions/setup-dotnet@v3 20 | with: 21 | dotnet-version: 8.0.x 22 | 23 | - run: dotnet restore ${{ env.PROJECT_PATH }} 24 | 25 | - run: dotnet publish ${{ env.PROJECT_PATH }} -c Release --self-contained -r win-x86 -p:PublishSingleFile=true -p:PlatformTarget=x86 --no-restore 26 | 27 | - name: Archive Release 28 | uses: thedoctor0/zip-release@main 29 | with: 30 | type: "zip" 31 | directory: "PrinterApp/bin/Release/net8.0-windows/win-x86/publish/" 32 | filename: "PrinterApp_x86.zip" 33 | exclusions: "*.pdb" 34 | 35 | - name: GH Release 36 | uses: softprops/action-gh-release@v0.1.15 37 | if: startsWith(github.ref, 'refs/tags/') 38 | with: 39 | files: "PrinterApp/bin/Release/net8.0-windows/win-x86/publish/PrinterApp_x86.zip" 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, Профком студентов физфака МГУ 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /PrinterApp/MarketingBody.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace PrinterApp; 4 | 5 | public class MarketingBody 6 | { 7 | public class AdditionalData 8 | { 9 | public AdditionalData(string status, string app_version, string terminal_user_id) 10 | { 11 | this.status = status; 12 | this.app_version = app_version; 13 | this.terminal_user_id = terminal_user_id; 14 | } 15 | 16 | public AdditionalData(string status, float available_mem, float current_mem, 17 | string app_version, string terminal_user_id) : this(status, app_version, 18 | terminal_user_id) 19 | { 20 | this.available_mem = available_mem; 21 | this.current_mem = current_mem; 22 | } 23 | 24 | public string? status { get; } 25 | public string? app_version { get; } 26 | public string? terminal_user_id { get; } 27 | public float? available_mem { get; } 28 | public float? current_mem { get; } 29 | } 30 | 31 | public MarketingBody(string action, AdditionalData additional_data) 32 | { 33 | this.action = action; 34 | JsonSerializerSettings jsonSerializerSettings = new() 35 | { 36 | NullValueHandling = NullValueHandling.Ignore, 37 | StringEscapeHandling = StringEscapeHandling.Default 38 | }; 39 | this.additional_data = 40 | JsonConvert.SerializeObject(additional_data, jsonSerializerSettings); 41 | } 42 | 43 | public MarketingBody(string action, AdditionalData additional_data, string path_from, 44 | string path_to) : this(action, additional_data) 45 | { 46 | this.path_from = path_from; 47 | this.path_to = path_to; 48 | } 49 | 50 | public int user_id { get; set; } = -1; 51 | public string action { get; } 52 | public string additional_data { get; } 53 | public string? path_from { get; } 54 | public string? path_to { get; } 55 | } -------------------------------------------------------------------------------- /PrinterApp/MouseOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace PrinterApp; 5 | 6 | public class MouseOperations 7 | { 8 | [Flags] 9 | public enum MouseEventFlags 10 | { 11 | LeftDown = 0x00000002, 12 | LeftUp = 0x00000004, 13 | MiddleDown = 0x00000020, 14 | MiddleUp = 0x00000040, 15 | Move = 0x00000001, 16 | Absolute = 0x00008000, 17 | RightDown = 0x00000008, 18 | RightUp = 0x00000010 19 | } 20 | 21 | [DllImport("user32.dll", EntryPoint = "SetCursorPos")] 22 | [return: MarshalAs(UnmanagedType.Bool)] 23 | private static extern bool SetCursorPos(int x, int y); 24 | 25 | [DllImport("user32.dll")] 26 | [return: MarshalAs(UnmanagedType.Bool)] 27 | private static extern bool GetCursorPos(out MousePoint lpMousePoint); 28 | 29 | [DllImport("user32.dll")] 30 | private static extern void 31 | mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo); 32 | 33 | public static void SetCursorPosition(int x, int y) 34 | { 35 | SetCursorPos(x, y); 36 | } 37 | 38 | public static void SetCursorPosition(MousePoint point) 39 | { 40 | SetCursorPos(point.X, point.Y); 41 | } 42 | 43 | public static MousePoint GetCursorPosition() 44 | { 45 | MousePoint currentMousePoint; 46 | var gotPoint = GetCursorPos(out currentMousePoint); 47 | if (!gotPoint) 48 | { 49 | currentMousePoint = new MousePoint(0, 0); 50 | } 51 | 52 | return currentMousePoint; 53 | } 54 | 55 | public static void MouseEvent(MouseEventFlags value) 56 | { 57 | MousePoint position = GetCursorPosition(); 58 | 59 | mouse_event 60 | ((int)value, 61 | position.X, 62 | position.Y, 63 | 0, 64 | 0) 65 | ; 66 | } 67 | 68 | [StructLayout(LayoutKind.Sequential)] 69 | public struct MousePoint 70 | { 71 | public int X; 72 | public int Y; 73 | 74 | public MousePoint(int x, int y) 75 | { 76 | X = x; 77 | Y = y; 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /PrinterApp/PrinterApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | x86 9 | win-x86 10 | main.ico 11 | PrinterApp 12 | gui for work https://app.profcomff.com/print/docs 13 | Dyakov EI 14 | 2.1.12.9 15 | dyakov.space 16 | dyakov.space @ 2023 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /PrinterApp/ConfigFile.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Serilog; 3 | using System; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | namespace PrinterApp; 8 | 9 | public class ConfigFile 10 | { 11 | public string ExitCode { get; set; } = "dyakov"; 12 | public string TempSavePath { get; set; } = Path.GetTempPath() + ".printerApp"; 13 | public bool StartWithWindows { get; set; } 14 | public bool AutoUpdate { get; set; } = true; 15 | public string AuthorizationToken { get; set; } = "token"; 16 | 17 | public void LoadConfig(string fileName) 18 | { 19 | Log.Information($"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name} start"); 20 | var configPath = 21 | Path.Combine( 22 | Path.GetDirectoryName(Environment.ProcessPath) ?? 23 | Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), 24 | $"{fileName}.json"); 25 | Log.Debug( 26 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Full config file patch: {configPath}"); 27 | if (File.Exists(configPath)) 28 | { 29 | try 30 | { 31 | var config = 32 | JsonConvert.DeserializeObject(File.ReadAllText(configPath)); 33 | if (config != null) 34 | { 35 | ExitCode = config.ExitCode; 36 | TempSavePath = config.TempSavePath; 37 | StartWithWindows = config.StartWithWindows; 38 | AutoUpdate = config.AutoUpdate; 39 | AuthorizationToken = config.AuthorizationToken; 40 | WriteConfig(configPath); 41 | Log.Debug("Load from config file\n" + 42 | JsonConvert.SerializeObject(this, Formatting.Indented)); 43 | } 44 | } 45 | catch (Exception e) 46 | { 47 | Log.Error( 48 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 49 | Console.WriteLine( 50 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 51 | WriteConfig(configPath); 52 | } 53 | } 54 | else 55 | { 56 | WriteConfig(configPath); 57 | } 58 | 59 | Log.Information($"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: finish"); 60 | } 61 | 62 | private void WriteConfig(string configPath) 63 | { 64 | try 65 | { 66 | File.WriteAllText(configPath, 67 | JsonConvert.SerializeObject(this, Formatting.Indented)); 68 | } 69 | catch (Exception e) 70 | { 71 | Console.WriteLine( 72 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: write error {e.Message}"); 73 | Log.Error( 74 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: write error {e.Message}"); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Терминал печати 2 | 3 | ![brutal-printer](https://user-images.githubusercontent.com/13213573/200373331-70c45e14-a81f-4069-8fcb-0a020ca89832.png) 4 | 5 | [![Release](https://github.com/profcomff/print-winapp/actions/workflows/deploy-printer-app.yml/badge.svg)](https://github.com/profcomff/print-winapp/actions/workflows/deploy-printer-app.yml/badge.svg) 6 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/profcomff/print-winapp) 7 | 8 | Позволяет выводить файл на печать, после загрузки файла на [printer.ui.profcomff.com](https://printer.ui.profcomff.com/). 9 | Мотивация создания была в том что была необходима система, позволяющая ограничить пользователю доступ к операционной системе и сократить количество действий для получения напечатанного документа. 10 | 11 | ## Функционал 12 | 13 | * Сокрытие доступа к операционной системе для пользователя. 14 | * Передачу скачанных документов pdf на печать через [Sumatra PDF](https://www.sumatrapdfreader.org/download-free-pdf-viewer) с параметрами пользователя. 15 | * Позволяет пользователю отправить файл на печать при помощи ввода кода документа. 16 | * Позволяет пользователю отправить файл на печать при помощи сканирования QR кода. 17 | * После успешной печати выдает комплимент пользователю. 18 | * Автоматическая смена дизайна на Новогодний период. 19 | * Имеет функцию автоматического обновления программы. 20 | * Имеет функцию автоматического обновления по запросу с сервера. 21 | * Имеет функцию автоматической перезагрузки по запросу с сервера. 22 | 23 | ## Быстрый старт 24 | 25 | ### Зависимости 26 | 27 | * Windows 10 и старше. 28 | * Для работы программы требуется наличие установленной программы просмотра PDF файлов [Sumatra PDF](https://www.sumatrapdfreader.org/download-free-pdf-viewer) (по стандартному ее пути установки или переносимой версии по пути `<терминал печати>/SumatraPDF/SumatraPDF.exe`). 29 | 30 | ### Установка 31 | 32 | * Скачайте последний архив с [выпуском](https://github.com/profcomff/print-winapp/releases/latest) программы. 33 | * Распакуйте архив (рекомендуется использовать путь `%localappdata%/PrinterWinApp`). 34 | * Запустите `PrinterApp.exe` в первый раз, затем появится файл настроек `PrinterApp.json`. 35 | 36 | Пример файла настроек `PrinterApp.json`: 37 | 38 | ```json 39 | { 40 | "ExitCode": "dyakov", 41 | "TempSavePath": "C:\\Users\\dyakov\\AppData\\Local\\Temp\\.printerApp", 42 | "StartWithWindows": false, 43 | "AutoUpdate": true 44 | } 45 | ``` 46 | 47 | ### Дополнительно 48 | 49 | Программа автоматически записывает историю своей работы в файл в папке `%userprofile%/.printerAppLogs/`. 50 | Путь для временного хранения файлов находится в `%temp%/.printerApp/`. 51 | 52 | ## Руководство по внесению изменений 53 | 54 | Программа написана под Windows на .NET 8 с использованием технологии [Windows Presentation Foundation](https://learn.microsoft.com/en-us/dotnet/desktop/wpf/?view=netdesktop-8.0). 55 | Минимально для сборки проекта понадобится установленный [Microsoft .NET 8 SDK](https://dotnet.microsoft.com/en-us/download). Для графического редактирования интерфейсов рекомендуется использовать microsoft Visual Studio Blend 2022. 56 | [Продолжение в CONTRIBUTING.md](CONTRIBUTING.md) 57 | 58 | ![doom-bigfont-good-luck-newbie](https://user-images.githubusercontent.com/13213573/200591035-6a69a06e-21dd-4145-a492-4c78a36e750b.png) 59 | -------------------------------------------------------------------------------- /PrinterApp/MemoryMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | using System.Windows.Threading; 6 | using Serilog; 7 | 8 | namespace PrinterApp; 9 | 10 | public class MemoryMonitor 11 | { 12 | private readonly DispatcherTimer _dispatcherTimer; 13 | private PerformanceCounter _systemAvailableMemoryCounter = null!; 14 | private PerformanceCounter _currentProcessUsingMemoryCounter = null!; 15 | 16 | //the interval is once an hour 17 | private const int LastWarningSendDelaySec = 60 * 60; 18 | 19 | //the interval is once an hour 20 | private const int LastStatusSendDelaySec = 60 * 60; 21 | private int _lastWarningSendSec; 22 | private int _lastStatusSendSec; 23 | 24 | public MemoryMonitor() 25 | { 26 | _dispatcherTimer = new DispatcherTimer 27 | { 28 | //interval of times per second 29 | Interval = new TimeSpan(0, 0, 0, 1, 0) 30 | }; 31 | _dispatcherTimer.Tick += MemoryMonitorTick; 32 | } 33 | 34 | ~MemoryMonitor() 35 | { 36 | if (_systemAvailableMemoryCounter != null!) 37 | _systemAvailableMemoryCounter.Dispose(); 38 | if (_currentProcessUsingMemoryCounter != null!) 39 | _currentProcessUsingMemoryCounter.Dispose(); 40 | } 41 | 42 | public void StartTimer() 43 | { 44 | if (_dispatcherTimer.IsEnabled) return; 45 | new Task(() => 46 | { 47 | _systemAvailableMemoryCounter = 48 | new PerformanceCounter("Memory", "Available MBytes"); 49 | _currentProcessUsingMemoryCounter = 50 | new PerformanceCounter("Process", "Working Set", 51 | Process.GetCurrentProcess().ProcessName); 52 | _dispatcherTimer.Start(); 53 | Log.Information( 54 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Start timer"); 55 | }).Start(); 56 | } 57 | 58 | public void StopTimer() 59 | { 60 | Log.Information( 61 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Stop timer"); 62 | _dispatcherTimer.Stop(); 63 | } 64 | 65 | private void MemoryMonitorTick(object? sender, EventArgs e) 66 | { 67 | var currentProcessUsingMemoryMBytes = 68 | _currentProcessUsingMemoryCounter.NextValue() / 1024f / 1024f; 69 | var systemAvailableMemoryMBytes = _systemAvailableMemoryCounter.NextValue(); 70 | if (currentProcessUsingMemoryMBytes > 300 && _lastWarningSendSec == 0) 71 | { 72 | Marketing.MemoryStatusWarning("high memory consumption by the program", 73 | systemAvailableMemoryMBytes, 74 | currentProcessUsingMemoryMBytes); 75 | _lastWarningSendSec = LastWarningSendDelaySec; 76 | } 77 | else if (systemAvailableMemoryMBytes < 500 && _lastWarningSendSec == 0) 78 | { 79 | Marketing.MemoryStatusWarning("low system RAM", systemAvailableMemoryMBytes, 80 | currentProcessUsingMemoryMBytes); 81 | _lastWarningSendSec = LastWarningSendDelaySec; 82 | } 83 | 84 | if (_lastStatusSendSec == 0) 85 | { 86 | Marketing.MemoryStatus(systemAvailableMemoryMBytes, currentProcessUsingMemoryMBytes); 87 | _lastStatusSendSec = LastStatusSendDelaySec; 88 | } 89 | 90 | if (_lastStatusSendSec > 0) 91 | { 92 | _lastStatusSendSec--; 93 | } 94 | 95 | if (_lastWarningSendSec > 0) 96 | { 97 | _lastWarningSendSec--; 98 | } 99 | 100 | Log.Debug($"{currentProcessUsingMemoryMBytes} {systemAvailableMemoryMBytes}"); 101 | } 102 | } -------------------------------------------------------------------------------- /PrinterApp/PrinterViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.Windows; 3 | using System.Windows.Media; 4 | 5 | namespace PrinterApp; 6 | 7 | public class PrinterViewModel : NotifyPropertyChangeBase 8 | { 9 | private bool _downloadNotInProgress = true; 10 | private string _codeTextBoxText = ""; 11 | private string _errorTextBlockText = ""; 12 | private Visibility _errorTextBlockVisibility = Visibility.Collapsed; 13 | private Geometry _printQr = Geometry.Empty; 14 | private Visibility _printQrVisibility = Visibility.Visible; 15 | private string _compliment = ""; 16 | private Visibility _flakesVisibility = Visibility.Collapsed; 17 | private ObservableCollection _flakesCanvasTop = new(); 18 | private ObservableCollection _flakesCanvasLeft = new(); 19 | 20 | public bool DownloadNotInProgress 21 | { 22 | get => _downloadNotInProgress; 23 | set 24 | { 25 | if (value == _downloadNotInProgress) return; 26 | _downloadNotInProgress = value; 27 | OnPropertyChanged(); 28 | } 29 | } 30 | 31 | public string CodeTextBoxText 32 | { 33 | get => _codeTextBoxText; 34 | set 35 | { 36 | if (value == _codeTextBoxText) return; 37 | _codeTextBoxText = value.ToUpper(); 38 | OnPropertyChanged(); 39 | } 40 | } 41 | 42 | public string ErrorTextBlockText 43 | { 44 | get => _errorTextBlockText; 45 | set 46 | { 47 | if (value == _errorTextBlockText) return; 48 | _errorTextBlockText = value; 49 | OnPropertyChanged(); 50 | } 51 | } 52 | 53 | public Visibility ErrorTextBlockVisibility 54 | { 55 | get => _errorTextBlockVisibility; 56 | set 57 | { 58 | if (value == _errorTextBlockVisibility) return; 59 | _errorTextBlockVisibility = value; 60 | OnPropertyChanged(); 61 | } 62 | } 63 | 64 | public Geometry PrintQr 65 | { 66 | get => _printQr; 67 | set 68 | { 69 | if (Equals(value, _printQr)) return; 70 | _printQr = value; 71 | OnPropertyChanged(); 72 | } 73 | } 74 | 75 | public Visibility PrintQrVisibility 76 | { 77 | get => _printQrVisibility; 78 | set 79 | { 80 | if (value == _printQrVisibility) return; 81 | _printQrVisibility = value; 82 | OnPropertyChanged(); 83 | } 84 | } 85 | 86 | public string Compliment 87 | { 88 | get => _compliment; 89 | set 90 | { 91 | if (value == _compliment) return; 92 | _compliment = value; 93 | OnPropertyChanged(); 94 | } 95 | } 96 | 97 | public Visibility FlakesVisibility 98 | { 99 | get => _flakesVisibility; 100 | set 101 | { 102 | if (value == _flakesVisibility) return; 103 | _flakesVisibility = value; 104 | OnPropertyChanged(); 105 | } 106 | } 107 | 108 | public ObservableCollection FlakesCanvasTop 109 | { 110 | get => _flakesCanvasTop; 111 | set 112 | { 113 | if (Equals(value, _flakesCanvasTop)) return; 114 | _flakesCanvasTop = value; 115 | OnPropertyChanged(); 116 | } 117 | } 118 | 119 | public ObservableCollection FlakesCanvasLeft 120 | { 121 | get => _flakesCanvasLeft; 122 | set 123 | { 124 | if (Equals(value, _flakesCanvasLeft)) return; 125 | _flakesCanvasLeft = value; 126 | OnPropertyChanged(); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /PrinterApp/Compliments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace PrinterApp; 4 | 5 | public static class Compliments 6 | { 7 | public static string GetRandomCompliment() 8 | { 9 | return Complements[Random.Next(0, Complements.Length - 1)]; 10 | } 11 | 12 | private static readonly Random Random = new(Guid.NewGuid().GetHashCode()); 13 | 14 | private static readonly string[] Complements = 15 | { 16 | "Спасибо, что ты есть.", 17 | "Ты просто совершенство.", 18 | "Я благодарен тому, что знаю тебя.", 19 | "Ты потрясающий!", 20 | "Мне нравится твой стиль.", 21 | "У тебя самый лучший смех.", 22 | "Я ценю тебя.", 23 | "У тебя безупречные манеры.", 24 | "Ты заслуживаешь обнимашки прямо сейчас.", 25 | "Ваша точка зрения, как глоток свежего воздуха.", 26 | "Ты озаряешь пространство.", 27 | "Ты намного полезнее, чем ты думаешь.", 28 | "Твоя доброта - просто, как бальзам на душу.", 29 | "Ты меня вдохновляешь.", 30 | "По шкале от 1 до 10, ты - 100.", 31 | "Внутри ты еще красивее, чем снаружи.", 32 | "Ты смел в своих убеждениях.", 33 | "Спасибо за то, что ты рядом несмотря ни на что.", 34 | "Ты меняешь мир к лучшему.", 35 | "Ты умеешь слушать.", 36 | "Удивляюсь, как у тебя получается всегда отлично выглядеть, даже в трениках.", 37 | "Жизнь была бы намного лучше, если бы больше людей были такими, как ты!", 38 | "Ты пробуждаешь в людях лучшее.", 39 | "Тебе безумно идет этот цвет.", 40 | "У тебя очень красивые руки. Серьезно!", 41 | "Ты всегда знаешь, что сказать.", 42 | "Ты можешь пережить конец света.", 43 | "Ты настоящий профессионал и прекрасно разбираешься во многих вещах.", 44 | "Твой голос великолепен.", 45 | "Ваш творческий потенциал просто неиссякаем.", 46 | "Твое имя так подходит тебе и отражает твой характер.", 47 | "Мне нравится, что ты заставляешь меня задумываться.", 48 | "Твоя энергия заразительна.", 49 | "Когда ты рядом, кажется, что нет ничего невозможного.", 50 | "У тебя такой мягкий взгляд.", 51 | "Твой смех - мой любимый звук.", 52 | "Мир был бы таким скучным без тебя.", 53 | "Ты впечатляешь меня каждый день.", 54 | "Я бы поучился у тебя человечности.", 55 | "В мире нет двух таких, как ты.", 56 | "Ты реальный супергерой.", 57 | "Ты меня мотивируешь.", 58 | "Некоторые люди, словно радиаторы - когда они рядом, становится теплее.", 59 | "Никогда не встречала таких заботливых людей.", 60 | "Никто не может заставить меня смеяться так, как это делаешь ты.", 61 | "У тебя самое доброе сердце.", 62 | "Знакомство с тобой делает меня счастливее.", 63 | "Забыть тебя невозможно.", 64 | "Никогда не переставай быть собой.", 65 | "Теперь, когда ты рядом, я улыбаюсь гораздо чаще.", 66 | "Твое прикосновение исцеляет.", 67 | "Одно твое присутствие успокаивает.", 68 | "Ты, как жизненный эликсир.", 69 | "Когда ты заходишь в комнату, все меняется.", 70 | "Один взгляд на тебя, и все вокруг исчезает.", 71 | "Когда ты рядом, мне больше ничего не нужно.", 72 | "Ты помогаешь мне забыть о всех невзгодах дня.", 73 | "У тебя прекрасная душа.", 74 | "С тобой и только с тобой я обретаю покой.", 75 | "Ты пример человека, которым я хотел бы стать.", 76 | "Тебе должны поставить памятник.", 77 | "Ты - олицетворение крутости.", 78 | "Что может быть лучше, чем проводить время с тобой.", 79 | "От одной мысли о тебе мне становится тепло и спокойно.", 80 | "Ты многое изменил в моей жизни.", 81 | "Не представляю, что бы я делал без тебя.", 82 | "Ты действительно хорош во всем, что делаешь.", 83 | "С тех пор как я узнал тебя, моя жизнь стала намного лучше.", 84 | "Когда ты станешь знаменитостью, я буду главным в твоем фан-клубе." 85 | }; 86 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Руководство по внесению изменений 2 | 3 | ## Структура кодовой базы 4 | 5 | Общая структура файлов отталкивается от [паттерна MVVM](https://metanit.com/sharp/wpf/22.1.php) (Model-View-ViewModel) позволяет отделить логику приложения от визуальной части (представления). [Wikipedia Паттерн MVVM](https://ru.wikipedia.org/wiki/Model-View-ViewModel). 6 | 7 | * Папка `Fonts` содержит используемые шрифты приложения в формате `.ttf`. Необходимо чтобы на любом компьютере шрифты были с приложением. 8 | * Папка `Images` содержит используемые картинки. Растровый формат `.png` предпочтительней `.jpg` из за потерь последнего и не поддержке прозрачности. 9 | * Файл `App.xaml`. Этот файл XAML определяет приложение WPF и все его ресурсы. Он также используется для определения пользовательского интерфейса, автоматически отображаемого при запуске приложения (в данном случае — MainWindow.xaml). 10 | * Файл `App.xaml.cs`. Этот файл содержит логику создания и настройки нашего окна из `MainWindow.xaml` и обработку закрытие окна и приложения, также загрузку конфига приложения с помощью вызова `LoadConfig()` у класса `ConfigFile.cs` 11 | * Файл `MainWindow.xaml`. View из паттерна MVVM. Этот файл XAML представляет главное окно приложения, в котором отображается созданное содержимое страниц. Класс Window определяет свойства окна, такие как заголовок, размер и значок. 12 | * Файл `MainWindow.xaml.cs`. Этот файл содержит обработчики событий элементов окна (нажатия на кнопки, вставка в блок с кодом и т.д.) 13 | * Файл `PrinterViewModel.cs`. ViewModel из паттерна MVVM. Этот файл реализует интерфейс `INotifyPropertyChanged` через класс `NotifyPropertyChangeBase.cs` и содержит поля, и свойства этих полей, которые используются для привязки к ним свойств графических элементов в `MainWindow.xaml` 14 | * Файл `PrinterModel.cs`. Model из паттерна MVVM. Содержит основную логику программы и обновляет свойства в `PrinterViewModel.cs`. Содержит обработку нажатия на печать, проводит запросы к [print-api](https://github.com/profcomff/print-api) для валидации кода печати, скачивания файлов, отправки их `Sumatra PDF`, работу c QR. 15 | * Файл `Marketing.cs`. Файл содержит методы для отправки сообщений на сервис [маркетинга](https://github.com/profcomff/marketing-api) 16 | 17 | ## Работа авто-обновления 18 | 19 | За авто-обновления отвечает класс `AutoUpdater.cs`. 20 | Принцип проверки новых версий заключается в том что каждый час сверяется текущее время системы на соответствие промежутку времени с 22:00 до 06:00. Если в него попадаем то делаем запрос на `https://github.com/profcomff/print-winapp/releases/latest` и в ответ получаем ссылку на последний выпуск `https://github.com/profcomff/print-winapp/releases/tag/v2.0.7`. Номер версии полученной от гитхаба дополняем если требуется до шаблона `число.число.число.число` сверяем с текущим номером версии программы 21 | 22 | ```c# 23 | string githubVersion = "2.0.7.0"; 24 | string currentVersion = "2.0.6.0"; 25 | if (githubVersion != currentVersion) 26 | { 27 | //DO download 28 | } 29 | ``` 30 | 31 | Если они отличаются то скачиваем новый архив `PrinterApp_x86.zip` пример запроса `https://github.com/profcomff/print-winapp/releases/download/v2.0.7/PrinterApp_x86.zip` и запускаем обновление. Распаковка архива происходит стандартными средствами windows. Подход в простом сравнении позволяет делать как обновление до новой версии, так и автоматический откат к старой версии. 32 | **Для автоматической сборки и публикации нового выпуска** на github необходимо создать метку вида `v*` например `v2.0.7` (Не стоит создавать релизы иным способом). 33 | 34 | Обновление можно запустить в ручном режиме, начиная с версии v2.0.11, введя в окно кода `UPDATE` и нажав `Alt+F4`. 35 | 36 | ## Версионирование 37 | 38 | При подготовке новой версии программы следует изменять версию программы согласно [нумерации версии программного обеспечения](https://ru.wikipedia.org/wiki/%D0%9D%D1%83%D0%BC%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F_%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D0%B9_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BE%D0%B1%D0%B5%D1%81%D0%BF%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D1%8F#%D0%A3%D0%BA%D0%B0%D0%B7%D0%B0%D0%BD%D0%B8%D0%B5_%D1%81%D1%82%D0%B0%D0%B4%D0%B8%D0%B8_%D1%80%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B8) в файле `PrinterApp/PrinterApp.csproj` переменная `2.0.7.0`. 39 | 40 | ## Дополнительные ссылки 41 | 42 | * [Руководство. Создание простого приложения WPF с помощью C #](https://learn.microsoft.com/ru-ru/visualstudio/get-started/csharp/tutorial-wpf?view=vs-2022) 43 | * [Руководство: Создание первого приложения WPF в Visual Studio 2019](https://learn.microsoft.com/ru-ru/dotnet/desktop/wpf/getting-started/walkthrough-my-first-wpf-desktop-application?view=netframeworkdesktop-4.8) 44 | * [Учебник. Создание приложения WPF с помощью .NET](https://learn.microsoft.com/ru-ru/dotnet/desktop/wpf/get-started/create-app-visual-studio?view=netdesktop-7.0) 45 | * [HttpClient Класс](https://learn.microsoft.com/ru-ru/dotnet/api/system.net.http.httpclient?view=net-7.0) 46 | * [ClientWebSocket Класс](https://learn.microsoft.com/ru-ru/dotnet/api/system.net.websockets.clientwebsocket?view=net-7.0) 47 | -------------------------------------------------------------------------------- /PrinterApp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Threading; 6 | using System.Windows; 7 | 8 | namespace PrinterApp; 9 | 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | private readonly AutoUpdater _autoUpdater = new(); 16 | private readonly ConfigFile _configFile = new(); 17 | private readonly MemoryMonitor _memoryMonitor = new(); 18 | private PrinterModel _printerModel; 19 | private MainWindow _mainWindow; 20 | 21 | private readonly string _assemblyVersion = 22 | Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; 23 | 24 | private App() 25 | { 26 | AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException; 27 | 28 | Thread.CurrentThread.CurrentCulture = 29 | System.Globalization.CultureInfo.CreateSpecificCulture("en-US"); 30 | 31 | var fileName = GetType().Namespace!; 32 | ConfigureLogger(fileName); 33 | _configFile.LoadConfig(fileName); 34 | if (!Directory.Exists(_configFile.TempSavePath)) 35 | Directory.CreateDirectory(_configFile.TempSavePath); 36 | 37 | var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey( 38 | "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true); 39 | var str = Environment.ProcessPath ?? string.Empty; 40 | key?.SetValue(GetType().Namespace, _configFile.StartWithWindows ? str : string.Empty); 41 | 42 | if (_configFile.AutoUpdate) _autoUpdater.StartTimer(); 43 | 44 | _printerModel = new PrinterModel(_configFile, _autoUpdater); 45 | _printerModel.Reboot += RebootHandler; 46 | _memoryMonitor.StartTimer(); 47 | _mainWindow = new MainWindow(_printerModel); 48 | _mainWindow.Title = $"{_mainWindow.Title} {_assemblyVersion}"; 49 | _mainWindow.Closing += MainWindowClosing; 50 | _mainWindow.Closed += MainWindowClosed; 51 | 52 | Marketing.LoadProgram(); 53 | } 54 | 55 | private void CurrentDomainOnUnhandledException(object sender, 56 | UnhandledExceptionEventArgs e) 57 | { 58 | Log.Error((Exception)e.ExceptionObject, "CurrentDomainOnUnhandledException"); 59 | } 60 | 61 | private void RebootHandler() 62 | { 63 | _printerModel.PrinterViewModel.DownloadNotInProgress = false; 64 | _printerModel.PrinterViewModel.PrintQrVisibility = Visibility.Collapsed; 65 | _printerModel.PrinterViewModel.CodeTextBoxText = "REBOOT"; 66 | _mainWindow.Close(); 67 | } 68 | 69 | private void MainWindowClosed(object? sender, EventArgs e) 70 | { 71 | if (_printerModel.PrinterViewModel.CodeTextBoxText != "REBOOT") return; 72 | _printerModel = new PrinterModel(_configFile, _autoUpdater); 73 | _printerModel.Reboot += RebootHandler; 74 | _mainWindow = new MainWindow(_printerModel); 75 | _mainWindow.Title = $"{_mainWindow.Title} {_assemblyVersion}"; 76 | _mainWindow.Closing += MainWindowClosing; 77 | _mainWindow.Closed += MainWindowClosed; 78 | _mainWindow.Show(); 79 | } 80 | 81 | private void MainWindowClosing(object? sender, System.ComponentModel.CancelEventArgs e) 82 | { 83 | switch (_printerModel.PrinterViewModel.CodeTextBoxText) 84 | { 85 | case "UPDATE": 86 | _printerModel.PrinterViewModel.CodeTextBoxText = ""; 87 | _autoUpdater.ManualUpdate(); 88 | e.Cancel = true; 89 | return; 90 | case "REBOOT": 91 | _printerModel.SocketsClose(); 92 | Marketing.ManualReboot(); 93 | return; 94 | } 95 | 96 | if (_printerModel.WrongExitCode()) 97 | { 98 | Marketing.CloseWithoutAccessProgram(); 99 | Log.Information( 100 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Attempt to close without access"); 101 | e.Cancel = true; 102 | return; 103 | } 104 | 105 | Marketing.ManualShutdown(); 106 | _printerModel.SocketsClose(); 107 | _autoUpdater.StopTimer(); 108 | _memoryMonitor.StopTimer(); 109 | Current.Shutdown(); 110 | } 111 | 112 | private void App_OnStartup(object sender, StartupEventArgs e) 113 | { 114 | _mainWindow.Show(); 115 | } 116 | 117 | private void App_OnExit(object sender, ExitEventArgs e) 118 | { 119 | Log.Debug("App_OnExit"); 120 | Log.CloseAndFlush(); 121 | } 122 | 123 | private static void ConfigureLogger(string fileName) 124 | { 125 | #if DEBUG 126 | var log = new LoggerConfiguration() 127 | .MinimumLevel.Debug() 128 | .WriteTo.Debug() 129 | .WriteTo.File( 130 | $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.printerAppLogs/{fileName}.txt", 131 | rollingInterval: RollingInterval.Day) 132 | .CreateLogger(); 133 | #else 134 | var log = new LoggerConfiguration() 135 | .MinimumLevel.Information() 136 | .WriteTo.Console() 137 | .WriteTo.File( 138 | $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}{Path.DirectorySeparatorChar}.printerAppLogs/{fileName}.txt", 139 | rollingInterval: RollingInterval.Day) 140 | .CreateLogger(); 141 | #endif 142 | Log.Logger = log; 143 | } 144 | } -------------------------------------------------------------------------------- /PrinterApp/Marketing.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Net.Http.Json; 4 | using System.Reflection; 5 | 6 | namespace PrinterApp; 7 | 8 | public static class Marketing 9 | { 10 | #if DEBUG 11 | private static readonly HttpClient SharedClient = new HttpClient 12 | { 13 | BaseAddress = new Uri("https://api.test.profcomff.com/marketing/v1/"), 14 | }; 15 | #else 16 | private static readonly HttpClient SharedClient = new HttpClient 17 | { 18 | BaseAddress = new Uri("https://api.profcomff.com/marketing/v1/"), 19 | }; 20 | #endif 21 | 22 | private static readonly string AssemblyVersion = 23 | Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? string.Empty; 24 | 25 | public static string TerminalUserId = ""; 26 | 27 | private static void Post(string action, string status, string pathFrom, 28 | string pathTo) 29 | { 30 | var body = new MarketingBody(action: action, 31 | additional_data: new MarketingBody.AdditionalData(status, AssemblyVersion, 32 | TerminalUserId), 33 | path_from: pathFrom, path_to: pathTo); 34 | SharedClient.PostAsJsonAsync("action", body); 35 | } 36 | 37 | private static void Post(string action, string status) 38 | { 39 | var body = new MarketingBody(action: action, 40 | additional_data: new MarketingBody.AdditionalData(status, AssemblyVersion, 41 | TerminalUserId)); 42 | SharedClient.PostAsJsonAsync("action", body); 43 | } 44 | 45 | private static void Post(string action, string status, float availableMem, 46 | float currentMem) 47 | { 48 | var body = new MarketingBody(action: action, 49 | additional_data: new MarketingBody.AdditionalData(status, availableMem, currentMem, 50 | AssemblyVersion, TerminalUserId)); 51 | SharedClient.PostAsJsonAsync("action", body); 52 | } 53 | 54 | public static void StartDownload(string pathFrom, 55 | string pathTo) 56 | { 57 | Post( 58 | action: "print terminal start download file", 59 | status: "start_download", 60 | pathFrom: pathFrom, 61 | pathTo: pathTo); 62 | } 63 | 64 | public static void DownloadException(string pathFrom, 65 | string status) 66 | { 67 | Post( 68 | action: "print terminal download exception", 69 | status: status, 70 | pathFrom: pathFrom, 71 | pathTo: null!); 72 | } 73 | 74 | public static void FinishDownload(string pathFrom, 75 | string pathTo) 76 | { 77 | Post( 78 | action: "print terminal finish download file", 79 | status: "finish_download", 80 | pathFrom: pathFrom, 81 | pathTo: pathTo); 82 | } 83 | 84 | public static void PrintException(string pathFrom, 85 | string status) 86 | { 87 | Post( 88 | action: "print terminal print exception", 89 | status: status, 90 | pathFrom: pathFrom, 91 | pathTo: null!); 92 | } 93 | 94 | public static void PrintNotFile(string pathFrom) 95 | { 96 | Post( 97 | action: "print terminal check filename", 98 | status: "not_file", 99 | pathFrom: pathFrom, 100 | pathTo: null!); 101 | } 102 | 103 | public static void CheckCode(string pathFrom, 104 | bool statusOk) 105 | { 106 | Post( 107 | action: "print terminal check code", 108 | status: $"{(statusOk ? "check_code_ok" : "check_code_fail")}", 109 | pathFrom: pathFrom, 110 | pathTo: null!); 111 | } 112 | 113 | public static void StartSumatra(string pathFrom) 114 | { 115 | Post( 116 | action: "print terminal start sumatra", 117 | status: "start_sumatra", 118 | pathFrom: pathFrom, 119 | pathTo: null!); 120 | } 121 | 122 | public static void LoadProgram() 123 | { 124 | Post( 125 | action: "print terminal load", 126 | status: "ok"); 127 | } 128 | 129 | public static void MainWindowLoaded() 130 | { 131 | Post( 132 | action: "print terminal main window loaded", 133 | status: "ok"); 134 | } 135 | 136 | public static void CloseWithoutAccessProgram() 137 | { 138 | Post( 139 | action: "print terminal attempt to close without access", 140 | status: "ok"); 141 | } 142 | 143 | public static void UpdateDownloaded() 144 | { 145 | Post( 146 | action: "print terminal update download", 147 | status: "ok"); 148 | } 149 | 150 | public static void ManualUpdate() 151 | { 152 | Post( 153 | action: "print terminal manual update", 154 | status: "ok"); 155 | } 156 | 157 | public static void ManualReboot() 158 | { 159 | Post( 160 | action: "print terminal manual reboot", 161 | status: "ok"); 162 | } 163 | 164 | public static void ManualShutdown() 165 | { 166 | Post( 167 | action: "print terminal manual shutdown", 168 | status: "ok"); 169 | } 170 | 171 | public static void SocketException(string status) 172 | { 173 | Post( 174 | action: "print terminal socket exception", 175 | status: status); 176 | } 177 | 178 | public static void SocketConnected() 179 | { 180 | Post( 181 | action: "print terminal socket connected", 182 | status: "ok"); 183 | } 184 | 185 | public static void MemoryStatus(float availableMem, float currentMem) 186 | { 187 | Post( 188 | action: "print terminal memory status", 189 | status: "ok", 190 | availableMem: availableMem, 191 | currentMem: currentMem); 192 | } 193 | 194 | public static void MemoryStatusWarning(string status, float availableMem, float currentMem) 195 | { 196 | Post( 197 | action: "print terminal memory status warning", 198 | status: status, 199 | availableMem: availableMem, 200 | currentMem: currentMem); 201 | } 202 | 203 | public static void QrGeneratorException(string status) 204 | { 205 | Post( 206 | action: "print terminal qr generator exception", 207 | status: status); 208 | } 209 | 210 | public static void HwndSourceError() 211 | { 212 | Post( 213 | action: "print terminal failed to get window HwndSource", 214 | status: "error"); 215 | } 216 | 217 | public static void TerminalUserIdError() 218 | { 219 | Post( 220 | action: "print terminal failed to get user id", 221 | status: "error"); 222 | } 223 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Nuget personal access tokens and Credentials 210 | nuget.config 211 | 212 | # Microsoft Azure Build Output 213 | csx/ 214 | *.build.csdef 215 | 216 | # Microsoft Azure Emulator 217 | ecf/ 218 | rcf/ 219 | 220 | # Windows Store app package directories and files 221 | AppPackages/ 222 | BundleArtifacts/ 223 | Package.StoreAssociation.xml 224 | _pkginfo.txt 225 | *.appx 226 | *.appxbundle 227 | *.appxupload 228 | 229 | # Visual Studio cache files 230 | # files ending in .cache can be ignored 231 | *.[Cc]ache 232 | # but keep track of directories ending in .cache 233 | !?*.[Cc]ache/ 234 | 235 | # Others 236 | ClientBin/ 237 | ~$* 238 | *~ 239 | *.dbmdl 240 | *.dbproj.schemaview 241 | *.jfm 242 | *.pfx 243 | *.publishsettings 244 | orleans.codegen.cs 245 | 246 | # Including strong name files can present a security risk 247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 248 | #*.snk 249 | 250 | # Since there are multiple workflows, uncomment next line to ignore bower_components 251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 252 | #bower_components/ 253 | 254 | # RIA/Silverlight projects 255 | Generated_Code/ 256 | 257 | # Backup & report files from converting an old project file 258 | # to a newer Visual Studio version. Backup files are not needed, 259 | # because we have git ;-) 260 | _UpgradeReport_Files/ 261 | Backup*/ 262 | UpgradeLog*.XML 263 | UpgradeLog*.htm 264 | ServiceFabricBackup/ 265 | *.rptproj.bak 266 | 267 | # SQL Server files 268 | *.mdf 269 | *.ldf 270 | *.ndf 271 | 272 | # Business Intelligence projects 273 | *.rdl.data 274 | *.bim.layout 275 | *.bim_*.settings 276 | *.rptproj.rsuser 277 | *- [Bb]ackup.rdl 278 | *- [Bb]ackup ([0-9]).rdl 279 | *- [Bb]ackup ([0-9][0-9]).rdl 280 | 281 | # Microsoft Fakes 282 | FakesAssemblies/ 283 | 284 | # GhostDoc plugin setting file 285 | *.GhostDoc.xml 286 | 287 | # Node.js Tools for Visual Studio 288 | .ntvs_analysis.dat 289 | node_modules/ 290 | 291 | # Visual Studio 6 build log 292 | *.plg 293 | 294 | # Visual Studio 6 workspace options file 295 | *.opt 296 | 297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 298 | *.vbw 299 | 300 | # Visual Studio LightSwitch build output 301 | **/*.HTMLClient/GeneratedArtifacts 302 | **/*.DesktopClient/GeneratedArtifacts 303 | **/*.DesktopClient/ModelManifest.xml 304 | **/*.Server/GeneratedArtifacts 305 | **/*.Server/ModelManifest.xml 306 | _Pvt_Extensions 307 | 308 | # Paket dependency manager 309 | .paket/paket.exe 310 | paket-files/ 311 | 312 | # FAKE - F# Make 313 | .fake/ 314 | 315 | # CodeRush personal settings 316 | .cr/personal 317 | 318 | # Python Tools for Visual Studio (PTVS) 319 | __pycache__/ 320 | *.pyc 321 | 322 | # Cake - Uncomment if you are using it 323 | # tools/** 324 | # !tools/packages.config 325 | 326 | # Tabs Studio 327 | *.tss 328 | 329 | # Telerik's JustMock configuration file 330 | *.jmconfig 331 | 332 | # BizTalk build output 333 | *.btp.cs 334 | *.btm.cs 335 | *.odx.cs 336 | *.xsd.cs 337 | 338 | # OpenCover UI analysis results 339 | OpenCover/ 340 | 341 | # Azure Stream Analytics local run output 342 | ASALocalRun/ 343 | 344 | # MSBuild Binary and Structured Log 345 | *.binlog 346 | 347 | # NVidia Nsight GPU debugger configuration file 348 | *.nvuser 349 | 350 | # MFractors (Xamarin productivity tool) working folder 351 | .mfractor/ 352 | 353 | # Local History for Visual Studio 354 | .localhistory/ 355 | 356 | # BeatPulse healthcheck temp database 357 | healthchecksdb 358 | 359 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 360 | MigrationBackup/ 361 | 362 | # Ionide (cross platform F# VS Code tools) working folder 363 | .ionide/ 364 | 365 | # Fody - auto-generated XML schema 366 | FodyWeavers.xsd 367 | 368 | # VS Code files for those working on multiple tools 369 | .vscode/* 370 | !.vscode/settings.json 371 | !.vscode/tasks.json 372 | !.vscode/launch.json 373 | !.vscode/extensions.json 374 | *.code-workspace 375 | 376 | # Local History for Visual Studio Code 377 | .history/ 378 | 379 | # Windows Installer files from build outputs 380 | *.cab 381 | *.msi 382 | *.msix 383 | *.msm 384 | *.msp 385 | 386 | # JetBrains Rider 387 | .idea/ 388 | *.sln.iml -------------------------------------------------------------------------------- /PrinterApp/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | using System.Reflection; 4 | using System.Text.RegularExpressions; 5 | using System.Timers; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Input; 9 | using System.Windows.Interop; 10 | using System.Windows.Threading; 11 | 12 | namespace PrinterApp; 13 | 14 | /// 15 | /// Interaction logic for MainWindow.xaml 16 | /// 17 | public partial class MainWindow : Window 18 | { 19 | private const int WM_KILLFOCUS = 0x8; 20 | 21 | private readonly Regex _regex = new("[a-zA-Z0-9]{0,8}"); 22 | private readonly Random _random = new(Guid.NewGuid().GetHashCode()); 23 | private const int FlakesCount = 12; 24 | private readonly double[] _flakesTargetsCanvasLeft = new double[FlakesCount]; 25 | private readonly PrinterModel _printerModel; 26 | 27 | public MainWindow(PrinterModel printerModel) 28 | { 29 | _printerModel = printerModel; 30 | 31 | for (var i = 0; i < FlakesCount; i++) 32 | { 33 | _printerModel.PrinterViewModel.FlakesCanvasTop.Add(0); 34 | _printerModel.PrinterViewModel.FlakesCanvasLeft.Add(0); 35 | } 36 | 37 | Loaded += (_, _) => 38 | { 39 | MoveFocus(new TraversalRequest(FocusNavigationDirection.First)); 40 | 41 | SetNewYearTimer(); 42 | for (var i = 0; i < FlakesCount; i++) 43 | { 44 | _printerModel.PrinterViewModel.FlakesCanvasLeft[i] = 45 | _random.Next(0, (int)RootCanvas.Width - 25); 46 | _printerModel.PrinterViewModel.FlakesCanvasTop[i] = 47 | _random.Next((int)RootCanvas.Height * -1, -25); 48 | _flakesTargetsCanvasLeft[i] = RootCanvas.Width * _random.NextDouble(); 49 | } 50 | 51 | _newYearDispatcherTimer.Tick += (_, _) => 52 | { 53 | for (var i = 0; i < FlakesCount; i++) 54 | { 55 | if (_printerModel.PrinterViewModel.FlakesCanvasTop[i] > RootCanvas.Height) 56 | { 57 | _printerModel.PrinterViewModel.FlakesCanvasTop[i] = -25; 58 | } 59 | else 60 | { 61 | _printerModel.PrinterViewModel.FlakesCanvasTop[i] += 0.3; 62 | } 63 | 64 | if (_printerModel.PrinterViewModel.FlakesCanvasLeft[i] > RootCanvas.Width) 65 | { 66 | _printerModel.PrinterViewModel.FlakesCanvasLeft[i] = 67 | RootCanvas.Width - 68 | _printerModel.PrinterViewModel.FlakesCanvasLeft[i]; 69 | } 70 | else if (_printerModel.PrinterViewModel.FlakesCanvasLeft[i] < 0) 71 | { 72 | _printerModel.PrinterViewModel.FlakesCanvasLeft[i] += RootCanvas.Width; 73 | } 74 | else 75 | { 76 | if (Math.Abs(_printerModel.PrinterViewModel.FlakesCanvasLeft[i] - 77 | _flakesTargetsCanvasLeft[i]) < 0.4) 78 | { 79 | _flakesTargetsCanvasLeft[i] = 80 | RootCanvas.Width * _random.NextDouble(); 81 | } 82 | 83 | _printerModel.PrinterViewModel.FlakesCanvasLeft[i] += 84 | _printerModel.PrinterViewModel.FlakesCanvasLeft[i] < 85 | _flakesTargetsCanvasLeft[i] 86 | ? 0.3 87 | : -0.3; 88 | } 89 | } 90 | }; 91 | }; 92 | DataContext = _printerModel.PrinterViewModel; 93 | InitializeComponent(); 94 | //what this? 95 | if (40 + Height < SystemParameters.PrimaryScreenHeight) 96 | Top = 40; 97 | Left = SystemParameters.PrimaryScreenWidth - 20 - Width; 98 | #if DEBUG 99 | WindowState = WindowState.Normal; 100 | WindowStyle = WindowStyle.SingleBorderWindow; 101 | ResizeMode = ResizeMode.CanResize; 102 | Topmost = false; 103 | ShowInTaskbar = true; 104 | #endif 105 | _printerModel.PrintAsyncCompleteEvent += () => { CodeBox.Focus(); }; 106 | Marketing.MainWindowLoaded(); 107 | } 108 | 109 | private bool IsTextAllowed(string text) 110 | { 111 | Log.Debug( 112 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Regex.IsMatch({text}) = {text.Equals(_regex.Match(text).Value)}"); 113 | return text.Equals(_regex.Match(text).Value); 114 | } 115 | 116 | private void TextBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e) 117 | { 118 | if (_printerModel.PrinterViewModel.ErrorTextBlockText != "") 119 | { 120 | _printerModel.PrinterViewModel.ErrorTextBlockVisibility = Visibility.Collapsed; 121 | _printerModel.PrinterViewModel.ErrorTextBlockText = ""; 122 | } 123 | 124 | e.Handled = !IsTextAllowed((sender as TextBox)?.Text + e.Text); 125 | } 126 | 127 | private void TextBoxPasting(object sender, DataObjectPastingEventArgs e) 128 | { 129 | if (e.DataObject.GetDataPresent(typeof(string))) 130 | { 131 | var text = (string)e.DataObject.GetData(typeof(string)); 132 | if (!IsTextAllowed(text)) 133 | { 134 | e.CancelCommand(); 135 | } 136 | } 137 | else 138 | { 139 | e.CancelCommand(); 140 | } 141 | } 142 | 143 | private void TextBox_OnKeyDown(object sender, KeyEventArgs e) 144 | { 145 | if (e.Key == Key.Back) 146 | { 147 | if (_printerModel.PrinterViewModel.ErrorTextBlockText != "") 148 | { 149 | _printerModel.PrinterViewModel.ErrorTextBlockVisibility = Visibility.Collapsed; 150 | _printerModel.PrinterViewModel.ErrorTextBlockText = ""; 151 | } 152 | } 153 | } 154 | 155 | private void Print_OnClick(object sender, RoutedEventArgs e) 156 | { 157 | if (sender is Button button) 158 | { 159 | _printerModel.PrintAsync(button.Name == "Print2"); 160 | } 161 | } 162 | 163 | private void UIElement_OnKeyDown(object sender, KeyEventArgs e) 164 | { 165 | if (e.Key != Key.Enter) return; 166 | _printerModel.PrintAsync( /*button.Name == "Print2"*/false); 167 | e.Handled = true; 168 | } 169 | 170 | private readonly DispatcherTimer _newYearDispatcherTimer = 171 | new() { Interval = new TimeSpan(0, 0, 0, 0, 23) }; 172 | 173 | private void SetNewYearTimer() 174 | { 175 | NewYearTimerOnElapsed(null, null!); 176 | var newYearTimer = new Timer(); 177 | newYearTimer.Elapsed += NewYearTimerOnElapsed; 178 | //check every 12 hours 179 | newYearTimer.Interval = 12 * 60 * 60 * 1000; 180 | newYearTimer.Start(); 181 | } 182 | 183 | private void NewYearTimerOnElapsed(object? sender, ElapsedEventArgs e) 184 | { 185 | if (DateTime.Now > new DateTime(DateTime.Now.Year, 11, 30) && 186 | DateTime.Now < new DateTime(DateTime.Now.Year + 1, 2, 1)) 187 | { 188 | if (!_newYearDispatcherTimer.IsEnabled) 189 | { 190 | _printerModel.PrinterViewModel.FlakesVisibility = Visibility.Visible; 191 | _newYearDispatcherTimer.Start(); 192 | } 193 | } 194 | else 195 | { 196 | if (_newYearDispatcherTimer.IsEnabled) 197 | { 198 | _printerModel.PrinterViewModel.FlakesVisibility = Visibility.Collapsed; 199 | _newYearDispatcherTimer.Stop(); 200 | } 201 | } 202 | } 203 | 204 | protected override void OnSourceInitialized(EventArgs e) 205 | { 206 | base.OnSourceInitialized(e); 207 | if (PresentationSource.FromVisual(this) is HwndSource source) 208 | { 209 | source.AddHook(WndProc); 210 | } 211 | else 212 | { 213 | Log.Error( 214 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: не удалось получить отслеживание окна"); 215 | Marketing.HwndSourceError(); 216 | } 217 | } 218 | 219 | private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 220 | { 221 | if (msg == WM_KILLFOCUS && wParam.ToInt32() == 0) 222 | { 223 | handled = true; 224 | MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.LeftDown); 225 | MouseOperations.MouseEvent(MouseOperations.MouseEventFlags.LeftUp); 226 | Activate(); 227 | CodeBox.Focus(); 228 | CodeBox.CaretIndex = CodeBox.Text.Length; 229 | } 230 | 231 | return IntPtr.Zero; 232 | } 233 | } -------------------------------------------------------------------------------- /PrinterApp/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 21 | 23 | 25 | 27 | 29 | 31 | 33 | 35 | 37 | 39 | 41 | 43 | 44 | 48 | 50 | 53 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 75 | 79 | 83 | 84 | 86 | 95 | 97 | 98 | 106 | 107 | 108 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /PrinterApp/AutoUpdater.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Threading.Tasks; 9 | using System.Windows.Threading; 10 | using Serilog; 11 | 12 | namespace PrinterApp; 13 | 14 | public class AutoUpdater 15 | { 16 | private readonly DispatcherTimer _dispatcherTimer; 17 | private readonly HttpClient _httpClient; 18 | private readonly Regex _regex = new(@"v\d+\..+"); 19 | 20 | public AutoUpdater() 21 | { 22 | _httpClient = new HttpClient 23 | { 24 | BaseAddress = new Uri("https://github.com/profcomff/print-winapp/releases/latest/"), 25 | }; 26 | 27 | _dispatcherTimer = new DispatcherTimer 28 | { 29 | Interval = TimeSpan.FromHours(1) 30 | }; 31 | _dispatcherTimer.Tick += DispatcherTimer_Tick; 32 | 33 | DeleteOlderExe(); 34 | DeleteOlderZip(); 35 | CreateUpdateBat(); 36 | } 37 | 38 | ~AutoUpdater() 39 | { 40 | _httpClient.Dispose(); 41 | } 42 | 43 | public void StartTimer() 44 | { 45 | if (_dispatcherTimer.IsEnabled) return; 46 | Log.Information( 47 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Start auto update timer"); 48 | _dispatcherTimer.Start(); 49 | } 50 | 51 | public void StopTimer() 52 | { 53 | Log.Information( 54 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Stop auto update timer"); 55 | _dispatcherTimer.Stop(); 56 | } 57 | 58 | public void ManualUpdate() 59 | { 60 | Log.Information( 61 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Manual update"); 62 | Marketing.ManualUpdate(); 63 | new Task(async () => await CheckNewVersion()).Start(); 64 | } 65 | 66 | private async void DispatcherTimer_Tick(object? sender, EventArgs e) 67 | { 68 | var timeNow = DateTime.Now.TimeOfDay; 69 | var timeNight = new TimeSpan(22, 0, 0); 70 | var timeEvning = new TimeSpan(6, 0, 0); 71 | if (timeNow < timeNight && timeNow > timeEvning) return; 72 | Log.Information( 73 | $"{GetType().Name} DispatcherTimer_Tick: Auto update timer tick"); 74 | await CheckNewVersion(); 75 | } 76 | 77 | private async Task CheckNewVersion() 78 | { 79 | var result = await _httpClient.GetAsync(_httpClient.BaseAddress); 80 | if (result.IsSuccessStatusCode) 81 | { 82 | var requestUri = result.RequestMessage?.RequestUri?.ToString(); 83 | if (requestUri != null) 84 | { 85 | var tempVersion = _regex.Match(requestUri).Value[1..].Split("."); 86 | var version = new int[4]; 87 | for (var i = 0; i < tempVersion.Length; i++) 88 | { 89 | version[i] = int.Parse(tempVersion[i]); 90 | } 91 | 92 | var githubVersion = 93 | new Version(version[0], version[1], version[2], version[3]).ToString(); 94 | var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); 95 | Log.Information( 96 | $"{GetType().Name} CheckNewVersion: GithubVersion: {githubVersion} AssemblyVersion: {assemblyVersion}"); 97 | if (githubVersion == assemblyVersion) return; 98 | //Download new version 99 | var uri = new Uri( 100 | $"https://github.com/profcomff/print-winapp/releases/download/{_regex.Match(requestUri).Value}/PrinterApp_x86.zip"); 101 | var response = await _httpClient.GetAsync(uri); 102 | if (response.IsSuccessStatusCode) 103 | { 104 | Log.Information( 105 | $"{GetType().Name} CheckNewVersion: Start download"); 106 | await using var fs = new FileStream( 107 | Path.Combine( 108 | Path.GetDirectoryName(Environment.ProcessPath) ?? 109 | Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), 110 | "PrinterApp_x86.zip"), 111 | FileMode.Create); 112 | await response.Content.CopyToAsync(fs); 113 | Log.Information( 114 | $"{GetType().Name} CheckNewVersion: Finish download"); 115 | 116 | Marketing.UpdateDownloaded(); 117 | Process.Start(new ProcessStartInfo(BatFileName) { UseShellExecute = true }); 118 | await Log.CloseAndFlushAsync(); 119 | Environment.Exit(0); 120 | } 121 | else 122 | { 123 | Log.Error($"{GetType().Name} CheckNewVersion: File not found {uri}"); 124 | } 125 | } 126 | } 127 | } 128 | 129 | private static void DeleteOlderExe() 130 | { 131 | Log.Information($"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: Start"); 132 | try 133 | { 134 | var di = new DirectoryInfo(Path.GetDirectoryName(Environment.ProcessPath) ?? 135 | Environment.GetFolderPath(Environment.SpecialFolder 136 | .UserProfile)); 137 | var date = DateTime.Today.AddDays(-14); 138 | var regex = new Regex(@"_\d+.\d+"); 139 | foreach (var fi in di.GetFiles("PrinterApp_*.exe")) 140 | { 141 | var dateAndMonth = regex.Match(fi.Name).Value[1..].Split("."); 142 | if (date.Month > int.Parse(dateAndMonth[1])) 143 | { 144 | File.Delete(fi.Name); 145 | } 146 | else if (date.Month == int.Parse(dateAndMonth[1]) && 147 | date.Day >= int.Parse(dateAndMonth[0])) 148 | { 149 | File.Delete(fi.Name); 150 | } 151 | } 152 | 153 | Log.Information($"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: Finish"); 154 | } 155 | catch (Exception e) 156 | { 157 | Log.Error( 158 | $"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 159 | Console.WriteLine( 160 | $"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 161 | } 162 | } 163 | 164 | private static void DeleteOlderZip() 165 | { 166 | Log.Information($"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: Start"); 167 | try 168 | { 169 | var di = new DirectoryInfo(Path.GetDirectoryName(Environment.ProcessPath) ?? 170 | Environment.GetFolderPath(Environment.SpecialFolder 171 | .UserProfile)); 172 | foreach (var fi in di.GetFiles("*.zip")) 173 | { 174 | File.Delete(fi.Name); 175 | } 176 | 177 | Log.Information($"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: Finish"); 178 | } 179 | catch (Exception e) 180 | { 181 | Log.Error( 182 | $"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 183 | Console.WriteLine( 184 | $"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 185 | } 186 | } 187 | 188 | private const string BatFileName = "update.bat"; 189 | 190 | private static void CreateUpdateBat() 191 | { 192 | Log.Information($"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: Start"); 193 | try 194 | { 195 | string path = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath) ?? 196 | Environment.GetFolderPath(Environment.SpecialFolder 197 | .UserProfile), BatFileName); 198 | using (FileStream fs = File.Create(path)) 199 | { 200 | byte[] info = new UTF8Encoding(true).GetBytes(BatFile); 201 | fs.Write(info, 0, info.Length); 202 | } 203 | 204 | Log.Information($"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: Finish"); 205 | } 206 | catch (Exception e) 207 | { 208 | Log.Error( 209 | $"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 210 | Console.WriteLine( 211 | $"AutoUpdater {MethodBase.GetCurrentMethod()?.Name}: error {e.Message}"); 212 | } 213 | } 214 | 215 | private const string BatFile = @"@echo off 216 | echo start 217 | setlocal 218 | timeout 61 219 | for /f ""tokens=2 delims=="" %%a in ('wmic OS Get localdatetime /value') do set ""dt=%%a"" 220 | set ""YY=%dt:~2,2%"" & set ""YYYY=%dt:~0,4%"" & set ""MM=%dt:~4,2%"" & set ""DD=%dt:~6,2%"" 221 | set ""HH=%dt:~8,2%"" & set ""Min=%dt:~10,2%"" & set ""Sec=%dt:~12,2%"" 222 | set ""timestamp=%HH%%Min%%Sec%"" 223 | ren ""%~dp0PrinterApp.exe"" PrinterApp_%date%_%timestamp%.exe 224 | call :UnZipFile ""%~dp0"" ""%~dp0PrinterApp_x86.zip"" 225 | start PrinterApp.exe 226 | echo close 227 | 228 | :UnZipFile 229 | SET vbs=""%temp%\_.vbs"" 230 | IF EXIST %vbs% DEL /f /q %vbs% 231 | >>%vbs% ECHO Set fso = CreateObject(""Scripting.FileSystemObject"") 232 | >>%vbs% ECHO If NOT fso.FolderExists(%1) Then 233 | >>%vbs% ECHO fso.CreateFolder(%1) 234 | >>%vbs% ECHO End If 235 | >>%vbs% ECHO set objShell = CreateObject(""Shell.Application"") 236 | >>%vbs% ECHO set FilesInZip = objShell.NameSpace(%2).items 237 | >>%vbs% ECHO objShell.NameSpace(%1).CopyHere FilesInZip, 16 238 | >>%vbs% ECHO Set fso = Nothing 239 | >>%vbs% ECHO Set objShell = Nothing 240 | cscript //nologo %vbs% 241 | IF EXIST %vbs% DEL /f /q %vbs%"; 242 | } -------------------------------------------------------------------------------- /PrinterApp/Styles.xaml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 66 | 117 | 118 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 189 | 190 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 286 | 287 | 288 | 289 | 290 | 295 | 298 | 301 | 302 | -------------------------------------------------------------------------------- /PrinterApp/PrinterModel.cs: -------------------------------------------------------------------------------- 1 | using Emgu.CV; 2 | using Newtonsoft.Json; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Diagnostics; 7 | using System.Drawing.Imaging; 8 | using System.IO; 9 | using System.Net; 10 | using System.Net.Http; 11 | using System.Net.Http.Headers; 12 | using System.Net.WebSockets; 13 | using System.Reflection; 14 | using System.Text; 15 | using System.Threading; 16 | using System.Threading.Tasks; 17 | using System.Windows; 18 | using ZXing; 19 | using ZXing.Windows.Compatibility; 20 | 21 | namespace PrinterApp; 22 | 23 | public class PrinterModel 24 | { 25 | #if DEBUG 26 | private const string ApiUrl = "https://api.test.profcomff.com"; 27 | private const string FileUrl = "https://api.test.profcomff.com/print/file"; 28 | private const string StaticUrl = "https://api.test.profcomff.com/print/static"; 29 | private const string WebSockUrl = "wss://api.test.profcomff.com/print/qr"; 30 | #else 31 | private const string ApiUrl = "https://api.profcomff.com"; 32 | private const string FileUrl = "https://api.profcomff.com/print/file"; 33 | private const string StaticUrl = "https://api.profcomff.com/print/static"; 34 | private const string WebSockUrl = "wss://api.profcomff.com/print/qr"; 35 | #endif 36 | private const string CodeError = "Некорректный код"; 37 | private const string HttpError = "Ошибка сети"; 38 | 39 | private const string SumatraError = 40 | "[Error] program SumatraPdf is not found\ninform the responsible person\n\n[Ошибка] программа SumatraPdf не найдена\nсообщите ответственному лицу"; 41 | 42 | private static readonly string SumatraPathSuffix = 43 | Path.DirectorySeparatorChar + "SumatraPDF" + 44 | Path.DirectorySeparatorChar + "SumatraPDF.exe"; 45 | 46 | public PrinterViewModel PrinterViewModel { get; } = new(); 47 | 48 | private readonly ConfigFile _configFile; 49 | private readonly AutoUpdater _autoUpdater; 50 | private readonly HttpClient _httpClient; 51 | private bool _socketClose; 52 | 53 | public delegate void RebootHandler(); 54 | 55 | public event RebootHandler? Reboot; 56 | 57 | public delegate void PrintAsyncCompleteHandler(); 58 | 59 | public event PrintAsyncCompleteHandler? PrintAsyncCompleteEvent; 60 | 61 | public PrinterModel(ConfigFile configFile, AutoUpdater autoUpdater) 62 | { 63 | _configFile = configFile; 64 | _autoUpdater = autoUpdater; 65 | 66 | ServicePointManager.SecurityProtocol = 67 | SecurityProtocolType.Tls | 68 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; 69 | 70 | _httpClient = new HttpClient(); 71 | _httpClient.DefaultRequestHeaders.Authorization 72 | = new AuthenticationHeaderValue(_configFile.AuthorizationToken); 73 | 74 | try 75 | { 76 | var response = _httpClient.GetAsync($"{ApiUrl}/auth/me"); 77 | response.Wait(5000); 78 | response.Result.EnsureSuccessStatusCode(); 79 | var responseBody = response.Result.Content.ReadAsStringAsync(); 80 | responseBody.Wait(1000); 81 | var responceString = responseBody.Result; 82 | var htmlAttributes = 83 | JsonConvert.DeserializeObject>(responceString) ?? 84 | throw new InvalidOperationException(); 85 | Log.Information(htmlAttributes["id"]); 86 | Marketing.TerminalUserId = htmlAttributes["id"]; 87 | } 88 | catch (Exception e) 89 | { 90 | Log.Error($"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {e}"); 91 | Marketing.TerminalUserIdError(); 92 | MessageBox.Show( 93 | "Терминал не смог получить id. Сообщите ответственному лицу. Перезапустите программу.", 94 | "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error); 95 | Close(); 96 | } 97 | 98 | 99 | if (SearchSumatraPdf() == "") 100 | { 101 | MessageBox.Show(SumatraError); 102 | Close(); 103 | } 104 | 105 | SocketsStartAsync(); 106 | 107 | AsyncSaveScreen("LoadTerminal"); 108 | } 109 | 110 | ~PrinterModel() 111 | { 112 | _httpClient.Dispose(); 113 | } 114 | 115 | private static void Close() 116 | { 117 | Log.CloseAndFlush(); 118 | Environment.Exit(0); 119 | } 120 | 121 | private static string SearchSumatraPdf() 122 | { 123 | var path = 124 | Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + 125 | SumatraPathSuffix; 126 | if (File.Exists(path)) 127 | return path; 128 | path = Directory.GetCurrentDirectory() + SumatraPathSuffix; 129 | if (File.Exists(path)) 130 | return path; 131 | path = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "SumatraPDF.exe"; 132 | if (File.Exists(path)) 133 | return path; 134 | path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) + 135 | SumatraPathSuffix; 136 | if (File.Exists(path)) 137 | return path; 138 | path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + 139 | SumatraPathSuffix; 140 | return File.Exists(path) ? path : ""; 141 | } 142 | 143 | private static void AsyncSaveScreen(string imageName = "") 144 | { 145 | new Task(() => { SaveScreen(imageName); }).Start(); 146 | } 147 | 148 | private static void SaveScreen(string imageName = "") 149 | { 150 | try 151 | { 152 | using var capture = new VideoCapture(); 153 | var image = capture.QueryFrame(); 154 | if (image == null) return; 155 | var bitmap = image.ToBitmap(); 156 | var directory = Path.Combine( 157 | Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), 158 | ".printerAppLogs", 159 | "img"); 160 | Directory.CreateDirectory(directory); 161 | var path = Path.Combine(directory, 162 | $"{imageName}{DateTime.Now:yyyy_MM_ddTHH_mm_ss}.jpeg"); 163 | bitmap.Save(path, ImageFormat.Jpeg); 164 | bitmap.Dispose(); 165 | image.Dispose(); 166 | } 167 | catch (Exception exception) 168 | { 169 | Log.Error(exception.Message); 170 | } 171 | } 172 | 173 | public async void PrintAsync(bool printDialog) 174 | { 175 | if (PrinterViewModel.CodeTextBoxText.Length < 1) 176 | { 177 | PrinterViewModel.ErrorTextBlockVisibility = Visibility.Visible; 178 | PrinterViewModel.ErrorTextBlockText = CodeError; 179 | return; 180 | } 181 | 182 | PrinterViewModel.DownloadNotInProgress = false; 183 | PrinterViewModel.PrintQrVisibility = Visibility.Collapsed; 184 | if (PrinterViewModel.CodeTextBoxText.ToUpper() == "IDDQD") 185 | { 186 | AsyncSaveScreen("iddqd"); 187 | var saveFilePath = _configFile.TempSavePath + Path.DirectorySeparatorChar + 188 | "iddqd.pdf"; 189 | saveFilePath = 190 | saveFilePath.Replace(Path.DirectorySeparatorChar.ToString(), "/"); 191 | ShowComplement(); 192 | await using var s = 193 | await _httpClient.GetStreamAsync( 194 | "https://cdn.profcomff.com/app/printer/iddqd.pdf"); 195 | await using var fs = new FileStream(saveFilePath, FileMode.OpenOrCreate); 196 | await s.CopyToAsync(fs); 197 | PrintFile(saveFilePath, new PrintOptions("", 1, false, "A4"), 198 | patchFrom: ""); 199 | PrinterViewModel.CodeTextBoxText = ""; 200 | PrinterViewModel.DownloadNotInProgress = true; 201 | Log.Information( 202 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Easter"); 203 | PrintAsyncCompleteEvent?.Invoke(); 204 | return; 205 | } 206 | 207 | Log.Debug( 208 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Start response code {PrinterViewModel.CodeTextBoxText}"); 209 | var patchFrom = $"{FileUrl}/{PrinterViewModel.CodeTextBoxText}"; 210 | try 211 | { 212 | var response = 213 | await _httpClient.GetAsync($"{FileUrl}/{PrinterViewModel.CodeTextBoxText}"); 214 | if (response.StatusCode == HttpStatusCode.OK) 215 | { 216 | Marketing.CheckCode( 217 | statusOk: true, 218 | pathFrom: patchFrom); 219 | 220 | try 221 | { 222 | PrinterViewModel.CodeTextBoxText = ""; 223 | var responseBody = await response.Content.ReadAsStringAsync(); 224 | var fileWithOptions = 225 | JsonConvert.DeserializeObject(responseBody); 226 | 227 | if (fileWithOptions != null && fileWithOptions.Filename.Length > 0) 228 | { 229 | DeleteOldFiles(); 230 | Marketing.StartDownload( 231 | pathFrom: patchFrom, 232 | pathTo: $"{StaticUrl}/{fileWithOptions.Filename}"); 233 | await Download(fileWithOptions, patchFrom, printDialog); 234 | } 235 | else 236 | { 237 | Marketing.PrintNotFile(pathFrom: patchFrom); 238 | } 239 | } 240 | catch (Exception exception) 241 | { 242 | Marketing.DownloadException(status: exception.Message, 243 | pathFrom: patchFrom); 244 | Log.Error( 245 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {exception}"); 246 | PrinterViewModel.ErrorTextBlockVisibility = Visibility.Visible; 247 | PrinterViewModel.ErrorTextBlockText = HttpError; 248 | } 249 | } 250 | else if (response.StatusCode is HttpStatusCode.NotFound 251 | or HttpStatusCode.UnsupportedMediaType) 252 | { 253 | AsyncSaveScreen("checkCodeFail"); 254 | Marketing.CheckCode(statusOk: false, pathFrom: patchFrom); 255 | 256 | PrinterViewModel.ErrorTextBlockVisibility = Visibility.Visible; 257 | PrinterViewModel.ErrorTextBlockText = CodeError; 258 | } 259 | } 260 | catch (Exception exception) 261 | { 262 | Marketing.PrintException(status: exception.Message, pathFrom: patchFrom); 263 | 264 | Log.Error($"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {exception}"); 265 | PrinterViewModel.ErrorTextBlockVisibility = Visibility.Visible; 266 | PrinterViewModel.ErrorTextBlockText = HttpError; 267 | } 268 | 269 | PrinterViewModel.DownloadNotInProgress = true; 270 | Log.Debug( 271 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: End response code {PrinterViewModel.CodeTextBoxText}"); 272 | PrintAsyncCompleteEvent?.Invoke(); 273 | } 274 | 275 | public bool WrongExitCode() 276 | { 277 | var answer = PrinterViewModel.CodeTextBoxText != _configFile.ExitCode.ToUpper(); 278 | if (answer) 279 | { 280 | AsyncSaveScreen($"WrongExitCode{answer}"); 281 | } 282 | else 283 | { 284 | SaveScreen($"WrongExitCode{answer}"); 285 | } 286 | 287 | return answer; 288 | } 289 | 290 | private async Task Download(FileWithOptions fileWithOptions, string patchFrom, 291 | bool printDialog = false) 292 | 293 | { 294 | Log.Debug( 295 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: Start download filename:{fileWithOptions.Filename}"); 296 | var name = Guid.NewGuid() + ".pdf"; 297 | var saveFilePath = _configFile.TempSavePath + Path.DirectorySeparatorChar + name; 298 | saveFilePath = saveFilePath.Replace(Path.DirectorySeparatorChar.ToString(), "/"); 299 | ShowComplement(); 300 | await using var s = 301 | await _httpClient.GetStreamAsync($"{StaticUrl}/{fileWithOptions.Filename}"); 302 | await using var fs = new FileStream(saveFilePath, FileMode.OpenOrCreate); 303 | await s.CopyToAsync(fs); 304 | Marketing.FinishDownload(pathFrom: patchFrom, 305 | pathTo: $"{StaticUrl}/{fileWithOptions.Filename}"); 306 | Log.Debug( 307 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: End download filename:{fileWithOptions.Filename}"); 308 | PrintFile(saveFilePath, fileWithOptions.Options, patchFrom, printDialog); 309 | } 310 | 311 | private void PrintFile(string saveFilePath, PrintOptions options, string patchFrom, 312 | bool printDialog = false) 313 | { 314 | var sumatraPath = SearchSumatraPdf(); 315 | if (sumatraPath != "") 316 | { 317 | var arguments = "-print-dialog"; 318 | if (!printDialog) 319 | { 320 | arguments = 321 | "-print-to-default -print-settings "; 322 | arguments += "\"" + (options.TwoSided ? "duplexlong" : "simplex"); 323 | if (options.Pages != "") 324 | { 325 | arguments += $",{options.Pages}"; 326 | } 327 | 328 | if (options.Copies > 1) 329 | { 330 | arguments += $",{options.Copies}x"; 331 | } 332 | 333 | arguments += ",paper=A4"; 334 | 335 | arguments += "\""; 336 | } 337 | 338 | var startInfo = new ProcessStartInfo(sumatraPath) 339 | { 340 | Arguments = $"{arguments} {saveFilePath}" 341 | }; 342 | 343 | Marketing.StartSumatra(pathFrom: patchFrom); 344 | 345 | Process currentProcess = new() { StartInfo = startInfo }; 346 | var _ = currentProcess.Start(); 347 | } 348 | else 349 | { 350 | Log.Warning( 351 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {SumatraError}"); 352 | MessageBox.Show(SumatraError); 353 | throw new Exception(); 354 | } 355 | } 356 | 357 | private void DeleteOldFiles() 358 | { 359 | foreach (FileInfo file in new DirectoryInfo(_configFile.TempSavePath).GetFiles()) 360 | { 361 | file.Delete(); 362 | } 363 | 364 | Log.Debug( 365 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: delete all files complete"); 366 | } 367 | 368 | private void ShowComplement() 369 | { 370 | new Task(async () => 371 | { 372 | PrinterViewModel.Compliment = Compliments.GetRandomCompliment(); 373 | await Task.Delay(5000); 374 | PrinterViewModel.Compliment = ""; 375 | if (PrinterViewModel.DownloadNotInProgress) 376 | PrinterViewModel.PrintQrVisibility = Visibility.Visible; 377 | }).Start(); 378 | } 379 | 380 | public void SocketsClose() 381 | { 382 | _socketClose = true; 383 | } 384 | 385 | private async void SocketsStartAsync() 386 | { 387 | var socket = new ClientWebSocket(); 388 | socket.Options.SetRequestHeader("Authorization", 389 | _httpClient.DefaultRequestHeaders.Authorization!.ToString()); 390 | try 391 | { 392 | await socket.ConnectAsync(new Uri(WebSockUrl), CancellationToken.None); 393 | if (socket.State != WebSocketState.Open) 394 | { 395 | Marketing.SocketException( 396 | status: $"WebSocketState not Open state:{socket.State}"); 397 | Log.Error( 398 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: WebSocketState not Open state:{socket.State}"); 399 | return; 400 | } 401 | 402 | Marketing.SocketConnected(); 403 | _socketClose = false; 404 | var buffer = new byte[128 * 1024]; 405 | while (!_socketClose) 406 | { 407 | var result = await socket.ReceiveAsync(new ArraySegment(buffer), 408 | CancellationToken.None); 409 | var json = Encoding.UTF8.GetString(buffer, 0, result.Count); 410 | if (!result.EndOfMessage) 411 | { 412 | Thread.Sleep(100); 413 | result = await socket.ReceiveAsync(new ArraySegment(buffer), 414 | CancellationToken.None); 415 | json += Encoding.UTF8.GetString(buffer, 0, result.Count); 416 | } 417 | 418 | await ParseResponseFromSocket( 419 | JsonConvert.DeserializeObject(json)); 420 | } 421 | 422 | await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Good Bye", 423 | CancellationToken.None); 424 | } 425 | catch (Exception exception) 426 | { 427 | _socketClose = true; 428 | Marketing.SocketException(status: exception.Message); 429 | Log.Error($"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {exception}"); 430 | PrinterViewModel.PrintQr = null!; 431 | socket.Abort(); 432 | await Task.Delay(5000); 433 | SocketsStartAsync(); 434 | } 435 | } 436 | 437 | private async Task ParseResponseFromSocket(WebsocketReceiveOptions? websocketReceiveOptions) 438 | { 439 | if (websocketReceiveOptions == null) 440 | { 441 | Marketing.SocketException("websocketReceiveOptions is null"); 442 | return; 443 | } 444 | 445 | if (websocketReceiveOptions.Error != null && websocketReceiveOptions.Error != "") 446 | { 447 | Marketing.SocketException(websocketReceiveOptions.Error); 448 | Close(); 449 | } 450 | 451 | if (websocketReceiveOptions.ManualUpdate) 452 | { 453 | _autoUpdater.ManualUpdate(); 454 | } 455 | 456 | if (websocketReceiveOptions.Reboot) 457 | { 458 | Application.Current.Dispatcher.Invoke(() => { Reboot?.Invoke(); }); 459 | } 460 | 461 | if (websocketReceiveOptions.QrToken == null!) 462 | { 463 | Marketing.SocketException("websocketReceiveOptions QrToken is null"); 464 | return; 465 | } 466 | 467 | PrinterViewModel.PrintQr = null!; 468 | if (websocketReceiveOptions.Files != null!) 469 | { 470 | PrinterViewModel.DownloadNotInProgress = false; 471 | PrinterViewModel.PrintQrVisibility = Visibility.Collapsed; 472 | DeleteOldFiles(); 473 | foreach (var fileWithOptions in websocketReceiveOptions.Files) 474 | { 475 | const string patchFrom = "websocket"; 476 | try 477 | { 478 | PrinterViewModel.CodeTextBoxText = ""; 479 | 480 | if (fileWithOptions.Filename.Length > 0) 481 | { 482 | Marketing.StartDownload( 483 | pathFrom: patchFrom, 484 | pathTo: $"{StaticUrl}/{fileWithOptions.Filename}"); 485 | await Download(fileWithOptions, patchFrom); 486 | } 487 | else 488 | { 489 | Marketing.PrintNotFile(pathFrom: patchFrom); 490 | } 491 | } 492 | catch (Exception exception) 493 | { 494 | Marketing.DownloadException(status: exception.Message, 495 | pathFrom: patchFrom); 496 | 497 | Log.Error( 498 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {exception}"); 499 | PrinterViewModel.ErrorTextBlockVisibility = Visibility.Visible; 500 | PrinterViewModel.ErrorTextBlockText = HttpError; 501 | } 502 | } 503 | 504 | PrinterViewModel.DownloadNotInProgress = true; 505 | } 506 | 507 | GenerateQr(websocketReceiveOptions.QrToken); 508 | } 509 | 510 | private readonly BarcodeWriterGeometry _barcodeWriterGeometry = new() 511 | { 512 | Format = BarcodeFormat.QR_CODE, 513 | Options = new ZXing.Common.EncodingOptions 514 | { 515 | Height = 212, 516 | Width = 212 517 | } 518 | }; 519 | 520 | private void GenerateQr(string value) 521 | { 522 | Log.Debug( 523 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: new qr code {value}"); 524 | try 525 | { 526 | var image = _barcodeWriterGeometry.Write(value); 527 | image.Freeze(); 528 | PrinterViewModel.PrintQr = image; 529 | } 530 | catch (Exception exception) 531 | { 532 | Log.Error( 533 | $"{GetType().Name} {MethodBase.GetCurrentMethod()?.Name}: {exception}"); 534 | Marketing.QrGeneratorException(exception.ToString()); 535 | PrinterViewModel.PrintQr = null!; 536 | } 537 | } 538 | } --------------------------------------------------------------------------------