├── 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 | 
4 |
5 | [](https://github.com/profcomff/print-winapp/actions/workflows/deploy-printer-app.yml/badge.svg)
6 | 
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 | 
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 |
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 | }
--------------------------------------------------------------------------------