Webclient.downloadfilecompleted событие

Сохраняем видео с интернет сайта с помощью браузеров

Video Download Helper – это расширение, с помощью которого можно сохранять на компьютер видеофайлы через
Гугл Хром, Яндекс Браузер и др. Любители онлайн-трансляций особенно оценят этот инструмент за возможность
вытащить ссылку на прямой эфир, который невозможно скачать обычным способом. Расширение также включает в
себя такие полезные опции, как блокировка навязчивой рекламы, переименование файлов, скачивание с помощью
горячих клавиш.

  1. На главной странице сервиса выберите тип расширения (пользователи Яндекс.Браузера должны выбрать
    Chrome).
  2. Сервис откроет лендинг из официального магазина расширений вашего браузера.
  3. Установите аддон и перейдите на страницу с видеоклипом.
  4. Кликните по иконке плагина в адресной строке или на панели инструментов.
  5. Появится окошко со списком доступных разрешений. Нажмите по нужному варианту, чтобы запустить закачку.

Из-за строгой политики Google относительно авторских прав данный
плагин не работает на сайте YouTube в браузере Chrome. В этом случае вам выведут предложение
использовать версию Firefox.

widget.cpp

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

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    downloader = new Downloader(); // Инициализируем Downloader

    // по нажатию кнопки запускаем получение данных по http
    connect(ui->pushButton, &QPushButton::clicked, downloader, &Downloader::getData);
    // по окончанию получения данных считываем данные из файл
    connect(downloader, &Downloader::onReady, this, &Widget::readFile);

}

Widget::~Widget()
{
    delete ui;
}

void Widget::readFile()
{
    QFile file("C:/example/file.xml");
    if (!file.open(QIODevice::ReadOnly)) // Открваем файл, если это возможно
            return; // если открытие файла невозможно, выходим из слота
    // в противном случае считываем данные и устанавилваем их в textEdit
    ui->textEdit->setText(file.readAll());
}

How to Send a Complex POST Request with WebClient

We’ve seen how to send a very basic GET request, but what happens if we want to send something more advanced?

Let’s look at a more complex example:

As we can see here, allows us to configure headers by either using dedicated methods for common cases () or generic keys and values ().

In general, using dedicated methods is preferable, as their stricter typings will help us provide the right values, and they include runtime validation to catch various invalid configurations too.

This example also shows how to add a body. There are a few options here:

  • We can call with a , which will build body content for us from form values, multipart values, data buffers, or other encodeable types.
  • We can call with a (including a ), which can stream content asynchronously to build the request body.
  • We can call to provide a string or other encodeable value directly.

Each of these has different use cases. Most developers who aren’t familiar with reactive streams will find the Flux API unhelpful initially, but as you invest more in the reactive ecosystem, asynchronous chains of streamed data like this will begin to feel more natural.

Загрузка файлов

Для загрузки файлов с использованием WebClient доступны два метода. Выбор метода зависит от того, как должно обрабатываться содержимое файла. Если необходимо просто сохранить файл на диске, следует применять метод DownloadFile(). Этот метод принимает два параметра: URI файла и местоположение (путь и имя файла) для сохранения запрошенных данных:

Часто приложение должно обрабатывать данные, извлеченные с веб-сайта. Это обеспечивает метод OpenRead(), возвращающий ссылку на Stream, которую можно использовать для извлечения данных в память:

В следующем примере демонстрируется применение метода WebClient.OpenRead(). Содержимое загруженной страницы будет отображено в элементе управления TextBox. Для начала создайте новый проект как стандартное приложение WPF и добавьте элемент управления TextBox по имени txb. В начало файла к списку директив using потребуется добавить ссылки на пространства имен System.Net и System.IO. Затем добавьте обработчик клика по кнопке:

В этом примере класс StreamReader из пространства имен System.IO подключается к сетевому потоку. Это позволяет получить данные из потока в виде текста, используя высокоуровневые методы вроде ReadLine(). На рис. показаны результаты запуска этого кода:

Класс WebClient также включает в себя метод OpenWrite(). Этот метод возвращает записываемый поток для отправки данных по определенному URI. При этом можно указать метод, который должен использоваться для отправки данных на хост, по умолчанию это POST.

Faster — многофункциональный ускоритель работы программиста 1С и других языков программирования Промо

Программа Faster 9.4 позволяет ускорить процесс работы программиста
(работает в любом текстовом редакторе).
Подсказка при вводе текста на основе ранее введенного текста и настроенных шаблонов.
Программа Faster позволяет делится кодом с другими программистами в два клика или передать ссылку через QR Код.
Исправление введенных фраз двойным Shift (с помощью speller.yandex). Переводчик текста. Переворачивает текст случайно набранный на другой раскладке.
Полезная утилита для тех, кто печатает много однотипного текста, кодирует в среде Windows на разных языках программирования.
Через некоторое время работы с программой у вас соберется своя база часто используемых словосочетаний и кусков кода.
Настройка любых шорткатов под себя с помощью скриптов.
Никаких установок и лицензий, все бесплатно.

1 стартмани

Как скачать видео с сайта

К счастью для всех нас есть добрые люди, которые сделали супер-сервис SaveFrom.net, который умеет скачивать видео с большинства популярных ресурсов, таких как YouTube, RuTube, ВКонтакте, Одноклассники, Facebook и многих других. Раньше он плохо скачивал с Vimeo, но теперь и с ним проблем не замечаю. Самое примечательное то, что не нужно устанавливать никаких программ.

Принцип работы простой:

1. Копируем ссылку, по которой проигрывается видео, в буфер обмена

2. Вставляем в поле ввода ссылку, нажимаем «Скачать» и получаем прямые ссылки на видео-файл

3. Скачиваем видео-файл обычным способом с помощью правой кнопки мышки

Прямые ссылки на видео есть в разных форматах, которые отличаются для разных видео: MP4, FLV, WebM, 3GP. И в разном качестве (количество строчек в видео-кадре): 240p, 360p, 480p, 720p и другие. Файлы FLV весят раза в полтора больше чем остальные форматы, поэтому рекомендую выбирать MP4 или WebM.

Качество: чем больше цифра, тем лучше качество и, соответственно, больше размер файла, т.е. скачивание будет дольше идти по времени. Если на видео нет мелкого текста и качество не критично, то смело выбирайте 360p или 480p.

Но не это мне нравится в сервисе SaveFrom.net, а то, что у него имеются расширения и плагины для браузеров Google Chrome, Opera, Mozilla Firefox, Яндекс.Браузер и Safari. После установки расширения для своего браузера на сайтах тупо появляется кнопка «Скачать». Так что ничего больше и делать не надо!

Устанавливаются расширения с этой страницы http://ru.savefrom.net/user.php. Это можно сделать вручную для каждого браузера как описано на сайте, или скачать фирменную утилиту, которая установит их сама для всех найденных браузеров — рекомендую именно этот способ.

Для этого нажимаем кнопку «Скачать»

далее запускаем файл «SaveFromNetHelper-Web-Inst.exe» и следуем мастеру установки. Рекомендую не устанавливать ничего кроме плагинов для браузеров, посему оставьте только эти галочки:

И да, в процессе придётся закрыть все открытые браузеры. При успешной установке в браузерах появляются новые значки, например в Google Chrome это выглядит вот так:

Можно нажать по значку правой кнопкой и выбрать «Скрыть кнопку» и тогда она не будет мешаться на панели, но расширение будет по-прежнему работать.

После этого открываем какой-нибудь сайт с видео и вуаля:

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

А вот как это работает в Одноклассниках:

и вот ещё скриншот как скачать видео с сайта ВКонтакте:

И конечно же всё это абсолютно бесплатно, без регистраций и без рекламы!

Обновлено: Изменения для Google Chrome!

Компания Google всячески борется с теми, кто качает с ютуба и помощник SaveFrom.net заблокирован для использования в Хроме. На сайте предлагается альтернативная инструкция по установке

Обратите внимание на другие способы

Программа с расширенными возможностями

Если вы готовы немного заморочиться, то скачайте и установите программу 4K Video Downloader. Работать с ней очень легко: вам нужно скопировать ссылку на видео и нажать в программе кнопку «Вставить». В отличии от простых онлайн-сервисов, которые заточены на быстрое скачивание, 4K Video Downloader распознаёт видео в разрешении Ultra HD 4K, 8K и панорамный формат «360 градусов». Интересно, что можно выбрать контейнер итогового файла: Matroska (MKV) или MP4. Также, можно сохранить субтитры на нужном языке в специальный SRT-файл, или записать их прямо в видеофайл. Из других приятностей:

  • Подписка на каналы с автоматической загрузкой новых видеоклипов
  • Сохранение плейлистов и всего видеоконтента канала одним кликом
  • Можно добавить в программу сразу большой список ссылок
  • Поддержка 60 fps и видео HDR
  • Настройка Прокси-сервера

downloader.cpp

ВНИМАНИЕ!!!

— Проверьте доступность URL из примера перед запуском приложения, чтобы не биться головой об стену, если сайт просто не доступен. А лучше замените на тот URL, до которого Вы желаете достучаться

А также возможно имеет смысл поменять расширение файла на txt.

#include "downloader.h"

Downloader::Downloader(QObject *parent) : QObject(parent)
{
    // Инициализируем менеджер ...
    manager = new QNetworkAccessManager();
    // ... и подключаем сигнал о завершении получения данных к обработчику полученного ответа
    connect(manager, &QNetworkAccessManager::finished, this, &Downloader::onResult);
}

void Downloader::getData()
{
    QUrl url("https://www.mtbank.by/currxml.php"); // URL, к которому будем получать данные
    QNetworkRequest request;    // Отправляемый запрос
    request.setUrl(url);        // Устанавлвиваем URL в запрос
    manager->get(request);      // Выполняем запрос
}

void Downloader::onResult(QNetworkReply *reply)
{
    // Если в процесе получения данных произошла ошибка
    if(reply->error()){
        // Сообщаем об этом и показываем информацию об ошибках
        qDebug() << "ERROR";
        qDebug() << reply->errorString();
    } else {
        // В противном случае создаём объект для работы с файлом
        QFile *file = new QFile("C:/example/file.xml");
        // Создаём файл или открываем его на перезапись ...
        if(file->open(QFile::WriteOnly)){
            file->write(reply->readAll());  // ... и записываем всю информацию со страницы в файл
            file->close();                  // закрываем файл
        qDebug() << "Downloading is completed";
        emit onReady(); // Посылаем сигнал о завершении получения файла
        }
    }
}

Способы отправки файлов

  • Классический способ отправки файлов клиенту обеспечивает чистый HTML. Данным способом можно пользоваться только если файлы хранятся в открытом каталоге сайта.
  • Можно загрузить файл в оперативную память, при необходимости добавить к контенту файла свой цифровой знак, и уже из памяти отдать поток в консоль.
  • Считать содержимое файла в массив байтов и далее отправить массив байтов в поток вывода HTTP. Этот способ удобен для отправки файлов, когда их содержимое считывается из базы данных.
  • Открыть поток с содержимым файла и сразу же отдать его на вывод HTTP. Похож на первый способ, но позволяет скачивать файлы с закрытых каталогов и хранимых в базе данных.

Для отправки файлов значительного размера годятся только классический HTML и способ с открытием потока непосредственно на вывод HTTP. Другие способы тоже работают с большими файлами, но в процессе скачивания, на сервере, будут потребляться значительные объемы оперативной памяти. При нехватке памяти могут происходить сбои в работе веб-страницы, а возможно и сайта.

Что такое Download Files?

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

Функциональность Download — это возможность скачивания файлов с серверной стороны на клиентскую машину. В результате этой операции копия файла, находящегося на сервере, сохраняется на машине клиента.

Способностью отсылать файлы клиентам обладают и HTML статические страницы и конечно же динамические интерактивные сайты. Download гораздо безопасней своей парной операции Upload, когда пользователи загружают, иногда спамовые и вредные, файлы на сервер.

Выгружаем видео с помощью скринкаста

Экранная Студия

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

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

  1. Установите на компьютер программу «Экранная Студия» и запустите приложение.
  2. В стартовом окне выберите вариант работы «Записать видео с экрана».
  3. Выберите формат записи: полный экран, фрагмент или выбранное окно. Отметьте «Записывать звук с
    микрофона», чтобы захватить звуковую дорожку.
  4. Откройте сайт с видеороликом и запустите воспроизведение видео.
  5. Нажмите «Начать запись». Чтобы остановить захват, кликните «Стоп».
  6. Видеозапись откроется во встроенном редакторе.
  7. Обрежьте видеоролик, если вы запустили запись раньше, чем нужно или остановили слишком поздно. Для
    этого кликните по иконке ножниц в панели инструментов и укажите начало и окончание клипа.
  8. Чтобы экспортировать видеозапись, нажмите «Сохранить видео». Выберите формат и разрешение видеофайла и
    запустите конвертацию.

Использование модуля urllib2

Другой способ загрузки файлов в Python – через модуль urllib2. Метод urlopen модуля urllib2 возвращает объект, содержащий данные файла

Чтобы прочитать содержание, обратите внимание, что в Python 3 urllib2 был объединен с urllib, как urllib.request и urllib.error. Поэтому этот скрипт работает только в Python 2.

import urllib2

filedata = urllib2.urlopen('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
datatowrite = filedata.read()
 
with open('/Users/scott/Downloads/cat2.jpg', 'wb') as f:
    f.write(datatowrite)

Открытый метод принимает два параметра: путь к локальному файлу и режим, в котором будут записаны данные. Здесь «wb» указывает, что открытый метод должен иметь разрешение на запись двоичных данных в данный файл.

Выполните приведенный выше сценарий и перейдите в каталог «Загрузки». Вы должны увидеть загруженный PDF-документ, как «cat2.jpg».

Использование модуля urllib.request

Модуль urllib.request используется для открытия или загрузки файла через HTTP. В частности, метод urlretrieve этого модуля – это то, что мы будем использовать для фактического получения файла.

Чтобы использовать этот метод, вам необходимо передать два аргумента методу urlretrieve: первый аргумент – это URL-адрес ресурса, который вы хотите получить, а второй аргумент – это путь к локальному файлу, в котором вы хотите сохранить загруженный файл.

Давайте посмотрим на следующий пример:

import urllib.request

print('Beginning file download with urllib2...')

url = 'http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg'
urllib.request.urlretrieve(url, '/Users/scott/Downloads/cat.jpg')

В приведенном выше коде мы сначала импортируем модуль urllib.request. Затем мы создаем URL-адрес переменной, который содержит путь к загружаемому файлу. Наконец, мы вызываем метод urlretrieve и передаем ему переменную url в качестве первого аргумента, «/Users/scott/Downloads/cat.jpg» в качестве второго параметра для места назначения файла. Имейте в виду, что вы можете передать любое имя файла в качестве второго параметра, и это местоположение и имя, которое будет иметь ваш файл, при условии, что у вас есть правильные разрешения.

Запустите указанный выше скрипт и перейдите в каталог «Загрузки». Вы должны увидеть загруженный файл с именем «cat.jpg».

Примечание. Этот urllib.request.urlretrieve считается «устаревшим интерфейсом» в Python 3, и в какой-то момент в будущем он может стать устаревшим. Из-за этого я бы не рекомендовал использовать его в пользу одного из методов ниже. Мы включили его сюда из-за его популярности в Python 2.

Возобновляемая Загрузка

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

Давайте перепишем первый пример из предыдущих, чтобы добавить эту функциональность.

Первое, что мы должны знать, это то, что мы можем прочитать размер файла с заданного URL-адреса, фактически не загружая его, используя метод HTTP HEAD:

URL url = new URL(FILE_URL);
HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.setRequestMethod("HEAD");
long removeFileSize = httpConnection.getContentLengthLong();

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

long existingFileSize = outputFile.length();
if (existingFileSize < fileLength) {
    httpFileConnection.setRequestProperty(
      "Range", 
      "bytes=" + existingFileSize + "-" + fileLength
    );
}

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

Другим распространенным способом использования заголовка Range является загрузка файла по частям путем установки различных диапазонов байтов. Например, для загрузки файла размером 2 КБ мы можем использовать диапазон 0 – 1024 и 1024 – 2048.

Еще одно тонкое отличие от кода в разделе 2. заключается в том, что FileOutputStream открывается с параметром append , установленным в true :

OutputStream os = new FileOutputStream(FILE_NAME, true);

После того, как мы внесли это изменение, остальная часть кода идентична той, которую мы видели в разделе 2.

Установите программу просмотра кода

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

Загрузите и установите Visual Studio Code , следуя указаниям мастера установки. После этого вы будете готовы просматривать файлы и код, которые можно загрузить с GitHub. 

Есть много разных редакторов кода. Если проект был создан в другой IDE (интегрированной среде разработки), Visual Studio может не подойти для редактирования этого кода. При этом Visual Studio Code позволит вам редактировать код большинства проектов на GitHub, и он всегда будет работать, если все, что вам нужно, — это просмотреть код. 

Использование Java IO

Самый простой API, который мы можем использовать для загрузки файла, – это Java IO . Мы можем использовать класс URL , чтобы открыть соединение с файлом, который мы хотим загрузить. Чтобы эффективно прочитать файл, мы будем использовать метод openStream() для получения InputStream:

BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream())

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

Увеличение производительности происходит за счет буферизации. При чтении одного байта за раз с помощью метода read() каждый вызов метода подразумевает системный вызов базовой файловой системы. Когда JVM вызывает системный вызов read () , контекст выполнения программы переключается из пользовательского режима в режим ядра и обратно.

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

Для записи байтов, считанных с URL-адреса, в ваш локальный файл мы будем использовать метод write() из класса FileOutputStream :

try (BufferedInputStream in = new BufferedInputStream(new URL(FILE_URL).openStream());
  FileOutputStream fileOutputStream = new FileOutputStream(FILE_NAME)) {
    byte dataBuffer[] = new byte;
    int bytesRead;
    while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
        fileOutputStream.write(dataBuffer, 0, bytesRead);
    }
} catch (IOException e) {
    // handle exception
}

При использовании BufferedInputStream метод read() будет считывать столько байтов, сколько мы задали для размера буфера. В нашем примере мы уже делаем это, читая блоки по 1024 байта за раз, поэтому BufferedInputStream не нужен.

Приведенный выше пример очень многословен, но, к счастью, начиная с Java 7, у нас есть класс Files , который содержит вспомогательные методы для обработки операций ввода-вывода. Мы можем использовать метод Files.copy () , чтобы прочитать все байты из InputStream и скопировать их в локальный файл:

InputStream in = new URL(FILE_URL).openStream();
Files.copy(in, Paths.get(FILE_NAME), StandardCopyOption.REPLACE_EXISTING);

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

К счастью, Java предлагает нам пакет NIO, который имеет методы для передачи байтов непосредственно между 2 каналами без буферизации.

Мы подробно рассмотрим это в следующем разделе.

donwloader.h

А теперь заголовочный файл самого виновника торжества. В нём Мы объявляем экземпляр класса

QNetworkAccesManager,

а также методы для инициализации запроса к сайт по его URL и обработки полученного ответа.

#ifndef DOWNLOADER_H
#define DOWNLOADER_H

#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QFile>
#include <QUrl>
#include <QDebug>

class Downloader : public QObject
{
    Q_OBJECT
public:
    explicit Downloader(QObject *parent = 0);

signals:
    void onReady();

public slots:
    void getData();     // Метод инициализации запроса на получение данных
    void onResult(QNetworkReply *reply);    // Слот обработки ответа о полученных данных

private:
    QNetworkAccessManager *manager; // менеджер сетевого доступа
};

#endif // DOWNLOADER_H

How to Handle an HTTP Response with WebClient

Once we’ve made a request, we usually want to read the contents of the response.

In the above example, we called to get a for a request. This is an asynchronous operation, which doesn’t block or wait for the request itself, which means that on the following line the request is still pending, and so we can’t yet access any of the response details.

Before we can get a value out of this asynchronous operation, you need to understand the and types from Reactor.

Flux

A represents a stream of elements. It’s a sequence that will asynchronously emit any number of items (0 or more) in the future, before completing (either successfully or with an error).

In reactive programming, this is our bread-and-butter. A is a stream that we can transform (giving us a new stream of transformed events), buffer into a List, reduce down to a single value, concatenate and merge with other Fluxes, or block on to wait for a value.

Mono

A Mono is a specific but very common type of : a that will asynchronously emit either 0 or 1 results before it completes.

In practice, it’s similar to Java’s own : it represents a single future value.

If you’d like more background on these, take a look at Spring’s own docs which explain the Reactive types and their relationship to traditional Java types in more detail.

Reading the Body

To read the response body, we need to get a (i.e: an async future value) for the contents of the response. We then need to unwrap that somehow, to trigger the request and get the response body content itself, once it’s available.

There are a few different ways to unwrap an asynchronous value. To start with, we’ll use the simplest traditional option, by blocking to wait for the data to arrive:

This gives us a string containing the raw body of the response. It’s possible to pass different classes here to parse content automatically into an appropriate format, or to use a here instead to receive a stream of response parts (fir example from an event-based API), but we’ll come back to that in just a minute.

Note that we’re not checking the status here ourselves. When we use , the client automatically checks the status code for us, providing a sensible default by throwing an error for any 4xx or 5xx responses. We’ll talk about custom status checks & error handling later on too.

Заключение

В этой статье мы рассмотрели несколько способов загрузки файла с URL-адреса на Java.

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

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

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

Исходный код статьи доступен на GitHub .

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

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