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