Структура qt-проекта на c++

Введение

После разработки проектов и задач на 1С, собственно как и в других языках программирования, приходится сталкиваться с техническим долгом.

Что такое технический долг? Технический долг — это метафора программной инженерии, обозначающая накопленные в программном коде или архитектуре проблемы, связанные с пренебрежением к качеству при разработке программного обеспечения и вызывающие дополнительные затраты труда в будущем (Wikipedia).

Причины возникновения могут быть разными:

  • Недостаточная компетентность кадров;

  • Давление со стороны бизнеса;

  • Плохо спроектированная архитектура и отсутствие документации;

  • Отсутствие процессов review и refactoring;

  • Отсутствует тестирование;

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

Управление техническим долгом

Управлять техническим долгом можно следующим подходами:

  • Внешний аудит — привлечение сторонней компании. Затратно, на результат может влиять человеческий фактор.

  • Внутренний аудит — визуальная проверка кода (code review).  Опять же на результат влияет человеческий фактор и иногда необъективность.

  • Автоматизированная проверка кода — применяются статические анализаторы. В мире 1С используется конфигурация 1С: АПК.

Существует еще один подход, который я считаю наиболее эффективным — непрерывная проверка кода (continuous code inspection). Более подробно можно изучить подход в статье Управление техническим долгом — Концепция Continuous Inspection. Мой выбор пал на SonarQube.

SonarQube — программное решение для непрерывного анализа и измерения качества кода. На текущий момент платформой поддерживается более 27 языков программирования. К сожалению, язык 1С не включен в официальный перечень поддерживаемых языков SonarQube. 

На рынке существует как минимум два плагина для поддержки языка 1С в SonarQube.

  • SonarQube 1C (BSL) Community Plugin — open source решение (https://github.com/1c-syntax/sonar-bsl-plugin-community).

  • SONARQUBE 1C (BSL) Plugin  — решение от SilverBulleters. (https://silverbulleters.org/sonarqube).

В статье используется плагин SonarQube 1C (BSL) Community Plugin, т.к. он бесплатен. Плагин основан на проекте BSL Language Server — реализации протокола language server protocol для языка 1С: Предприятие 8 и OneScript. 

Быстрый старт

В рамках базового примера мы развернем SonarQube, подготовим рабочий каталог проекта 1С, загрузим результаты проверки кода на сервис. В примере будет использоваться операционная система Windows. Повторить то же самое для unix / macos систем возможно, но с небольшими изменениями. Все скрипты будем выполнять в консоли CMD или Powershell (не имеет значения).

 
 Кто используют Git

Клонируем на компьютер с помощью консоли:

Визуально проект выглядит:

В его состав входят:

  • project1c — рабочий каталог с проектом для анализа.

    • src — каталог для исходных кодов 1С

    • check.bat — скрипт запуска проверки кода

    • sonar-project.properties — файл настроек проекта для SonarQube

Создание многопроектного шаблона из существующего решения

  1. Создайте решения и добавьте два или более проектов.

  2. Настройте проекты для экспорта в шаблон.

    Dica

    Если вы используете параметры шаблона и хотите ссылаться на переменные из родительского шаблона, укажите перед именем параметра префикс . Например, . Кроме того, задайте атрибуту CopyParameters элемента ProjectTemplateLink значение true.

  3. В меню Проект выберите команду Экспорт шаблона.

    Открывается мастер экспорта шаблонов.

  4. На странице Выбор типа шаблона выберите Шаблон проекта. Выберите один из проектов, который необходимо экспортировать в шаблон, а затем нажмите кнопку Далее. (Вы будете повторять эти действия для каждого проекта в решении.)

  5. На странице Выбор параметров шаблона введите имя шаблона и необязательное описание, значок и рисунок предварительного просмотра для шаблона. Нажмите кнопку Готово.

    Проект будет экспортирован в ZIP-файл и помещен в указанное выходное расположение.

    Observação

    Каждый проект необходимо отдельно экспортировать в шаблон, поэтому повторите предыдущие шаги для каждого проекта в решении.

  6. Создайте каталог для шаблона, содержащий вложенный каталог для каждого проекта.

  7. Извлеките содержимое ZIP-файла каждого проекта в соответствующий созданный вложенный каталог.

  8. В базовом каталоге создайте XML-файл с расширением VSTEMPLATE. Этот файл содержит метаданные для многопроектного шаблона. Пример структуры файла приведен ниже. Укажите относительный путь к файлу VSTEMPLATE каждого проекта.

  9. Выберите все файлы в базовом каталоге и в контекстном меню выберите пункты Отправить в > Сжатая ZIP-папка.

    Файлы и папки сжимаются в ZIP-файл.

  10. Скопируйте ZIP-файл в пользовательский каталог шаблона проекта. По умолчанию это каталог %USERPROFILE%\Documents\Visual Studio <version>\Templates\ProjectTemplates.

  11. В Visual Studio выберите Файл > Создать > Проект и убедитесь, что шаблон отображается.

Отладка исполняемых файлов PyInstaller ↑

Как вы видели выше, вы можете столкнуться с проблемами при запуске исполняемого файла. В зависимости от сложности вашего проекта исправления могут быть такими простыми, как включение файлов данных, таких как пример программы чтения каналов. Однако иногда требуется больше методов отладки.

Ниже приведены несколько распространенных стратегий, которые не указаны в определенном порядке.Часто одна из этих стратегий или их комбинация приводит к прорыву в сложных сеансах отладки.

Используйте терминал   

Сначала попробуйте запустить исполняемый файл из терминала, чтобы увидеть весь вывод.

Не забудьте удалить флаг -w build, чтобы увидеть весь стандартный вывод в окне консоли. Часто вы увидите исключения ImportError, если зависимость отсутствует.

Файлы отладки   

Проверьте файл build/cli/warn-cli.txt на наличие проблем. PyInstaller создает множество выходных данных, чтобы помочь вам понять, что именно он создает. Копание в папке build/ — отличное место для начала.

Используйте режим распространения —onedir для создания папки распространения вместо одного исполняемого файла. Опять же, это режим по умолчанию. Сборка с помощью —onedir дает вам возможность проверить все включенные зависимости вместо того, чтобы все было скрыто в одном исполняемом файле.

—onedir полезен для отладки, но —onefile обычно легче понять пользователям. После отладки вы можете переключиться в режим —onefile, чтобы упростить распространение.

Дополнительные параметры интерфейса командной строки   

PyInstaller также имеет параметры для управления объемом информации, печатаемой в процессе сборки. Перестройте исполняемый файл с параметром —log-level=DEBUG в PyInstaller и просмотрите вывод.

PyInstaller создаст много вывода при увеличении детализации с помощью —log-level=DEBUG. Полезно сохранить этот вывод в файл, к которому вы можете обратиться позже, вместо того, чтобы прокручивать его в Терминале. Для этого вы можете использовать функцию перенаправления вашей оболочки. Вот пример:

$ pyinstaller --log-level=DEBUG cli.py 2> build.txt

Используя указанную выше команду, вы получите файл build.txt, содержащий множество дополнительных сообщений DEBUG.

Вот пример того, как может выглядеть ваш файл build.txt:

67 INFO: PyInstaller: 3.4
67 INFO: Python: 3.6.6
73 INFO: Platform: Darwin-18.2.0-x86_64-i386-64bit
74 INFO: wrote /Users/realpython/pyinstaller/reader/cli.spec
74 DEBUG: Testing for UPX ...
77 INFO: UPX is not available.
78 DEBUG: script: /Users/realptyhon/pyinstaller/reader/cli.py
78 INFO: Extending PYTHONPATH with paths
['/Users/realpython/pyinstaller/reader',
 '/Users/realpython/pyinstaller/reader']

В этом файле будет много подробной информации о том, что было включено в вашу сборку, почему что-то не было включено и как был упакован исполняемый файл.

Вы также можете перестроить свой исполняемый файл с помощью параметра —debug в дополнение к параметру —log-level для получения дополнительной информации.

Дополнительные документы PyInstaller   

PyInstaller GitHub Wiki содержит множество полезных ссылок и советов по отладке. В первую очередь это разделы о том, , и что делать, если что-то пойдет не так.

Помощь в обнаружении зависимостей   

Наиболее частая проблема, с которой вы столкнетесь, — это исключения ImportError, если PyInstaller не может правильно определить все ваши зависимости. Как упоминалось ранее, это может произойти, если вы используете , импорт внутри функций или другие типы .

Многие из этих типов проблем можно решить с помощью —hidden-import параметр CLI PyInstaller. Это указывает PyInstaller включить модуль или пакет, даже если он не обнаруживает их автоматически. Это самый простой способ обойти множество магических действий динамического импорта в вашем приложении.

Другой способ обойти проблемы — файлы перехвата. Эти файлы содержат дополнительную информацию, которая поможет PyInstaller упаковать зависимость. Вы можете написать свои собственные хуки и указать PyInstaller использовать их с параметром CLI —additional-hooks-dir.

Файлы ловушек — это то, как сам PyInstaller работает внутри, поэтому вы можете найти множество примеров файлов ловушек в исходном коде PyInstaller.

Обновите IDE и плагины

Со временем как Eclipse, так и плагины и надстройки программы обновляются для добавления новых функций или исправления ошибок и обеспечения правильной работы. Каждый раз, когда выходит новая версия программы, нет необходимости загружать и устанавливать ее снова, но мы можем обновить ее, используя фактическое средство обновления, которое включено в основную среду IDE.

Для этого мы нажимаем на меню «Справка» и выбираем « Проверить наличие обновлений «.

Программа сама подключится к своим серверам и будет искать новую версию программы или что-то в этом роде. Если есть, это позволит нам загрузить его прямо из инструмента. В противном случае мы увидим сообщение, которое укажет, что действительно новых доступных версий нет.

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

Что такое затмение

Eclipse — это самая известная IDE (интегрированная среда разработки ), когда речь идет о программировании с использованием языка Java. Однако его возможности не ограничиваются только Java, но он также интегрирует в программу другую серию IDE, которая позволяет нам программировать на других языках программирования, таких как C / C ++, JavaScript / TypeScript, Python или PHP, в том числе другие, благодаря плагинам. Таким образом, мы можем использовать эту программу для одновременного объединения нескольких языков в одном пакете и без использования других дополнительных инструментов.

Эта программная среда IDE возникла как эволюция VisualAge , инструмент, разработанный IBM программировать как на Java, так и на Smaltalk. Когда в 2001 году Java стала очень популярной, IBM решила отказаться от поддержки Smaltalk и создать новый инструмент для программирования на Java: Eclipse. Eclipse Foundation (который состоит из компьютерных гигантов, таких как HP, Oracle или Red Hat, среди других) несет ответственность за эту программу, полностью бесплатную среду IDE с открытым исходным кодом, защищенную с помощью Общественная лицензия Eclipse .

Хотя изначально она создавалась как виртуальная машина Java, сегодня среда разработки спроектирована таким образом, что пользователи сами настраивают ее по своему вкусу с помощью плагинов. По умолчанию программа поставляется с Плагин JDT включен, что добавляет поддержку Java. Но мы можем загрузить и установить другие плагины в зависимости от языка или языков, которые мы собираемся использовать.

Вот некоторые из многих функций, которые предлагает нам эта среда программирования:

  • Ярлыки кода . Если мы нажмем Control + Space во время программирования, мы сможем увидеть панель с рядом предложений, которые помогут нам автоматически заполнять функции. Кроме того, мы также можем увидеть описание того, что делает каждая из функций.
  • Самокоррекция . Программа отвечает за анализ кода, который мы пишем, и в случае обнаружения каких-либо несоответствий или синтаксических проблем помечает их красным цветом. Это даже даст нам некоторые идеи по исправлению ошибки и возможность автоматически исправить ее, когда она очень очевидна.
  • Инструменты для рефакторинга . Мы можем найти ряд инструментов, которые помогут нам переименовывать или перемещать элементы, отслеживать ссылки и изменять их в коде. Кроме того, в нем есть расширенные инструменты, которые помогут нам, например, создавать интерфейсы для классов путем копирования методов.
  • Сравнить файлы . Если у нас есть два разных файла кода, мы можем сравнивать их одновременно, работать с ними одновременно и точно видеть, каковы их изменения.
  • Код формата . Eclipse помогает нам сделать наш код читабельным. Мы можем настроить нужный текстовый формат, придать ему цвет и т. Д.

До 2015 года Eclipse была официальной средой разработки для программирования для создания всевозможных приложений для Android, хотя Google решил отделиться от него и создать собственную Android Studio (разумеется, на базе Eclipse), специализирующуюся на разработке приложений такого типа.

Объектный код

Запуск gcc позволяет обработать файл с исходным кодом препроцессором и далее скомпилировать его. Однако при этом сам инструмент gcc не компилирует файл исходного кода в конечный исполняемый файл. Он компилирует его в объектный файл, после чего вызывает так называемый линковщик, или компоновщик. Но зачем надо сначала получать объектный файл, а потом из него уже исполняемый? Для программ, состоящих из одного файла, такой необходимости нет. Хотя при желании здесь также можно отказаться от компоновки, если выполнить команду gcc с ключом -c:

gcc -c hello.c

В результате получится файл с расширением *.o. Чтобы получить из объектного файла исполняемый, надо использовать ключ -o:

gcc -o hello hello.o

Для программ, состоящих из нескольких файлов исходного кода, получение объектных файлов является необходимым. Именно из них потом компонуется единственный исполняемый файл.

Создание заголовочных файлов

Продолжим разбирать приведенную выше программу. Что будет, если в функции осуществить неправильный вызов функций и ? Например, указать неверное количество параметров. В таком случае создание объектных файлов пройдет без ошибок, и скорее всего удастся получить исполняемый файл; но вот работать программа будет неправильно. Такое возможно потому, что ничего не контролирует соответствие вызовов прототипам (объявлениям) функций.

Куда правильней сообщать о неверном вызове функций уже при получении объектного файла. Поэтому хотя можно обойтись и без этого, но очень желательно сообщать функции прототипы функций, которые из нее вызываются. Это можно сделать, прописав объявления функций в файле main.c:

…
void l2r (char **c, int n);
void r2l (char **c, int n);

main () {
…

Теперь, если мы передадим неправильные параметры, ошибка возникнет уже на этапе получения объектных файлов.

А теперь представим, что программа у нас несколько больше и содержит десяток файлов исходного кода. Файл aa.c требует функций из файла bb.c, dd.c, ee.c. В свою очередь dd.c вызывает функции из ee.c и ff.c, а эти два последних файла активно пользуются неким файлом stars.c и одной из функций в bb.c. Программист замучится сверять, что чего вызывает откуда и куда, где и какие объявления надо прописывать. Поэтому все прототипы (объявления) функций проекта, а также совместно используемые символические константы и макросы выносят в отдельный файл, который подключают к каждому файлу исходного кода. Такие файлы называются заголовочными; с ними мы уже не раз встречались. В отличие от заголовочных файлов стандартной библиотеки, заголовочные файлы, которые относятся только к вашему проекту, при подключении к файлу исходного кода заключаются в кавычки, а не скобки. Об этом упоминалось в предыдущем уроке.

Итак, более грамотно будет не добавлять объявления функций в файл main.c, а создать заголовочный файл, например, myprint.h и поместить туда прототипы функций и . А в файле следует прописать директиву препроцессора:

#include "myprint.h"

В принципе смысла подключать myprint.h к файлу superprint.c в данном случае нет, т.к. последний не использует никаких сторонних функций, кроме стандартной библиотеки. Но если планируется расширять программу и есть вероятность, что в файле superprint.c будут вызываться сторонние для него функции, то будет надежней сразу подключить заголовочный файл.

Обратим внимание еще на один момент. Стоит ли в описанном в этом уроке примере выносить константу N в заголовочный файл? Здесь нельзя дать однозначный ответ

Если ее туда вынести, то она станет доступна в обоих файлах, и поэтому можно изменить прототипы функций так, чтобы они принимали только один параметр (указатель), а значение N будет известно функциям их заголовочного файла. Однако стоит ли так делать? В функции второй параметр изменяется в процессе ее выполнения, что делать с константой будет невозможно. Придется переписывать тело функции. Кроме того, вдруг в последствии нам захочется использовать файл superprint.c в другом проекте, где будут свои порядки, и константы N в заголовочном файле не найдется.

В таком случае лучше N не выносить в заголовочный файл. Хотя имейте в виду, в каких-то проектах символическая константа может использоваться так часто и во множестве функций, что ее будет лучше поместить в заголовочный файл.

Добавление классов

Чтобы добавить классы, вы должны сделать две вещи:

  1. Поместите исходный файл в каталог, соответствующий пакету, в который вы хотите поместить класс.
  2. Объявите этот класс как часть пакета.

Первый пункт довольно прост. Создайте корневой каталог источника и внутри него создайте каталоги для каждого пакета и подпакета рекурсивно. Поместите файлы классов в каталог, соответствующий пакету, в который вы хотите добавить его.

Когда вы поместили свой исходный файл в правильный каталог (соответствующий пакету, к которому должен принадлежать класс), вы должны объявить внутри этого файла класса, что он принадлежит этому пакету:

package com.blog.navigation;

public class Page {
    ...
}

Первая строка в приведенном выше коде – это то, что объявляет класс Page принадлежащим к com.blog.navigation.

Подготовка проекта ↑

PyInstaller требует, чтобы ваше приложение соответствовало некоторой минимальной структуре, а именно, чтобы у вас был сценарий CLI для запуска вашего приложения. Часто это означает создание небольшого скрипта вне вашего пакета Python, который просто импортирует ваш пакет и запускает main().

Скрипт точки входа — это скрипт Python. Технически вы можете делать в сценарии точки входа все, что захотите, но вам следует избегать использования явного . Вы по-прежнему можете использовать относительный импорт во всем остальном приложении, если это ваш стиль.

Вы можете попробовать это в своем собственном проекте или попробовать на проекте чтения каналов Real Python. Для получения более подробной информации о проекте для чтения ознакомьтесь с руководством по публикации пакета на PyPI.

Первым шагом к созданию исполняемой версии такого проекта является добавление сценария точки входа. К счастью,проект программы чтения каналов хорошо структурирован, поэтому все, что вам нужно, это небольшой сценарий вне пакета для его запуска. Например, вы можете создать файл с именем cli.py вместе с пакетом для чтения со следующим кодом:

from reader.__main__ import main

if __name__ == '__main__':
    main()

Этот скрипт cli.py вызывает для запуска программы чтения каналов.

Создать этот сценарий точки входа несложно, когда вы работаете над собственным проектом, потому что вы знакомы с кодом. Однако найти точку входа в чужой код не так просто. В этом случае вы можете начать с просмотра файла setup.py в третьем -партийный проект.

Найдите ссылку на аргумент entry_points в файле setup.py проекта. Например, вот проект для чтения setup.py:

setup(
    name="realpython-reader",
    version="1.0.0",
    description="Read the latest Real Python tutorials",
    long_description=README,
    long_description_content_type="text/markdown",
    url="https://github.com/realpython/reader",
    author="Real Python",
    author_email="info@ realpython.com",
    license="MIT",
    classifiers=,
    packages=,
    include_package_data=True,
    install_requires=,
    entry_points={"console_scripts": },
)

Как видите, сценарий cli.py точки входа вызывает ту же функцию, что и аргумент entry_points.

После этого изменения каталог проекта ридера должен выглядеть так, если вы извлекли его из папки с именем reader:

reader/
|
├── reader/
|   ├── __init__.py
|   ├── __main__.py
|   ├── config.cfg
|   ├── feed.py
|   └── viewer.py
|
├── cli.py
├── LICENSE
├── MANIFEST.in
├── README.md
├── setup.py
└── tests

Обратите внимание, что сам код считывателя не изменился, только новый файл с именем cli.py. Этот сценарий точки входа обычно является всем, что необходимо для использования вашего проекта с PyInstaller

Однако вам также стоит обратить внимание на использование или импорта внутри функций. В терминологии PyInstaller они называются скрытым импортом

Вы можете вручную указать скрытый импорт, чтобы PyInstaller включил эти зависимости, если изменение импорта в вашем приложении слишком сложно. Вы увидите, как это сделать позже в этом руководстве.

Как только вы сможете запустить свое приложение с помощью скрипта Python вне вашего пакета, вы готовы попробовать PyInstaller при создании исполняемого файла.

Что такое файл в Java?

Файл – это не что иное, как простое хранилище данных на языке Java. Файловая система может реализовывать ограничения для определенных операций, таких как чтение, запись и выполнение. Эти ограничения известны как права доступа.

При чтении файла в Java мы должны знать класс файлов Java. Класс Java File представляет файлы и имена каталогов в абстрактной манере. Класс File имеет несколько методов для работы с каталогами и файлами, таких как создание новых каталогов или файлов, удаление и переименование каталогов или файлов и т. д. Объект File представляет фактический файл / каталог на диске.

Теперь давайте разберемся с различными методами создания файла.

-u — исключает повторяющиеся строки и т.д.

Практическая работа №3(3) Создание ссылок на файлы.

1) Зарегистрируйтесь в ОС Linux. Запустите на выполнение программу Терминал.
2) Выведите на экран Терминала справку по следующим командам: ln, ls. Назначение и формат команд приведите в отчете.
3) Перейдите в Ваш личный каталог и создайте в нем текстовый файл и любой подкаталог.
4) Создайте жесткую ссылку с другим именем в созданном подкаталоге на созданный файл. Проанализируйте и сравните все атрибуты связанных файлов. Объясните отличия.
5) Уничтожьте один из связанных файлов. Проанализируйте и сравните все атрибуты связанных с ним файлов. Объясните изменения.
6) Просмотрите содержание связанных файлов. Каков был результат просмотра?
7) Создайте символьную ссылку с другим именем в созданном ранее подкаталоге. Проанализируйте и сравните все атрибуты связанных файлов. Объясните отличия.
8) Создайте новый подкаталог в Вашем личном каталоге.
9) Создайте ссылки на несколько файлов, принадлежащих одному из каталогов домашней директории, в созданном ранее каталоге одной командной строкой. Проанализируйте и сравните все атрибуты связанных файлов. Объясните отличия.
10) Завершите работу с программой Терминал.
sort — сортировка файлов;
1) Зарегистрируйтесь в ОС Linux. Запустите на выполнение программу Терминал.
2) Выведите на экран Терминала справку по следующим командам: sort, touch. Назначение и формат команд приведите в отчете.
3) Просмотрите содержимое корневого каталога с выводом размеров файлов, содержащихся в директории, в блоках.
4) Упорядочите по алфавиту строки суммарного файла HOME-каталога.
5) Просмотрите содержимое любой директории отсортировав отображаемую информацию по времени последнего доступа к файлу.
6) Обновите временные характеристики одного из существующих файлов. Проанализируйте результат.
7) Просмотрите содержимое директории, содержащей файл с обновленными временными характеристиками отсортировав его по времени модификации файлов
8) Обновите временные характеристики несуществующего файла. Проанализируйте результат работы команды.
9) Просмотрите содержимое директории, содержащей созданный файл, используя сортировку по времени создания файла
10) Проанализируйте с использованием команды history содержание лабораторной работы, продумайте ответы на нижеприведенные контрольные вопросы и сдайте выполненную работу преподавателю.

Как обратиться к файлам параллельных ветвей дерева

Какие условия поиска файлов Вы знаете? Как комбинируются условия поиска? Как осуществить поиск по дереву каталогов?

Какова последовательность действий при удалении одного каталога? Цепочки каталогов?

Тема 2.2. Управление процессами

Выгрузка результат

Запускаем скрипт acc.bat, затем check.bat и дожидаемся завершения:

Когда SonarQube обработает результат проверки, нам будут доступны новые данные для аналитики.

На закладке замечания список правил проверки расширился:

В итоге, мы имеем загруженные результаты проверки 1С: АПК в SonarQube для дальнейшей аналитики.

Подведем итоги

В результате проверки кода и загрузки результатов в SonarQube, мы получаем метрики, наглядно показывающие текущее состояние качества проекта.

Что теперь с этим делать?

  • Исправлять критические ошибки, уязвимости.
  • Исправлять дефекты, дублирование кода, если реализуются попутные задачи.
  • Уменьшать количество дублирующего кода.
  • Установить пороги качества.

Дальнейшее развитие темы

  • Автоматический запуск проверки качества кода. Автоматизируем запуск проверки качества, используя, например, Jenkins или GitLabCI. 
  • Исключение из проверки объектов конфигурации, которые находятся на поддержке.  В этом может помочь проект edt-export-bugs. Также с его помощью можно автоматизировать получение проверок из EDT. Более подробно можно почитать здесь (https://github.com/Stepa86/edt-export-bugs).
  • Рассылка результатов проверки кода ответственным / виновникам замечаний.
Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: