/*
* Copyright (C) 2001-2024 Jacek Sieka, arnetheduck on gmail point com
*
* 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, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include"stdafx.h"
#include"TransferView.h"
#include<dcpp/ClientManager.h>
#include<dcpp/ConnectionManager.h>
#include<dcpp/Download.h>
#include<dcpp/DownloadManager.h>
#include<dcpp/GeoManager.h>
#include<dcpp/HttpConnection.h>
#include<dcpp/HttpManager.h>
#include<dcpp/QueueManager.h>
#include<dcpp/SettingsManager.h>
#include<dcpp/Upload.h>
#include<dcpp/UploadManager.h>
#include<dcpp/UserConnection.h>
#include<dwt/resources/Pen.h>
#include<dwt/util/StringUtils.h>
#include<dwt/widgets/TableTree.h>
#include"HoldRedraw.h"
#include"resource.h"
#include"ShellMenu.h"
#include"TypedTable.h"
#include"WinUtil.h"
usingdwt::util::escapeMenu;
staticconstColumnInfocolumns[]={
{N_("File"),200,false},
{N_("Path"),300,false},
{N_("Status"),350,false},
{N_("User"),125,false},
{N_("Hub"),100,false},
{N_("Time left"),100,true},
{N_("Speed"),100,true},
{N_("Transferred (Ratio)"),80,true},
{N_("Size"),80,true},
{N_("Cipher"),100,false},
{N_("IP"),100,false},
{N_("Country"),100,false}
};
TransferView::TransferView(dwt::Widget*parent,TabViewPtrmdi_):
dwt::Container(parent),
transfers(0),
mdi(mdi_),
downloadIcon(WinUtil::createIcon(IDI_DOWNLOAD,16)),
uploadIcon(WinUtil::createIcon(IDI_UPLOAD,16)),
updateList(false)
{
create();
setHelpId(IDH_TRANSFERS);
transfers=addChild(WidgetTransfers::Seed(WinUtil::Seeds::table));
transfers->setSmallImageList(WinUtil::fileImages);
WinUtil::makeColumns(transfers,columns,COLUMN_LAST,SETTING(TRANSFERS_ORDER),SETTING(TRANSFERS_WIDTHS));
WinUtil::setTableSort(transfers,COLUMN_LAST,SettingsManager::TRANSFERS_SORT,COLUMN_STATUS);
WinUtil::setColor(transfers);
transfers->onCustomDraw([this](NMLVCUSTOMDRAW&data){returnhandleCustomDraw(data);});
transfers->onKeyDown([this](intc){returnhandleKeyDown(c);});
transfers->onDblClicked([this]{handleDblClicked();});
transfers->onContextMenu([this](constdwt::ScreenCoordinate&sc){returnhandleContextMenu(sc);});
onDestroy([this]{handleDestroy();});
noEraseBackground();
layout();
setTimer([this]{returnhandleTimer();},500);
ConnectionManager::getInstance()->addListener(this);
DownloadManager::getInstance()->addListener(this);
UploadManager::getInstance()->addListener(this);
QueueManager::getInstance()->addListener(this);
HttpManager::getInstance()->addListener(this);
}
TransferView::~TransferView(){
}
voidTransferView::prepareClose(){
QueueManager::getInstance()->removeListener(this);
ConnectionManager::getInstance()->removeListener(this);
DownloadManager::getInstance()->removeListener(this);
UploadManager::getInstance()->removeListener(this);
HttpManager::getInstance()->removeListener(this);
}
TransferView::ItemInfo::ItemInfo():
speed(0),
actual(0),
transferred(0),
size(0)
{
}
consttstring&TransferView::ItemInfo::getText(intcol)const{
returncolumns[col];
}
intTransferView::ItemInfo::getImage(intcol)const{
return-1;
}
intTransferView::ItemInfo::compareItems(constItemInfo*a,constItemInfo*b,intcol){
if(const_cast<ItemInfo*>(a)->transfer().type!=const_cast<ItemInfo*>(b)->transfer().type){
// sort downloads first, then uploads, then PMs.
returnconst_cast<ItemInfo*>(a)->transfer().type<const_cast<ItemInfo*>(b)->transfer().type?-1:1;
}
autoca=dynamic_cast<constConnectionInfo*>(a),cb=dynamic_cast<constConnectionInfo*>(b);
if(ca&&cb&&ca->status!=cb->status){
// sort running conns first.
returnca->status==STATUS_RUNNING?-1:1;
}
switch(col){
caseCOLUMN_STATUS:
{
return!a->transferred&&!b->transferred?compare(a->size,b->size):
!b->transferred?-1:
!a->transferred?1:
compare(a->size/a->transferred,b->size/b->transferred);
}
caseCOLUMN_TIMELEFT:returncompare(a->timeleft,b->timeleft);
caseCOLUMN_SPEED:returncompare(a->speed,b->speed);
caseCOLUMN_TRANSFERRED:returncompare(a->transferred,b->transferred);
caseCOLUMN_SIZE:returncompare(a->size,b->size);
default:returncompare(a->getText(col),b->getText(col));
}
}
TransferView::ConnectionInfo::ConnectionInfo(constHintedUser&u,TransferInfo&parent):
ItemInfo(),
UserInfoBase(u),
transferFailed(false),
status(STATUS_WAITING),
parent(parent)
{
columns[COLUMN_FILE]=parent.getText(COLUMN_FILE);
columns[COLUMN_PATH]=parent.getText(COLUMN_PATH);
columns[COLUMN_USER]=WinUtil::getNicks(u);
columns[COLUMN_HUB]=WinUtil::getHubNames(u).first;
columns[COLUMN_STATUS]=T_("Idle");
}
boolTransferView::ConnectionInfo::operator==(constConnectionInfo&other)const{
returnother.parent.type==parent.type&&other.getUser()==getUser();
}
intTransferView::ConnectionInfo::getImage(intcol)const{
returncol==COLUMN_FILE?WinUtil::TRANSFER_ICON_USER:ItemInfo::getImage(col);
}
voidTransferView::ConnectionInfo::update(constUpdateInfo&ui){
if(ui.updateMask&UpdateInfo::MASK_PATH){
parent.path=ui.path;
parent.updatePath();
columns[COLUMN_FILE]=parent.getText(COLUMN_FILE);
columns[COLUMN_PATH]=parent.getText(COLUMN_PATH);
}
if(ui.updateMask&UpdateInfo::MASK_STATUS){
status=ui.status;
}
if(ui.updateMask&UpdateInfo::MASK_STATUS_STRING){
// No slots etc from transfermanager better than disconnected from connectionmanager
if(!transferFailed)
columns[COLUMN_STATUS]=ui.statusString;
transferFailed=ui.transferFailed;
}
if(ui.updateMask&UpdateInfo::MASK_TRANSFERRED){
if(parent.type==CONNECTION_TYPE_DOWNLOAD&&ui.transferred>transferred){
parent.transferred+=ui.transferred-transferred;
}
actual=ui.actual;
transferred=ui.transferred;
size=ui.size;
if(transferred>0){
columns[COLUMN_TRANSFERRED]=str(TF_("%1% (%2$0.2f)")
%Text::toT(Util::formatBytes(transferred))
%(static_cast<double>(actual)/static_cast<double>(transferred)));
}else{
columns[COLUMN_TRANSFERRED].clear();
}
if(size>0){
columns[COLUMN_SIZE]=Text::toT(Util::formatBytes(size));
}else{
columns[COLUMN_SIZE].clear();
}
}
if(ui.updateMask&UpdateInfo::MASK_SPEED){
speed=std::max(ui.speed,0LL);// sometimes the speed is negative; avoid problems.
}
if((ui.updateMask&UpdateInfo::MASK_STATUS)||(ui.updateMask&UpdateInfo::MASK_SPEED)){
if(status==STATUS_RUNNING&&speed>0){
columns[COLUMN_SPEED]=str(TF_("%1%/s")%Text::toT(Util::formatBytes(speed)));
}else{
columns[COLUMN_SPEED].clear();
}
}
if((ui.updateMask&UpdateInfo::MASK_STATUS)||(ui.updateMask&UpdateInfo::MASK_TRANSFERRED)||(ui.updateMask&UpdateInfo::MASK_SPEED)){
if(status==STATUS_RUNNING&&size>0&&speed>0){
timeleft=static_cast<double>(size-transferred)/speed;
columns[COLUMN_TIMELEFT]=Text::toT(Util::formatSeconds(timeleft));
}else{
timeleft=0;
columns[COLUMN_TIMELEFT].clear();
}
}
if(ui.updateMask&UpdateInfo::MASK_CIPHER){
columns[COLUMN_CIPHER]=ui.cipher;
}
if(ui.updateMask&UpdateInfo::MASK_IP){
columns[COLUMN_IP]=ui.ip;
}
if(ui.updateMask&UpdateInfo::MASK_COUNTRY){
columns[COLUMN_COUNTRY]=ui.country;
}
}
TransferView::TransferInfo&TransferView::ConnectionInfo::transfer(){
returnparent;
}
doubleTransferView::ConnectionInfo::barPos()const{
returnstatus==STATUS_RUNNING&&size>0&&transferred>=0?
static_cast<double>(transferred)/static_cast<double>(size):-1;
}
voidTransferView::ConnectionInfo::force(){
ConnectionManager::getInstance()->force(user);
}
voidTransferView::ConnectionInfo::disconnect(){
ConnectionManager::getInstance()->disconnect(user,parent.type);
}
voidTransferView::ConnectionInfo::removeFileFromQueue(){
QueueManager::getInstance()->remove(transfer().path);
}
voidTransferView::ConnectionInfo::setPriority(QueueItem::Priorityp){
QueueManager::getInstance()->setPriority(transfer().path,p);
}
TransferView::TransferInfo::TransferInfo(constTTHValue&tth,ConnectionTypetype,conststring&path,conststring&tempPath):
ItemInfo(),
tth(tth),
type(type),
path(path),
tempPath(tempPath)
{
}
boolTransferView::TransferInfo::operator==(constTransferInfo&other)const{
returnother.type==type&&other.path==path;
}
intTransferView::TransferInfo::getImage(intcol)const{
returntype==CONNECTION_TYPE_PM?WinUtil::TRANSFER_ICON_PM:
col==COLUMN_FILE?static_cast<int>(WinUtil::getFileIcon(path)):ItemInfo::getImage(col);
}
voidTransferView::TransferInfo::update(){
timeleft=0;
speed=0;
if(type!=CONNECTION_TYPE_DOWNLOAD){transferred=0;}
if(conns.empty()){
// this should never happen, but let's play safe.
for(auto&col:columns){
col.clear();
}
return;
}
size_trunning=0;
set<string>hubs;
for(auto&conn:conns){
if(type!=CONNECTION_TYPE_DOWNLOAD){
timeleft+=conn.timeleft;
transferred+=conn.transferred;
}
if(conn.status==STATUS_RUNNING){
++running;
speed+=conn.speed;
}
hubs.insert(conn.getUser().hint);
}
if(size==-1){
// this can happen with file lists... get the size of the first connection.
size=conns.front().size;
}
if(type==CONNECTION_TYPE_DOWNLOAD&&size>0&&speed>0){
timeleft=std::max(static_cast<double>(size-transferred),0.0)/speed;
}
autousers=conns.size();
if(users==1){
auto&conn=conns.front();
columns[COLUMN_STATUS]=conn.getText(COLUMN_STATUS);
columns[COLUMN_USER]=conn.getText(COLUMN_USER);
columns[COLUMN_HUB]=conn.getText(COLUMN_HUB);
columns[COLUMN_CIPHER]=conn.getText(COLUMN_CIPHER);
columns[COLUMN_IP]=conn.getText(COLUMN_IP);
columns[COLUMN_COUNTRY]=conn.getText(COLUMN_COUNTRY);
}else{
if(running>0){
tstringuserStr=Text::toT(std::to_string(running)+"/"+std::to_string(users));
columns[COLUMN_STATUS]=type==CONNECTION_TYPE_DOWNLOAD?
str(TF_("Downloading from %1% users")%userStr):
str(TF_("Uploading to %1% users")%userStr);
}else{
columns[COLUMN_STATUS]=T_("Idle");
}
columns[COLUMN_USER]=str(TF_("%1% users")%users);
if(hubs.size()==1){
columns[COLUMN_HUB]=conns.front().getText(COLUMN_HUB);
}else{
columns[COLUMN_HUB]=str(TF_("%1% hubs")%hubs.size());
}
columns[COLUMN_CIPHER].clear();
columns[COLUMN_IP].clear();
columns[COLUMN_COUNTRY].clear();
}
columns[COLUMN_TIMELEFT]=Text::toT(Util::formatSeconds(timeleft));
columns[COLUMN_SPEED]=str(TF_("%1%/s")%Text::toT(Util::formatBytes(speed)));
columns[COLUMN_TRANSFERRED]=Text::toT(Util::formatBytes(transferred));
columns[COLUMN_SIZE]=Text::toT(Util::formatBytes(size));
}
voidTransferView::TransferInfo::updatePath(){
columns[COLUMN_FILE]=Text::toT(Util::getFileName(path));
columns[COLUMN_PATH]=Text::toT(Util::getFilePath(path));
}
TransferView::TransferInfo&TransferView::TransferInfo::transfer(){
return*this;
}
doubleTransferView::TransferInfo::barPos()const{
if(type==CONNECTION_TYPE_DOWNLOAD){
// "transferred" is computed from previous download events so the ratio might exceed 100%...
returnsize>0&&transferred>=0?
std::min(static_cast<double>(transferred)/static_cast<double>(size),1.0):-1;
}else{
returnconns.size()==1?conns.front().barPos():-1;
}
}
voidTransferView::TransferInfo::force(){
for(auto&conn:conns){
conn.force();
}
}
voidTransferView::TransferInfo::disconnect(){
for(auto&conn:conns){
conn.disconnect();
}
}
voidTransferView::TransferInfo::removeFileFromQueue(){
for(auto&conn:conns){
conn.removeFileFromQueue();
}
}
voidTransferView::TransferInfo::setPriority(QueueItem::Priorityp){
for(auto&conn:conns){
conn.setPriority(p);
}
}
TransferView::HttpInfo::HttpInfo(conststring&url):
TransferInfo(TTHValue(),CONNECTION_TYPE_DOWNLOAD,url,Util::emptyString),
status(STATUS_WAITING)
{
columns[COLUMN_PATH]=Text::toT(url);
autoslash=columns[COLUMN_PATH].rfind('/');
columns[COLUMN_FILE]=slash!=tstring::npos?columns[COLUMN_PATH].substr(slash+1):columns[COLUMN_PATH];
}
voidTransferView::HttpInfo::update(constUpdateInfo&ui){
if(ui.updateMask&UpdateInfo::MASK_STATUS){
status=ui.status;
}
if(ui.updateMask&UpdateInfo::MASK_STATUS_STRING){
columns[COLUMN_STATUS]=ui.statusString;
}
if(ui.updateMask&UpdateInfo::MASK_TRANSFERRED){
transferred=ui.transferred;
size=ui.size;
if(transferred>0){
columns[COLUMN_TRANSFERRED]=Text::toT(Util::formatBytes(transferred));
}else{
columns[COLUMN_TRANSFERRED].clear();
}
if(size>0){
columns[COLUMN_SIZE]=Text::toT(Util::formatBytes(size));
}else{
columns[COLUMN_SIZE].clear();
}
}
if(ui.updateMask&UpdateInfo::MASK_SPEED){
speed=ui.speed;
columns[COLUMN_SPEED]=str(TF_("%1%/s")%Text::toT(Util::formatBytes(speed)));
}
if((ui.updateMask&UpdateInfo::MASK_STATUS)||(ui.updateMask&UpdateInfo::MASK_TRANSFERRED)||(ui.updateMask&UpdateInfo::MASK_SPEED)){
if(status==STATUS_RUNNING&&size>0&&speed>0){
timeleft=static_cast<double>(size-transferred)/speed;
columns[COLUMN_TIMELEFT]=Text::toT(Util::formatSeconds(timeleft));
}else{
timeleft=0;
columns[COLUMN_TIMELEFT].clear();
}
}
}
voidTransferView::HttpInfo::disconnect(){
HttpManager::getInstance()->disconnect(path);
}
voidTransferView::handleDestroy(){
SettingsManager::getInstance()->set(SettingsManager::TRANSFERS_ORDER,WinUtil::toString(transfers->getColumnOrder()));
SettingsManager::getInstance()->set(SettingsManager::TRANSFERS_WIDTHS,WinUtil::toString(transfers->getColumnWidths()));
SettingsManager::getInstance()->set(SettingsManager::TRANSFERS_SORT,WinUtil::getTableSort(transfers));
}
boolTransferView::handleContextMenu(dwt::ScreenCoordinatept){
autosel=transfers->getSelection();
if(!sel.empty()){
if(pt.x()==-1&&pt.y()==-1){
pt=transfers->getContextMenuPos();
}
autoselData=(sel.size()==1)?transfers->getSelectedData():nullptr;
autotransfer=dynamic_cast<TransferInfo*>(selData);
automenu=addChild(ShellMenu::Seed(WinUtil::Seeds::menu));
tstringtitle;
dwt::IconPtricon;
if(selData){
title=escapeMenu(selData->getText(transfer?COLUMN_FILE:COLUMN_USER));
if(title.empty()){
title=escapeMenu(selData->getText(COLUMN_USER));
icon=WinUtil::fileImages->getIcon(WinUtil::TRANSFER_ICON_USER);
}else{
icon=WinUtil::fileImages->getIcon(selData->getImage(COLUMN_FILE));
}
}else{
title=str(TF_("%1% items")%sel.size());
}
menu->setTitle(title,icon);
appendUserItems(mdi,menu.get(),false);
set<TransferInfo*>files;
autoonlyHttp=true;
autoonlyDownloads=true;
for(autoi:sel){
auto&transfer=transfers->getData(i)->transfer();
if(!dynamic_cast<HttpInfo*>(&transfer)){
onlyHttp=false;
if(!transfer.getText(COLUMN_FILE).empty()){
files.insert(&transfer);
}
}
if(transfer.type!=CONNECTION_TYPE_DOWNLOAD){
onlyDownloads=false;
}
}
if(files.size()==1){
menu->appendSeparator();
transfer=*files.begin();
WinUtil::addHashItems(menu.get(),transfer->tth,transfer->getText(COLUMN_FILE),transfer->size);
}elseif(!files.empty()){
menu->appendSeparator();
for(autotransfer:files){
autofile=transfer->getText(COLUMN_FILE);
WinUtil::addHashItems(
menu->appendPopup(file,WinUtil::fileImages->getIcon(transfer->getImage(COLUMN_FILE))),
transfer->tth,file,transfer->size);
}
}
StringListpaths;
for(autotransfer:files){
if(File::getSize(transfer->path)!=-1){
paths.push_back(transfer->path);
}elseif(!transfer->tempPath.empty()&&File::getSize(transfer->tempPath)!=-1){
paths.push_back(transfer->tempPath);
}
}
menu->appendShellMenu(paths);
if(!onlyHttp){
menu->appendSeparator();
autosub=menu->appendPopup(T_("Set priority"),dwt::IconPtr(),onlyDownloads);
sub->appendItem(T_("Paused"),[this]{handlePriority(QueueItem::PAUSED);},dwt::IconPtr(),onlyDownloads);
sub->appendItem(T_("Lowest"),[this]{handlePriority(QueueItem::LOWEST);},dwt::IconPtr(),onlyDownloads);
sub->appendItem(T_("Low"),[this]{handlePriority(QueueItem::LOW);},dwt::IconPtr(),onlyDownloads);
sub->appendItem(T_("Normal"),[this]{handlePriority(QueueItem::NORMAL);},dwt::IconPtr(),onlyDownloads);
sub->appendItem(T_("High"),[this]{handlePriority(QueueItem::HIGH);},dwt::IconPtr(),onlyDownloads);
sub->appendItem(T_("Highest"),[this]{handlePriority(QueueItem::HIGHEST);},dwt::IconPtr(),onlyDownloads);
menu->appendSeparator();
menu->appendItem(T_("&Force attempt"),[this]{handleForce();},dwt::IconPtr(),onlyDownloads);
menu->appendSeparator();
menu->appendItem(T_("&Remove file from queue"),[this]{handleRemoveFileFromQueue();},dwt::IconPtr(),onlyDownloads);
menu->appendSeparator();
}
menu->appendItem(T_("&Disconnect"),[this]{handleDisconnect();});
WinUtil::addCopyMenu(menu.get(),transfers);
set<string>hubs;
for(auto&i:selectedUsersImpl()){
hubs.insert(i->getUser().hint);
}
prepareMenu(menu.get(),UserCommand::CONTEXT_HUB,StringList(hubs.begin(),hubs.end()));
menu->open(pt);
returntrue;
}
returnfalse;
}
voidTransferView::handlePriority(QueueItem::Priorityp){
transfers->forEachSelectedT([this,&p](ItemInfo*ii){ii->setPriority(p);});
}
voidTransferView::handleForce(){
transfers->forEachSelected(&ItemInfo::force);
}
voidTransferView::handleDisconnect(){
transfers->forEachSelected(&ItemInfo::disconnect);
}
voidTransferView::handleRemoveFileFromQueue()
{
transfers->forEachSelected(&ItemInfo::removeFileFromQueue);
}
boolTransferView::handleKeyDown(intc){
if(c==VK_DELETE){
handleDisconnect();
returntrue;
}
returnfalse;
}
voidTransferView::handleDblClicked(){
autousers=selectedUsersImpl();
if(users.size()==1){
users[0]->pm(mdi);
}
}
namespace{voiddrawProgress(HDChdc,constdwt::Rectangle&rcItem,intitem,intcolumn,
constdwt::IconPtr&icon,consttstring&text,doublepos,booldownload)
{
// draw something nice...
COLORREFbarBase=download?SETTING(DOWNLOAD_BG_COLOR):SETTING(UPLOAD_BG_COLOR);
COLORREFbgBase=WinUtil::bgColor;
intmod=(HLS_L(RGB2HLS(bgBase))>=128)?-30:30;
// Dark, medium and light shades
COLORREFbarPal[3]{HLS_TRANSFORM(barBase,-40,50),barBase,HLS_TRANSFORM(barBase,40,-30)};
// Two shades of the background color
COLORREFbgPal[2]{HLS_TRANSFORM(bgBase,mod,0),HLS_TRANSFORM(bgBase,mod/2,0)};
dwt::FreeCanvascanvas{hdc};
dwt::Rectanglerc=rcItem;
// draw background
{
dwt::Brushbrush{::CreateSolidBrush(bgPal[1])};
autoselectBg(canvas.select(brush));
dwt::Penpen{bgPal[0]};
autoselectPen(canvas.select(pen));
// TODO Don't draw where the finished part will be drawn
canvas.rectangle(rc.left(),rc.top()-1,rc.right(),rc.bottom());
}
rc.pos.x+=1;
rc.size.x-=2;
rc.size.y-=1;
{
// draw the icon then shift the rect.
constlongiconSize=16;
constlongiconTextSpace=2;
dwt::RectangleiconRect{rc.left(),rc.top()+std::max(rc.height()-iconSize,0L)/2,iconSize,iconSize};
canvas.drawIcon(icon,iconRect);
rc.pos.x+=iconSize+iconTextSpace;
rc.size.x-=iconSize+iconTextSpace;
}
dwt::RectangletextRect;
if(pos>=0){
// the transfer is ongoing; paint a background to represent that.
dwt::Brushbrush{::CreateSolidBrush(barPal[1])};
autoselectBg(canvas.select(brush));
{
dwt::Penpen{barPal[0]};
autoselectPen(canvas.select(pen));
// "Finished" part
rc.size.x*=pos;
canvas.rectangle(rc);
}
textRect=rc;
// draw progressbar highlight
if(rc.width()>2){
dwt::Penpen{barPal[2],dwt::Pen::Solid,1};
autoselectPen(canvas.select(pen));
rc.pos.y+=2;
canvas.moveTo(rc.left()+1,rc.top());
canvas.lineTo(rc.right()-2,rc.top());
}
}else{
textRect=rc;
}
// draw status text
autobkMode(canvas.setBkMode(true));
auto&font=download?WinUtil::downloadFont:WinUtil::uploadFont;
if(!font.get()){
font=WinUtil::font;
}
autoselectFont(canvas.select(*font));
textRect.pos.x+=6;
textRect.size.x-=6;
longleft=textRect.left();
TEXTMETRICtm;
canvas.getTextMetrics(tm);
longtop=textRect.top()+(textRect.bottom()-textRect.top()-tm.tmHeight)/2;
canvas.setTextColor(download?SETTING(DOWNLOAD_TEXT_COLOR):SETTING(UPLOAD_TEXT_COLOR));
/// @todo ExtTextOut to dwt
::RECTr=textRect;
::ExtTextOut(hdc,left,top,ETO_CLIPPED,&r,text.c_str(),text.size(),NULL);
r.left=r.right;
r.right=rcItem.right();
canvas.setTextColor(WinUtil::textColor);
::ExtTextOut(hdc,left,top,ETO_CLIPPED,&r,text.c_str(),text.size(),NULL);
}}
LRESULTTransferView::handleCustomDraw(NMLVCUSTOMDRAW&data){
switch(data.nmcd.dwDrawStage){
caseCDDS_PREPAINT:
returnCDRF_NOTIFYITEMDRAW;
caseCDDS_ITEMPREPAINT:
returnCDRF_NOTIFYSUBITEMDRAW;
caseCDDS_SUBITEM|CDDS_ITEMPREPAINT:
{
// Let's draw a box if needed...
autocol=data.iSubItem;
if(col==COLUMN_STATUS){
auto&info=*reinterpret_cast<ItemInfo*>(data.nmcd.lItemlParam);
autotype=info.transfer().type;
if(type==CONNECTION_TYPE_DOWNLOAD||type==CONNECTION_TYPE_UPLOAD){
autoitem=static_cast<int>(data.nmcd.dwItemSpec);
drawProgress(data.nmcd.hdc,transfers->getRect(item,col,LVIR_BOUNDS),item,col,
type==CONNECTION_TYPE_DOWNLOAD?downloadIcon:uploadIcon,info.getText(col),
info.barPos(),type==CONNECTION_TYPE_DOWNLOAD);
returnCDRF_SKIPDEFAULT;
}
}
}
// Fall through
default:
returnCDRF_DODEFAULT;
}
}
boolTransferView::handleTimer(){
if(updateList){
updateList=false;
callAsync([this]{execTasks();});
}
returntrue;
}
voidTransferView::runUserCommand(constUserCommand&uc){
if(!WinUtil::getUCParams(this,uc,ucLineParams))
return;
set<CID>users;
for(auto&i:selectedUsersImpl()){
if(!i->getUser().user->isOnline()){continue;}
if(uc.once()){
if(users.find(i->getUser().user->getCID())!=users.end())
continue;
users.insert(i->getUser().user->getCID());
}
autotmp=ucLineParams;
ClientManager::getInstance()->userCommand(i->getUser(),uc,tmp,true);
}
}
voidTransferView::layout(){
transfers->resize(dwt::Rectangle(getClientSize()));
}
voidTransferView::addConn(constUpdateInfo&ui){
TransferInfo*transfer=nullptr;
autoconn=findConn(ui.user,ui.type);
if(ui.updateMask&UpdateInfo::MASK_PATH){
// adding a connection we know the transfer of.
dcassert(!ui.path.empty());// transfers are indexed by path; it can't be empty.
transfer=findTransfer(ui.path,ui.type);
if(conn&&&conn->parent!=transfer){
removeConn(*conn);
conn=nullptr;
}
if(!transfer){
transferItems.emplace_back(ui.tth,ui.type,ui.path,ui.tempPath);
transfer=&transferItems.back();
transfers->insert(transfer);
if(ui.type==CONNECTION_TYPE_DOWNLOAD){
QueueManager::getInstance()->getSizeInfo(transfer->size,transfer->transferred,ui.path);
}elseif(ui.type==CONNECTION_TYPE_UPLOAD){
transfer->size=File::getSize(ui.path);
}
}
}else{
// this connection has just been created; we don't know what file it is for yet.
if(conn){
removeConn(*conn);
conn=nullptr;
}
transferItems.emplace_back(TTHValue(),ui.type,ui.user.user->getCID().toBase32(),Util::emptyString);
transfer=&transferItems.back();
transfers->insert(transfer);
}
if(!conn){
transfer->conns.emplace_back(ui.user,*transfer);
conn=&transfer->conns.back();
// only show the child connection item when there are multiple children.
autoconnCount=transfer->conns.size();
if(connCount>1){
if(connCount==2){
// add the previous child.
transfers->insertChild(reinterpret_cast<LPARAM>(transfer),reinterpret_cast<LPARAM>(&transfer->conns.front()));
}
transfers->insertChild(reinterpret_cast<LPARAM>(transfer),reinterpret_cast<LPARAM>(conn));
}
}
conn->update(ui);
transfer->update();
}
voidTransferView::updateConn(constUpdateInfo&ui){
autoconn=findConn(ui.user,ui.type);
if(conn){
conn->update(ui);
conn->parent.update();
}
}
voidTransferView::removeConn(constUpdateInfo&ui){
autoconn=findConn(ui.user,ui.type);
if(conn){
removeConn(*conn);
}
}
TransferView::ConnectionInfo*TransferView::findConn(constHintedUser&user,ConnectionTypetype){
if(!user){returnnullptr;}
for(auto&transfer:transferItems){
if(transfer.type==type){
for(auto&conn:transfer.conns){
if(conn.getUser()==user){
return&conn;
}
}
}
}
returnnullptr;
}
TransferView::TransferInfo*TransferView::findTransfer(conststring&path,ConnectionTypetype){
for(auto&transfer:transferItems){
if(transfer.type==type&&transfer.path==path){
return&transfer;
}
}
returnnullptr;
}
voidTransferView::removeConn(ConnectionInfo&conn){
auto&transfer=conn.parent;
transfers->eraseChild(reinterpret_cast<LPARAM>(&conn));
transfer.conns.remove(conn);
if(transfer.conns.empty()){
removeTransfer(transfer);
}else{
if(transfer.conns.size()==1){
// ungroup
transfers->eraseChild(reinterpret_cast<LPARAM>(&transfer.conns.front()));
}
transfer.update();
}
}
voidTransferView::removeTransfer(TransferInfo&transfer){
transfers->erase(&transfer);
transferItems.remove(transfer);
}
voidTransferView::addHttpConn(constUpdateInfo&ui){
autoitem=findHttpItem(ui.path);
if(item){
removeHttpItem(*item);
}
httpItems.emplace_back(ui.path);
item=&httpItems.back();
item->update(ui);
transfers->insert(item);
}
voidTransferView::updateHttpConn(constUpdateInfo&ui){
autoitem=findHttpItem(ui.path);
if(item){
item->update(ui);
}
}
voidTransferView::removeHttpConn(constUpdateInfo&ui){
autoitem=findHttpItem(ui.path);
if(item){
removeHttpItem(*item);
}
}
TransferView::HttpInfo*TransferView::findHttpItem(conststring&url){
for(auto&item:httpItems){
if(item.path==url){
return&item;
}
}
returnnullptr;
}
voidTransferView::removeHttpItem(HttpInfo&item){
transfers->erase(&item);
httpItems.remove(item);
}
TransferView::UserInfoListTransferView::selectedUsersImpl()const{
// AspectUserInfo::usersFromTable won't do because not every item represents a user.
UserInfoListusers;
transfers->forEachSelectedT([&users](ItemInfo*ii){
autoconn=dynamic_cast<ConnectionInfo*>(ii);
if(conn){
users.push_back(conn);
}else{
autotransfer=dynamic_cast<TransferInfo*>(ii);
if(transfer&&transfer->conns.size()==1){
users.push_back(&transfer->conns.front());
}
}
});
returnusers;
}
voidTransferView::execTasks(){
updateList=false;
HoldRedrawhold{transfers};
for(auto&task:tasks){
task.first(*task.second);
}
tasks.clear();
transfers->resort();
}
voidTransferView::on(ConnectionManagerListener::Added,ConnectionQueueItem*aCqi)noexcept{
autoui=newUpdateInfo(aCqi->getUser(),aCqi->getType());
ui->setStatus(STATUS_WAITING);
ui->setStatusString(aCqi->getType()==CONNECTION_TYPE_PM?T_("Direct encrypted private message channel"):
T_("Connecting"));
addedConn(ui);
}
voidTransferView::on(ConnectionManagerListener::Removed,ConnectionQueueItem*aCqi)noexcept{
removedConn(newUpdateInfo(aCqi->getUser(),aCqi->getType()));
}
voidTransferView::on(ConnectionManagerListener::Failed,ConnectionQueueItem*aCqi,conststring&aReason)noexcept{
autoui=newUpdateInfo(aCqi->getUser(),aCqi->getType());
ui->setStatusString(aCqi->getUser().user->isSet(User::OLD_CLIENT)?
T_("Remote client does not fully support TTH - cannot download"):
Text::toT(aReason));
updatedConn(ui);
}
voidTransferView::on(ConnectionManagerListener::StatusChanged,ConnectionQueueItem*aCqi)noexcept{
autoui=newUpdateInfo(aCqi->getUser(),aCqi->getType());
ui->setStatusString((aCqi->getState()==ConnectionQueueItem::CONNECTING)?T_("Connecting"):T_("Waiting to retry"));
updatedConn(ui);
}
namespace{tstringgetFile(Transfer*t){
if(t->getType()==Transfer::TYPE_TREE){
returnT_("TTH");
}
if(t->getType()==Transfer::TYPE_FULL_LIST||t->getType()==Transfer::TYPE_PARTIAL_LIST){
returnT_("file list");
}
returnText::toT(str(F_("(%1%)")%(Util::formatBytes(t->getStartPos())+" - "+
Util::formatBytes(t->getStartPos()+t->getSize()))));
}}
voidTransferView::on(DownloadManagerListener::Complete,Download*d)noexcept{
onTransferComplete(d,true);
}
voidTransferView::on(DownloadManagerListener::Failed,Download*d,conststring&aReason)noexcept{
onFailed(d,aReason);
}
voidTransferView::on(DownloadManagerListener::Starting,Download*d)noexcept{
autoui=newUpdateInfo(d->getHintedUser(),CONNECTION_TYPE_DOWNLOAD);
tstringstatusString;
if(d->getUserConnection().isSecure()){
if(d->getUserConnection().isTrusted()){
statusString+=_T("[S]");
}else{
statusString+=_T("[U]");
}
}
if(d->isSet(Download::FLAG_TTH_CHECK)){
statusString+=_T("[T]");
}
if(d->isSet(Download::FLAG_ZDOWNLOAD)){
statusString+=_T("[Z]");
}
if(!statusString.empty()){
statusString+=_T(" ");
}
statusString+=str(TF_("Downloading %1%")%getFile(d));
ui->setStatusString(move(statusString));
updatedConn(ui);
}
voidTransferView::on(DownloadManagerListener::Tick,constDownloadList&dl)noexcept{
for(autoi:dl){
onTransferTick(i,true);
}
}
voidTransferView::on(DownloadManagerListener::Requesting,Download*d)noexcept{
autoui=newUpdateInfo(d->getHintedUser(),CONNECTION_TYPE_DOWNLOAD);
starting(ui,d);
ui->setTempPath(d->getTempTarget());
ui->setStatusString(str(TF_("Requesting %1%")%getFile(d)));
addedConn(ui);
}
voidTransferView::on(UploadManagerListener::Complete,Upload*u)noexcept{
onTransferComplete(u,false);
}
voidTransferView::on(UploadManagerListener::Starting,Upload*u)noexcept{
autoui=newUpdateInfo(u->getHintedUser(),CONNECTION_TYPE_UPLOAD);
starting(ui,u);
tstringstatusString;
if(u->getUserConnection().isSecure()){
if(u->getUserConnection().isTrusted()){
statusString+=_T("[S]");
}else{
statusString+=_T("[U]");
}
}
if(u->isSet(Upload::FLAG_ZUPLOAD)){
statusString+=_T("[Z]");
}
if(!statusString.empty()){
statusString+=_T(" ");
}
statusString+=str(TF_("Uploading %1%")%getFile(u));
ui->setStatusString(move(statusString));
addedConn(ui);
}
voidTransferView::on(UploadManagerListener::Tick,constUploadList&ul)noexcept{
for(autoi:ul){
onTransferTick(i,false);
}
}
voidTransferView::on(QueueManagerListener::CRCFailed,Download*d,conststring&aReason)noexcept{
onFailed(d,aReason);
}
voidTransferView::on(HttpManagerListener::Added,HttpConnection*c)noexcept{
autoui=makeHttpUI(c);
ui->setStatus(STATUS_RUNNING);
tstringstatusString=T_("Downloading");
ui->setStatusString(move(statusString));
ui->setTransferred(c->getDone(),c->getDone(),c->getSize());
addedConn(ui);
}
voidTransferView::on(HttpManagerListener::Updated,HttpConnection*c,constuint8_t*data,size_tlen)noexcept{
autoui=makeHttpUI(c);
ui->setTransferred(c->getDone(),c->getDone(),c->getSize());
ui->setSpeed(c->getSpeed());
updatedConn(ui);
}
voidTransferView::on(HttpManagerListener::Failed,HttpConnection*c,conststring&str)noexcept{
autoui=makeHttpUI(c);
ui->setStatus(STATUS_WAITING);
ui->setStatusString(Text::toT(str));
ui->setTransferred(c->getDone(),c->getDone(),c->getSize());
updatedConn(ui);
}
voidTransferView::on(HttpManagerListener::Complete,HttpConnection*c,OutputStream*)noexcept{
autoui=makeHttpUI(c);
ui->setStatus(STATUS_WAITING);
tstringstatusString=T_("Download finished");
ui->setStatusString(move(statusString));
ui->setTransferred(c->getDone(),c->getDone(),c->getSize());
updatedConn(ui);
}
voidTransferView::on(HttpManagerListener::Removed,HttpConnection*c)noexcept{
removedConn(makeHttpUI(c));
}
voidTransferView::addedConn(UpdateInfo*ui){
callAsync([this,ui]{
tasks.emplace_back([=](constUpdateInfo&ui){ui.isHttp()?addHttpConn(ui):addConn(ui);},unique_ptr<UpdateInfo>(ui));
updateList=true;
});
}
voidTransferView::updatedConn(UpdateInfo*ui){
callAsync([this,ui]{
tasks.emplace_back([=](constUpdateInfo&ui){ui.isHttp()?updateHttpConn(ui):updateConn(ui);},unique_ptr<UpdateInfo>(ui));
updateList=true;
});
}
voidTransferView::removedConn(UpdateInfo*ui){
callAsync([this,ui]{
tasks.emplace_back([=](constUpdateInfo&ui){ui.isHttp()?removeHttpConn(ui):removeConn(ui);},unique_ptr<UpdateInfo>(ui));
updateList=true;
});
}
voidTransferView::starting(UpdateInfo*ui,Transfer*t){
ui->setTTH(t->getTTH());
ui->setFile(t->getPath());
ui->setStatus(STATUS_RUNNING);
ui->setTransferred(t->getPos(),t->getActual(),t->getSize());
constUserConnection&uc=t->getUserConnection();
ui->setCipher(Text::toT(uc.getCipherName()));
ui->setIP(Text::toT(uc.getRemoteIp()));
ui->setCountry(Text::toT(GeoManager::getInstance()->getCountry(uc.getRemoteIp())));
}
voidTransferView::onTransferTick(Transfer*t,booldownload){
autoui=newUpdateInfo(t->getHintedUser(),download?CONNECTION_TYPE_DOWNLOAD:CONNECTION_TYPE_UPLOAD);
ui->setTransferred(t->getPos(),t->getActual(),t->getSize());
ui->setSpeed(t->getAverageSpeed());
updatedConn(ui);
}
voidTransferView::onTransferComplete(Transfer*t,booldownload){
autoui=newUpdateInfo(t->getHintedUser(),download?CONNECTION_TYPE_DOWNLOAD:CONNECTION_TYPE_UPLOAD);
ui->setFile(t->getPath());
ui->setStatus(STATUS_WAITING);
ui->setStatusString(T_("Idle"));
ui->setTransferred(t->getPos(),t->getActual(),t->getSize());
updatedConn(ui);
}
voidTransferView::onFailed(Download*d,conststring&aReason){
autoui=newUpdateInfo(d->getHintedUser(),CONNECTION_TYPE_DOWNLOAD,true);
ui->setFile(d->getPath());
ui->setStatus(STATUS_WAITING);
ui->setStatusString(Text::toT(aReason));
updatedConn(ui);
}
TransferView::UpdateInfo*TransferView::makeHttpUI(HttpConnection*c){
autoui=newUpdateInfo(CONNECTION_TYPE_DOWNLOAD);
ui->setHttp();
ui->setFile(c->getUrl());
returnui;
}