Реалізація генератора QR кодів в ZennoPoster

Недавно я описував задачу, згідно якої потрібно було в багатопотоковому режимі генерувати QR коди. І от, вирішив написати це рішення, щоб воно вже було під руками у випадку необхідності. Розкажу як це було і як виглядає код.

Перед тим як писати код я сформулював і описав в текстовому вигляді завдання. Робив я це у довільній формі, щоб зрозуміти з чого потрібно почати і які функції повинні бути в реалізації.

Почав з того, що в мене поряд з проєктом повинні бути текстові файли і папка для збереження картинок QR кодів. Щоб не створювати їх руками я створив блок Свій C# код з таким фрагментом коду, який зразу і виконав (після чого блок можна було видалити, тому що робота зроблена).

string[] files = new[]{"input.txt", "bad.txt", "good.txt"};
string dir = Path.Combine(project.Directory, "data");
if(!Directory.Exists(dir)) Directory.CreateDirectory(dir);

foreach(string name in files) {
	string path = Path.Combine(project.Directory, name);
	if(!File.Exists(path)) File.WriteAllText(path, string.Empty);
}

Дальше вже створив необхідних три списки – input, good, bad і прив’язав їх до створених текстових файлів.
Так як текстовий файл input.txt не містив ніяких даних, то я вирішив згенерувати декілька посиланнь, щоб можна було на них потренуватись.
Для цього сформував слідуючий фрагмент коду, який також запустив одноразово на виконання:

var input = project.Lists["input"];

for(int i =0;i<100;i++){
	string url = string.Format("https://blog.yosyfovych.te.ua/?p={0}", i);
	input.Add(url);
}

Тепер в мене вже були вхідні дані, і були необхідні списки для збереження результату роботи.
Вирішення завдання в мене складається з трьох блоків – створення імені файла використовуючи sha256, скачування QR коду, збереження файла в папку.
Тому я зразу і написав ці функції і помістив їх в контекст.
Для цього створив новий блок Свій C# код з таким фрагментом коду:

Func<string, string> CreateName = delegate(string text) {
	var sb = new StringBuilder();
	using (var hash = System.Security.Cryptography.SHA256Managed.Create()) {
		var enc = Encoding.UTF8;
		foreach (var b in hash.ComputeHash(enc.GetBytes(text))) sb.Append(b.ToString("x2"));	 
	}
	return	string.Format("{0}.jpg",sb.ToString());
};


Func<string, byte[]> Get = delegate(string link) {
	
	string url = string.Format("https://chart.googleapis.com/chart?chs=350x350&cht=qr&chl={0}&choe=UTF-8&chld=H|4", link);
	var type = ZennoLab.InterfacesLibrary.Enums.Http.ResponceType.BodyOnly;
	var method = ZennoLab.InterfacesLibrary.Enums.Http.HttpMethod.GET;
	byte[] bytes = new Byte[0];
	try {
		bytes = ZennoPoster.HTTP.RequestBytes(
			method: method, // метод яким буде відправлено запит
			url: url,// по якому адресу буде відправлено запит
			content: string.Empty,
			contentPostingType: string.Empty,
			proxy: string.Empty,
			respType: type, // в якому вигляді повернути результат
			UseOriginalUrl: true, // не змінювати параметри запиту
			removeDefaultHeaders: true // видалення стандартних заголовків
		);
	}
	catch {
		project.SendErrorToLog("error get",true);
	}
	return bytes;
};


Func<byte[], string, string, bool> Save = delegate(byte[] qr, string dir, string name) {
	bool check = false;
	try {
		using (MemoryStream ms = new MemoryStream(qr)){
			using (var img = Image.FromStream(ms)) {
				if(img.Width > 0){	
					img.Save(Path.Combine(dir, name),System.Drawing.Imaging.ImageFormat.Jpeg);
					check = true;			
				}		 
			}
		}
	}
	catch {
		project.SendInfoToLog("error save",true);
	}
	return check;
};

	
project.Context["name"] = CreateName;
project.Context["sw"] = System.Diagnostics.Stopwatch.StartNew();
project.Context["get"] = Get;
project.Context["save"] = Save;


Власне цей блок необхідно буде виконати один раз на початку виконання проєкту, а дальше уже визивати ці фунції з контексту необхідну кількість разів. Зручно тим, що визивати їх можна одним рядком, і можна більше зосередити увагу при написанні на самій логіці.

Після чого уже в новому блоці написав реалізацію, яка одержує перший рядок з видаленням з файлу який містить вхідні дані. І якщо файл пустий – виходить з блоку по помилці (щоб можна було зупинити виконання проєкту згідно не успішних виконаннь). А коли є рядки з даними – то в лог виводиться повідомлення скільки їх там залишилось.

Після чого, по черзі визиваються фунції створення імені, скачування файлу і власне його збереження.
Ім’я завжди буде створено без помилок.
Скачування картинки QR коду може бути з помилкою, тому в фунції яка описана вище код одягнутий в конструкцію try/catch. І якщо виникла помилка – повертається з фунції пустий масив, тобто без даних.
Дальше переходимо до збереження картинки, для цього з масиву байт створюється зображення. Якщо в масиві не зображення – то створена картинка не буде мати ширини – це я і перевіряю. У випадку коли це картинка – зберігаю і повертаю true, інакше false.

Залишилось тільки зберегти результат в список з успішним чи не успішним виконанням і вивести повідомлення в лог скільки рядків є у кожному списку.
Вишенькою на торті буде також повідомлення про те, скільки часу було затрачено на скачування і збереження QR коду, що може знадобитись, коли захочеться пришвидшити процес.

Нижче наведений описаний код:

var input = project.Lists["input"];
var bad = project.Lists["bad"];
var good = project.Lists["good"];
var sw = project.Context["sw"];

string dir = Path.Combine(project.Directory, "data");
string link = string.Empty;
string name = string.Empty;

int count = 0;
lock(SyncObjects.ListSyncer){
	count = input.Count;
	if(count > 0) {
		link = input.GetItem("0", true);
		project.SendInfoToLog(string.Format("count all: {0}", count-1), true);
	}
}
if(count == 0) {
	project.SendInfoToLog("Stop", true);
	throw new Exception();
}

name =  project.Context["name"](link);
byte[] qr = project.Context["get"](link);
bool check = project.Context["save"](qr, dir, name);

if(check) {
	lock(SyncObjects.ListSyncer) good.Add(string.Join("|", new[]{name, link }));
}
else lock(SyncObjects.ListSyncer) bad.Add(link);	

project.SendInfoToLog(string.Format("count all: {0} good: {1} bad: {2}", input.Count,good.Count, bad.Count), true);

sw.Stop();
project.SendInfoToLog(string.Format("Стоп: {0:c} ms", sw.Elapsed));

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *