1 //////////////////////////////////////////////////////////////////////
2 //
3 // BeeBEEP Copyright (C) 2010-2021 Marco Mastroddi
4 //
5 // BeeBEEP is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published
7 // by the Free Software Foundation, either version 3 of the License,
8 // or (at your option) any later version.
9 //
10 // BeeBEEP is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with BeeBEEP. If not, see <http://www.gnu.org/licenses/>.
17 //
18 // Author: Marco Mastroddi <marco.mastroddi(AT)gmail.com>
19 //
20 // $Id: GuiShareNetwork.cpp 1455 2020-12-23 10:17:53Z mastroddi $
21 //
22 //////////////////////////////////////////////////////////////////////
23
24 #include "BeeUtils.h"
25 #include "GuiIconProvider.h"
26 #include "GuiFileInfoList.h"
27 #include "GuiShareNetwork.h"
28 #include "FileShare.h"
29 #include "IconManager.h"
30 #include "Settings.h"
31 #include "UserManager.h"
32
33
GuiShareNetwork(QWidget * parent)34 GuiShareNetwork::GuiShareNetwork( QWidget *parent )
35 : QWidget( parent ), m_fileInfoList()
36 {
37 setupUi( this );
38 setObjectName( "GuiShareNetwork" );
39
40 QString window_title = QString( "<b>%1</b>" ).arg( tr( "Files and folders shared in your network" ) );
41 mp_lTitle->setText( window_title );
42
43 mp_twShares->setToolTip( tr( "Right click to open menu" ) );
44
45 m_fileInfoList.initTree( mp_twShares, false );
46
47 mp_menuContext = new QMenu( this );
48
49 connect( mp_twShares, SIGNAL( itemDoubleClicked( QTreeWidgetItem*, int ) ), this, SLOT( checkItemDoubleClicked( QTreeWidgetItem*, int ) ) );
50 connect( mp_twShares, SIGNAL( customContextMenuRequested( const QPoint& ) ), this, SLOT( openDownloadMenu( const QPoint& ) ) );
51 }
52
setupToolBar(QToolBar * bar)53 void GuiShareNetwork::setupToolBar( QToolBar* bar )
54 {
55 QLabel *label;
56
57 /* scan button */
58 mp_actScan = bar->addAction( IconManager::instance().icon( "network-scan.png" ), tr( "Scan network" ), this, SLOT( scanNetwork() ) );
59 mp_actScan->setStatusTip( tr( "Search shared files in your network" ) );
60
61 /* Reload button */
62 mp_actReload = bar->addAction( IconManager::instance().icon( "update.png" ), tr( "Reload list" ), this, SLOT( reloadList() ) );
63 mp_actReload->setStatusTip( tr( "Clear and reload list" ) );
64 mp_actReload->setEnabled( false );
65
66 /* Download button */
67 mp_actDownload = bar->addAction( IconManager::instance().icon( "download.png" ), tr( "Download" ), this, SLOT( downloadSelected() ) );
68 mp_actDownload->setStatusTip( tr( "Download single or multiple files simultaneously" ) );
69 mp_actDownload->setEnabled( false );
70
71 /* filter by keywords */
72 label = new QLabel( bar );
73 label->setObjectName( "GuiLabelFilterText" );
74 label->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
75 label->setText( QString( " " ) + tr( "Filter" ) + QString( " " ) );
76 bar->addWidget( label );
77 mp_leFilter = new QLineEdit( bar );
78 mp_leFilter->setObjectName( "GuiLineEditFilter" );
79 mp_leFilter->setMaximumWidth( 140 );
80 #if QT_VERSION >= 0x040700
81 mp_leFilter->setPlaceholderText( tr( "Search" ) );
82 #endif
83 bar->addWidget( mp_leFilter );
84 connect( mp_leFilter, SIGNAL( textChanged( const QString& ) ), this, SLOT( filterByText( const QString& ) ) );
85
86 /* filter by file type */
87 label = new QLabel( bar );
88 label->setObjectName( "GuiLabelFilterFileType" );
89 label->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
90 label->setText( QString( " " ) + tr( "File Type" ) + QString( " " ) );
91 bar->addWidget( label );
92 mp_comboFileType = new QComboBox( bar );
93 mp_comboFileType->setObjectName( "GuiComboBoxFilterFileType" );
94 for( int i = Bee::FileAudio; i < Bee::NumFileType; i++ )
95 mp_comboFileType->insertItem( i, GuiIconProvider::instance().iconFromFileType( i ), Bee::fileTypeToString( static_cast<Bee::FileType>(i) ), i );
96 mp_comboFileType->insertItem( Bee::NumFileType, IconManager::instance().icon( "star.png" ), tr( "All Files" ), Bee::NumFileType );
97 mp_comboFileType->setCurrentIndex( Bee::NumFileType );
98 bar->addWidget( mp_comboFileType );
99 connect( mp_comboFileType, SIGNAL( currentIndexChanged( int ) ), this, SLOT( applyFilter() ), Qt::QueuedConnection );
100
101 /* filter by user */
102 label = new QLabel( bar );
103 label->setObjectName( "GuiLabelFilterUser" );
104 label->setAlignment( Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter );
105 label->setText( QString( " " ) + tr( "User" ) + QString( " " ) );
106 label->setMinimumWidth( 40 );
107 bar->addWidget( label );
108 mp_comboUsers = new QComboBox( bar );
109 mp_comboUsers->setObjectName( "GuiComboBoxFilterUser" );
110 mp_comboUsers->setMinimumSize( QSize( 100, 0 ) );
111 resetComboUsers();
112 bar->addWidget( mp_comboUsers );
113 connect( mp_comboUsers, SIGNAL( currentIndexChanged( int ) ), this, SLOT( applyFilter() ), Qt::QueuedConnection );
114
115 }
116
resetComboUsers()117 void GuiShareNetwork::resetComboUsers()
118 {
119 mp_comboUsers->blockSignals( true );
120 if( mp_comboUsers->count() > 0 )
121 mp_comboUsers->clear();
122
123 mp_comboUsers->insertItem( 0, tr( "All users" ), 0 );
124 mp_comboUsers->setCurrentIndex( 0 );
125 mp_comboUsers->setEnabled( false );
126 mp_comboUsers->blockSignals( false );
127 }
128
initShares()129 void GuiShareNetwork::initShares()
130 {
131 if( FileShare::instance().network().isEmpty() )
132 QTimer::singleShot( 200, this, SLOT( scanNetwork() ) );
133 mp_leFilter->setFocus();
134 }
135
enableScanButton()136 void GuiShareNetwork::enableScanButton()
137 {
138 if( !mp_actScan->isEnabled() )
139 mp_actScan->setEnabled( true );
140 }
141
scanNetwork()142 void GuiShareNetwork::scanNetwork()
143 {
144 resetComboUsers();
145 m_queue.clear();
146 m_fileInfoList.clearTree();
147 mp_actScan->setEnabled( false );
148 mp_actReload->setEnabled( true );
149 showStatus( tr( "%1 is searching shared files in your network" ).arg( Settings::instance().programName() ) );
150 emit fileShareListRequested();
151 QTimer::singleShot( 30000, this, SLOT( enableScanButton() ) );
152 }
153
applyFilter()154 void GuiShareNetwork::applyFilter()
155 {
156 if( mp_actReload->isEnabled() )
157 reloadList();
158 else
159 updateList();
160 }
161
reloadList()162 void GuiShareNetwork::reloadList()
163 {
164 mp_actReload->setEnabled( false );
165 updateList();
166 }
167
loadShares(const User & u)168 void GuiShareNetwork::loadShares( const User& u )
169 {
170 setCursor( Qt::WaitCursor );
171 QApplication::processEvents();
172
173 m_fileInfoList.setUpdatesEnabled( false );
174 int file_shared = 0;
175 FileSizeType share_size = 0;
176 QTime timer;
177 timer.start();
178
179 GuiFileInfoItem *item = m_fileInfoList.userItem( u.id() );
180 if( item )
181 item->removeChildren();
182
183 if( u.isStatusConnected() )
184 {
185 foreach( FileInfo fi, FileShare::instance().network().values( u.id() ) )
186 {
187 if( fi.isValid() )
188 {
189 if( filterPassThrough( u.id(), fi ) )
190 m_queue.enqueue( UserFileInfo( u, fi ) );
191
192 file_shared++;
193 share_size += fi.size();
194
195 if( timer.elapsed() > 10000 )
196 {
197 qWarning() << "File share operation is too long, time out!";
198 break;
199 }
200 }
201 }
202 }
203
204 m_fileInfoList.setUpdatesEnabled( true );
205 mp_actDownload->setEnabled( !m_fileInfoList.isEmpty() );
206
207 mp_comboUsers->blockSignals( true );
208 if( file_shared > 0 )
209 {
210 if( mp_comboUsers->findData( u.id() ) == -1 )
211 mp_comboUsers->addItem( Bee::userNameToShow( u, false ), u.id() );
212 }
213 else
214 {
215 int user_id_index_to_remove = mp_comboUsers->findData( u.id() );
216 if( user_id_index_to_remove > 0 )
217 {
218 if( user_id_index_to_remove == mp_comboUsers->currentIndex() )
219 mp_comboUsers->setCurrentIndex( 0 );
220 mp_comboUsers->removeItem( user_id_index_to_remove );
221 }
222 }
223
224 mp_comboUsers->setEnabled( mp_comboUsers->count() > 1 );
225 mp_comboUsers->blockSignals( false );
226
227 QString status_msg = tr( "%1 has shared %2 files (%3)" ).arg( u.name() ).arg( file_shared ).arg( Bee::bytesToString( share_size ) );
228 #ifdef BEEBEEP_DEBUG
229 qDebug() << qPrintable( status_msg );
230 #endif
231 showStatus( status_msg );
232 setCursor( Qt::ArrowCursor );
233 QTimer::singleShot( 1000, this, SLOT( processNextItemInQueue() ) );
234 }
235
processNextItemInQueue()236 void GuiShareNetwork::processNextItemInQueue()
237 {
238 setCursor( Qt::WaitCursor );
239 m_fileInfoList.setUpdatesEnabled( false );
240
241 for( int i = 0; i < 100; i++ )
242 {
243 if( m_queue.isEmpty() )
244 break;
245 UserFileInfo ufi = m_queue.dequeue();
246 GuiFileInfoItem* item = m_fileInfoList.createFileItem( ufi.first, ufi.second );
247 FileInfo file_info_downloaded = FileShare::instance().downloadedFile( ufi.second.fileHash() );
248 if( file_info_downloaded.isValid() )
249 {
250 showFileTransferCompleted( item, file_info_downloaded.path() );
251 }
252 else
253 {
254 item->setFilePath( "" );
255 item->setToolTip( GuiFileInfoItem::ColumnFile, tr( "Double click to download %1" ).arg( ufi.second.name() ) );
256 }
257 item->setToolTip( GuiFileInfoItem::ColumnFile, QString( "%1\n%2" ).arg( item->toolTip( GuiFileInfoItem::ColumnFile ), mp_twShares->toolTip() ) );
258 }
259
260 m_fileInfoList.setUpdatesEnabled( true );
261 mp_actDownload->setEnabled( !m_fileInfoList.isEmpty() );
262
263 setCursor( Qt::ArrowCursor );
264
265 if( !m_queue.isEmpty() )
266 QTimer::singleShot( 1000, this, SLOT( processNextItemInQueue() ) );
267 }
268
checkItemDoubleClicked(QTreeWidgetItem * item,int)269 void GuiShareNetwork::checkItemDoubleClicked( QTreeWidgetItem* item, int )
270 {
271 if( !item )
272 return;
273
274 GuiFileInfoItem* file_info_item = dynamic_cast<GuiFileInfoItem*>(item);
275
276 if( !file_info_item->isObjectFile() )
277 return;
278
279 if( !file_info_item->filePath().isEmpty() )
280 emit openFileCompleted( QUrl::fromLocalFile( file_info_item->filePath() ) );
281 else
282 emit downloadSharedFile( file_info_item->userId(), file_info_item->fileInfoId() );
283 }
284
updateList()285 void GuiShareNetwork::updateList()
286 {
287 m_queue.clear();
288 m_fileInfoList.clearTree();
289 foreach( User u, UserManager::instance().userList().toList() )
290 loadShares( u );
291
292 if( m_fileInfoList.countFileItems() < 100 )
293 mp_twShares->expandAll();
294 }
295
filterPassThrough(VNumber user_id,const FileInfo & fi)296 bool GuiShareNetwork::filterPassThrough( VNumber user_id, const FileInfo& fi )
297 {
298 QString filter_name = mp_leFilter->text().simplified();
299 if( filter_name == QString( "*" ) || filter_name == QString( "*.*" ) )
300 filter_name = "";
301
302 if( !filter_name.isEmpty() )
303 {
304 QStringList filter_name_list = filter_name.split( QString( " " ), QString::SkipEmptyParts );
305 foreach( QString filter_name_item, filter_name_list )
306 {
307 if( !fi.name().contains( filter_name_item, Qt::CaseInsensitive ) )
308 return false;
309 }
310 }
311
312 VNumber filter_user_id = mp_comboUsers->currentIndex() <= 0 ? 0 : Bee::qVariantToVNumber( mp_comboUsers->itemData( mp_comboUsers->currentIndex() ) );
313 if( filter_user_id > 0 && user_id != filter_user_id )
314 return false;
315
316 if( mp_comboFileType->currentIndex() == static_cast<int>(Bee::NumFileType) )
317 return true;
318 else
319 return mp_comboFileType->currentIndex() == static_cast<int>(Bee::fileTypeFromSuffix( fi.suffix() ));
320 }
321
showMessage(VNumber user_id,VNumber file_info_id,const QString & msg)322 void GuiShareNetwork::showMessage( VNumber user_id, VNumber file_info_id, const QString& msg )
323 {
324 GuiFileInfoItem* item = m_fileInfoList.fileItem( user_id, file_info_id );
325 if( !item )
326 return;
327
328 item->setText( GuiFileInfoItem::ColumnStatus, msg );
329 }
330
showStatus(const QString & status_text)331 void GuiShareNetwork::showStatus( const QString& status_text )
332 {
333 if( status_text.isEmpty() )
334 {
335 int share_size = FileShare::instance().network().size();
336 int file_items = m_fileInfoList.countFileItems();
337
338 QString status_msg;
339 if( share_size != file_items )
340 status_msg = tr( "%1 files are shown in list (%2 are available in your network)" ).arg( file_items ).arg( share_size );
341 else
342 status_msg = tr( "%1 files shared in your network" ).arg( share_size );
343
344 emit updateStatus( status_msg, 0 );
345 }
346 else
347 {
348 emit updateStatus( status_text, 0 );
349 }
350 }
351
showSharesForUser(const User & u)352 void GuiShareNetwork::showSharesForUser( const User& u )
353 {
354 bool user_is_not_in_list = mp_comboUsers->findData( u.id() ) == -1;
355 int num_file_shared = FileShare::instance().network().count( u.id() );
356
357 if( user_is_not_in_list || num_file_shared < 999 )
358 {
359 loadShares( u );
360 if( m_fileInfoList.countFileItems() < 100 )
361 mp_twShares->expandAll();
362 }
363 else
364 mp_actReload->setEnabled( true );
365 }
366
openDownloadMenu(const QPoint & p)367 void GuiShareNetwork::openDownloadMenu( const QPoint& p )
368 {
369 QTreeWidgetItem* item = mp_twShares->itemAt( p );
370 int selected_items;
371 if( item )
372 {
373 if( !item->isSelected() )
374 item->setSelected( true );
375 selected_items = m_fileInfoList.parseSelectedItems();
376 }
377 else
378 selected_items = 0;
379
380 mp_menuContext->clear();
381
382 if( selected_items )
383 {
384 QAction* act = mp_menuContext->addAction( IconManager::instance().icon( "download.png" ), "", this, SLOT( downloadSelected() ) );
385 QString action_text;
386 if( selected_items >= Settings::instance().maxQueuedDownloads() )
387 {
388 action_text += tr( "You cannot download more than %1 files" ).arg( Settings::instance().maxQueuedDownloads() );
389 act->setDisabled( true );
390 }
391 else
392 action_text = selected_items == 1 ? tr( "Download single file" ) : tr( "Download %1 selected files" ).arg( selected_items );
393 act->setText( action_text );
394
395 mp_menuContext->addSeparator();
396 mp_menuContext->addAction( IconManager::instance().icon( "clear.png" ), tr( "Clear selection" ), &m_fileInfoList, SLOT( clearTreeSelection() ) );
397
398 }
399 else
400 {
401 mp_menuContext->addAction( mp_actScan );
402 mp_menuContext->addAction( mp_actReload );
403 }
404
405 mp_menuContext->addSeparator();
406 mp_menuContext->addAction( IconManager::instance().icon( "add.png" ), tr( "Expand all items" ), mp_twShares, SLOT( expandAll() ) );
407 mp_menuContext->addAction( IconManager::instance().icon( "remove.png" ), tr( "Collapse all items" ), mp_twShares, SLOT( collapseAll() ) );
408 mp_menuContext->exec( QCursor::pos() );
409 }
410
downloadSelected()411 void GuiShareNetwork::downloadSelected()
412 {
413 if( m_fileInfoList.selectedFileInfoList().isEmpty() )
414 {
415 int selected_items = m_fileInfoList.parseSelectedItems();
416 if( selected_items <= 0 )
417 {
418 QMessageBox::information( this, Settings::instance().programName(), tr( "Please select one or more files to download." ) );
419 return;
420 }
421 }
422
423 QList<SharedFileInfo> selected_items = m_fileInfoList.selectedFileInfoList();
424 if( selected_items.size() == 1 )
425 emit downloadSharedFile( selected_items.first().first, selected_items.first().second.id() );
426 else
427 emit downloadSharedFiles( selected_items );
428 }
429
filterByText(const QString &)430 void GuiShareNetwork::filterByText( const QString& )
431 {
432 updateList();
433 }
434
updateUser(const User & u)435 void GuiShareNetwork::updateUser( const User& u )
436 {
437 if( u.isLocal() )
438 return;
439
440 int user_index = mp_comboUsers->findData( u.id() );
441 if( user_index > 0 )
442 {
443 if( !u.isStatusConnected() )
444 {
445 if( mp_comboUsers->currentIndex() == user_index )
446 {
447 m_queue.clear();
448 m_fileInfoList.clearTree();
449 }
450
451 mp_comboUsers->removeItem( user_index );
452 }
453 else
454 mp_comboUsers->setItemText( user_index, Bee::userNameToShow( u, false ) );
455
456 GuiFileInfoItem* user_item = m_fileInfoList.userItem( u.id() );
457 if( user_item )
458 user_item->initUser( u.id(), Bee::userNameToShow( u, false ) );
459 }
460 }
461
onFileTransferProgress(VNumber,const User & u,const FileInfo & fi,FileSizeType bytes,qint64 elapsed_time)462 void GuiShareNetwork::onFileTransferProgress( VNumber /* unused_peer_id */, const User& u, const FileInfo& fi, FileSizeType bytes, qint64 elapsed_time )
463 {
464 if( fi.size() < 1 )
465 return;
466 GuiFileInfoItem* item = m_fileInfoList.fileItem( u.id(), fi.id() );
467 if( !item )
468 return;
469 QString file_transfer_progress = QString( "%1 %2 of %3 (%4% - %5)" ).arg( fi.isDownload() ? tr( "Downloading" ) : tr( "Uploading" ),
470 Bee::bytesToString( bytes ), Bee::bytesToString( fi.size() ),
471 QString::number( static_cast<FileSizeType>( (bytes * 100) / fi.size())),
472 Bee::transferTimeLeft( bytes, fi.size(), fi.startingPosition(), elapsed_time ) );
473 item->setText( GuiFileInfoItem::ColumnStatus, file_transfer_progress );
474 }
475
onFileTransferCompleted(VNumber,const User & u,const FileInfo & file_info)476 void GuiShareNetwork::onFileTransferCompleted( VNumber /* unused_peer_id */, const User& u, const FileInfo& file_info )
477 {
478 GuiFileInfoItem* item = m_fileInfoList.fileItem( u.id(), file_info.id() );
479 if( !item )
480 return;
481 showFileTransferCompleted( item, file_info.path() );
482 }
483
showFileTransferCompleted(GuiFileInfoItem * item,const QString & file_path)484 void GuiShareNetwork::showFileTransferCompleted( GuiFileInfoItem* item, const QString& file_path )
485 {
486 item->setFilePath( file_path );
487 item->setToolTip( GuiFileInfoItem::ColumnFile, tr( "Double click to open %1" ).arg( file_path ) );
488 item->setText( GuiFileInfoItem::ColumnStatus, tr( "Transfer completed" ) );
489 for( int i = 0; i < mp_twShares->columnCount(); i++ )
490 item->setBackgroundColor( i, QColor( "#91D606" ) );
491 }
492