/*
  Проект SieGet Downloader
                          */
#include <stdio.h>
#include "include.h"
#include "socket.h"
#include "inet.h"
#include "mem.h"
#include "log.h"
#include "dialog.h"
#include "url_utils.h"
#include "file_works.h"

extern "C"
{
    extern unsigned long strtoul(const char *, char **, int);
    extern const unsigned int WRITE_BUFFER_SIZE;
};

DownloadHandler * DownloadHandler::Top = NULL;

//Проверить процесс (сокеты только в хелпере)
inline int CheckCepId()
{
    if (GBS_GetCurCepid()==MMI_CEPID) return 1;
    return 0;
}

//---------------------------------------------------------------

HttpDNR::HttpDNR()
{
    Callback = NULL;
}

void HttpDNR::Bind(HttpAbstract * sock)
{
    Callback = sock;
    log = sock->log;
}

void HttpDNR::onResolve(DNR_RESULT_MSG result_msg, int value)
{
    if (Callback)
        Callback->onResolve(result_msg, value);
}

//---------------------------------------------------------------

HttpAbstract::HttpAbstract()
{
    ip = 0;
    port = 0;
    req_buf = NULL;
    recv_buf = NULL;

    http_state = HTTP_IDLE;
    HTTPResponse = NULL;
    HTTPRequest = NULL;
    Resolver = new HttpDNR();
    Resolver->Bind(this);
}

HttpAbstract::~HttpAbstract()
{
    _safe_delete(HTTPResponse);
    _safe_delete(HTTPRequest);
    _safe_delete(recv_buf);
    _safe_delete(req_buf);
    _safe_delete(Resolver);
}

void HttpAbstract::Start(int _ip, short _port)
{
    if (http_state!=HTTP_IDLE && http_state!=HTTP_REDIRECT)
        return;
    if (!IsGPRSEnabled())
    {
        log->Print(LangPack::Active->data[LGP_EnableGPRSFirst], CLR_Red);
        onError(SOCK_ERROR_CONNECTING);
        return;
    }
    _safe_delete(HTTPResponse);
    _safe_delete(recv_buf);

    HTTPResponse = new HTTP_Response(log);
    recv_buf = new Buffer();
    ip = _ip;
    port = _port;
    http_state = HTTP_HEADER;
    doConnect();
}

void HttpAbstract::Start(const char * host, short _port)
{
    if (http_state!=HTTP_IDLE && http_state!=HTTP_REDIRECT)
        return;
    if (!IsGPRSEnabled())
    {
        log->Print(LangPack::Active->data[LGP_EnableGPRSFirst], CLR_Red);
        onError(SOCK_ERROR_CONNECTING);
        return;
    }
    _safe_delete(HTTPResponse);
    _safe_delete(recv_buf);

    HTTPResponse = new HTTP_Response(log);
    recv_buf = new Buffer();
    ip = 0;
    port = _port;
    http_state = HTTP_RESOLVE;
    download_state = DOWNLOAD_CONNECT;
    log->Print(LangPack::Active->data[LGP_ResolvingHost], CLR_Green);
    Resolver->Start(host, 3);
    if (SieGetDialog::Active)
        SieGetDialog::Active->RefreshList();
}

void _do_connect(HttpAbstract *obj)
{
    obj->doConnect();
}

void HttpAbstract::doConnect()
{
    if (CheckCepId())
    {
        SUBPROC((void *)_do_connect, this);
    }
    else
        Create();
}

void HttpAbstract::onResolve(DNR_RESULT_MSG result_msg, int value)
{
    if (download_state == DOWNLOAD_STOPPED || download_state == DOWNLOAD_ERROR)
        return;

    char * msg = new char[64];
    //sprintf(msg, "HttpAbstract::onResolve(%d, 0x%X)", result_msg, value);
    //log->Print(msg, CLR_Yellow);

    switch(result_msg)
    {
    case DNR_RESULT_OK:
        ip = value;
        http_state = HTTP_HEADER;
        download_state = DOWNLOAD_CONNECT;
        if (SieGetDialog::Active)
            SieGetDialog::Active->RefreshList();
        doConnect();
        break;
    case DNR_RESULT_ERROR:
        sprintf(msg, LangPack::Active->data[LGP_DNRError], value);
        log->Print(msg, CLR_Red);
        onError(SOCK_ERROR_CONNECTING);
        break;
    case DNR_RESULT_OUT_OF_TRIES:
        log->Print(LangPack::Active->data[LGP_DNROutOfTries], CLR_Red);
        onError(SOCK_ERROR_CONNECTING);
        break;
    }
    delete msg;
}

void HttpAbstract::onCreate()
{
    if (download_state == DOWNLOAD_STOPPED || download_state == DOWNLOAD_ERROR)
        return;

    char * msg=new char[128];
    sprintf(msg, LangPack::Active->data[LGP_ConnectToIPPort], ip&0xff, (ip>>8)&0xff, (ip>>16)&0xff, (ip>>24)&0xff, port);
    log->Print(msg, CLR_Green);
    delete msg;
    download_state = DOWNLOAD_CONNECT;
    if (SieGetDialog::Active)
        SieGetDialog::Active->RefreshList();
    Connect(ip, port);
}

void HttpAbstract::onConnected()
{
    if (download_state == DOWNLOAD_STOPPED || download_state == DOWNLOAD_ERROR)
        return;

    Send(req_buf->data, req_buf->size);
    _safe_delete(req_buf);
    onHTTPConnect();
}

void HttpAbstract::onDataRead()
{
    // Проверяем статус закачки, чтобы не качать при остановленной закачке
    if (download_state == DOWNLOAD_STOPPED || download_state == DOWNLOAD_ERROR)
        return;

    char tmp_buf[4096]; // Буфер приема даннях
    int nrecv = Recv(tmp_buf, sizeof(tmp_buf)); // Получаем даные
    if(nrecv < 1) return; // Если нет данных - выходим
    int hsize; // Длина HTTP заголовка

    switch(http_state)
    {
    case HTTP_HEADER: // Принимаем заголовок HTTP
        download_state = DOWNLOAD_GET_INFO;
        recv_buf->Write(tmp_buf, nrecv);

        if(hsize = HTTPResponse->Parse(recv_buf->data, recv_buf->size)) // Обработали весь заголовок
        {
            char * tmp_msg = new char[128];
            sprintf(tmp_msg, LangPack::Active->data[LGP_HTTPParserReturned], hsize, recv_buf->size);
            log->Print(tmp_msg, CLR_Green);
            delete tmp_msg;

            if (!onHTTPHeaders()) // Обрабатываем заголовок на предмет редиректа и т.п.
                return;

            if (hsize < recv_buf->size) // Если есть данные помимо заголовка
                onHTTPData(recv_buf->data + hsize, recv_buf->size - hsize); // Пишем в файл

            http_state = HTTP_STREAM; // Дальше у нас идет чистый файл
        }
        break;
    case HTTP_STREAM: // Принимаем данные
        onHTTPData(tmp_buf, nrecv);
        break;
    }
    if (SieGetDialog::Active)
        SieGetDialog::Active->RefreshList();
}

void HttpAbstract::onClose()
{
    if(download_state!=DOWNLOAD_STOPPED && download_state!=DOWNLOAD_ERROR && http_state!=HTTP_REDIRECT)
        onHTTPFinish();
    http_state = HTTP_IDLE;
}

void HttpAbstract::onRemoteClose()
{
    Close();
}

void HttpAbstract::onError(SOCK_ERROR err)
{
    switch(err)
    {
    case SOCK_ERROR_CREATING:
        log->Print(LangPack::Active->data[LGP_SocketCreateError], CLR_Red);
        break;
    case SOCK_ERROR_CONNECTING:
        log->Print(LangPack::Active->data[LGP_SocketConnectError], CLR_Red);
        break;
    case SOCK_ERROR_SENDING:
        log->Print(LangPack::Active->data[LGP_SocketSendError], CLR_Red);
        break;
    case SOCK_ERROR_CLOSING:
        log->Print(LangPack::Active->data[LGP_SocketCloseError], CLR_Red);
        break;
    case SOCK_ERROR_INVALID_SOCKET:
        log->Print(LangPack::Active->data[LGP_InvalidSocket], CLR_Red);
        break;
    }
    http_state = HTTP_IDLE;

    if(download_state != DOWNLOAD_STOPPED)
    {
        download_state = DOWNLOAD_ERROR;
        StartVibra(0);
        Play_Sound("$sounds\\error.wav");
    }

    if (SieGetDialog::Active)
        SieGetDialog::Active->RefreshList();

    SUBPROC((void *)_save_queue, DownloadHandler::Top);
}

//---------------------------------------------------------------

int Download::onHTTPHeaders()
{
    unsigned int io_error;
    char * tmp_msg = new char[256];

    sprintf(tmp_msg, LangPack::Active->data[LGP_GotResponseCode], HTTPResponse->resp_code);
    log->Print(tmp_msg, CLR_Green);

    if (HTTPResponse->resp_msg)
        sprintf(tmp_msg, LangPack::Active->data[LGP_GotResponseReason], HTTPResponse->resp_msg);
    else
        sprintf(tmp_msg, LangPack::Active->data[LGP_GotResponseReason], "[No reason specified]");
    log->Print(tmp_msg, CLR_Green);

    delete tmp_msg;

    if(RESP_CODE_CLIENT_ERROR(HTTPResponse->resp_code)) // Ошибка клиента
    {
        onError(SOCK_ERROR_CLIENT);
        return 0;
    }
    if(RESP_CODE_SERVER_ERROR(HTTPResponse->resp_code)) // Ошибка сервера
    {
        onError(SOCK_ERROR_SERVER);
        return 0;
    }
    if(RESP_CODE_REDIRECTED(HTTPResponse->resp_code)) // Редирект
    {
        onHTTPRedirect();
        return 0;
    }
    /* Строка Content-Disposition */
    if(char * content_disposition_str = HTTPResponse->headers->GetValue("Content-Disposition"))
    {
        if (!is_const_file_name) // Если имя не указано юзером
        {
            char * new_fname = Get_Param_Value(content_disposition_str, "filename", 1, ';'); // Получаем имя из заголовка
            if(new_fname)
            {
                _safe_delete(file_name);
                remove_bad_chars(new_fname); // Удаляем из имени недопустимые символы, хотя наверное сервер таких имен не держит :)
                file_name = utf82filename(new_fname); // utf8 в имя файла
                delete new_fname;

                _safe_delete(full_file_name);
                full_file_name = new char[strlen(file_path) + strlen(file_name) + 2];
                sprintf(full_file_name, "%s%s", file_path, file_name);
                make_dirs(full_file_name);
                log->ChangeFileName(file_name);
            }
        }
    }
    /* Строка Content-Length */
    if(char * content_length_str = HTTPResponse->headers->GetValue("Content-Length")) // Получаем размер файла из заголовка
    {
        file_size = 0;
        file_size = strtoul(content_length_str, 0, 10);
        if (RESP_CODE_PARTIAL(HTTPResponse->resp_code)) // Если докачка
        {
            ranges_support = 1; // Значит сервер поддерживает докачку ;)
            file_size += file_loaded_size;  // Добавляем размер того, что уже закачано
        }
        else // Если сервер докачку не держит
        {
            file_loaded_size = 0; // Размер файла = 0
            _unlink(full_file_name, &io_error); // Удаляем файл, если он есть
            if(file_loaded_size) log->Print(LangPack::Active->data[LGP_RangesNotSupportFileDelete], CLR_Yellow);
        }
        int free_space = GetFreeFlexSpace(full_file_name[0]-'0', &io_error);
        if (file_size >= free_space)
        {
            onError(SOCK_ERROR_CLIENT);
            return 0;
        }
    }
    if (!RESP_CODE_PARTIAL(HTTPResponse->resp_code)) // Partial Content
    {
        if (HTTPResponse->headers->GetValue("Accept-Ranges")) // Если есть заголовок "Accept-Ranges"
            ranges_support = 1; // Значит сервер держит докачку
        else
            ranges_support = 0; // Иначе не держит
    }
    if (SieGetDialog::Active)
        SieGetDialog::Active->RefreshList(); // Перерисовываем список
    return 1;
}

void Download::onHTTPData(char * data, int size) // Запись данных в файл
{
    unsigned int io_error;

#warning fopen
    download_state = DOWNLOAD_DATA; // Статус закачки - загрузка данных

    if ( !hFile ) // Если файл не открыт, открываем его
    {
        hFile = fopen(full_file_name, "a+"); // Только запись с созданием и добавлением в конец
        if(WRITE_BUFFER_SIZE > 0)
            setvbuf(hFile, 0, _IOFBF, WRITE_BUFFER_SIZE);
    }

    if(!hFile)
    {
        ShowMSG(1, (int)full_file_name);
        this->StopDownload();
        return;
    }

    int writed = fwrite(data, 1, size, hFile); // Записываем принятые данные в файл

    if(writed < 0)
    {
        ShowMSG(1, (int)"Загрузка была остановлена из за ошибки");
        this->StopDownload();
        return;
    }

    if(writed != size)
    {
        ShowMSG(1, (int)"Загрузка остановлена. Не записанно нужное количество данных");
        this->StopDownload();
        return;
    }

    if(CFG_FFLUSH) fflush(hFile); // Точно записываем в файл)
    file_loaded_size += writed; // Обновляем размер загруженного куска
}

void Download::onHTTPRedirect() // Переадресация
{
    http_state = HTTP_REDIRECT;
    // В заголовке "Location" указывается реальное местонахождение файла
    if (char * Location = HTTPResponse->headers->GetValue("Location")) // Есть такой заголовок
    {
        if (strstr(Location, "http://")) // Если в заголовке URL
        {
            _safe_delete(HTTPRequest); // Удаляем запрос
            _safe_delete(url); // Удаляем URL
            url = new char[strlen(Location) + 1]; // Создаем новый URL
            strcpy(url, Location);
        }
        else
        {
            _safe_delete(HTTPRequest->Path); // Иначе изменяем только путь к файлу в запросе
            HTTPRequest->Path = new char[strlen(Location) + 1];
            strcpy(HTTPRequest->Path, Location);

            if (!is_const_file_name)
            {
                _safe_delete(file_name);
                file_name = get_fname_from_path(HTTPRequest->Path);

                _safe_delete(full_file_name);
                full_file_name = new char[strlen(file_path) + strlen(file_name) + 2];
                sprintf(full_file_name, "%s%s", file_path, file_name);
                make_dirs(full_file_name);
                log->ChangeFileName(file_name);
            }
            file_loaded_size = 0;
            file_size = 0;
        }

        StartDownload();
    }
}

void Download::onHTTPStopped()
{
    unsigned int io_error;

    download_state = DOWNLOAD_STOPPED;
    Close();

    if ( hFile )
    {
        fflush(hFile);
        fclose(hFile);
    }

    hFile = 0;

    log->Print(LangPack::Active->data[LGP_DownloadStopped], CLR_Yellow);

    SUBPROC((void *)_save_queue, DownloadHandler::Top);

    if (SieGetDialog::Active)
        SieGetDialog::Active->RefreshList();
}

void Download::onHTTPFinish()
{
    unsigned int io_error;

    if ( hFile )
    {
        fflush(hFile);
        fclose(hFile);
    }
    hFile = 0;

    download_state = DOWNLOAD_COMPLETE;
    http_state = HTTP_IDLE;

    StartVibra(0);
    Play_Sound("$sounds\\complete.wav");

    log->Print(LangPack::Active->data[LGP_DownloadCompleted], CLR_Yellow);

    SUBPROC((void *)_save_queue, DownloadHandler::Top);

    if (SieGetDialog::Active)
        SieGetDialog::Active->RefreshList();
}

void _start_download(Download * download)
{
    download->StartDownload();
}

void Download::StartDownload()
{
    download_state = DOWNLOAD_WAITING;
    log->Print(LangPack::Active->data[LGP_DownloadStarted], CLR_Yellow);
    if (!HTTPRequest)
    {
        URL_unescape(url); // На всякий случай конвертируем url в обычный вид

        URL * tmp_url = new URL; // Создаем новый URL
        tmp_url->Parse(url); // Парсим

        if (!is_const_file_name)
        {
            _safe_delete(file_name);
            char * new_fname = get_fname_from_path(tmp_url->path); // Получаем имя из url->path
            remove_bad_chars(new_fname); // Удаляем из имени недопустимые символы
            file_name = utf82filename(new_fname); // utf8 в имя файла
            delete new_fname;
        }

        _safe_delete(full_file_name);
        full_file_name = new char[strlen(file_path) + strlen(file_name) + 2];
        sprintf(full_file_name, "%s%s", file_path, file_name); // Полное имя файла
        make_dirs(full_file_name);
        log->ChangeFileName(file_name); // Сменяем имя файла лога

        char * encoded_url = URL_reencode_escapes(url); // Конвертируем URL в Web формат
        delete url;
        url = encoded_url;

        tmp_url->Parse(url);
        //tmp_url->Print(log);

        HTTPRequest = new HTTP_Request; // Создаем новый запрос
        // Host. Берем из URL
        HTTPRequest->Host = new char[strlen(tmp_url->host)+1];
        strcpy(HTTPRequest->Host, tmp_url->host);
        // Path. Берем из URL
        if(tmp_url->param)
        {
            if(tmp_url->fragment)
            {
                HTTPRequest->Path = new char[strlen(tmp_url->path)+strlen(tmp_url->param)+strlen(tmp_url->fragment)+3];
                sprintf(HTTPRequest->Path, "%s?%s#%s", tmp_url->path, tmp_url->param, tmp_url->fragment);
            }
            else
            {
                HTTPRequest->Path = new char[strlen(tmp_url->path)+strlen(tmp_url->param)+2];
                sprintf(HTTPRequest->Path, "%s?%s", tmp_url->path, tmp_url->param);
            }
        }
        else
        {
            HTTPRequest->Path = new char[strlen(tmp_url->path)+1];
            strcpy(HTTPRequest->Path, tmp_url->path);
        }
        // Port. Берем из URL
        HTTPRequest->Port = tmp_url->port;
        // Referer.
        if(referer) HTTPRequest->Referer = URL_reencode_escapes(referer); // Если Пришел/ввели referer - юзаем его
        else
        {
            HTTPRequest->Referer = new char[strlen(url) + 1];
            strcpy(HTTPRequest->Referer, url);  // Иначе копируем туда URL как раньше
        }
        // Cookies
        if(cookies)
        {
            HTTPRequest->Cookies = new char[strlen(cookies) + 1];
            strcpy(HTTPRequest->Cookies, cookies);
        }
        URL_unescape(url); // Конвертируем URL из Web формата в обычный вид
        delete tmp_url;
    }
    if(!strlen(HTTPRequest->Host))
    {
        MsgBoxError(1, (int)"No host!");
        delete this;
        return;
    }
    _safe_delete(req_buf); //Удаляем старый буфер запроса. Мало ли что там ;)
    req_buf = new Buffer; // Создаем новый буфер
    int req_len = 0; // Длина одной строки запроса
    char * req_str = new char[2048]; // Строка запроса

    req_len=snprintf(req_str, 511, "GET /%s HTTP/1.1\r\n", HTTPRequest->Path); // Формируем строку
    req_buf->Write(req_str, req_len); // Пишем строку в буфер
    log->Print(req_str, CLR_Blue);
    // Host
    req_len=snprintf(req_str, 511, "Host: %s\r\n", HTTPRequest->Host);
    req_buf->Write(req_str, req_len);
    log->Print(req_str, CLR_Blue);
    // Range. Это для докачки
    if (is_file_exists(full_file_name)) // Если файл уже есть
    {
        if(int size = get_file_size(full_file_name)) // И длина файла != 0
        {
            req_len=snprintf(req_str, 511, "Range: bytes=%u-\r\n", size);
            req_buf->Write(req_str, req_len);
            log->Print(req_str, CLR_Blue);
            file_loaded_size = size;
        }
    }
    // Referer
    req_len=snprintf(req_str, 511, "Referer: %s\r\n", HTTPRequest->Referer);
    req_buf->Write(req_str, req_len);
    log->Print(req_str, CLR_Blue);
    // Cookies
    if(HTTPRequest->Cookies)
    {
        req_len=snprintf(req_str, 1023, "Cookie: %s\r\n", HTTPRequest->Cookies);
        req_buf->Write(req_str, req_len);
        log->Print(req_str, CLR_Blue);
    }
    // User-Agent
    req_len=snprintf(req_str, 511, "User-Agent: %s\r\n", HTTPRequest->User_Agent);
    req_buf->Write(req_str, req_len);
    log->Print(req_str, CLR_Blue);
    // Connection
    req_len=snprintf(req_str, 511, "Connection: close\r\n");
    req_buf->Write(req_str, req_len);
    log->Print(req_str, CLR_Blue);
    // CRLF
    req_len=snprintf(req_str, 511, "\r\n");
    req_buf->Write(req_str, req_len);

    if(ip || (ip = str2ip(HTTPRequest->Host)) != 0xFFFFFFFF) Start(ip, HTTPRequest->Port);
    else Start(HTTPRequest->Host, HTTPRequest->Port);

    SUBPROC((void *)_save_queue, DownloadHandler::Top);

    delete req_str;
}

void Download::StopDownload()
{
    onHTTPStopped();
}

Download::Download()
{
    ranges_support = -1;
    url = 0;
    file_name = 0;
    is_const_file_name = 0;
    full_file_name = 0;
    file_path = 0;
    file_size = 0;
    file_loaded_size = 0;
    referer = 0;
    cookies = 0;

    hFile = 0;
    if (DownloadHandler::Top)
        DownloadHandler::Top->CreateDownload(this);
}

Download::~Download()
{
    unsigned int io_error;

    StopDownload();

    if ( hFile )
    {
        fflush(hFile);
        fclose(hFile);
    }
    hFile = 0;

    _safe_delete(url);
    _safe_delete(file_name);
    _safe_delete(full_file_name);
    _safe_delete(file_path);
    _safe_delete(referer);
    _safe_delete(cookies);

    if (DownloadHandler::Top)
        DownloadHandler::Top->DeleteDownload(this);
}

/*******************************************************************************
  DownloadHandler
  Обработчик закачек
*******************************************************************************/

void DownloadHandler::CreateDownload(Download * download)
{
    DownloadQ * tmp = new DownloadQ;
    tmp->download = download;
    tmp->next = queue;
    queue = tmp;
}

void DownloadHandler::DeleteDownload(Download * download)
{
    DownloadQ *tmp = queue;
    if (queue->download==download)
    {
        queue = queue->next;
        tmp->download->log->DeleteFile();
        delete tmp;
    }
    else
    {
        DownloadQ * prev_download = queue;
        tmp = queue->next;
        while (tmp)
        {
            if (tmp->download==download)
            {
                prev_download->next = tmp->next;
                tmp->download->log->DeleteFile();
                delete tmp;
            }
            prev_download = tmp;
            tmp = tmp->next;
        }
    }
    SUBPROC((void *)_save_queue, this);
}

Download * DownloadHandler::GetDownloadbyID(int socket_id)
{
    DownloadQ * tmp = queue;
    while(tmp)
    {
        if (tmp->download->socket_id==socket_id)
            return tmp->download;// Нашли!
        tmp = tmp->next;
    }
    return 0; // Не нашли...
}

Download * DownloadHandler::GetDownloadbyN(int n)
{
    DownloadQ * tmp = queue;
    for (int i = 0; i != n && tmp; i ++)
        tmp = tmp->next;
    if(tmp)
        return tmp->download;
    return NULL;
}

int DownloadHandler::GetNumOfDownloadsByState(DOWNLOAD_STATE state)
{
    int n = 0;
    DownloadQ * tmp = queue;
    while(tmp)
    {
        if (tmp->download->download_state==state)
            n++;
        tmp = tmp->next;
    }
    return n;
}

Download * DownloadHandler::GetDownloadByState(DOWNLOAD_STATE state)
{
    DownloadQ *tmp = queue;
    while(tmp)
    {
        if (tmp->download->download_state==state)
            return tmp->download;
        tmp = tmp->next;
    }
    return NULL;
}

int DownloadHandler::IsAnyDownloadActive()
{
    DownloadQ *tmp = queue;
    while(tmp)
    {
        if (ACTIVE_DOWNLOAD_STATE(tmp->download->download_state))
            return 1;
        tmp = tmp->next;
    }
    return 0;
}

int DownloadHandler::GetNumOfDownloads()
{
    int n = 0;
    DownloadQ *tmp = queue;
    while(tmp)
    {
        n++;
        tmp = tmp->next;
    }
    return n;
}

char QL_List[]="SieGet downloads list";
char QL_GeneratedDateTime[]="GeneratedDateTime=";
char QL_Total_downloads[]="TotalDownloads=";
char QL_Download[]="Download";
char QL_FileName[]="FileName=";
char QL_FullName[]="FullName=";
char QL_SaveTo[]="SaveTo=";
char QL_URL[]="URL=";
char QL_Referer[]="Referer=";
char QL_State[]="State=";
char QL_Size[]="Size=";
char QL_Downloaded[]="Downloaded=";

void _load_queue(DownloadHandler * dh)
{
    if (dh)
        dh->LoadQueue();
}

void DownloadHandler::LoadQueue()
{
    volatile int hFile;
    unsigned int io_error = 0;
    char * buf; // Буфер под файл
    int line_size = 0; // Длина текущей строки
    int data_len = 0;
    Download * top_download = 0;
    FSTATS fstat;
    char * list_file = getSymbolicPath("$sieget\\List.txt");

    if (GetFileStats(list_file, &fstat, &io_error)!=-1) // Получаем размер файла
    {
        if((hFile=_open(list_file, A_ReadOnly + A_BIN, P_READ, &io_error))!=-1) // Открываем файл для чтения
        {
            if (buf = new char[fstat.size+1]) // Выделяем память под буфер
            {
                buf[_read(hFile, buf, fstat.size, &io_error)]=0; // Читаем файл в буфер
                char* s = buf;
                s[fstat.size]=0;
                while(*s && s <= buf+fstat.size) // Пока не конец файла
                {
                    char* c = find_eol(s);
                    line_size = c - s;
                    if (line_size > 0)
                    {
                        if(!strncmp(s,QL_FileName,sizeof(QL_FileName)-1))
                        {
                            data_len = line_size - sizeof(QL_FileName)+1;
                            top_download->file_name = new char[data_len+1];
                            memcpy(top_download->file_name, s+sizeof(QL_FileName)-1, data_len);
                            top_download->file_name[data_len] = 0;
                            goto end_of_loop; // Дальше проверять уже ничего не нужно. Идем в конец цикла
                        }
                        if(!strncmp(s,QL_FullName,sizeof(QL_FullName)-1))
                        {
                            data_len = line_size - sizeof(QL_FullName)+1;
                            top_download->full_file_name = new char[data_len+1];
                            memcpy(top_download->full_file_name, s+sizeof(QL_FullName)-1, data_len);
                            top_download->full_file_name[data_len] = 0;
                            goto end_of_loop;
                        }
                        if(!strncmp(s,QL_SaveTo,sizeof(QL_SaveTo)-1))
                        {
                            data_len = line_size - sizeof(QL_SaveTo)+1;
                            top_download->file_path = new char[data_len+1];
                            memcpy(top_download->file_path, s+sizeof(QL_SaveTo)-1, data_len);
                            top_download->file_path[data_len] = 0;
                            goto end_of_loop;
                        }
                        if(!strncmp(s,QL_URL,sizeof(QL_URL)-1))
                        {
                            data_len = line_size - sizeof(QL_URL)+1;
                            top_download->url = new char[data_len+1];
                            memcpy(top_download->url, s+sizeof(QL_URL)-1, data_len);
                            top_download->url[data_len] = 0;
                            goto end_of_loop;
                        }
                        if(!strncmp(s,QL_Referer,sizeof(QL_Referer)-1))
                        {
                            data_len = line_size - sizeof(QL_Referer)+1;
                            top_download->referer = new char[data_len+1];
                            memcpy(top_download->referer, s+sizeof(QL_Referer)-1, data_len);
                            top_download->referer[data_len] = 0;
                            goto end_of_loop;
                        }
                        if(!strncmp(s,QL_State,sizeof(QL_State)-1))
                        {
                            top_download->download_state = (DOWNLOAD_STATE)strtoul(s+sizeof(QL_State)-1, 0, 10);
                            goto end_of_loop;
                        }
                        if(!strncmp(s,QL_Size,sizeof(QL_Size)-1))
                        {
                            top_download->file_size = strtoul(s+sizeof(QL_Size)-1, 0, 10);
                            goto end_of_loop;
                        }
                        if(!strncmp(s,QL_Downloaded,sizeof(QL_Downloaded)-1))
                        {
                            top_download->file_loaded_size = strtoul(s+sizeof(QL_Downloaded)-1, 0, 10);
                            goto end_of_loop;
                        }
end_of_loop: // Конец цикла
                        s+=line_size+2;
                        if (*s==0x0D && *(s+1)==0x0A && *(s+2)!=0)
                        {
                            Download * new_download = new Download;
                            top_download = new_download;
                            s+=2;
                        }
                    }
                    else s+=2;
                }
                delete buf; // Удаляем буфер. он нам уже не понадобится

                DownloadQ *tmp = queue;
                while(tmp)
                {
                    tmp->download->log->ChangeFileName(tmp->download->file_name); // Получаем имя файла лога
                    if (ACTIVE_DOWNLOAD_STATE(tmp->download->download_state))
                        tmp->download->download_state = DOWNLOAD_STOPPED;
                    tmp = tmp->next;
                }
                if (SieGetDialog::Active)
                    SieGetDialog::Active->RefreshList();
            }
            _close(hFile, &io_error);
        }
    }
    delete list_file;
}

void _save_queue(DownloadHandler * dh)
{
    if (dh)
        dh->SaveQueue();
}

void DownloadHandler::SaveQueue()
{
    volatile int hFile;
    unsigned int io_error = 0;
    int download_n = 0; // Номер текущей закачки
    char * tmp_str = new char[1024]; // Временная строка(пока только увеличу, хз как резиновой сделать)
    int tmp_str_len = 0; // Длина временной строки
    Buffer * tmp_buf = new Buffer; // Буфер данных

    char * list_file = getSymbolicPath("$sieget\\List.txt"); // ...\Sieget\List.txt

    _unlink(list_file, &io_error); // Удаляем старый файл

    hFile = _open(list_file, A_ReadWrite + A_Create + A_Append + A_BIN, P_READ + P_WRITE, &io_error); // Открываем файл для записи
    if(!io_error)
    {
        DownloadQ * tmp = queue; // Очередь закачек
        tmp_str_len = snprintf(tmp_str, 1023, "SieGet downloads list\r\n");
        tmp_buf->Write(tmp_str, tmp_str_len);
        // Добавляем в буфер дату и время генерации файла
        TTime time;
        TDate date;
        GetDateTime(&date, &time); // Получаем текущие дату и время
        tmp_str_len = snprintf(tmp_str, 1023, "%s%02d.%02d.%02d %02d:%02d:%02d\r\n", QL_GeneratedDateTime, date.day, date.month, date.year, time.hour, time.min, time.sec);
        tmp_buf->Write(tmp_str, tmp_str_len);
        // Добавляем в буфер число закачек в очереди
        tmp_str_len = snprintf(tmp_str, 1023, "%s%d\r\n\r\n", QL_Total_downloads, GetNumOfDownloads());
        tmp_buf->Write(tmp_str, tmp_str_len);
        while(tmp) // Далее делаем в цикле для каждой закачки
        {
            // Добавляем в буфер номер текущей сохраняемой закачки
            tmp_str_len = snprintf(tmp_str, 1023, "%s %d:\r\n", QL_Download, download_n + 1);
            tmp_buf->Write(tmp_str, tmp_str_len);
            // Добавляем в буфер имя закачиваемого файла
            tmp_str_len = snprintf(tmp_str, 1023, "%s%s\r\n", QL_FileName, tmp->download->file_name);
            tmp_buf->Write(tmp_str, tmp_str_len);
            // Добавляем в буфер имя закачиваемого файла
            tmp_str_len = snprintf(tmp_str, 1023, "%s%s\r\n", QL_FullName, tmp->download->full_file_name);
            tmp_buf->Write(tmp_str, tmp_str_len);
            // Добавляем в буфер папку, куда сохраняется файл
            tmp_str_len = snprintf(tmp_str, 1023, "%s%s\r\n", QL_SaveTo, tmp->download->file_path);
            tmp_buf->Write(tmp_str, tmp_str_len);
            // Добавляем в буфер URL, по которому производилась закачка
            tmp_str_len = snprintf(tmp_str, 1023, "%s%s\r\n", QL_URL, tmp->download->url);
            tmp_buf->Write(tmp_str, tmp_str_len);
            // Добавляем в буфер Referer, если вписывали
            if(tmp->download->referer)
            {
                tmp_str_len = snprintf(tmp_str, 1023, "%s%s\r\n", QL_Referer, tmp->download->referer);
                tmp_buf->Write(tmp_str, tmp_str_len);
            }
            // Добавляем в буфер состояние закачки
            tmp_str_len = snprintf(tmp_str, 1023, "%s%d\r\n", QL_State, tmp->download->download_state);
            tmp_buf->Write(tmp_str, tmp_str_len);
            // Добавляем в буфер размер закачиваемого файла
            tmp_str_len = snprintf(tmp_str, 1023, "%s%u\r\n", QL_Size, tmp->download->file_size);
            tmp_buf->Write(tmp_str, tmp_str_len);
            // Добавляем в буфер число закачанных байт файла
            tmp_str_len = snprintf(tmp_str, 1023, "%s%u\r\n\r\n", QL_Downloaded, tmp->download->file_loaded_size);
            tmp_buf->Write(tmp_str, tmp_str_len);

            download_n++;
            tmp = tmp->next;
        }
        // Пишем весь буфер в файл
        _write(hFile, tmp_buf->data, tmp_buf->size, &io_error);
        _close(hFile, &io_error);
    }
    delete list_file;
    delete tmp_str;
    delete tmp_buf;
}

DownloadHandler::DownloadHandler()
{
    Top = this;
    queue = NULL;
    LoadQueue();
}

DownloadHandler::~DownloadHandler()
{
    SaveQueue();
    DownloadQ * tmp;
    while (tmp = queue)
    {
        queue = queue->next;
        tmp->download->log->CloseFile();
        delete tmp;
    }
}
