понедельник, 26 ноября 2018 г.

Распространение Windows-приложений (Chocolatey)

Менеджеры пакетов для ОС Windows

В большинстве дистрибутивов Linux есть свои менеджеры пакетов: в Ubuntu/Mint это apt и deb, в OpenSuse это zypper. Менеджеры пакетов хороши тем, что позволяют устанавливать софт со всеми требуемыми зависимостями, к сожалению, магазин приложений Windows, на мой взгляд, не удобен. Однако довольно давно существует неплохое решение - chocolatey.org. Прочитав мануалы я понял, что создать пакет в принципе совсем не трудно, поэтому выкладываю свой гайд по созданию собственного Chocolatey-пакета для ОС семейства Windows (начиная от 7/Server 2003 до 10 и далее).

Алгоритм создания Chocolatey-пакета

Набор операции по созданию Chocolatey-пакетов довольно прост:

0. Установка необходимых инструментов в первую очередь нам понадобится сам chocolatey (choco) и набор утилит командной строки Nuget.Commandline. Как установить chocolatey можно посмотреть здесь: (https://chocolatey.org/docs/installation). Как установить Nuget.Commandline: (https://chocolatey.org/packages/NuGet.CommandLine).

1. Генерация шаблона - генерация шаблона происходит при помощи утилиты choco, при этом генерация происходит в текущей директории, поэтому прежде чем сгенерировать шаблон нужно перейти в директорию с помощью cd и выполнить команду choco new spectrumviewer , spectrumviewer - название создаваемого приложения. В .nuspec файле содержится описание приложения, а в директории tools - скрипты для установки и апгрэйда приложения. В nuspec-файле сдержится общая информация о приложении, например, название, версия, URL-адреса документации, файла лицензии и т.п. В папке tools содержится powershell-скрипт для установки приложения (chocolateyinstall.ps1). Chocolatey-приложение не может содержать файлы внутри себя, а лишь может ссылаться на какой-то URL: для загрузки и установки. Возможен вариант с установщиком exe/msi (командлет Install-ChocolateyPackage @packageArgs), путь к файлу установщику задается следующим образом:
$url        = 'https://github.com/MossbauerLab/SpectrumViewerDocs/raw/master/2.0/SpectrumViewer.msi'
$url64      = 'https://github.com/MossbauerLab/SpectrumViewerDocs/raw/master/2.0/SpectrumViewer.msi'
И вариант с portable-версией программного обеспечения (командлет Install-ChocolateyZipPackage @packageArgs).

2. Упаковка, генерация nupkg-файла
После довольно несложной настройки nuspec и chocolateyinstall.ps1 можно переходить к следующему этапу - генерации nupkg-файла: из директории, содержащей nuspec-файл необходимо выполнить следующую команду: choco pack. После генерации упакованного файла приложения можно испытать установку choco install .\spectrumviewer.2.0.nupkg .Если установка прошла благополучно, то можно проталкивать в галерею chocolatey.

3. Публикация пакета
Самая простая часть, необходимо присоединить ключ (см. свою учетную запись на chocolatey.org):
choco apikey --key ... --source https://push.chocolatey.org/
После этого проталкиваем приложение в chocolatey: choco push spectrumviewer.2.0.nupkg --source https://push.chocolatey.org/
Последнее, что остается - дождаться подтверждения размещения приложения.

среда, 1 августа 2018 г.

Easy and powerful pdf processing

Introduction

Applications usually works with data. Sometimes applications depends on different input data format but in any case data is processing like binary or text (particular case of binary data). PDF (Printable data format) is widely using format for writing official documents, letters, scientific and other articles and for many other different cases. Sometimes our business solution must extract text or some other data from structured document. I discovered that very powerfull solution could be build using Aspose commercial solutions (https://www.aspose.com/). My solution was implemented and deployed on dev.activedictionary.ru.

Application issue

The main task is to getting text from any pdf document. Current developing application is Web but pdf processing could be built with libraries for standalone application it is cheaper and does not carry too much troubles. Prices for Aspose solutions are listed here (https://purchase.aspose.com/pricing/pdf/) . I have used one for Java (small bussines licence type) for 1000$ - https://purchase.aspose.com/pricing/pdf/java. I have not mentioned it previously but application which must contain PDF processing is Python-based application built with Django framework.

Solution with Aspose libraries

Getting text from pdf is not easy when you work with well-structured PDF documents, Aspose libraries are awared of how to process structured PDF documents: defines paragraphes and images on a page. I don't know every particular cases but in 80% cases processing documents consists of 2-20 pages separated into blocks with titles and there are could be images or tables here. Documents also could be structured with cover and back pages, index and so on. The  main features of processing documents:
- mostly contains text (60-80% of document);
- text could be on a different language with possibly different character encoding (ANSI, KOI8 family, Unicode)
- documents could either structured and without structure (because target system allows users to load any type of document).
Main feature of working with PDF is to extract text for further manual texts analysis therefore text extraction must be accurate (we can't afford to loose block/paragraphes or even sentences or words) because developing application is learning management system (LMS).
We have choosed Aspose libraries  by suggestion of one of our developers As for me: pdf processing is important but not the only feature of our system and must be working reliable without fails. We checked Aspose libtraries with documents that we written on several different languages (ru, en, de) with various encodings and after tests we consider that this library is suitable for our project: it works fairly reliable on most of documents (we have tried on approximetely 800-1000 different docs: articles, books, standards).

For upper described business proceses we have built java console application which takes pdf input file via command line argument with --source key. Result of processing - text is output to stdout via System.out.printf("%s\n", text);  Processing consists from following steps:

1) Instantation of License object with license file:
     License license = new License();
       license.setLicense(LICENSE_FILENAME);

2) Creation of container object - Document:
    document = new Document(commandLineArgs.getSource());

3) Content processing consists of absorbing images first (using ImagePlacementAbsorber) and paragraph absorbing (ParagraphAbsorber) and returning list of Content object (full details of implementation are not listed until we open project).

From python web application it looks like:

extractor_dir = os.path.join(EXTERNAL_SOURCE_DIR, 'pdf')
source_param = u'--source={0}'.format(source_path)
args = ['java', '-jar', 'PdfContentExtractor-0.3.jar', source_param]
proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=extractor_dir)
out, error = proc.communicate()
returncode = proc.wait()
if returncode > 0:
    raise Exception()

content (a composition of images and text=paragraphes) is stored in out variable as Base-64 string (result of extraction was converted before output in Java console application). Further processing is following:

import StringIO
import base64
buffer = StringIO.StringIO(output)
try:
    self.__text = base64.b64decode(buffer.readline())
    self.__images = []
    while True:
        name = buffer.readline().rstrip()
        if not name:
            break
        data = base64.b64decode(buffer.readline())
        self.__images.append((name, data))
finally:
    buffer.close()
 

A bitter taste in a honey pot (disadvantages)

There is one (personally for me) disadvantages is a price, i think 1000$ it is sufficient price for small Russian team especially if you are building non-profit open solution. In my hamble opinion for such cases price should be reduced a twice.

Conclusion

I must say that Aspose libraries are great for PDF documents processing, it was easy to built a component of developing application with solution based on Aspose libraries. Don't forget to comment/like this article about my expirience working with PDFs.



четверг, 28 июня 2018 г.

Как работают индексы и их использование в Django


О чем эта статья

Данная статья не будет академическим изложением проблем индексирования в база данных, а будет затрагивать один маленький практический аспект использования индексов для выборки данных. Если отвечать на вопрос заголовка - о деталях и внимательном чтении документации, но я не такой педант (во всяком случае не всегда), мне значительно более интересно экспериментально посмотреть суть проблемы.

Окружение и приложение

Есть Django Web-приложение на python 2.7 (не спрашивайте почему не 3.х). Web-приложение состоит из нескольких типов Worker классов для взаимодействия как с внешними сервисами по HTTP, так и с локальным аналогом данных сервисов, использующих для хранения СУБД MySQL. Мы имеем следующие объекты в модели данных: языковая пара и общая таблица для отображения одного текста в другой:

class LocalTranslationMatrixLanguagesPair(models.Model):
    @classmethod
    def create(cls, language1, language2):
        return cls(language1=language1, language2=language2)

    language1 = models.CharField(u'language1', max_length=4, null=False)
    language2 = models.CharField(u'language2', max_length=4, null=False)

    class Meta:
        unique_together = (u'language1', u'language2')

class LocalTranslationMatrix(models.Model):
    @classmethod
    def create(cls, dictionary, languages, text1, text2, is_main):
        return cls(dictionary=dictionary, languages=languages, text1=text1, text2=text2, is_main=is_main)

    dictionary = models.ForeignKey(Dictionary, related_name=u'local_matrix_dictionary', null=False)
    languages = models.ForeignKey(LocalTranslationMatrixLanguagesPair, related_name=u'local_matrix_language_pair')
    text1 = models.CharField(u'text1', max_length=512, null=False, db_index=True)
    text2 = models.CharField(u'text2', max_length=512, null=False, db_index=True)
    is_main = models.BooleanField(u'is_main')

Самое главное здесь наличие индексов по полям text1 и text2. В MySQL в таблице, на которую отображается LocalTranslationMatrix хранится около 2 млн. строк с перспективой расширения до 10 млн и более.

Использование индексов

Из вышеприведенного кода можно сказать, что у нас есть пары текстов для сопоставления для разных пар языков. Есть задача поиска текста как по столбцу text1, так и по text2 с учетом того, что повторы текста могут быть по этим столбцам, но для разных пар языков. Для решения этой задачи будет использоваться функция filter с использованием функции Q для составления запросов. Так вот вся суть этой статьи заключается в следующем предложении: экспериментально я определил, что наилучшим образом поиск строк происходит в том случае, когда составление запроса происходит по столбцам, с которыми связаны индексы, и чем короче запрос, тем быстрее происходит выборка данных, по сравнению с поиском по индексам и фильтрация по другим столбцам.

Например, такой запрос БЕЗ ИНДЕКСОВ:
translations = LocalTranslationMatrix.objects.filter(Q(text1=translation_request.text) &
                                                                                   Q(languages=lang_pair.id))
на 2 млн строк выполняется за время ~2-3 сек

Если его модифицировать следующим образом и использовать индексы:
translations = LocalTranslationMatrix.objects.filter(Q(text1=translation_request.text))

то он будет выполнен за ~1 мс и меньше

А фильтрация на объектах в памяти питоновского процесса будет выполнена намного быстрее.

Маленькая хитрость или проблемы с длинными индексами

 В mysql при индексах на столбцах длиннее 767 байт (255 символов на utf-8 с 3 байтами на символ) возникают проблемы: не создается база данных или не применяется миграция, для этого я создаю отдельную миграцию, которая выполняет следующее:

 # -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from django.db import migrations, models



class Migration(migrations.Migration):

    #initial = False

    dependencies = [
        ('translator', '0001_initial')
    ]
   
    operations = [
        migrations.RunSQL("SET GLOBAL innodb_large_prefix = ON; SET GLOBAL innodb_file_format=Barracuda;"),
        migrations.RunSQL("ALTER TABLE translator_translation ROW_FORMAT=DYNAMIC;"),
        migrations.RunSQL("ALTER TABLE translator_localtranslationmatrix ROW_FORMAT=DYNAMIC;")

]

Иногда возникают проблемы с созданием индексов через модель, их также можно создать через RunSQL:

 migrations.RunSQL("CREATE INDEX translator_translation_i_original_text ON translator_translation(original_text)"),

Заключение

Практика показывает, что самый лучший вариант - поиск ИСКЛЮЧИТЕЛЬНО только по индексам, а фильтрацию лучше проводить на объектах в памяти.


понедельник, 2 апреля 2018 г.

Восстановление БД с изменением имени в MSSQL

Проблема
Необходимо скопировать и восстановить одну из БД на том же инстансе (экземпляре) SQL сервер.

У MSSQL  все базы данных имеют логическое имя и обычно состоят из 2 файлов (файла данных и файла лога), имеющие следующие имена:

logicalName_Data.mdf
и
logicalName_Log.ldf

Иногда возникают ситуации когда необходимо "скопировать" БД на этот же инстанс SQL-сервера, но с новым логическим именем т.к., например, копируемая БД используется одним или несколькими приложениями.

Решение
Я не нашел как решить эту проблему в SQL Management Studio без операций по переименовыванию исходной БД (одно из условий: БД используется одним из приложений, поэтому ее трогать нельзя). Решение довольно простое, но для этого еужно написать TSQL-инструкцию.

1. Вывод списка файлов бэкапа:
RESTORE FILELISTSTONLY FROM DISK = 'E:\backups\mydb.bak'

в моем случае я получил mydb] для Data-файла и mydb]_log для файла логов

2. теперь скопируем имена файлов из выведенной таблицы в слебующую инструкцию:

RESTORE DATABASE myNewDb FROM DISK = 'E:\backups\mydb.bak'
WITH
RECOVERY,
MOVE 'mydb]' TO 'E:\db\data\myNewDb.mdf',
MOVE 'mydb]_log' TO 'E:\db\logs\myNewDb.ldf'; 

База восстановлена с новым именем myNewDb.

вторник, 2 января 2018 г.

Развертывание Asp Net Core на IIS

Как развертывается Asp Net Core

Asp Net Core - это как Проктор энд Гэмбл т.е. два в одном флаконе: фрэймворк и Web-сервер, а само веб приложение  имеет вид консольного приложения, развертываемого через IWebHost с предварительной настройкой сервисов, миддлваре и т.п. Все относительно просто в случаях, когда разработка идет на машине с установленной Visual Studio. Для запуска веб приложений в Asp Net Core используется веб-сервер Kestrel. И, казалось, бы нет никаких сложностей для запуска веб-приложения на IIS. Но, столкнувшись с такой задачей я набил кучу шишек, о чем и хочу рассказать в этом посте.

Создание Net Core пула и подключение модуля AspNetCore

Как известно в IIS имеются пулы, которые, используются во-первых, для изоляции одних приложений от других, а, во-вторых, для простоты настройки однотипных приложений. И если запустить настройку пула приложения на IIS, то можно увидеть, что Net Core в нем напрочь отсутствует (предварительно я установил Net Core Sdk), всего 3 варианта таргет фрэймворка для пула: 2.0, 4.0 и неуправляемый код. Для Asp Net Core нужно выбрать последний. 

Но и это еще не все: выше я писал, что Asp Net Core веб-приложение развертывается на веб-сервере Kestrel, а как же тогда подключить IIS, дело в том, что IIS подключается как прокси: все запросы, пришедшие на HTTP порт приложения, развернутого на IIS, маршрутизируются через модуль AspNetCore в Kestrel, обрабатываются приложением, а ответ направляется в IIS. За это взаимодействие отвечает AspNetCore модуль, который необходимо установить в IIS отдельно от Net Core Sdk!!!! (https://docs.microsoft.com/ru-ru/aspnet/core/fundamentals/servers/aspnet-core-module). После инсталляции в списке модулей должна быть эта строка (предварительно (сначала) необходимо установить VS2015 C++ Redistributable Package):
Кроме всего этого в Web.config необходимо указать использование этого модуля и путь к целевой сборке (приложению), например, мое приложение - E3App.Web.dll и Web.Config имеет следующий вид:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" />
    </handlers>
    <aspNetCore
        processPath="dotnet"
        arguments=".\E3App.Web.dll"
        stdoutLogEnabled="true"
        stdoutLogFile=".\logs\stdout" />
  </system.webServer>
</configuration>

Этот файл, как и многое другое, генерируется в процессе публикации проекта (Publish), однако, я не думал, что он играет роль, т.к. он в явном виде отсутствует в проекте. Но он очень важный! Особенно атрибут stdoutLogEnabled, по умолчанию этот атрибут имеет значение false, что для разработчика означает следующее: любое исключение произошедшее при запуске приложение будет "проглочено", а в качестве результата мы увидим на странице что-то вроде: "Оопс, а что-то сломалось". Включаем и идем дальше.

Старая добрая БД и EF Core

Следующим приколом на пути к запуску приложения стала сама БД: я поставил Sql Server 2012, предполагая, что EF нет дела до версии SQL сервера, но не тут-то было (хотя мне казалось, что ву меня был рабочий проект под 12 версию сервера). Текст исключения в стандартном выводе мне ничего не говорил о том, почему у меня ломается приложение при старте. Однако, подозрительным было то, что при отладке приложение стартовало без каких-либо проблем, а на IIS - ломалось. Тогда я решил заменить 12 SQL Server на SQL Server 2016 и вуаля, текст ошибки стал другим. Теперь приложение жаловалось на то, что нет прав на создании новой БД при подключении к таблице master (тут все тривиально, нужна роль sysadmin для пользователя под которым выполняется подключение). Хотелось бы сделать ремарку о том, что EF работает с БД по парадигме Code First (сам создает скрипт для создания БД, всех таблиц и связей между ними). Тут тоже пришлось помучиться, т.к. можно сделать строку подключения с полным указаниям логина и пароля (в appsettings.json):

  "ConnectionStrings": {
    "Storage": "Server=DEV-HOST\\SQLEXPRESS;Database=e3app;
    User ID=developer;Password=123;MultipleActiveResultSets=true"
  }


 Но это не всегда удобно использовать заранее созданную учетную запись, иногда необходимо использовать Windows-аутентификацию, казалось бы просто выдал права (роль sysadmin) своей Windows учетной записи:

"ConnectionStrings": {
    "Storage": "Server=DEV-HOST\\SQLEXPRESS;Database=e3app;
    Integrated Security=SSPI;MultipleActiveResultSets=true"
 }


 но нет в БД, оказывается, EF лезет через другую учетку. Экспериментальным способом включая данную роль у логинов по очереди, определил, что это  учетная запись BULTIN\Users (а если посмотреть, кто создал БД - IIS APPPOOL/AspNetCore).



Заключение

Казалось бы тривиальная задача, но она отняла у меня кучу времени, я убил, наверное, пару дней чистого времени пытаясь понять, что происходит и почему все работает не так, как нужно. Надеюсь, что этот пост поможет тем, кто столкнулся с такой же проблемой: деплоя на IIS Asp Net Core веб приложения.



воскресенье, 12 ноября 2017 г.

Анонимный доступ в Samba

Как настроить полностью анонимный доступ к Samba сервису

Данное очень короткое сообщение, скорее напоминание самому себе как это делается,  т.к. на одном из моих одноплатников, а именно на том, на котором крутился мой файлообменник, умерла карта памяти, чтобы не вспоминать по 10 раз снова как это делается (а внутри домашней сети мне нет смысла делать доступ по паролю) я здесь выложу файл конфигурации Samba для полностью анонимного доступа:

[global]
workgroup = FAMILY
server string = Cubie Samba Server
dos charset = cp1251
unix charset = utf8
dns proxy = no
security = user
browseable = yes
log file = /media/other/samba/smbd.log
syslog = 0
map to guest = Bad Password
guest account = nobody
browseable = yes

[shared]
comment = Guest access share
path = /media/samba/shared
browseable = yes
read only = no
guest ok = yes
writable = yes
public = yes
available = yes

Комментировать тут особенно нечего, поэтому если кому-то понадобится, пользуйтесь на здоровье.

четверг, 9 ноября 2017 г.

Десериализация в ASP NET Core 2.0

Будни кросс-платформенного Web от Microsoft

ASP NET Core базируется на платформе NET Core, которая, в свою очередь является кросс-платформенной, на данный момент доступна версия 2.0.х (у меня NET Core работает, что под Windows 7, что под Linux Mint 18).  Сам ASP NET Core включает в себя Entity Framework, Json Converter (от Newtonsoft), IoC и т.п. Сегодня пойдет речь о работе с Json, точнее о том, что я в итоге получил.

Приколы с POST и PUT и десериализацией из Json

Предположим у нас есть два проекта в солюшене, пусть они называются ClickPlatform.Dto и ClickPlatform.Mvc. В первом у нас объявлены классы Dto - объектов, а вот втором - Web API с набором контроллеров с методами (Get, Post, Put и Delete для обработки одноименных HTTP-методов и действий).

У нас есть следующий класс Dto:

    public class Listing
    {
          public Listing() { }

          public Listing(Guid id, string name, AdShortInfo[] linkedAd = null, string status = null,                                              TotalStatistics stats = null)
          {
               Id = id;
               Name = name;
               LinkedAd = linkedAd;
               Status = status;
               Stats = stats;
          }

          public Guid Id { get; set; }
          public string Name { get; set; }
          public AdShortInfo[] LinkedAd { get; set; }
          public string Status { get; set; }
          public TotalStatistics Stats { get; set; }
    }

Понятно, что для получения объекта и коллекции объектов используем Get-методы, с сериализацией тут все ок, все хорошо работает. и мы получаем либо коллекцию объектов, либо отдельный объект по id. Чудеса начинаются при использовании методов Post и Put внутри следующего контроллера

    [Produces("application/json")]
    [Route("api/[controller]")]
    public class ListingController : Controller
    {
        .// ...
       
        [HttpPost]
        public void Post([FromBody] Dto.Internal.Listing listing)
        {
    _listingManager.Create(listing);
        }

        [HttpPut("{id}")]
        public void Put([FromRoute]Guid id, [FromBody]Dto.Internal.Listing listing)
        {
            _listingManager.Update(id, listing);
        }
       
        // ...
   }

   Пробуем дернуть Post при этом устанавливаем content-type=application/json и charset=utf-8. и передаем следующий json: {"id":"","status":"active","name":"Evil_One","linkedAd":[], "stats":""}


Точка останова показывает нам, что наш объект - null.


Если мы поставим object, то получим следующее:

[HttpPost]
public void Post([FromBody] /*Dto.Internal.Listing*/ object listing)
{
     //_listingManager.Create(listing);
}


Теперь объект в теле определился, получается, что Json не может десериализовать объект в Dto.Internal.Listing. Почему же так происходит?  Все дело оказывается в том, что если для value type значение в Json не задано, то считается, что там null, а null нельзя использовать для value-типов, поэтому приходится везде ставить Nullable<T>. В вышеприведенном коде поменяет  свойство Guid Id {get; set;} на Guid? Id {get; set;}. И теперь смело можем передавать запрос со значением ""  в качестве Guid

При этом сам Newtonsoft.Json не соизволил проинформировать о том, что он не может десериализовать тип. Что ж вот такая она десериализация объектов в Asp NET.

Послесловие (послевкусие)
Попробовал я ASP NET Core и не могу сказать, что это супер платформа, не могу сказать, что это именно то, что нужно для разработки. В ней вроде как бы все есть, но что-то в ней не так. Многие вещи кажутся странными: как например молчаливая обработка ошибок, подтягивание Nuget пакетов для самых очевидных вещей вроде System.Data и многое другое. Если сюда добавить работу только на VS17 (на мой взгляд, худшая студия начиная с 2003, даже 2010 была лучше).





Распространение Windows-приложений (Chocolatey)

Менеджеры пакетов для ОС Windows В большинстве дистрибутивов Linux есть свои менеджеры пакетов: в Ubuntu/Mint это apt и deb, в OpenSuse э...