/* FTP File YM plugin Copyright (C) 2007-2010 Jan Holub This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "stdafx.h" Event UploadJob::jobDone; mir_cs UploadJob::mutexJobCount; int UploadJob::iRunningJobCount = 0; UploadJob::UploadJob(MCONTACT _hContact, int _iFtpNum, EMode _mode) : GenericJob(_hContact, _iFtpNum, _mode), m_fp(nullptr) { m_szFileLink[0] = 0; } UploadJob::UploadJob(UploadJob *job) : GenericJob(job), m_fp(nullptr), m_uiSent(0), m_uiTotalSent(0), m_uiFileSize(0) { strncpy_s(m_szFileLink, job->m_szFileLink, _TRUNCATE); for (int i = 0; i < _countof(m_lastSpeed); i++) m_lastSpeed[i] = 0; } UploadJob::UploadJob(PackerJob *job) : GenericJob(job), m_fp(nullptr), m_uiSent(0), m_uiTotalSent(0), m_uiFileSize(0) { for (int i = 0; i < _countof(m_lastSpeed); i++) m_lastSpeed[i] = 0; makeSafeString(job->m_tszFileName); m_status = STATUS_CREATED; } UploadJob::~UploadJob() { if (m_fp) fclose(m_fp); if (m_mode != FTP_RAWFILE) DeleteFile(m_tszFilePath); } void UploadJob::addToUploadDlg() { for (UINT i = 0; i < m_files.size(); i++) { UploadJob *jobCopy = new UploadJob(this); wcsncpy_s(jobCopy->m_tszFilePath, m_files[i], _TRUNCATE); wcsncpy_s(jobCopy->m_tszFileName, Utils::getFileNameFromPath(jobCopy->m_tszFilePath), _TRUNCATE); jobCopy->makeSafeString(jobCopy->m_tszFileName); UploadDialog::Tab *newTab = new UploadDialog::Tab(jobCopy); jobCopy->m_tab = newTab; jobCopy->start(); } delete this; } void UploadJob::autoSend() { if (m_hContact == NULL) return; char *szProto = Proto_GetBaseAccountName(m_hContact); if (szProto == nullptr) return; DBEVENTINFO dbei = {}; dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = DBEF_SENT; dbei.szModule = szProto; dbei.timestamp = (DWORD)time(0); dbei.cbBlob = (DWORD)mir_strlen(m_szFileLink) + 1; dbei.pBlob = (PBYTE)m_szFileLink; db_event_add(m_hContact, &dbei); ProtoChainSend(m_hContact, PSS_MESSAGE, 0, (LPARAM)m_szFileLink); CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)m_hContact, 0); } void UploadJob::copyLinkToML() { if (m_hContact != NULL) { char buff[256]; mir_snprintf(buff, "%s\r\n", m_szFileLink); CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)m_hContact, (LPARAM)buff); } } void UploadJob::pause() { if (!isCompleted()) { curl_easy_pause(m_hCurl, CURLPAUSE_SEND); setStatus(STATUS_PAUSED); } } void UploadJob::pauseHandler() { if (isPaused()) { resume(); SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)Utils::loadIconEx("pause")); SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BUTTONADDTOOLTIP, (WPARAM)Translate("Pause"), 0); } else { pause(); SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)Utils::loadIconEx("resume")); SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BUTTONADDTOOLTIP, (WPARAM)Translate("Resume"), 0); } } void UploadJob::resume() { m_uiSent = 0; m_startTS = time(0); if (!isCompleted()) { curl_easy_pause(m_hCurl, CURLPAUSE_CONT); setStatus(STATUS_UPLOADING); } } void UploadJob::cancel() { setStatus(STATUS_CANCELED); curl_easy_pause(m_hCurl, CURLPAUSE_CONT); } void UploadJob::waitingThread(UploadJob *job) { while (!Miranda_IsTerminated()) { mir_cslockfull lock(mutexJobCount); if (iRunningJobCount < MAX_RUNNING_JOBS) { iRunningJobCount++; lock.unlock(); job->upload(); if (!job->isCompleted()) delete job; lock.lock(); iRunningJobCount--; lock.unlock(); jobDone.release(); return; } lock.unlock(); jobDone.wait(); job->m_status = GenericJob::STATUS_WAITING; } delete job; } void UploadJob::start() { mir_forkThread(&UploadJob::waitingThread, this); } char* UploadJob::getChmodString() { if (m_ftp->m_ftpProto == ServerList::FTP::FT_SSH) mir_snprintf(m_buff, "%s \"%s/%s\"", m_ftp->m_szChmod, m_ftp->m_szDir, m_szSafeFileName); else mir_snprintf(m_buff, "%s %s", m_ftp->m_szChmod, m_szSafeFileName); return m_buff; } char* UploadJob::getDelFileString() { if (m_ftp->m_ftpProto == ServerList::FTP::FT_SSH) mir_snprintf(m_buff, "rm \"%s/%s\"", m_ftp->m_szDir, m_szSafeFileName); else mir_snprintf(m_buff, "DELE %s", m_szSafeFileName); return m_buff; } char* UploadJob::getUrlString() { if (m_ftp->m_szDir[0]) mir_snprintf(m_buff, "%s%s/%s/%s", m_ftp->getProtoString(), m_ftp->m_szServer, m_ftp->m_szDir, m_szSafeFileName); else mir_snprintf(m_buff, "%s%s/%s", m_ftp->getProtoString(), m_ftp->m_szServer, m_szSafeFileName); return m_buff; } char* UploadJob::getDelUrlString() { if (m_ftp->m_szDir[0] && m_ftp->m_ftpProto != ServerList::FTP::FT_SSH) mir_snprintf(m_buff, "%s%s/%s/", m_ftp->getProtoString(), m_ftp->m_szServer, m_ftp->m_szDir); else mir_snprintf(m_buff, "%s%s/", m_ftp->getProtoString(), m_ftp->m_szServer); return m_buff; } CURL* UploadJob::curlInit(char *szUrl, struct curl_slist *headerList) { m_hCurl = curl_easy_init(); if (!m_hCurl) return nullptr; Utils::curlSetOpt(m_hCurl, m_ftp, szUrl, headerList, m_szError); curl_easy_setopt(m_hCurl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)m_uiFileSize); curl_easy_setopt(m_hCurl, CURLOPT_READDATA, this); curl_easy_setopt(m_hCurl, CURLOPT_READFUNCTION, &UploadJob::ReadCallback); return m_hCurl; } bool UploadJob::fileExistsOnServer() { int result = curl_easy_perform(m_hCurl); return result != CURLE_REMOTE_FILE_NOT_FOUND; } ///////////////////////////////////////////////////////////////////////////////////////// const wchar_t from_chars[] = L" !@#$%^&=,{}[];'`"; void UploadJob::makeSafeString(const wchar_t *input) { CMStringW tmp(input); for (size_t i = 0; i < _countof(from_chars)-1; i++) tmp.Replace(from_chars[i], '_'); strncpy_s(m_szSafeFileName, T2Utf(tmp), _TRUNCATE); } ///////////////////////////////////////////////////////////////////////////////////////// INT_PTR CALLBACK UploadJob::DlgProcFileExists(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: TranslateDialogDefault(hwndDlg); { wchar_t buff[256]; wchar_t *fileName = mir_a2u((char *)lParam); mir_snwprintf(buff, TranslateT("File exists - %s"), fileName); SetWindowText(hwndDlg, buff); FREE(fileName); } return TRUE; case WM_COMMAND: if (HIWORD(wParam) == BN_CLICKED) EndDialog(hwndDlg, LOWORD(wParam)); break; } return FALSE; } void UploadJob::upload() { refreshTab(true); m_fp = _wfopen(m_tszFilePath, L"rb"); if (m_fp == nullptr) { Utils::msgBox(TranslateT("Error occurred when opening local file.\nAborting file upload..."), MB_OK | MB_ICONERROR); return; } curl_slist *headerList = nullptr; if (m_ftp->m_szChmod[0]) headerList = curl_slist_append(headerList, getChmodString()); struct _stat fileInfo; _wstat(m_tszFilePath, &fileInfo); m_uiFileSize = (UINT64)fileInfo.st_size; CURL *hCurl = curlInit(getUrlString(), headerList); if (!hCurl) { Utils::msgBox(TranslateT("Error occurred when initializing libcurl.\nAborting file upload..."), MB_OK | MB_ICONERROR); return; } bool uploadFile = true; if (fileExistsOnServer()) { int res = DialogBoxParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_DLG_FILEEXISTS), nullptr, DlgProcFileExists, (LPARAM)m_szSafeFileName); if (res == IDC_RENAME) { if (Utils::setFileNameDlg(_A2T(m_szSafeFileName)) == true) curl_easy_setopt(hCurl, CURLOPT_URL, getUrlString()); } else if (res == IDC_COPYURL) { uploadFile = false; } else if (res == IDC_CANCEL) { setStatus(STATUS_CANCELED); delete m_tab; return; } } if (uploadFile) { curl_easy_setopt(m_hCurl, CURLOPT_UPLOAD, 1L); setStatus(STATUS_CONNECTING); m_startTS = time(0); int result = curl_easy_perform(hCurl); curl_slist_free_all(headerList); curl_easy_cleanup(hCurl); if (result != CURLE_OK && result != CURLE_ABORTED_BY_CALLBACK) { char buff[256]; mir_snprintf(buff, Translate("FTP error occurred.\n%s"), m_szError); Utils::msgBoxA(buff, MB_OK | MB_ICONERROR); } if (result > CURLE_OPERATION_TIMEDOUT) { curl_slist *headers2 = curl_slist_append(headerList, getDelFileString()); CURL *hCurl2 = curl_easy_init(); if (hCurl2) { Utils::curlSetOpt(hCurl2, m_ftp, getDelUrlString(), headers2, m_szError); curl_easy_perform(hCurl2); curl_slist_free_all(headers2); curl_easy_cleanup(hCurl2); } } if (result != CURLE_OK && result != CURLE_QUOTE_ERROR) { if (!isCanceled()) { setStatus(STATUS_CANCELED); delete m_tab; } return; } DBEntry::add(this); Skin_PlaySound(SOUND_UPCOMPLETE); } setStatus(STATUS_COMPLETED); Utils::createFileDownloadLink(m_ftp->m_szUrl, m_szSafeFileName, m_szFileLink, sizeof(m_szFileLink)); Utils::copyToClipboard(m_szFileLink); if (m_tab->m_bOptAutosend) autoSend(); else if (m_tab->m_bOptCopyLink) copyLinkToML(); if (!m_tab->m_bOptCloseDlg) { m_tab->labelCompleted(); m_tab->select(); } else closeTab(); } size_t UploadJob::ReadCallback(void *ptr, size_t size, size_t nmemb, void *arg) { UploadJob *job = (UploadJob *)arg; if (job->m_uiTotalSent == 0) job->m_status = UploadJob::STATUS_UPLOADING; if (job->isCanceled()) return CURL_READFUNC_ABORT; size_t readed = fread(ptr, size, nmemb, job->m_fp); job->m_uiSent += readed; job->m_uiTotalSent += readed; job->updateStats(); return readed; } void UploadJob::updateStats() { if (m_uiSent && (time(0) > m_startTS)) { double speed = ((double)m_uiSent / 1024) / (time(0) - m_startTS); m_avgSpeed = speed; for (int i = 0; i < _countof(m_lastSpeed); i++) { m_avgSpeed += (m_lastSpeed[i] == 0 ? speed : m_lastSpeed[i]); if (i < _countof(m_lastSpeed) - 1) m_lastSpeed[i + 1] = m_lastSpeed[i]; } m_avgSpeed /= _countof(m_lastSpeed) + 1; m_lastSpeed[0] = speed; mir_snwprintf(m_tab->m_stzSpeed, L"%0.1f kB/s", m_avgSpeed); double perc = m_uiFileSize ? ((double)m_uiTotalSent / m_uiFileSize) * 100 : 0; mir_snwprintf(m_tab->m_stzComplet, L"%0.1f%% (%d kB/%d kB)", perc, (int)m_uiTotalSent / 1024, (int)m_uiFileSize / 1024); long s = (m_uiFileSize - m_uiTotalSent) / (long)(m_avgSpeed * 1024); int d = (s / 60 / 60 / 24); int h = (s - d * 60 * 60 * 24) / 60 / 60; int m = (s - d * 60 * 60 * 24 - h * 60 * 60) / 60; s = s - (d * 24 * 60 * 60) - (h * 60 * 60) - (m * 60); wchar_t buff[256]; if (d > 0) mir_snwprintf(buff, L"%dd %02d:%02d:%02d", d, h, m, s); else mir_snwprintf(buff, L"%02d:%02d:%02d", h, m, s); mir_snwprintf(m_tab->m_stzRemain, L"%s (%d kB/%d kB)", buff, (m_uiFileSize - m_uiTotalSent) / 1024, m_uiFileSize / 1024); refreshTab(false); } } void UploadJob::refreshTab(bool bTabChanged) { if (uDlg->m_activeTab == m_tab->index()) { GenericJob::refreshTab(bTabChanged); ShowWindow(GetDlgItem(uDlg->m_hwnd, IDC_BTN_CLIPBOARD), isCompleted() ? SW_SHOW : SW_HIDE); ShowWindow(GetDlgItem(uDlg->m_hwnd, IDC_BTN_DOWNLOAD), isCompleted() ? SW_SHOW : SW_HIDE); EnableWindow(GetDlgItem(uDlg->m_hwnd, IDC_BTN_PAUSE), !isCompleted() && !isConnecting()); if (isCompleted()) SetDlgItemText(uDlg->m_hwnd, IDC_STATUSBAR, TranslateT("COMPLETED")); else if (isConnecting()) SetDlgItemText(uDlg->m_hwnd, IDC_STATUSBAR, TranslateT("CONNECTING...")); else if (isPaused()) SetDlgItemText(uDlg->m_hwnd, IDC_STATUSBAR, TranslateT("PAUSED")); else if (isWaitting()) SetDlgItemText(uDlg->m_hwnd, IDC_STATUSBAR, TranslateT("WAITING...")); else SetDlgItemText(uDlg->m_hwnd, IDC_STATUSBAR, TranslateT("UPLOADING...")); if (bTabChanged) { if (isPaused()) { SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)Utils::loadIconEx("resume")); SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Resume"), BATF_UNICODE); } else { SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)Utils::loadIconEx("pause")); SendDlgItemMessage(uDlg->m_hwnd, IDC_BTN_PAUSE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Pause"), BATF_UNICODE); } ShowWindow(GetDlgItem(uDlg->m_hwnd, IDC_ST_REMAIN), !isCompleted() ? SW_SHOW : SW_HIDE); ShowWindow(GetDlgItem(uDlg->m_hwnd, IDC_UP_COMPLETED), !isCompleted() ? SW_SHOW : SW_HIDE); ShowWindow(GetDlgItem(uDlg->m_hwnd, IDC_UP_REMAIN), !isCompleted() ? SW_SHOW : SW_HIDE); ShowWindow(GetDlgItem(uDlg->m_hwnd, IDC_ED_URL), isCompleted() ? SW_SHOW : SW_HIDE); SetDlgItemText(uDlg->m_hwnd, IDC_ST_COMPLETED, isCompleted() ? TranslateT("Download link:") : TranslateT("Completed:")); } if (isCompleted()) { SetDlgItemText(uDlg->m_hwnd, IDC_UP_SPEED, L""); SetDlgItemText(uDlg->m_hwnd, IDC_UP_COMPLETED, L""); SetDlgItemText(uDlg->m_hwnd, IDC_UP_REMAIN, L""); SetDlgItemTextA(uDlg->m_hwnd, IDC_ED_URL, m_szFileLink); SendDlgItemMessage(uDlg->m_hwnd, IDC_PB_UPLOAD, PBM_SETRANGE32, 0, (LPARAM)100); SendDlgItemMessage(uDlg->m_hwnd, IDC_PB_UPLOAD, PBM_SETPOS, (WPARAM)100, 0); } else { SetDlgItemText(uDlg->m_hwnd, IDC_UP_SPEED, m_tab->m_stzSpeed); SetDlgItemText(uDlg->m_hwnd, IDC_UP_COMPLETED, m_tab->m_stzComplet); SetDlgItemText(uDlg->m_hwnd, IDC_UP_REMAIN, m_tab->m_stzRemain); SendDlgItemMessage(uDlg->m_hwnd, IDC_PB_UPLOAD, PBM_SETRANGE32, 0, (LPARAM)m_uiFileSize); SendDlgItemMessage(uDlg->m_hwnd, IDC_PB_UPLOAD, PBM_SETPOS, (WPARAM)m_uiTotalSent, 0); } } } void UploadJob::closeTab() { if (!isCompleted()) { pause(); if (Utils::msgBox(TranslateT("Do you really want to cancel running upload?"), MB_YESNO | MB_ICONQUESTION) == IDNO) { resume(); return; } cancel(); } delete m_tab; } void UploadJob::closeAllTabs() { if (!isCompleted()) cancel(); delete m_tab; } void UploadJob::createToolTip() { mir_snwprintf(uDlg->m_tszToolTipText, TranslateT("Status: %s\r\nFile: %s\r\nServer: %S"), getStatusString(), m_tszFileName, m_ftp->m_szServer); if (m_tab->m_stzSpeed[0] && m_tab->m_stzComplet[0] && m_tab->m_stzRemain[0]) mir_snwprintf(uDlg->m_tszToolTipText, TranslateT("%s\r\nSpeed: %s\r\nCompleted: %s\r\nRemaining: %s"), uDlg->m_tszToolTipText, m_tab->m_stzSpeed, m_tab->m_stzComplet, m_tab->m_stzRemain); }