Делаем скриншоты пользователей. Обсудим реализацию на языке программирования C#.
Описываемый в данной статье способ является простым, никакого лишнего функционала. Вы можете воспользоваться готовой разработкой или изменить исходный код как захотите. В конце статьи будет ссылка на GitHub.
Необходимый функционал:
-
Программа должна делать скриншоты
-
Программы не должно быть видно на панели задач
-
Возможность задавать интервал в секундах между выполнением скриншота
-
Возможность задавать путь к директории хранилища скриншотов
-
Возможность задавать максимальный размер хранилища скриншотов
-
Очистка самых старых файлов из хранилища скриншотов при достижении максимума в хранилище
-
Логирование работы программы
Алгоритм работы программы:
Код работы метода Main программы:
static void Main(string[] args) { Log.Instance.Info($"started"); // Скрыть окно программы var handle = GetConsoleWindow(); ShowWindow(handle, SW_HIDE); // Чтение конфигурации ReadSettings(); // Запуск бесконечной работы скриншотов while (true) { // Проверить хранилище CheckStorage(); // Выполнить скриншот DoScreen(); // Подождать интервал времени Thread.Sleep(_interval * 1000); } }
Далее необходимо разработать следующие методы:
ReadSettings - Чтение конфигурации
CheckStorage - Проверить хранилище
DoScreen - Выполнить скриншот
ReadSettings (чтение конфигурации)
Для создания файла конфигурации необходимо в Visual Studio
открыть свойства проекта и перейти в меню Параметры. Здесь
необходимо прописать какие переменные будут вынесены в файл
конфигурации и задать их типы и значения.
interval - Как часто делать скриншот, в секундах
limit - Размер хранилища скриншотов, в MB
path - Путь к хранилищу скриншотов, пример C:\temp
Для удобства создадим свойства для доступа к настройкам.
/// <summary>/// Как часто делать скриншот, в секундах/// </summary>static int _interval { get; set; }/// <summary>/// Размер хранилища скриншотов, в MB/// </summary>static int _limit { get; set; }/// <summary>/// Путь к хранилищу скриншотов, пример C:\temp/// </summary>static string _path { get; set; }
Затем прочитаем настройки программы из файла конфигурации Screenshoter.exe.config который лежит около исполняемого файла приложения Screenshoter.exe. Запишем прочитанные настройки в выше созданные поля.
/// <summary>/// Чтение настроек из файла конфигурации/// </summary>static void ReadSettings(){ _interval = 10; if (Properties.Settings.Default.interval > 0) _interval = Properties.Settings.Default.interval; Log.Instance.Info($"set interval = {_interval} sec"); _limit = 20; if (Properties.Settings.Default.limit > 0) _limit = Properties.Settings.Default.limit; Log.Instance.Info($"set storage = {_limit} Mb"); _path = @"C:\temp"; if (!string.IsNullOrEmpty(Properties.Settings.Default.path)) _path = Properties.Settings.Default.path; Log.Instance.Info($"set path = {_path}");}
CheckStorage (Проверка хранилища)
Алгоритм проверки хранилища: если хранилище заполнено, то необходимо рассчитать насколько хранилище заполнено больше чем установленный лимит в настройках и затем соответственно очистить хранилище.
/// <summary>/// Проверка доступного места в хранилище/// </summary>static void CheckStorage(){ var currentSize = StorageSize(); if (currentSize > _limit) { // Сколько нужно очистить MB var totalToTrash = currentSize - _limit; // Очистить необходимое кол-во KB StorageClear(totalToTrash * 1024); }}
Дополнительно приведу методы StorageSize и StorageClear.
StorageSize принимает аргумент насколько нужно
очистить в KB. Почему в KB (килобайтах), а не в MB (мегабайтах) ? 1
скриншот занимает в хранилище размер меньший чем 1 Мегабайт, а
значит корректнее удалять в Килобайтах чтобы не удалять за 1 раз
например 5 скриншотов чтобы после этого в хранилище был записан
всего 1 скриншот.
Метод StorageSize подсчитывает размер всех файлов в директории
хранилища, без учета вложенных директорий (не сложно добавить если
нужно).
/// <summary>/// Заполненность хранилища, в MB/// </summary>/// <returns></returns>static long StorageSize(){ long i = 0; try { DirectoryInfo directory = new DirectoryInfo(_path); FileInfo[] files = directory.GetFiles(); foreach (FileInfo file in files) { i += file.Length; } } catch (Exception ex) { Log.Instance.Error(3, ex.Message); return _limit; } return i /= (1024 * 1024);}
StorageClear принимает аргумент сколько нужно в хранилище очистить Килобайт. Метод считывает список файлов в хранилище, отсортировывает полученную коллекцию по времени создания файла, удаляет файлы начиная с самых старых пока размер удаленных файлов не будет больше чем нужно удалить.
/// <summary>/// Очистка хранилища/// </summary>/// <param name="sizeKb"></param>static void StorageClear(long sizeKb){ try { Log.Instance.Info($"clear = {sizeKb} Kb"); DirectoryInfo directory = new DirectoryInfo(_path); FileInfo[] files = directory.GetFiles().OrderBy(f => f.CreationTime).ToArray(); foreach (FileInfo file in files) { var size = file.Length / 1024; File.Delete(file.FullName); sizeKb -= size; if (sizeKb <= 0) break; } } catch (Exception ex) { Log.Instance.Error(2, ex.Message); }}
DoScreen (Создание скриншота)
Данный метод пытается создать скриншот в хранилище скриншотов. Формат создаемых файлов - PNG. В файл попадает весь экран основного монитора пользователя. Если создать файл не удалось, то отправка в класс Log сообщения об ошибке.
/// <summary>/// Создание скриншота/// </summary>static void DoScreen(){ try { Bitmap printscreen = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); Graphics graphics = Graphics.FromImage(printscreen as Image); graphics.CopyFromScreen(0, 0, 0, 0, printscreen.Size); printscreen.Save(Path.Combine(_path, GetFileName()), System.Drawing.Imaging.ImageFormat.Png); } catch (Exception ex) { Log.Instance.Error(1, ex.Message); }}/// <summary>/// Имя файла создаваемого скриншота/// </summary>/// <returns></returns>static string GetFileName(){ var time = DateTime.Now; return $"{time.ToString("yyyy_MM_dd__HH_mm_ss")}.png";}
Чтобы программы не было видно на панели задач необходимо окно программы спрятать. Вызов данного функционала происходит из метода Main.
#region DllImport[DllImport("kernel32.dll")]static extern IntPtr GetConsoleWindow();[DllImport("user32.dll")]static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);const int SW_HIDE = 0;const int SW_SHOW = 5;#endregion
Логирование действий программы состоит в виде статического класса Log сделанного согласно паттерну Singleton (одиночка).
public sealed class Log{ private static volatile Log _instance; private static readonly object SyncRoot = new object(); private readonly object _logLocker = new object(); private Log() { CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory; LogDirectory = Path.Combine(CurrentDirectory, "log"); } public string CurrentDirectory { get; set; } public string LogDirectory { get; set; } public static Log Instance { get { if (_instance == null) { lock (SyncRoot) { if (_instance == null) _instance = new Log(); } } return _instance; } } public void Error(int errorNumber, string errorText) { Add($"Ошибка {(errorNumber.ToString()).PadLeft(4, '0')}: {errorText}", "[ERROR]"); } public void Info(string log) { Add(log, "[INFO]"); } private void Add(string log, string logLevel) { lock (_logLocker) { try { if (!Directory.Exists(LogDirectory)) { // Создание директории log в случае отсутствия Directory.CreateDirectory(LogDirectory); } // Запись в лог файл вместе с датой и уровнем лога. string newFileName = Path.Combine(LogDirectory, String.Format("{0}.txt", DateTime.Now.ToString("yyyyMMdd"))); File.AppendAllText(newFileName, $"{DateTime.Now} {logLevel} {log} \r\n", Encoding.UTF8); } catch { } } }}