Fix update_tree_view() to actually fall back to slow locate-and-add when needed.
[kmk.git] / src / kmk.cpp
bloba4257e97577b55836584383a9b5d3ea7c23ed4d8
1 /***************************************************************************
2 * KMK - KDE Music Cataloger - the tool for personal *
3 * audio collection management *
4 * *
5 * Copyright (C) 2006,2007 by Plamen Petrov *
6 * carpo@abv.bg *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
22 ***************************************************************************/
24 #include <klocale.h>
25 #include <kstdaccel.h>
26 #include <kmenubar.h>
27 #include <kpopupmenu.h>
28 #include <kprogress.h>
29 #include <kmessagebox.h>
30 #include <kapplication.h>
31 #include <kconfig.h>
32 #include <qfiledialog.h>
33 #include <qtabwidget.h>
34 #include <qprocess.h>
35 #include <qdir.h>
37 #include "tag.h"
38 #include "fileref.h"
39 #include "tstring.h"
40 #include "tfile.h"
42 #include "kmk.h"
43 #include "kmksettings_dialog.h"
44 #include "kmk_progress_disp.h"
45 #include "kmktaged.h"
47 #define _KMK_UPDATE_PERIOD 250
48 #define _KMK_NEWCATALOG "NEW.kmk"
49 #define A__PLAY 1
50 #define A__ENQUEUE 0
51 #undef __KMK_DEBUG
53 const QString player_bin = "xmms";
55 kmk::kmk()
56 : KMainWindow( 0, i18n("KDE Music Kataloger") )
58 // allocate settings object
59 s = new KmkGlobalSettings;
60 // be safe here
61 Q_CHECK_PTR( s );
62 if ( !s ) kdDebug() << "no mem for s!" << endl;
63 // if all is fine - then read the settings
64 s->readSettings( kapp->config() );
65 // save the caption - this one will be used when we need to restore it
66 savedCaption = this->caption();
67 init_interface();
69 // set config object to read GUI group
70 kapp->config()->setGroup( "GUI" );
71 // reads the splitter settings from the config object
72 splitter1->setSizes( kapp->config()->readIntListEntry( "splitter config" ) );
73 // then moves the window to its last position
74 move( kapp->config()->readPointEntry( "main win position" ) );
76 kmkSmooth = new QTime();
77 kmkProgress = new kmk_progress_disp();
78 kmkProgressTimer = new QTimer();
80 player = new KmkExtPlayer( this, 0 );
82 KPopupMenu * file = new KPopupMenu( this );
83 // Actions
84 fileNewAction = KStdAction::openNew(this,
85 SLOT(slotFileNew()),actionCollection());
86 fileOpenAction = KStdAction::open(this,
87 SLOT(slotFileOpen()),actionCollection());
88 catalogAddNewFolderAction = new KAction( i18n("Add new folder to catalog"), KShortcut(""), this,
89 SLOT(slotCatalogAddNewFolder()), actionCollection(), "add_new_folder" );
90 fileSaveAction = KStdAction::save(this,
91 SLOT(slotFileSave()),actionCollection());
92 fileSaveAsAction = KStdAction::saveAs(this,
93 SLOT(slotFileSaveAs()),actionCollection());
94 fileCatalogFileStats = new KAction( i18n("Catalog statistics"), KShortcut(""), this,
95 SLOT(slotCatalogFileStats()), actionCollection(), "catalog_stats" );
96 fileCatalogFileClose = KStdAction::close(this,
97 SLOT(slotCatalogFileClose()),actionCollection());
98 fileQuitAction = KStdAction::quit(this,
99 SLOT(slotFileQuit()),actionCollection());
100 listTableToggleTitleAction = new KAction( i18n("Toggle Title"), KShortcut(""), this,
101 SLOT(slotListTableToggleTitle()), actionCollection(), "toggle_title" );
102 TagEditAction = new KAction( i18n("Tag editor"), KShortcut(""), this,
103 SLOT(slotTagEdit()), actionCollection(), "tag_editor" );
104 listTableLocateAction = new KAction( i18n("Locate"), KShortcut(""), this,
105 SLOT(slotLocateRequested()), actionCollection(), "locate" );
106 programSettingsAction = KStdAction::preferences(this,
107 SLOT(slotProgramSettings()),actionCollection());
108 playerPreviousAction = new KAction( i18n("Previous "), KShortcut("ALT+Z"), this,
109 SLOT(slotPlayerPrevious()), actionCollection(), "prev");
110 playerPlayAction = new KAction(i18n("Play "), KShortcut("ALT+X"), this,
111 SLOT(slotPlayerPlay()), actionCollection(), "play");
112 playerPauseAction = new KAction(i18n("Pause "), KShortcut("ALT+C"), this,
113 SLOT(slotPlayerPause()), actionCollection(), "pause");
114 playerStopAction = new KAction(i18n("Stop "), KShortcut("ALT+V"), this,
115 SLOT(slotPlayerStop()), actionCollection(), "stop");
116 playerNextAction = new KAction(i18n("Next "), KShortcut("ALT+B"), this,
117 SLOT(slotPlayerNext()), actionCollection(), "next");
118 playerEnqueueAction = new KAction(i18n("Enqueue selection "), KShortcut(""), this,
119 SLOT(slotPlayerEnqueue()), actionCollection(), "enqueue");
120 playerEnqueueDirAction = new KAction(i18n("Enqueue selected folder ONLY "), KShortcut(""), this,
121 SLOT(slotPlayerEnqueueDir()), actionCollection(), "enqueue_dir");
122 playerEnqueueDirSubdirsAction = new KAction(i18n("Enqueue folder with subfolders "), KShortcut(""), this,
123 SLOT(slotPlayerEnqueueDirSubdirs()), actionCollection(), "enqueue_dir_subdirs");
124 playerPlayDirAction = new KAction(i18n("Play selected folder ONLY "), KShortcut(""), this,
125 SLOT(slotPlayerPlayDir()), actionCollection(), "play_dir");
126 playerPlayDirSubdirsAction = new KAction(i18n("Play folder with subfolders "), KShortcut(""), this,
127 SLOT(slotPlayerPlayDirSubdirs()), actionCollection(), "play_dir_subdirs");
128 playerPlaySelectionAction = new KAction(i18n("Play selection "), KShortcut(""), this,
129 SLOT(slotPlayerPlaySelection()), actionCollection(), "play_sel");
130 catalogUpdateSubtreeAction = new KAction(i18n("Update catalog subtree "), KShortcut(""), this,
131 SLOT(slotCatalogUpdateSubtree()), actionCollection(), "update_subtree");
133 // File menu actions
134 fileNewAction->plug( file );
135 fileOpenAction->plug( file );
136 // catalogAddNewFolderAction->setEnabled( FALSE );
137 catalogAddNewFolderAction->plug( file );
138 fileSaveAction->setEnabled( FALSE );
139 fileSaveAction->plug( file );
140 fileSaveAsAction->setEnabled( FALSE );
141 fileSaveAsAction->plug( file );
142 fileCatalogFileStats->setEnabled( FALSE );
143 fileCatalogFileStats->plug( file );
144 fileCatalogFileClose->setEnabled( FALSE );
145 fileCatalogFileClose->plug( file );
146 file->insertSeparator();
147 fileQuitAction->plug( file );
148 menuBar()->insertItem( i18n("Catalog"), file );
149 // Tools menu actions
150 file = new KPopupMenu( this );
151 TagEditAction->plug( file );
152 menuBar()->insertItem( i18n("Tools"), file );
153 // Player controls menu actions
154 file = new KPopupMenu( this );
155 playerPreviousAction->plug( file );
156 playerPlayAction->plug( file );
157 playerPauseAction->plug( file );
158 playerStopAction->plug( file );
159 playerNextAction->plug( file );
160 menuBar()->insertItem( i18n("Player controls"), file );
161 // Settings menu actions
162 file = new KPopupMenu( this );
163 programSettingsAction->plug( file );
164 // listTableToggleTitleAction->plug( file );
165 menuBar()->insertItem( i18n("Settings"), file );
166 // Help menu actions
167 file = helpMenu(); menuBar()->insertItem( i18n("Help"), file );
169 // Toolbar menu
170 KToolBar * fileToolsToolbar = new KToolBar( this, "File operations" );
171 fileToolsToolbar->setOrientation( Qt::Horizontal );
172 fileToolsToolbar->setIconText( KToolBar::IconOnly );
173 fileToolsToolbar->setLabel( i18n("File operations") );
175 fileNewAction->plug( fileToolsToolbar );
176 fileOpenAction->plug( fileToolsToolbar );
177 fileSaveAction->plug( fileToolsToolbar );
178 fileToolsToolbar->insertSeparator();
179 fileQuitAction->plug( fileToolsToolbar );
181 KToolBar* playerActionsToolbar = new KToolBar( this, "Player controls" );
182 playerActionsToolbar->setOrientation( Qt::Horizontal );
183 playerActionsToolbar->setIconText( KToolBar::TextOnly );
184 playerActionsToolbar->setLabel( i18n("Player controls") );
185 playerPreviousAction->plug( playerActionsToolbar );
186 playerPlayAction->plug( playerActionsToolbar );
187 playerPauseAction->plug( playerActionsToolbar );
188 playerStopAction->plug( playerActionsToolbar );
189 playerNextAction->plug( playerActionsToolbar );
191 moveDockWindow( fileToolsToolbar, Top );
192 moveDockWindow( playerActionsToolbar, Top );
194 // popup menus
195 CTree_PopupMenu = new QPopupMenu( this, "catalog tree popup" );
196 fileNewAction->plug( CTree_PopupMenu );
197 catalogAddNewFolderAction->plug( CTree_PopupMenu );
198 CTree_PopupMenu->insertSeparator(2);
199 playerPlayDirAction->plug( CTree_PopupMenu );
200 playerPlayDirSubdirsAction->plug( CTree_PopupMenu );
201 CTree_PopupMenu->insertSeparator(5);
202 playerEnqueueDirAction->plug( CTree_PopupMenu );
203 playerEnqueueDirSubdirsAction->plug( CTree_PopupMenu );
204 CTree_PopupMenu->insertSeparator(8);
205 catalogUpdateSubtreeAction->plug( CTree_PopupMenu );
207 CList_PopupMenu = new QPopupMenu( this, "files list popup" );
208 playerPlaySelectionAction->plug( CList_PopupMenu );
209 playerPlaySelectionAction->setEnabled( FALSE );
210 playerEnqueueAction->plug( CList_PopupMenu );
211 playerEnqueueAction->setEnabled( FALSE );
212 CList_PopupMenu->insertSeparator(2);
213 TagEditAction->plug( CList_PopupMenu );
214 TagEditAction->setEnabled( FALSE );
216 CSearchList_PopupMenu = new QPopupMenu( this, "search list popup" );
217 playerPlaySelectionAction->plug( CSearchList_PopupMenu );
218 playerEnqueueAction->plug( CSearchList_PopupMenu );
219 CSearchList_PopupMenu->insertSeparator(2);
220 TagEditAction->plug( CSearchList_PopupMenu );
221 listTableLocateAction->plug( CSearchList_PopupMenu );
223 if( s->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: connecting signals to slots..." << endl;
225 connect( treeListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
226 this, SLOT( slotTreeListViewPopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
227 connect( filesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
228 this, SLOT( slotFileListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
229 connect( searchFilesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
230 this, SLOT( slotSearchListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
231 connect( treeListView, SIGNAL( currentChanged( QListViewItem* ) ),
232 this, SLOT( slotTreeListViewCurrentChanged( QListViewItem* ) ) );
233 connect( leSearchFor, SIGNAL( returnPressed() ),
234 this, SLOT( slotSearchButtonClicked() ) );
235 connect( pbSearch, SIGNAL( clicked() ),
236 this, SLOT( slotSearchButtonClicked() ) );
237 connect( pbClear, SIGNAL( clicked() ),
238 this, SLOT( slotClearButtonClicked() ) );
239 connect( pbDone, SIGNAL( clicked() ),
240 this, SLOT( slotDoneButtonClicked() ) );
242 if( s->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: done!" << endl;
244 FileSaveCalledFromFileSaveAs = FALSE;
245 tree_needs_update = FALSE;
247 QDir tmp_dir; tmp_dir.mkdir( "/tmp/kmk", TRUE );
249 setAutoSaveSettings();
250 applyMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
252 clearCatalogData();
253 if ( s->loadLast() )
255 if ( !s->lastCatalogUsed().isEmpty() )
257 show();
258 loadCatalog( s->lastCatalogUsed() );
259 update_tree_view();
264 void kmk::init_interface()
266 main_container_widget = new QWidget( this, "main_container_widget" );
268 setCentralWidget( main_container_widget );
270 kmkwidgetbaseLayout = new QVBoxLayout( main_container_widget, 11, 6, "kmkwidgetbaseLayout");
272 tabWidget1 = new QTabWidget( main_container_widget, "level1_tabwidget" );
274 tab = new QWidget( tabWidget1, "level1_tab1" );
275 tabLayout = new QVBoxLayout( tab, 11, 6, "tab1_Layout");
277 splitter1 = new QSplitter( tab, "splitter1" );
278 splitter1->setMinimumSize( QSize( 182, 60 ) );
279 splitter1->setOrientation( QSplitter::Horizontal );
281 treeListView = new kmk_tree_KListView( splitter1, "treeListView" );
282 treeListView->addColumn( i18n( "Catalog tree " ) );
283 treeListView->setResizePolicy( KListView::AutoOneFit );
284 treeListView->setShowSortIndicator( TRUE );
285 treeListView->setRootIsDecorated( TRUE );
286 treeListView->setItemsMovable( TRUE );
287 treeListView->setDragEnabled( TRUE );
288 treeListView->setDropVisualizer( TRUE );
290 filesListView = new kmk_files_KListView( splitter1, "filesListView" );
291 filesListView->addColumn( i18n( "Folder" ) );
292 filesListView->addColumn( i18n( "Filename" ) );
293 filesListView->addColumn( i18n( "Artist" ) );
294 filesListView->addColumn( i18n( "Title" ) );
295 filesListView->addColumn( i18n( "Album" ) );
296 filesListView->addColumn( i18n( "Genre" ) );
297 filesListView->addColumn( i18n( "Comment" ) );
298 filesListView->addColumn( i18n( "Year" ) );
299 filesListView->addColumn( i18n( "Track #" ) );
300 filesListView->addColumn( i18n( "Duration" ) );
301 filesListView->addColumn( i18n( "Size" ) );
302 filesListView->addColumn( i18n( "Bitrate" ) );
303 filesListView->addColumn( i18n( "Sampling rate" ) );
304 filesListView->addColumn( i18n( "Channels" ) );
305 filesListView->addColumn( i18n( "Type" ) );
306 filesListView->addColumn( i18n( "READ ONLY" ) );
307 filesListView->setResizePolicy( KListView::AutoOneFit );
308 filesListView->restoreLayout( kapp->config(), "listView" );
309 filesListView->setAllColumnsShowFocus( TRUE );
310 filesListView->setShowSortIndicator( TRUE );
311 filesListView->setRootIsDecorated( FALSE );
312 filesListView->setItemsMovable( FALSE );
313 filesListView->setSelectionMode( QListView::Extended );
314 filesListView->setDragEnabled( TRUE );
315 filesListView->setDropVisualizer( TRUE );
316 tabLayout->addWidget( splitter1 );
317 tabWidget1->insertTab( tab, i18n("Catalog") );
319 tab_2 = new QWidget( tabWidget1, "level1_tab2" );
320 tabLayout_2 = new QVBoxLayout( tab_2, 11, 6, "tab2_Layout");
322 layout5 = new QVBoxLayout( 0, 0, 6, "layout5");
324 tabWidget2 = new QTabWidget( tab_2, "level2_tabwidget" );
325 tabWidget2->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0,
326 tabWidget2->sizePolicy().hasHeightForWidth() ) );
327 tabWidget2->setMinimumSize( QSize( 480, 158 ) );
329 tab_3 = new QWidget( tabWidget2, "level2_tab1" );
331 pbSearch = new QPushButton( tab_3, "pbSearch" );
332 pbSearch->setGeometry( QRect( 370, 10, 100, 26 ) );
333 pbSearch->setText( i18n( "Go!" ) );
334 pbSearch->setDefault( TRUE );
336 pbClear = new QPushButton( tab_3, "pbClear" );
337 pbClear->setGeometry( QRect( 370, 50, 100, 26 ) );
338 pbClear->setText( i18n( "Clear" ) );
340 pbDone = new QPushButton( tab_3, "pbDone" );
341 pbDone->setGeometry( QRect( 370, 90, 100, 26 ) );
342 pbDone->setText( i18n( "Done" ) );
344 frame1 = new QFrame( tab_3, "frame1" );
345 frame1->setGeometry( QRect( 10, 10, 350, 110 ) );
346 frame1->setFrameShape( QFrame::StyledPanel );
347 frame1->setFrameShadow( QFrame::Raised );
349 textLabel1 = new QLabel( frame1, "textLabel1" );
350 textLabel1->setGeometry( QRect( 10, 10, 100, 16 ) );
351 textLabel1->setText( i18n( "Look for" ) );
353 leSearchFor = new KLineEdit( frame1, "leSearchFor" );
354 leSearchFor->setGeometry( QRect( 10, 30, 330, 24 ) );
355 tabWidget2->insertTab( tab_3, i18n("Base") );
357 tab_4 = new QWidget( tabWidget2, "level2_tab2" );
358 tabWidget2->insertTab( tab_4, i18n("Advanced") );
359 layout5->addWidget( tabWidget2 );
361 searchFilesListView = new kmk_files_KListView( tab_2, "searchFilesListView" );
362 searchFilesListView->addColumn( i18n( "Folder" ) );
363 searchFilesListView->addColumn( i18n( "Filename" ) );
364 searchFilesListView->addColumn( i18n( "Artist" ) );
365 searchFilesListView->addColumn( i18n( "Title" ) );
366 searchFilesListView->addColumn( i18n( "Album" ) );
367 searchFilesListView->addColumn( i18n( "Genre" ) );
368 searchFilesListView->addColumn( i18n( "Comment" ) );
369 searchFilesListView->addColumn( i18n( "Year" ) );
370 searchFilesListView->addColumn( i18n( "Track #" ) );
371 searchFilesListView->addColumn( i18n( "Duration" ) );
372 searchFilesListView->addColumn( i18n( "Size" ) );
373 searchFilesListView->addColumn( i18n( "Bitrate" ) );
374 searchFilesListView->addColumn( i18n( "Sampling rate" ) );
375 searchFilesListView->addColumn( i18n( "Channels" ) );
376 searchFilesListView->addColumn( i18n( "Type" ) );
377 searchFilesListView->addColumn( i18n( "READ ONLY" ) );
378 searchFilesListView->setResizePolicy( KListView::AutoOneFit );
379 searchFilesListView->restoreLayout( kapp->config(), "searchListView" );
380 searchFilesListView->setAllColumnsShowFocus( TRUE );
381 searchFilesListView->setShowSortIndicator( TRUE );
382 searchFilesListView->setRootIsDecorated( FALSE );
383 searchFilesListView->setItemsMovable( FALSE );
384 searchFilesListView->setDragEnabled( TRUE );
385 searchFilesListView->setDropVisualizer( TRUE );
386 searchFilesListView->setSelectionMode( QListView::Extended );
387 layout5->addWidget( searchFilesListView );
388 tabLayout_2->addLayout( layout5 );
389 tabWidget1->insertTab( tab_2, i18n("Search") );
390 kmkwidgetbaseLayout->addWidget( tabWidget1 );
391 // resize( QSize(742, 507).expandedTo(minimumSizeHint()) );
392 // clearWState( WState_Polished );
394 // tab order
395 setTabOrder( tabWidget1, treeListView );
396 setTabOrder( treeListView, filesListView );
397 setTabOrder( filesListView, tabWidget2 );
398 setTabOrder( tabWidget2, leSearchFor );
399 setTabOrder( leSearchFor, pbSearch );
400 setTabOrder( pbSearch, pbClear );
401 setTabOrder( pbClear, pbDone );
402 setTabOrder( pbDone, searchFilesListView );
405 kmk::~kmk()
408 filesListView->saveLayout( kapp->config(), "listView" );
409 searchFilesListView->saveLayout( kapp->config(), "searchListView" );
411 if( s->dbg() & KMK_DBG_OTHER ) kdDebug() << "destructor: shutting down kmk "VERSION"..." << endl;
412 saveMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
413 Q_CHECK_PTR( s );
414 switch ( CatalogState )
416 case NoCatalog:
417 s->setLastCatalogUsed( "" );
418 break;
419 case Modified:
420 case Saved:
421 s->setLastCatalogUsed( catalogFileName );
422 break;
423 default:
424 break;
426 s->saveSettings( kapp->config() );
428 kapp->config()->setGroup( "GUI" );
429 kapp->config()->writeEntry( "splitter config", splitter1->sizes() );
430 kapp->config()->writeEntry( "main win position", pos() );
432 clearCatalogData();
433 if (kmkProgress) delete ( kmkProgress );
434 if (kmkProgressTimer) delete ( kmkProgressTimer );
435 if (kmkSmooth) delete ( kmkSmooth );
436 if (player) delete ( player );
438 if( s->dbg() & KMK_DBG_OTHER ) kdDebug() << "destructor: done!" << endl;
440 delete s;
443 void kmk::slotUpdateProgressDisp()
445 if ( files_to_read )
446 kmkProgress->kPrg->setProgress( total_files );
447 /* qDebug( " setting progres to "+QString::number( (int)((total_bytes/(float)bytes_to_read)*100 )) );
448 qDebug( " need to read: "+QString::number( bytes_to_read ) );
449 qDebug( " read: "+QString::number( total_bytes ) );*/
452 void kmk::slotFileNew()
454 if( CatalogState == Modified )
455 switch( KMessageBox::questionYesNoCancel( this,
456 i18n("The catalog [%1] contains unsaved changes.\n"
457 "Do you want to save the changes?")
458 .arg(catalogFileName),
459 i18n("KDE Music Kataloger") ) ) {
460 case KMessageBox::Yes: // Save
461 slotFileSave();
462 break;
463 case KMessageBox::No: // Discard - just continue
464 break;
465 case KMessageBox::Cancel: // Cancel creating new catalog
466 return;
467 break;
469 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
470 fd->setCaption( i18n("Select directory to scan") );
471 fd->setMode( QFileDialog::Directory );
472 fd->setFilter( "*" );
473 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
474 t->exec(); */
475 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
476 if ( fd->exec() == QDialog::Accepted ) {
477 // reset counters;
478 clearCatalogData();
479 subDlevel = 0;
480 QString dirName = fd->selectedFile();
481 // determine bytes to read;
482 kmkSmooth->start();
483 kmkProgress->kPrg->setTotalSteps(0);
484 kmkProgress->kPrg->setProgress(0);
485 kmkProgress->show();
486 QTime t; t.start();
487 break_long_disk_operation = FALSE;
488 bytes_to_read = 0; files_to_read = 0;
489 bytes_to_read_by_traverse( dirName );
490 if ( files_to_read )
492 if( s->dbg() & KMK_DBG_FS_OPS )
493 kdDebug() << "slotFileNew: Time elapsed: " << t.elapsed()
494 << " ms, need to read: " << (bytes_to_read/(1024*1024))
495 << " MB, files to read: " << files_to_read << endl;
496 t.restart();
497 kmkProgress->kPrg->setTotalSteps( files_to_read );
498 // arm timer event; 300 ms interval - can be done in constructor
499 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
500 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
501 // show widget, displaying load progress
502 // start timer
503 traverse_tree( dirName ); // should update total_bytes as it scans
504 update_tree_view();
505 setCatalogStateAndUpdate( Modified );
506 average_file_size = ( total_bytes/total_files );
507 if( s->dbg() & ( KMK_DBG_FS_OPS | KMK_DBG_OTHER ) )
509 kdDebug() << "slotFileNew: Total time: " << t.elapsed() << " ms, read: "
510 << (total_bytes/(1024*1024)) << " MB, files read: " << total_files << endl;
511 kdDebug() << "slotFileNew:STATS: total_files = " << total_files << ";" << endl;
512 kdDebug() << "slotFileNew:STATS: total_folders = " << total_folders << ";" << endl;
513 kdDebug() << "slotFileNew:STATS: total_bytes = " << total_bytes << " B;" << endl;
514 kdDebug() << "slotFileNew:STATS: total_playtime = " << total_play_time << " sec;" << endl;
515 kdDebug() << "slotFileNew:STATS: average_file_size = " << average_file_size << " B;" << endl;
517 kmkProgress->hide();
518 // hide widget
519 // stop timer
520 // Enable actions wich work only with data...
521 if( treeListView->firstChild() )
522 slotTreeListViewCurrentChanged( (QListViewItem*) treeListView->firstChild() );
523 else qWarning( "slotFileNew: NEED SOMEONE TO CREATE THE TREE FOR US...." );
524 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
526 else
528 setCatalogStateAndUpdate( NoCatalog );
529 clearCatalogData();
530 subDlevel = 0;
531 KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
532 i18n("KDE Music Kataloger") );
535 delete( fd );
538 void kmk::slotFileOpen()
540 if( CatalogState == Modified )
541 switch( KMessageBox::questionYesNoCancel( this,
542 i18n("The catalog [%1] contains unsaved changes.\n"
543 "Do you want to save the changes?")
544 .arg(catalogFileName),
545 i18n("KDE Music Kataloger") ) ) {
546 case KMessageBox::Yes: // Save
547 slotFileSave();
548 break;
549 case KMessageBox::No: // Discard - just continue
550 break;
551 case KMessageBox::Cancel: // Cancel creating new catalog
552 return;
553 break;
555 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
556 fd->setCaption( i18n("Select catalog file to load") );
557 fd->setMode( QFileDialog::ExistingFile );
558 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
559 if ( fd->exec() != QDialog::Accepted ) delete fd;
560 else
562 QString fileName = fd->selectedFile(); delete fd;
563 qApp->processEvents();
564 loadCatalog( fileName );
565 update_tree_view();
566 Q_CHECK_PTR( s );
567 s->setLastCatalogUsed( fileName );
568 s->saveSettings( kapp->config() );
572 void kmk::slotCatalogAddNewFolder()
574 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
575 fd->setCaption( i18n("Select directory to scan") );
576 fd->setMode( QFileDialog::Directory );
577 fd->setFilter( "*" );
578 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
579 t->exec(); */
580 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
581 if ( fd->exec() == QDialog::Accepted )
583 subDlevel = 0;
584 QString dirName = fd->selectedFile();
585 if ( catalog_has_dir( dirName ) )
587 KMessageBox::sorry( this, i18n("Sorry, but the folder [%1]\nis already present in this catalog!")
588 .arg(dirName),i18n("KDE Music Kataloger") );
589 delete fd;
590 return;
592 // clearCatalogData();
593 // reset counters;
594 Q_ULLONG bc01 = total_bytes; Q_ULLONG bc02 = total_files;
595 Q_ULLONG bc03 = total_folders; Q_ULLONG bc04 = total_play_time;
596 bytes_to_read = 0; files_to_read = 0;
597 total_bytes = 0; total_files = 0;
598 total_folders = 0; total_play_time = 0;
599 // determine bytes to read;
600 kmkSmooth->start();
601 kmkProgress->kPrg->setProgress(0);
602 kmkProgress->show();
603 QTime t; t.start();
604 break_long_disk_operation = FALSE;
605 bytes_to_read_by_traverse( dirName );
606 if( s->dbg() & KMK_DBG_FS_OPS )
607 kdDebug() << "slotCatalogAddNewFolder: Time elapsed: " << t.elapsed()
608 << " ms, need to read: " << (bytes_to_read/(1024*1024))
609 << " MB, files to read: " << files_to_read << endl;
610 t.restart();
611 // arm timer event
612 if ( files_to_read )
614 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
615 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
616 // show widget, displaying load progress
617 // start timer
618 traverse_tree( dirName ); // should update total_bytes as it scans
619 update_tree_view();
620 setCatalogStateAndUpdate( Modified );
621 // kdDebug() << "Total time: " << t.elapsed() << " ms, read: " << (total_bytes/(1024*1024))
622 // << " MB, files read: " << total_files << endl;
623 total_bytes += bc01; total_files += bc02;
624 total_folders += bc03; total_play_time += bc04;
625 if ( total_files ) average_file_size = ( total_bytes/total_files );
626 if( s->dbg() & KMK_DBG_FS_OPS & KMK_DBG_OTHER )
628 kdDebug() << "slotCatalogAddNewFolder:STATS: total_files = " << total_files << ";" << endl;
629 kdDebug() << "slotCatalogAddNewFolder:STATS: total_folders = " << total_folders << ";" << endl;
630 kdDebug() << "slotCatalogAddNewFolder:STATS: total_bytes = " << total_bytes << " B;" << endl;
631 kdDebug() << "slotCatalogAddNewFolder:STATS: total_playtime = " << total_play_time << " sec;" << endl;
632 kdDebug() << "slotCatalogAddNewFolder:STATS: average_file_size = " << average_file_size << " B;" << endl;
634 // hide widget
635 kmkProgress->hide();
636 // stop timer
637 // QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
639 else KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
640 i18n("KDE Music Kataloger") );
642 delete( fd );
645 /** Saves the catalog in memory to the file @p catalogFileName;
646 * If the string, being a filename in @p catalogFileName does not have
647 * a .kmk extension - this slot adds it
649 void kmk::slotFileSave()
651 if( CatalogState != Modified ) return;
652 /* We need to check whether catalogFileName exists, and what permissions it has.
653 If there is a problem - inform the user. */
654 bool fileOK=TRUE;
655 if( catalogFileName.isEmpty() || catalogFileName.isNull() )
656 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
657 QFileInfo * _cf = new QFileInfo( catalogFileName );
658 /* Make sure .kmk is appended to the file name...*/
659 if (_cf->extension(FALSE).lower().compare("kmk") != 0)
660 { catalogFileName.append(".kmk"); _cf->setFile( catalogFileName ); }
661 if( (_cf->exists())&&(!_cf->isWritable()) ){ /* File is NOT OK, existing, but not writable. Inform. */
662 fileOK = FALSE;
663 KMessageBox::sorry( this, i18n("Could not open %1 for writing!").arg(catalogFileName),
664 i18n("KDE Music Kataloger") );
666 if( (_cf->exists())&&(!_cf->isFile()) ){ /* File is NOT OK, it is not even a file. Inform. */
667 fileOK = FALSE;
668 KMessageBox::sorry( this, i18n("The filesystem item %1 exists, but it is not a file!").arg(catalogFileName),
669 i18n("KDE Music Kataloger") );
671 if( (FileSaveCalledFromFileSaveAs) && (_cf->exists())
672 && (_cf->isWritable()) ) /* File is OK, but we need to overwrite it... ask what to do. */
674 if( KMessageBox::questionYesNo( this,
675 i18n("A file called [%1] already exists.\nDo you want to overwrite it?").arg(catalogFileName),
676 i18n("KDE Music Kataloger") ) != KMessageBox::Yes ) fileOK = FALSE;
678 FileSaveCalledFromFileSaveAs = FALSE;
679 if( fileOK ) /* After the checks above are passed - lets write! */
681 QDomDocument doc( "KMK_catalog_file" ); QDomElement e = doc.createElement( "KMK_catalog_file_data" );
682 e.setAttribute( "Version", 1 ); doc.appendChild( e );
683 QDomElement e2 = doc.createElement( "CatalogFile_Statistics" ); e.appendChild( e2 );
684 e2.setAttribute( "Total_files_count", (double) total_files );
685 e2.setAttribute( "Total_bytes_count", (double) total_bytes );
686 e2.setAttribute( "Total_dirs_count", (double) total_folders );
687 e2.setAttribute( "Total_secs_count", (double) total_play_time );
688 e2.setAttribute( "Average_file_size", (double) average_file_size );
689 e2 = doc.createElement( "CatalogFile_TreeDescription" ); e.appendChild( e2 );
691 generateListViewXML( treeListView, doc, e2 );
693 QDomElement tag;
694 e2 = doc.createElement( "CatalogFile_Objects" ); e.appendChild( e2 );
695 e2.setAttribute( "Total_MObjs_count", (double) MusicCatalog.count() );
697 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
699 /* ask our DOM Document object to create a new DomElement, and fill this
700 new element with appropriate data */
701 tag = doc.createElement( "MObj" );
702 tag.setAttribute( "Folder", (*it).Folder().utf8() );
703 tag.setAttribute( "Filename", (*it).FileName().utf8() );
704 tag.setAttribute( "Artist", (*it).Artist().utf8() );
705 tag.setAttribute( "Title", (*it).Title().utf8() );
706 tag.setAttribute( "Album", (*it).Album().utf8() );
707 tag.setAttribute( "Genre", (*it).Genre().utf8() );
708 tag.setAttribute( "Comment", (*it).Comment().utf8() );
709 tag.setAttribute( "Year", (*it).Year() );
710 tag.setAttribute( "TrackNumber", (*it).TrackNum() );
711 tag.setAttribute( "Length", (*it).Length() );
712 tag.setAttribute( "Modified", (*it).ModifiedTime() );
713 tag.setAttribute( "Size", (*it).FileSize() );
714 tag.setAttribute( "Channels", (*it).Channels() );
715 tag.setAttribute( "BitRate", (*it).BitRate() );
716 tag.setAttribute( "SampleRate", (*it).SampleRate() );
717 tag.setAttribute( "IsReadOnly", ( ((*it).IsReadOnly()) ? "YES" : "NO" ) );
718 tag.setAttribute( "IsFolder", ( ((*it).IsDir()) ? "YES" : "NO" ) );
719 /* Then, ask the DOM Document to add this new element in the correct place... */
720 e2.appendChild( tag );
723 QString xml = doc.toString(2);
724 QFile file( catalogFileName );
725 if ( file.open( IO_WriteOnly ) ) {
726 file.writeBlock( xml, xml.length() );
727 file.flush();
728 file.close();
729 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
730 setCatalogStateAndUpdate( Saved );
731 } else QMessageBox::warning( this, i18n("KDE Music Kataloger"), i18n("Could not open %1 for writing!").arg(catalogFileName),
732 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
734 else this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
737 void kmk::slotFileSaveAs()
739 QFileDialog* fd = new QFileDialog( 0, "file dialog", TRUE );
740 fd->setCaption( i18n("Select file to save catalog to") );
741 fd->setMode( QFileDialog::AnyFile );
742 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
743 QString fileName;
744 if ( fd->exec() == QDialog::Accepted )
746 catalogFileName = fd->selectedFile();
747 setCatalogStateAndUpdate( Modified );
748 FileSaveCalledFromFileSaveAs = TRUE;
749 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSave() ) );
751 delete fd;
754 void kmk::slotCatalogFileClose()
756 if( CatalogState == Modified )
757 switch( KMessageBox::questionYesNoCancel( this,
758 i18n("The catalog [%1] contains unsaved changes.\n"
759 "Do you want to save the changes?")
760 .arg(catalogFileName),
761 i18n("KDE Music Kataloger") ) ) {
762 case KMessageBox::Yes: // Save
763 slotFileSave();
764 break;
765 case KMessageBox::No: // Discard - just continue
766 break;
767 case KMessageBox::Cancel: // Cancel opreation
768 return;
769 break;
771 // Ensure there is no data laying around...
772 clearCatalogData();
775 void kmk::slotCatalogFileStats()
777 QString tmp = formatted_string_from_seconds( total_play_time );
778 KMessageBox::information( 0,
779 i18n("<h3>Catalog statistics</h3>"
780 "<h4>This catalog represents:</h4>"
781 "<pre>Total capacity : %1 MB<br/>"
782 "Total files : %2<br/>"
783 "Total folders : %3<br/>"
784 "Total playtime : %4<br/>"
785 "Average filesize : %5 MB</pre>")
786 .arg(total_bytes/(float)(1024*1024),-8,'f',1).arg(total_files,-8).arg(total_folders,-8)
787 .arg(tmp).arg(average_file_size/(float)(1024*1024),-8,'f',3),
788 i18n("KDE Music Kataloger"));
791 void kmk::slotFileQuit()
793 close();
796 void kmk::closeEvent( QCloseEvent* ce )
798 if( !ce )
800 if( s->dbg() & KMK_DBG_SIGNALS & KMK_DBG_OTHER )
801 kdDebug() << "closeEvent: bad object" << endl;
802 return;
804 if( CatalogState == Modified )
805 switch( KMessageBox::questionYesNoCancel( this,
806 i18n("The catalog [%1] contains unsaved changes.\n"
807 "Do you want to save the changes before exiting?")
808 .arg(catalogFileName),
809 i18n("KDE Music Kataloger") ) ) {
810 case KMessageBox::Yes: // Save & Exit
811 slotFileSave();
812 ce->accept();
813 break;
814 case KMessageBox::No: // Discard - just Exit
815 ce->accept();
816 break;
817 case KMessageBox::Cancel: // Cancel - no nothing
818 ce->ignore();
819 break;
821 else ce->accept();
824 void kmk::slotProgramSettings()
826 // WARNING: kmkSettingsDialog has Qt::WDestructiveClose flag set, it WILL destroy itself
827 // also, it internally calls exec() - so we only create it, passing relevant data
828 // to its contructor
829 uint * k = new uint;
830 s->saveSettings( kapp->config() );
831 new kmkSettingsDialog( k );
832 // if( k ) // config changed? if so - re-read it..
833 s->readSettings( kapp->config() );
834 player->handleConfigChange();
835 /* If we need to choose the font kmk uses to display the lists and stuff,
836 here is how its done:
838 QFont f ( "Courier", 16, 50 );
839 main_container_widget->setFont( f );
843 void kmk::slotTagEdit()
845 MmDataPList *files = new MmDataPList; if( !files ) return;
846 MmData *tmp = 0; // zero-init our MmData pointer
847 KListViewItem *node = 0;
848 if( _popup_at_search )
849 node = (KListViewItem*) searchFilesListView->firstChild();
850 else
851 node = (KListViewItem*) filesListView->firstChild();
853 while ( node )
855 if ( node->isSelected() )
857 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
858 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
860 node = (KListViewItem*) node->nextSibling();
863 if ( files->isEmpty() ) { delete files; return; }
865 /** kmkTagEdit's constructor takes care of everything for us -
866 * we don't even need to call show() or exec() - it is done automagically;
868 uint k = 0;
869 new kmkTagEdit( files, &k );
870 if( s->dbg() & KMK_DBG_SIGNALS & KMK_DBG_OTHER )
871 kdDebug() << "slotTagEdit: TagEditDialog returns: " << k << endl;
872 if( k ) {
873 setCatalogStateAndUpdate( Modified );
874 slotTreeListViewCurrentChanged( treeListView->currentItem() );
876 delete files;
879 void kmk::slotListTableToggleTitle()
881 // QTable* tbl = ((kmkWidget*) kmk_widg )->FileList();
882 // tbl->hideColumn(1);
885 void kmk::slotPlayerPrevious()
887 player->previous();
890 void kmk::slotPlayerPlay()
892 player->play();
895 void kmk::slotPlayerPause()
897 player->pause();
900 void kmk::slotPlayerStop()
902 player->stop();
905 void kmk::slotPlayerNext()
907 player->next();
911 * In this member we use the fact, that when QTable's selectionMode() is MultiRow,
912 * meaning that whole rows selection is only allowed, QTable passes these
913 * rows as seperate selections - 2 successive rows will give us 2 selections
914 * with each selections topRow() and bottomRow() showing the number of the selected
915 * row in the table ---THIS ALL IS FALSE
917 void kmk::slotPlayerPlaySelection()
919 MmDataPList *files = new MmDataPList;
920 MmData *tmp = 0; // zero-init our MmData pointer
921 KListViewItem *node = 0;
922 if( _popup_at_search)
923 node = (KListViewItem*) searchFilesListView->firstChild();
924 else
925 node = (KListViewItem*) filesListView->firstChild();
927 while ( node )
929 if ( node->isSelected() )
931 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
932 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
934 node = (KListViewItem*) node->nextSibling();
937 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
938 QFile file( flNm );
939 if ( file.open( IO_WriteOnly ) )
941 QTextStream stream( &file );
942 stream << "#EXTM3U" << endl;
943 for ( tmp = files->first(); tmp; tmp = files->next() )
945 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
946 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
948 file.close();
950 player->playPlaylist( flNm );
952 // file.remove();
954 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
955 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
956 delete files;
959 void kmk::slotPlayerEnqueue()
961 MmDataPList *files = new MmDataPList;
962 MmData *tmp = 0; // zero-init our MmData pointer
963 KListViewItem *node = 0;
964 if( _popup_at_search)
965 node = (KListViewItem*) searchFilesListView->firstChild();
966 else
967 node = (KListViewItem*) filesListView->firstChild();
969 while ( node )
971 if ( node->isSelected() )
973 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
974 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
976 node = (KListViewItem*) node->nextSibling();
979 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
980 QFile file( flNm );
981 if ( file.open( IO_WriteOnly ) )
983 QTextStream stream( &file );
984 stream << "#EXTM3U" << endl;
985 for ( tmp = files->first(); tmp; tmp = files->next() )
987 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
988 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
990 file.close();
992 player->addPlaylist( flNm );
995 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
996 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
997 delete files;
1001 void kmk::slotPlayerEnqueueDir()
1003 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
1004 playerDir( A__ENQUEUE ); // here we enqueue
1007 void kmk::slotPlayerEnqueueDirSubdirs()
1009 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
1010 playerDir( A__ENQUEUE ); // here we enqueue
1013 void kmk::slotPlayerPlayDir()
1015 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
1016 playerDir( A__PLAY ); // and here we play
1019 void kmk::slotPlayerPlayDirSubdirs()
1021 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
1022 playerDir( A__PLAY ); // and here we play
1025 void kmk::slotCatalogUpdateSubtree()
1027 // init *lv* to currently selected item in the Tree view
1028 QListViewItem* lv = treeListView->currentItem();
1029 // this one will hold the full-blown path to the folder we should update
1030 QString folder_name;
1031 // this loop extracts a full path of the selected dir in the Tree view by
1032 // going from it to the top, collecting data on the way in *folder_name*
1033 while ( TRUE ) {
1034 folder_name.prepend( lv->text(0) );
1035 if( lv->parent() )
1036 { if( lv->parent()->text(0).compare("/") != 0 ) folder_name.prepend( "/" ); lv = lv->parent(); }
1037 // when the top is reached - the loop "break"s
1038 else break;
1040 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1042 kdDebug() << "slotCatalogUpdateSubtree: should update " << folder_name << endl;
1043 kdDebug() << "slotCatalogUpdateSubtree: subDlevel shows " << subDlevel << "; zeroing it..." << endl;
1045 subDlevel=0;
1046 traverseUpdate_tree( folder_name );
1047 if( tree_needs_update )
1049 tree_needs_update = FALSE;
1050 update_tree_view();
1051 if( MusicCatalog.isEmpty() ) setCatalogStateAndUpdate( NoCatalog );
1052 else setCatalogStateAndUpdate( Modified );
1056 void kmk::slotTreeListViewPopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1058 /* Take care of annoying warnings ..... */
1059 Q_UNUSED(itm); Q_UNUSED(col);
1060 if( treeListView->currentItem()>0 )
1061 catalogUpdateSubtreeAction->setEnabled( TRUE );
1062 else
1063 catalogUpdateSubtreeAction->setEnabled( FALSE );
1064 CTree_PopupMenu->popup( pos );
1067 void kmk::slotTreeListViewCurrentChanged( QListViewItem * itm )
1069 if ( MusicCatalog.isEmpty() ) { kdDebug() << "LVupdate: called with empty catalog" << endl; return; }
1070 // TagEditAction->setEnabled( FALSE );
1071 QListViewItem* lv = itm; QString p;
1072 // kdDebug() << "treeListView dropHighlighter() says: " << treeListView->dropHighlighter() << endl;
1074 // QTime ptime; ptime.start();
1076 while ( TRUE ) {
1077 p.prepend( lv->text(0) );
1078 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) p.prepend( "/" ); lv = lv->parent(); }
1079 else break;
1081 // kdDebug() << "LVupdate: user clicked: " << p << endl;
1082 // kdDebug() << "(kmk):LVupdate: determine hole selected folder name time: " << ptime.elapsed() << " ms." << endl;
1083 // ptime.restart();
1085 // Find the MmData folder item and get the number of files it contains
1086 MmDataList::iterator it;
1087 bool found = FALSE;
1088 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1089 if( ((*it).IsDir()) && ( (*it).Folder().compare(p)==0 ) ){ found = TRUE; break; }
1090 int folder_files = (found) ? (*it).FileSize() : 0;
1092 // kdDebug() << "(kmk):LVupdate: locate folder as *MmData time: " << ptime.elapsed() << " ms. ff=" << folder_files << endl;
1093 // ptime.restart();
1095 // disble list for manipulation : flicker care
1096 filesListView->setEnabled( FALSE );
1097 while( folder_files > filesListView->childCount() )
1098 new KListViewItem( (QListView*) filesListView, 0 );
1099 while( folder_files < filesListView->childCount() ) {
1100 KListViewItem* t = (KListViewItem*) filesListView->lastItem();
1101 if ( t ) delete t;
1103 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1104 // ptime.restart();
1106 // If there are items, go to the next...
1107 if ( found ) {
1108 it++;
1109 // ...and find the first MmData item, which is inside folder *p*
1110 // The result should be files, because MmData folder's Folder() holds their full path,
1111 // and there should be only one folder with *p* as Folder(), and we are already past that...
1112 while ( (( (*it).Folder().compare(p)!=0 )) ) it++;
1114 QDateTime dt;
1115 KListViewItem * sel_item = 0;
1116 KListViewItem * new_item = (KListViewItem*) filesListView->firstChild();
1117 // This loop's main requirement is that files within a dir are consecutive
1118 for ( int k=0; k<folder_files; it++,k++ )
1120 if ( !new_item ) qFatal("Something horrible happened!");
1121 if ( locateMode )
1122 if ( ( (*it).Folder().compare( DelSelDir ) == 0 ) && ( (*it).FileName().compare( DelSelFile ) == 0 ) )
1123 { sel_item = new_item; locateMode = FALSE; }
1124 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1125 new_item->setText( 1, (*it).FileName() ); // file
1126 new_item->setText( 2, (*it).Artist() ); // artist
1127 new_item->setText( 3, (*it).Title() ); // title
1128 new_item->setText( 4, (*it).Album() ); // album
1129 new_item->setText( 5, (*it).Genre() ); // genre
1130 new_item->setText( 6, (*it).Comment() ); // comment
1131 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1132 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1133 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1134 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1135 // dt.setTime_t( (*it).ModifiedTime() );
1136 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1137 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1138 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1139 QFileInfo ext_info( (*it).FileName() );
1140 new_item->setText( 14, ext_info.extension(FALSE).upper() ); // type
1141 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1142 new_item = (KListViewItem*) new_item->nextSibling();
1145 // kdDebug() << "(kmk):LVupdate: table fill time: " << ptime.elapsed() << " ms." << endl;
1146 // ptime.restart();
1148 // update table contets after manipulation
1149 filesListView->clearSelection();
1150 // here we update the first 5 columns' width
1151 Q_CHECK_PTR( s );
1152 // honor user setting whether he/she wants auto-column-width, or likes speed more
1153 if ( s->autoColumnWidth() )
1154 for ( ushort k=0; k<7; k++) filesListView->adjustColumn( k );
1155 if ( sel_item ) {
1156 filesListView->ensureItemVisible( sel_item );
1157 filesListView->setSelected( sel_item, TRUE );
1158 DelSelDir = ""; DelSelFile = "";
1160 filesListView->setEnabled( TRUE );
1163 void kmk::slotFileListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1165 Q_UNUSED(itm); Q_UNUSED(col);
1166 _popup_at_search = FALSE;
1167 CList_PopupMenu->exec( pos );
1170 void kmk::slotSearchListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1172 Q_UNUSED(itm); Q_UNUSED(col);
1173 _popup_at_search = TRUE;
1174 CSearchList_PopupMenu->exec( pos );
1178 * This function locates the correct place in the catalog tree, selects the
1179 * correct folder and then informs the method updating
1180 * the file list to select AND show the reqested file
1182 void kmk::slotLocateRequested()
1184 // this below is for locating results in the same folder to work,
1185 // without the need to manually change the currently selected folder
1186 // !!! kmk_widg->TreeList()->setCurrentItem( listViewRootItem );
1187 KListViewItem *node = (KListViewItem*) searchFilesListView->firstChild();
1188 if ( !node ) return;
1190 // inform the apropriate method that we are in locate mode:
1191 // set all relevant vars
1192 while ( node )
1194 // as we are called - and this is LOCATE slot - only ONE item in the search
1195 // results should be selected; if there are more - they are ignored
1196 if ( node->isSelected() )
1198 DelSelDir = node->text( 0 );
1199 DelSelFile = node->text( 1 );
1200 locateMode = TRUE;
1201 break;
1203 node = (KListViewItem*) node->nextSibling();
1206 // kdDebug() << " locating " << DelSelDir << " | " << DelSelFile << endl;
1207 QString ci;
1208 // get a list of all listview items to iterate over them
1209 QListViewItemIterator it( treeListView );
1210 while ( it.current() )
1212 QListViewItem *item = it.current();
1213 ci = QString::null;
1214 // this while loop exits when it reaches top level; result is slash ("/") separated path in ci
1215 bool not_found = TRUE;
1216 while ( not_found ) {
1217 ci.prepend( item->text(0) );
1218 if( item->parent() ) { if( item->parent()->text(0).compare("/") != 0 ) ci.prepend( "/" ); item = item->parent(); }
1219 else break;
1221 // kdDebug() << " ci now is " << ci << endl;
1222 item = it.current();
1223 if ( DelSelDir.compare( ci ) == 0 )
1225 treeListView->ensureItemVisible ( item );
1226 if( treeListView->currentItem() == item )
1227 slotTreeListViewCurrentChanged( item );
1228 else
1229 treeListView->setCurrentItem( item );
1230 not_found = FALSE;
1231 break;
1233 ++it;
1235 // switch from search tab to catalog view tab
1236 tabWidget1->setCurrentPage(0);
1239 void kmk::slotSearchButtonClicked()
1241 // if( MusicCatalog.isEmpty() ) return;
1242 QString p = leSearchFor->text();
1243 // TagEditAction->setEnabled( FALSE );
1245 // QTime ptime; ptime.start();
1247 QDateTime dt; long new_rows=0;
1248 // disble table for manipulation : flicker care
1249 searchFilesListView->setEnabled( FALSE );
1250 // Count the actuall matches
1251 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1253 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1254 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1255 ((*it).Album().contains(p,FALSE)>0) ) ) )
1256 { new_rows++; }
1258 // kdDebug() << new_rows << " rows should be added." << endl;
1259 // If the current search KListView has too many items (rows) - remove the unneeded
1260 while( new_rows > searchFilesListView->childCount() )
1261 new KListViewItem( (QListView*) searchFilesListView, 0 );
1262 // If the search KListView's items count is low - add the needed items (rows)
1263 while( new_rows < searchFilesListView->childCount() ) {
1264 KListViewItem* t = (KListViewItem*) searchFilesListView->lastItem();
1265 if ( t ) delete t;
1267 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1268 // ptime.restart();
1270 // If there are any matches at all...
1271 if ( new_rows )
1273 // use this var as a counter
1274 new_rows = 0;
1275 // and new_item is a pointer we'll set to the item we are currently adjusting...
1276 // all the items we need should have been added with the previous loops
1277 KListViewItem * new_item = (KListViewItem*) searchFilesListView->firstChild();
1278 // This is the exact same loop as above, but here will do something with what matches
1279 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1281 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1282 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1283 ((*it).Album().contains(p,FALSE)>0) ) ) )
1285 if ( !new_item ) qFatal("slotSearchButtonClicked: supposedly \"equal\" checks are giving "
1286 "different results!");
1287 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1288 new_item->setText( 1, (*it).FileName() ); // file
1289 new_item->setText( 2, (*it).Artist() ); // artist
1290 new_item->setText( 3, (*it).Title() ); // title
1291 new_item->setText( 4, (*it).Album() ); // album
1292 new_item->setText( 5, (*it).Genre() ); // genre
1293 new_item->setText( 6, (*it).Comment() ); // comment
1294 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1295 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1296 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1297 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1298 // dt.setTime_t( (*it).ModifiedTime() );
1299 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1300 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1301 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1302 QFileInfo ext_info( (*it).FileName() );
1303 new_item->setText( 14, ext_info.extension(FALSE).upper() ); // type
1304 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1305 new_item = (KListViewItem*) new_item->nextSibling();
1306 new_rows++;
1309 // kdDebug() << new_rows << " rows have been added." << endl;
1310 // update table contets after manipulation
1311 searchFilesListView->clearSelection();
1312 // here we update the first 5 columns' width
1313 Q_CHECK_PTR( s );
1314 // honor user setting whether he/she wants auto-column-width, or likes speed more
1315 if ( s->autoColumnWidth() )
1316 for ( ushort k=0; k<7; k++) searchFilesListView->adjustColumn( k );
1318 searchFilesListView->setEnabled( TRUE );
1319 if(!(new_rows)) KMessageBox::sorry( this, i18n("No files were found, that match your search criterias!"),
1320 i18n("KDE Music Kataloger") );
1323 void kmk::slotClearButtonClicked()
1325 leSearchFor->clear();
1326 searchFilesListView->clear();
1329 void kmk::slotDoneButtonClicked()
1331 filesListView->clearSelection();
1332 tabWidget1->setCurrentPage(0);
1337 /**============================================================================================================**
1338 ** NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS **
1339 **============================================================================================================**/
1341 void kmk::clearCatalogData( const bool UpdateState )
1343 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1344 kdDebug() << "clearCatalogData: clearing all in-memory catalog data and stats...." << endl;
1345 // clear catalog tree
1346 MusicCatalog.clear();
1347 treeListView->clear();
1348 listViewRootItem = (KListViewItem*) treeListView;
1349 cur_vlItem = listViewRootItem;
1350 // clear table showing files
1351 filesListView->clear();
1352 // clear table showing search result files
1353 searchFilesListView->clear();
1354 // reset stats counters, locate mode helper variables
1355 locateMode = FALSE; DelSelFile = ""; DelSelDir = "";
1356 bytes_to_read = 0; total_bytes = 0; total_files = 0;
1357 total_folders = 0; total_play_time = 0; average_file_size = 0;
1358 // and finally, update state, title and actions accordingly
1359 if( UpdateState ) setCatalogStateAndUpdate( NoCatalog );
1360 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1361 kdDebug() << "clearCatalogData: ...done!" << endl;
1364 void kmk::setCatalogStateAndUpdate( const kmk::CatalogStateEnum state )
1366 if( s->dbg() & KMK_DBG_OTHER )
1367 switch (state) {
1368 case kmk::NoCatalog:
1369 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"NO CATALOG\"..." << endl;
1370 break;
1371 case kmk::Modified:
1372 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"MODIFIED\"..." << endl;
1373 break;
1374 case kmk::Saved:
1375 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"SAVED\"..." << endl;
1376 break;
1377 default:
1378 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"UNSPECIFIED\"..." << endl;
1379 break;
1381 CatalogState = state;
1382 switch (state) {
1383 case kmk::NoCatalog:
1384 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
1385 this->setCaption( i18n("No catalog - ") + savedCaption );
1386 fileSaveAction->setEnabled( FALSE );
1387 fileSaveAsAction->setEnabled( FALSE );
1388 fileCatalogFileStats->setEnabled( FALSE );
1389 fileCatalogFileClose->setEnabled( FALSE );
1390 catalogAddNewFolderAction->setEnabled( FALSE );
1391 TagEditAction->setEnabled( TRUE );
1392 listTableLocateAction->setEnabled( FALSE );
1393 playerEnqueueAction->setEnabled( FALSE );
1394 playerEnqueueDirAction->setEnabled( FALSE );
1395 playerEnqueueDirSubdirsAction->setEnabled( FALSE );
1396 playerPlayDirAction->setEnabled( FALSE );
1397 playerPlayDirSubdirsAction->setEnabled( FALSE );
1398 playerPlaySelectionAction->setEnabled( FALSE );
1399 main_container_widget->setEnabled( FALSE );
1400 break;
1401 case kmk::Modified:
1402 this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
1403 fileSaveAction->setEnabled( TRUE );
1404 fileSaveAsAction->setEnabled( TRUE );
1405 fileCatalogFileStats->setEnabled( TRUE );
1406 fileCatalogFileClose->setEnabled( TRUE );
1407 catalogAddNewFolderAction->setEnabled( TRUE );
1408 TagEditAction->setEnabled( TRUE );
1409 listTableLocateAction->setEnabled( TRUE );
1410 playerEnqueueAction->setEnabled( TRUE );
1411 playerEnqueueDirAction->setEnabled( TRUE );
1412 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1413 playerPlayDirAction->setEnabled( TRUE );
1414 playerPlayDirSubdirsAction->setEnabled( TRUE );
1415 playerPlaySelectionAction->setEnabled( TRUE );
1416 main_container_widget->setEnabled( TRUE );
1417 break;
1418 case kmk::Saved:
1419 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
1420 fileSaveAction->setEnabled( FALSE );
1421 fileSaveAsAction->setEnabled( TRUE );
1422 fileCatalogFileStats->setEnabled( TRUE );
1423 fileCatalogFileClose->setEnabled( TRUE );
1424 catalogAddNewFolderAction->setEnabled( TRUE );
1425 TagEditAction->setEnabled( TRUE );
1426 listTableLocateAction->setEnabled( TRUE );
1427 playerEnqueueAction->setEnabled( TRUE );
1428 playerEnqueueDirAction->setEnabled( TRUE );
1429 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1430 playerPlayDirAction->setEnabled( TRUE );
1431 playerPlayDirSubdirsAction->setEnabled( TRUE );
1432 playerPlaySelectionAction->setEnabled( TRUE );
1433 main_container_widget->setEnabled( TRUE );
1434 break;
1435 default:
1436 break;
1438 if( s->dbg() & KMK_DBG_OTHER )
1439 kdDebug() << "setCatalogStateAndUpdate: done!" << endl;
1442 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1444 unsigned long kmk::traverse_tree( const QString& dir )
1446 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1447 { kmkSmooth->restart(); qApp->processEvents(); }
1448 /* Here we mark our tree depth - subDlevel is a kmk private var */
1449 subDlevel++;
1450 //qWarning( "looking in \""+dir+"\"...");
1451 /* Create a QDir object, set to the "dir-for-scan"
1452 The filter is set to DIRECTORIEs, without symlinks */
1453 QDir d ( dir );
1454 d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1455 /* This gets our "dir-for-scan" subdirs' in @p list */
1456 const QFileInfoList *list = d.entryInfoList();
1457 QFileInfoListIterator it( *list );
1458 /* @p fi is used to get the dir name and last modification time */
1459 QFileInfo *fi = new QFileInfo( d.absPath() );
1460 QString parent_dir = d.absPath();
1461 if( subDlevel == 1 )
1462 parent_dir = "TREE_BASE";
1463 else
1464 parent_dir.setLength( parent_dir.length() - (d.dirName().length() + 1) );
1465 if( s->dbg() & KMK_DBG_FS_OPS )
1466 kdDebug() << "traverse_tree: parent_dir set to : " << parent_dir << endl;
1467 /* Create a new MmDataList entry with the extracted above data
1468 for the "dir-for-scan" and set @p new_dir to point at it;
1469 we need a pointer to this new item, because if we add any files,
1470 contained in this dir, or its subdirs, we must update its
1471 MmFileSize field according to this below... */
1472 MmDataList::iterator new_dir =
1473 /* Some explanation : as we use the same elements to store info
1474 for audio files, and the dirs containing them - we do this trick:
1475 if the MmData item holds info on DIRECTORY - we set its fields like this:
1476 MmFolder - set to the ABSOLUTE DIR NAME
1477 MmFileName - set to the SHORT DIR NAME
1478 MmIsFolder - set to TRUE
1479 MmReadOnly - wether or not we have permission to write in DIR
1480 MmLength - set to the number of files in "dir-to-scan" subdirs
1481 MmFileSize - set to the number of files contained - updated later */
1482 MusicCatalog.append( MmData( d.absPath(), // folder
1483 d.dirName(), // filename
1484 "", // artist
1485 "", // title
1486 "", // album
1487 "", // genre
1488 parent_dir, // comment
1489 0, // year
1490 0, // track number
1491 0, // file size - updated later
1492 fi->lastModified().toTime_t(), // modified time
1493 fi->isWritable(), // read only? - we may use this to determine if we can "rename" dirs
1494 TRUE, // is dir?
1495 0, // audio duration (length) in secs - upd. later
1496 0, // channels
1497 0, // samplerate
1498 0 ) ); // bitrate
1499 /* We are done with @p fi for now - so, get rid of it */
1500 delete fi;
1501 /* Create a new KListView entry, pointed by @p ptr and if it is at
1502 the catalog root make its name the absolute path
1503 to "dir-for-scan", otherwise use short dir name;
1504 we set this item's parent to be what's pointed by kmk's private
1505 var cur_vlItem - it should point to what should be this item's
1506 parent in the catalog tree */
1507 // KListViewItem *ptr;
1508 //////////////////////// kdDebug() << "about to crash..." << endl;
1509 // if ( subDlevel == 1 ) ptr = new KListViewItem( (QListView*) treeListView, d.absPath() );
1510 // else ptr = new KListViewItem( (QListViewItem*) cur_vlItem, d.dirName() );
1511 //////////////////////// kdDebug() << "Strange, did not crash...." << endl;
1512 /* Set the new catalog tree item to be closed, non-expandable:
1513 if it happens to be parent of another - we will fix it later */
1514 // ptr->setOpen( FALSE ); ptr->setExpandable( FALSE );
1515 /* Here we initialize our subdirs' loaded files counter @p lf */
1516 unsigned long lf = 0;
1517 /* This checks if "dir-for-scan" has any subdirs */
1518 if ( !list->isEmpty() )
1520 /* And if it has, we go over the list of "dir-for-scan" subdirs */
1521 while ( (fi = it.current()) != 0 )
1523 /* Check if these subdirs are not the FS reserved "." and ".."
1524 FIX: AND that we can also access them !!! */
1525 if ( (fi->isReadable()) && (fi->isExecutable()) )
1526 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
1528 /* So, we've found a valid "dir-for-scan" subdirectory.
1529 If you remember - we used kmk's private var cur_vlItem as a parent
1530 of the KListViewItem we added earlier; so we want it to be
1531 "preset" when we are called, don't we? Let's make sure it is! */
1532 // cur_vlItem = ptr;
1533 /* We are setup and ready to call ourselves recursievely on this one.
1534 Because at the end of traverse_tree we provide a count of the
1535 actually added files to the catalog, including the added files
1536 from each subdir, lets increase @p lf with the numer of files in
1537 the directory we want to check...
1538 Here is the tracking : */
1539 lf += traverse_tree( d.filePath( fi->fileName() ) );
1541 /* Then go to the next in the list */
1542 ++it;
1544 /* We finished calling ourselves recursievly. So, if the subdirs'
1545 scan added any files - set the new catalog item to represent this
1546 accordingly - we hold subentries, so we need to be expandable;
1547 Maybe the user could set wheter or not the new catalog is already
1548 open or collapsed */
1549 // if ( lf > 0 ) { ptr->setOpen( FALSE ); ptr->setExpandable( TRUE ); }
1550 //qWarning("having "+QString::number(lf)+" files reported back...");
1552 /* Then we set our "dir-for-scan" filter to files, without symlinks */
1553 d.setFilter( QDir::Files | QDir::NoSymLinks );
1554 /* And get a list of its contents, after the filtering is apllied */
1555 const QFileInfoList *list2 = d.entryInfoList();
1556 QFileInfoListIterator it2( *list2 );
1557 QFileInfo *fi2;
1558 /* These we will use for storage of the exctracted data from files */
1559 QString art, ttl, alb, gen, cmt;
1560 /* This will be our "dir-for-scan" ONLY (i.e. without including
1561 the count from the subdirs) loaded files counter; zero-init it.
1562 Nowadays there are file systems, capable of handling enormous amounts
1563 of files in a single directory - make sure we don't choke on someones
1564 huge collection, by using extra large counter.*/
1565 unsigned long loaded_files = 0;
1566 /* A cycle to iterate over the files in "dir-for-scan" */
1567 while ( (fi2 = it2.current()) != 0 )
1569 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1570 { kmkSmooth->restart(); qApp->processEvents(); }
1571 /* If the files aren't with the supported extensions - ignore them;
1572 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
1573 if ( fi2->isReadable() )
1574 // LOSSLESS first
1575 if ( ( fi2->extension(FALSE).lower().compare("wv" ) == 0 ) || // wav-pack
1576 ( fi2->extension(FALSE).lower().compare("tta" ) == 0 ) || // true-audio
1577 ( fi2->extension(FALSE).lower().compare("ape" ) == 0 ) || // monkey's audio
1578 /* Is it correct for a FLAC file to have any other extension than "flac"?
1579 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
1580 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) || // flac
1581 // LOSSY next
1582 ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) || // mp3
1583 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) || // ogg-vorbis
1584 ( fi2->extension(FALSE).lower().compare("spx") == 0 ) || // ogg-speex
1585 ( fi2->extension(FALSE).lower().compare("mpc") == 0 ) ) // muse-pack
1587 /* Init our storage for this file... */
1588 art = ""; ttl = ""; alb = ""; gen = ""; cmt = "";
1589 using namespace TagLib;
1590 /* Use TagLib's functions to extract meta data and audio properties;
1591 This tells TagLib to read tags, and be as fast as possible */
1592 if( s->dbg() & KMK_DBG_FS_OPS )
1593 kdDebug() << "traverse_tree: Now checking: " << d.filePath( fi2->fileName().latin1() ) << endl;
1594 TagLib::FileRef f( d.filePath( fi2->fileName().latin1() ), TRUE, TagLib::AudioProperties::Fast );
1595 /* If TagLib found the file to be valid - then add it to the catalog.
1596 Maybe here would be a good place to update some global catalog stats,
1597 like total storage used by the collection we are cataloguining, number
1598 of files in it, average bitrate, highest one, lowest one, biggest file,
1599 smallest file... and anything else someone might consider "interesting"!*/
1600 if( f.file()->isValid() )
1602 TagLib::String s;
1603 s = f.tag()->artist(); art = s.toCString();
1604 s = f.tag()->title(); ttl = s.toCString();
1605 s = f.tag()->album(); alb = s.toCString();
1606 s = f.tag()->genre(); gen = s.toCString();
1607 s = f.tag()->comment(); cmt = s.toCString();
1608 /* Some of the extracted file data is in our storage vars now.
1609 Create a new MmData item, and fill it with what we've read. */
1610 MmData ni = MmData();
1611 ni.setFolder( d.absPath() );
1612 ni.setFileName( fi2->fileName() );
1613 ni.setArtist( art );
1614 ni.setTitle( ttl );
1615 ni.setAlbum( alb );
1616 ni.setGenre( gen );
1617 ni.setComment( cmt );
1618 ni.setYear( f.tag()->year() );
1619 ni.setTrackNum( f.tag()->track() );
1620 ni.setFileSize( fi2->size() );
1621 // qDebug( "To "+QString::number( total_bytes )+" B, adding "+QString::number((Q_ULLONG) fi2->size())+" B." );
1622 total_bytes += (Q_ULLONG) fi2->size();
1623 ni.setModifiedTime( fi2->lastModified().toTime_t() );
1624 /* How about using Qt's functions QFile and QDir to check this?
1625 Not a problem! I've checked it - TagLib gives correct data; in a bunch of mp3s, I
1626 set to 2 of them to have only read acces - TagLib said exactly this! The only thing
1627 left in mind is speed, but... how do I check IT? Pl.Petrov */
1628 ni.setIsReadOnly( f.file()->readOnly() );
1629 ni.setIsDir( FALSE ); // we are adding a file
1630 ni.setLength( f.audioProperties()->length() ); total_play_time += ni.Length();
1631 ni.setChannels( f.audioProperties()->channels() );
1632 ni.setSampleRate( f.audioProperties()->sampleRate() );
1633 ni.setBitRate( f.audioProperties()->bitrate() );
1634 /* Now, after we've filled our new catalog item - lets add it
1635 to the catalog list */
1636 MusicCatalog.append( ni );
1637 /* We said we will keep track of loaded files number - do so */
1638 loaded_files++; total_files++;
1641 /* Go to the next file in "dir-for-scan"... */
1642 ++it2;
1644 /* Now here is what we've got so far:
1645 o) new_dir - it is a pointer to the MmData item,
1646 containing the "dir-for-scan" directory info;
1647 o) ptr - a pointer the KListViewItem, which we created and added to the catalog tree;
1648 o) lf - contains the number of files added from all "dir-for-scan" SUBDIRs;
1649 o) loaded_files - contains the number of files added from "dir-for-scan" itself;
1650 If any of the last two in the list above is non-zero... */
1651 if ( loaded_files || lf )
1652 /* ...we must update the MmData item, containing the info for "dir-for-scan" to comply with this:
1653 [ ...if a MmData item holds info for a directory,
1654 its MmFileSize field holds a count of the files contained
1655 in it, !!! NOT !!! including its subdirs. ]
1656 We also update our overall folder counter.*/
1657 { (*new_dir).setFileSize( loaded_files ); (*new_dir).setLength( lf ); total_folders++; }
1658 /* If the sum of the last two is zero (0), or... lets put it this way -
1659 if both of them are zeros - then the first two ought to be removed; */
1660 else { MusicCatalog.remove( new_dir ); /* delete( ptr ); */ }
1661 /* We are done on this level - return subDlevel to where it was... */
1662 subDlevel--;
1663 if( (subDlevel == 0) && (s->dbg() & KMK_DBG_FS_OPS ) )
1664 kdDebug() << "traverse_tree: finishing MAIN traverse_tree..." << endl;
1665 /* And then return the sum of files in "dir-for-scan" and the files in
1666 its subdirectories.*/
1667 return (loaded_files + lf);
1671 * This private function starts by clearing current contents of
1672 * Tree KListView; next it goes trough kmk's MusicCatalog, recreating
1673 * the tree, described in the MmData structure, inside the Tree view
1675 void kmk::update_tree_view()
1677 long dirs = 0;
1678 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1679 kdDebug() << "update_tree_view: starting..." << endl;
1680 if( MusicCatalog.isEmpty() )
1682 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1683 kdDebug() << "update_tree_view: sorry, no data in catalog!" << endl;
1684 return;
1686 // clear current contents of treeListView
1687 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1688 kdDebug() << "update_tree_view: clearing contents of treeListView...." << endl;
1689 treeListView->clear();
1690 listViewRootItem = (KListViewItem*) treeListView;
1691 cur_vlItem = listViewRootItem;
1693 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1694 kdDebug() << "update_tree_view: cleared contents of treeListView!" << endl;
1695 // go through MusicCatalog...
1696 MmDataList::iterator it;
1697 KListViewItem *ptr;
1699 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1701 kdDebug()<<"******* SHOWING MUSIC CATALOG DATA SEQUENTLY ********"<<endl;
1702 for( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1704 kdDebug()<<"FileName(): "<<(*it).FileName()<<" "
1705 "Folder(): "<<(*it).Folder()<<endl;
1706 kdDebug()<<"Comment(): "<<(*it).Comment()<<" "
1707 "IsDir(): "<<(*it).IsDir()<<endl;
1708 kdDebug()<<"---------------------------"<<endl;
1710 kdDebug()<<"******** DONE MUSIC CATALOG DATA *************"<<endl;
1713 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1714 kdDebug() << "update_tree_view: setting all dirs to [NOT ADDED]..." << endl;
1715 // Go trough MusicCatalog and for each dir, set its BitRate() to 22
1716 // where non-zero value means that the dir is NOT in tree view and
1717 // and should be added at the correct place
1718 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1719 if ( (*it).IsDir() ) { (*it).setBitRate( 22 ); dirs++; }
1721 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1723 // look for a dir, that is NOT ADDED
1724 if ( (*it).IsDir() && ( (*it).BitRate() > 0 ) )
1726 // when found one, if its parent is "TREE_BASE" - add it to the top...
1727 if ( (*it).Comment().compare( "TREE_BASE" )==0 )
1729 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1730 kdDebug() << "update_tree_view: adding " << (*it).Folder()
1731 << " as a top-level TREE item..." << endl;
1732 ptr = new KListViewItem( (QListView*) treeListView, (*it).Folder() );
1733 // and reflect the fact that its added to tree view
1734 cur_vlItem = ptr;
1735 (*it).setBitRate( 0 );
1736 dirs--;
1737 it = MusicCatalog.begin();
1739 // and if its not - find the location where the new one has to be added
1740 else
1742 // just for speed's sake....
1743 bool added = FALSE;
1744 while( cur_vlItem != listViewRootItem )
1746 QListViewItem* lv = cur_vlItem;
1747 // this one will hold the full-blown path to the last added folder
1748 QString folder_name = QString::null;
1749 // this loop extracts a full path of the selected dir in the Tree view by
1750 // going from it to the top, collecting data on the way in *folder_name*
1751 while ( TRUE ) {
1752 folder_name.prepend( lv->text(0) );
1753 if( lv->parent() )
1755 if( lv->parent()->text(0).compare("/") != 0 ) folder_name.prepend( "/" );
1756 lv = lv->parent();
1758 // when the top is reached - the loop "break"s
1759 else break;
1761 if( (*it).Comment().compare( folder_name )==0 )
1763 if( s->dbg() & (KMK_DBG_CAT_MEM_OPS | KMK_DBG_OTHER ) )
1764 kdDebug() << "update_tree_view: FAST adding " << (*it).FileName() << "." << endl;
1765 // and if it is - add it as a child of trit
1766 ptr = new KListViewItem( (QListViewItem*) cur_vlItem, (*it).FileName() );
1767 // and reflect the fact that its added to tree view
1768 cur_vlItem = ptr;
1769 (*it).setBitRate( 0 );
1770 dirs--;
1771 it = MusicCatalog.begin();
1772 added = TRUE;
1773 break;
1775 else if( cur_vlItem->parent() ) cur_vlItem = (KListViewItem*) cur_vlItem->parent(); else break;
1777 if( added ) continue;
1778 else if( s->dbg() & (KMK_DBG_CAT_MEM_OPS | KMK_DBG_OTHER ) )
1779 kdDebug() << "update_tree_view: doing a slow search..." << endl;
1780 QListViewItemIterator trit( (QListView*) treeListView );
1781 while ( trit.current() )
1783 QListViewItem* lv = trit.current();
1784 // this one will hold the full-blown path to the last added folder
1785 QString folder_name = QString::null;
1786 // this loop extracts a full path of the selected dir in the Tree view by
1787 // going from it to the top, collecting data on the way in *folder_name*
1788 while ( TRUE ) {
1789 folder_name.prepend( lv->text(0) );
1790 if( lv->parent() )
1792 if( lv->parent()->text(0).compare("/") != 0 ) folder_name.prepend( "/" );
1793 lv = lv->parent();
1795 // when the top is reached - the loop "break"s
1796 else break;
1798 if( (*it).Comment().compare( folder_name )==0 )
1800 if( s->dbg() & (KMK_DBG_CAT_MEM_OPS | KMK_DBG_OTHER ) )
1801 kdDebug() << "update_tree_view: adding " << (*it).FileName() << "." << endl;
1802 // and if it is - add it as a child of trit
1803 ptr = new KListViewItem( (QListViewItem*) trit.current(), (*it).FileName() );
1804 // and reflect the fact that its added to tree view
1805 cur_vlItem = ptr;
1806 (*it).setBitRate( 0 );
1807 dirs--;
1808 it = MusicCatalog.begin();
1809 break;
1811 ++trit;
1816 // check if we added all the dirs...
1817 if( dirs ) qWarning("Possible catalog incosistency detected!!! Better Re-Scan!!!");
1818 if( dirs && ( s->dbg() & KMK_DBG_CAT_MEM_OPS ) )
1819 kdDebug() << "update_tree_view: Possible catalog incosistency detected!!! Better Re-Scan!!!" << endl;
1820 // ok, we are done
1821 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1822 kdDebug() << "update_tree_view: done updating treeListView !" << endl;
1826 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1828 #define TUT "traverseUpdate_tree<"<<subDlevel<<">: "
1829 unsigned long kmk::traverseUpdate_tree( const QString& dir )
1831 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1832 { kmkSmooth->restart(); qApp->processEvents(); }
1833 /* Here we mark our tree depth - subDlevel is a kmk private var */
1834 subDlevel++;
1835 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1836 kdDebug() << TUT"starting with param [" << dir << "]..." << endl;
1837 /* Create a QDir object, set to the "dir-for-scan"
1838 The filter is set to DIRECTORIEs, without symlinks */
1839 QDir d ( dir );
1840 if( ! d.exists() )
1842 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1844 kdDebug() << TUT"starting folder does NOT EXIST on drive;" << endl;
1845 kdDebug() << TUT"removing it and all its children from catalog..." << endl;
1847 MmDataList::iterator sm_mm;
1848 QString qq1 = dir + "/";
1849 for ( sm_mm = MusicCatalog.begin(); sm_mm != MusicCatalog.end(); ++sm_mm )
1851 if ( ((*sm_mm).Folder().startsWith( qq1 ))||((*sm_mm).Folder().compare( dir )==0) )
1853 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1854 kdDebug() << TUT"REMOVING FROM CATALOG ["<<(*sm_mm).FileName()<<"], Folder()=["
1855 <<(*sm_mm).Folder()<<"]..."<<endl;
1856 MusicCatalog.remove( sm_mm );
1857 sm_mm = MusicCatalog.begin();
1858 tree_needs_update = TRUE;
1861 if(s->dbg() & KMK_DBG_FS_OPS )
1862 kdDebug() << TUT"finishing MAIN traverse_tree..." << endl;
1863 subDlevel--;
1864 return 0;
1866 d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1867 /* This gets our "dir-for-scan" subdirs' in @p list */
1868 const QFileInfoList *list = d.entryInfoList();
1869 QFileInfoListIterator it( *list );
1870 /* @p fi is used to get the dir name and last modification time */
1871 QFileInfo *fi = new QFileInfo( d.absPath() );
1872 QString parent_dir = d.absPath();
1873 /* Create a new MmDataList entry with the extracted above data
1874 for the "dir-for-scan" and set @p new_dir to point at it;
1875 we need a pointer to this new item, because if we add any files,
1876 contained in this dir, or its subdirs, we must update its
1877 MmFileSize field according to this below... */
1878 MmDataList::iterator checked_mm_dir;
1880 bool mmdir_found = FALSE;
1881 bool dir_needs_update = FALSE;
1882 for ( checked_mm_dir = MusicCatalog.begin(); checked_mm_dir != MusicCatalog.end(); ++checked_mm_dir )
1884 if ( (*checked_mm_dir).IsDir() )
1886 if ( (*checked_mm_dir).Folder().compare( dir )==0 )
1888 mmdir_found = TRUE;
1889 if( fi->lastModified().toTime_t() != (*checked_mm_dir).ModifiedTime() )
1890 dir_needs_update = TRUE;
1891 break;
1896 if( mmdir_found )
1897 parent_dir = (*checked_mm_dir).Comment();
1898 else
1900 bool found = FALSE;
1901 MmDataList::iterator ckit;
1902 QString looked_for = dir;
1903 looked_for.setLength( looked_for.length() - ( d.dirName().length() + 1 ) );
1904 for ( ckit = MusicCatalog.begin(); ckit != MusicCatalog.end(); ++ckit ) {
1905 if ( (*ckit).IsDir() ) {
1906 if ( (*ckit).Folder().compare( looked_for )==0 ) { found = TRUE; break; }
1910 if(found) parent_dir = looked_for;
1911 else
1913 parent_dir = "TREE_BASE";
1914 checked_mm_dir = MusicCatalog.begin();
1918 if( s->dbg() & ( KMK_DBG_FS_OPS + KMK_DBG_CAT_MEM_OPS ) )
1920 kdDebug() << TUT"Checking STATUS of FOLDER [" << dir << "]..." << endl;
1921 kdDebug() << TUT"parent_dir set to : [" << parent_dir << "]." << endl;
1924 /* Some explanation : as we use the same elements to store info
1925 for audio files, and the dirs containing them - we do this trick:
1926 if the MmData item holds info on DIRECTORY - we set its fields like this:
1927 MmFolder - set to the ABSOLUTE DIR NAME
1928 MmFileName - set to the SHORT DIR NAME
1929 MmIsFolder - set to TRUE
1930 MmReadOnly - wether or not we have permission to write in DIR
1931 MmLength - set to the number of files in "dir-to-scan" subdirs
1932 MmFileSize - set to the number of files contained - updated later */
1933 MmData m = MmData( d.absPath(), // folder
1934 d.dirName(), // filename
1935 "", // artist
1936 "", // title
1937 "", // album
1938 "", // genre
1939 parent_dir, // comment
1940 0, // year
1941 0, // track number
1942 0, // file size - updated later
1943 fi->lastModified().toTime_t(), // modified time
1944 fi->isWritable(), // read only? - we may use this to determine if we can "rename" dirs
1945 TRUE, // is dir?
1946 0, // audio duration (length) in secs - upd. later
1947 0, // channels
1948 0, // samplerate
1949 0 ); // bitrate
1950 // If the dir is in catalog
1951 if( mmdir_found )
1953 // And needs update (its changed on drive)
1954 if( dir_needs_update )
1956 // Replace the outdated one with an actualized version
1957 // Some of the fields actually are not up-to-date - they get updated at the end
1958 if( checked_mm_dir==MusicCatalog.begin() )
1959 kdDebug()<<TUT"XXX CURRENT DIR IS AT LIST BEGINNING..."<<endl;
1961 checked_mm_dir = MusicCatalog.remove( checked_mm_dir );
1962 if( checked_mm_dir==MusicCatalog.begin() )
1963 kdDebug()<<TUT"XXX2 CURRENT DIR IS AT LIST BEGINNING..."<<endl;
1964 checked_mm_dir = MusicCatalog.insert( checked_mm_dir, m );
1965 tree_needs_update = TRUE;
1967 if( checked_mm_dir==MusicCatalog.begin() )
1968 kdDebug()<<TUT"XXX3 CURRENT DIR IS AT LIST BEGINNING..."<<endl;
1970 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1971 kdDebug() << TUT"IN catalog, NOT in SYNC" << endl;
1973 else
1975 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1976 kdDebug() << TUT"IN catalog, IN SYNC" << endl;
1979 // If the checked dir isn't in catalog - add it
1980 else
1982 for ( checked_mm_dir = MusicCatalog.begin(); checked_mm_dir != MusicCatalog.end(); ++checked_mm_dir )
1984 kdDebug()<<TUT"YYY Checking if "<<(*checked_mm_dir).IsDir()<<" is true and "
1985 <<(*checked_mm_dir).Folder()<<" is "<<parent_dir<<"..."<<endl;
1986 if ( ( (*checked_mm_dir).IsDir() ) && ( (*checked_mm_dir).Folder().compare( parent_dir )==0 ) )
1987 { if( checked_mm_dir!=MusicCatalog.end() ) checked_mm_dir++; break; }
1990 while( ( (( !(*checked_mm_dir).IsDir() )&&( (*checked_mm_dir).Folder().compare( parent_dir )==0 )) ||
1991 ((*checked_mm_dir).Folder().contains(parent_dir+"/")>=1) )&&(checked_mm_dir != MusicCatalog.end() ))
1992 checked_mm_dir++;
1994 if( checked_mm_dir==MusicCatalog.end() )
1995 kdDebug()<<TUT"YYY2 CURRENT DIR IS AT LIST END..."<<endl;
1997 kdDebug()<<TUT"YYY3 inserting "<<dir<<" in front of "<<(*checked_mm_dir).Folder()<<", "
1998 <<(*checked_mm_dir).FileName()<<endl;
1999 checked_mm_dir = MusicCatalog.insert( checked_mm_dir, m );
2000 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2001 kdDebug() << TUT"NOT IN catalog, adding it..." << endl;
2003 /* We are done with @p fi for now - so, get rid of it */
2004 delete fi;
2007 kdDebug()<<TUT"!!!! Now item's .Folder() shows "<<(*checked_mm_dir).Folder()<<endl;
2008 kdDebug()<<TUT"!!!! Now item's .FileName() shows "<<(*checked_mm_dir).FileName()<<endl;
2009 kdDebug()<<TUT"!!!! Now item's .IsDir() shows "<<(*checked_mm_dir).IsDir()<<endl;
2012 /* Here we initialize our subdirs' loaded files counter @p lf */
2013 unsigned long lf = 0;
2014 /* This checks if "dir-for-scan" has any subdirs */
2015 if ( !list->isEmpty() )
2017 /* And if it has, we go over the list of "dir-for-scan" subdirs */
2018 while ( (fi = it.current()) != 0 )
2020 /* Check if these subdirs are not the FS reserved "." and ".."
2021 FIX: AND that we can also access them !!! */
2022 if ( (fi->isReadable()) && (fi->isExecutable()) )
2023 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
2025 /* So, we've found a valid "dir-for-scan" subdirectory.
2026 If you remember - we used kmk's private var cur_vlItem as a parent
2027 of the KListViewItem we added earlier; so we want it to be
2028 "preset" when we are called, don't we? Let's make sure it is! */
2029 // cur_vlItem = ptr;
2030 /* We are setup and ready to call ourselves recursievely on this one.
2031 Because at the end of traverse_tree we provide a count of the
2032 actually added files to the catalog, including the added files
2033 from each subdir, lets increase @p lf with the numer of files in
2034 the directory we want to check...
2035 Here is the tracking : */
2036 lf += traverseUpdate_tree( d.filePath( fi->fileName() ) );
2038 /* Then go to the next in the list */
2039 ++it;
2041 /* We finished calling ourselves recursievly. So, if the subdirs'
2042 scan added any files - set the new catalog item to represent this
2043 accordingly - we hold subentries, so we need to be expandable;
2044 Maybe the user could set wheter or not the new catalog is already
2045 open or collapsed */
2048 // Now its time to remove folders, that are in catalog, but not on disk
2049 // Walk on trough all folders in *dir*...
2050 if ( !list->isEmpty() )
2052 MmDataList::iterator some_mm_dir;
2053 bool found;
2054 for ( some_mm_dir = MusicCatalog.begin(); some_mm_dir != MusicCatalog.end(); ++some_mm_dir )
2056 // Comment() field of MmData holds parent directory OR text "TREE_BASE" for folders
2057 if ( ( (*some_mm_dir).IsDir() ) && ( (*some_mm_dir).Comment().compare( dir )==0 ) )
2059 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2060 kdDebug() << TUT"Looking at [" << dir << "]'s subfolder..." << endl;
2062 found = FALSE;
2063 QFileInfoListIterator it3( *list );
2064 // And if it has, we go over the list of "dir-for-scan" subdirs
2065 while ( (fi = it3.current()) != 0 )
2067 // Check if these subdirs are not the FS reserved "." and ".."
2068 if ( (fi->isReadable()) && (fi->isExecutable()) )
2069 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
2071 /* if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2072 kdDebug() << TUT"Found REAL folder [" << fi->absFilePath() << "]." << endl;*/
2073 if( fi->absFilePath().compare( (*some_mm_dir).Folder() )==0 )
2075 // When we find one, which is in catalog - mark it
2076 found = TRUE;
2077 break;
2080 /* Go to the next file in "dir-for-scan"... */
2081 ++it3;
2084 /* if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2085 kdDebug() << TUT"Done listing." << endl;*/
2087 if( found==FALSE )
2089 QString pftd = (*some_mm_dir).Folder(); // pftd == Parent Folder To Delete
2090 QString pftd2 = pftd + "/";
2091 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2093 kdDebug() << TUT"Folder [" << pftd <<"] is in catalog ONLY!" << endl;
2094 kdDebug() << TUT"REMOVING it from catalog..." << endl;
2096 MusicCatalog.remove( some_mm_dir );
2097 tree_needs_update = TRUE;
2098 for ( some_mm_dir = MusicCatalog.begin(); some_mm_dir != MusicCatalog.end(); ++some_mm_dir )
2100 kdDebug() << TUT"Folder()=["<<(*some_mm_dir).Folder()<<"];"<<endl;
2101 // wipe out all items which happen to be children of *dir*
2102 //if ( ((*sm_mm).Folder().startsWith( dir ))&&((*sm_mm).Folder().contains( dir )>=1) )
2103 if( ((*some_mm_dir).Folder().compare( pftd )==0) || ((*some_mm_dir).Folder().startsWith( pftd2 )) )
2105 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2106 kdDebug() << TUT"REMOVING from CATALOG ["<<(*some_mm_dir).FileName()<<"], Folder()=["
2107 <<(*some_mm_dir).Folder()<<"]..."<<endl;
2108 MusicCatalog.remove( some_mm_dir );
2109 some_mm_dir = MusicCatalog.begin();
2110 tree_needs_update = TRUE;
2113 some_mm_dir = MusicCatalog.begin();
2115 else
2116 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2117 kdDebug() << TUT"Folder [" << (*some_mm_dir).FileName()
2118 << "] is a real a sub-folder of [" << dir << "]." << endl;
2125 MmDataList::iterator item_to_inset_at;
2126 // These few lines should get a pointer to first file inside *dir* - the new ones should be
2127 // added right on top of it
2128 for ( item_to_inset_at = MusicCatalog.begin(); item_to_inset_at != MusicCatalog.end(); ++item_to_inset_at )
2130 if ( ( !(*item_to_inset_at).IsDir() ) && ( (*item_to_inset_at).Folder().compare( dir )==0 ) )
2131 { if( item_to_inset_at!=MusicCatalog.end() ) item_to_inset_at++; break; }
2133 /* Then we set our "dir-for-scan" filter to files, without symlinks */
2134 d.setFilter( QDir::Files | QDir::NoSymLinks );
2135 /* And get a list of its contents, after the filtering is apllied */
2136 const QFileInfoList *list2 = d.entryInfoList();
2137 QFileInfoListIterator it2( *list2 );
2138 QFileInfo *fi2;
2139 /* These we will use for storage of the exctracted data from files */
2140 QString art, ttl, alb, gen, cmt;
2141 /* This will be our "dir-for-scan" ONLY (i.e. without including
2142 the count from the subdirs) loaded files counter; zero-init it.
2143 Nowadays there are file systems, capable of handling enormous amounts
2144 of files in a single directory - make sure we don't choke on someones
2145 huge collection, by using extra large counter.*/
2146 unsigned long loaded_files = 0;
2147 bool found_file = FALSE;
2148 bool file_needs_update = FALSE;
2149 MmDataList::iterator checked_mm_file;
2150 /* A cycle to iterate over the files in "dir-for-scan" */
2151 while ( (fi2 = it2.current()) != 0 )
2153 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
2154 { kmkSmooth->restart(); qApp->processEvents(); }
2155 /* If the files aren't with the supported extensions - ignore them;
2156 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
2157 if ( fi2->isReadable() )
2158 // LOSSLESS first
2159 if ( ( fi2->extension(FALSE).lower().compare("wv" ) == 0 ) || // wav-pack
2160 ( fi2->extension(FALSE).lower().compare("tta" ) == 0 ) || // true-audio
2161 ( fi2->extension(FALSE).lower().compare("ape" ) == 0 ) || // monkey's audio
2162 /* Is it correct for a FLAC file to have any other extension than "flac"?
2163 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
2164 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) || // flac
2165 // LOSSY next
2166 ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) || // mp3
2167 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) || // ogg-vorbis
2168 ( fi2->extension(FALSE).lower().compare("spx") == 0 ) || // ogg-speex
2169 ( fi2->extension(FALSE).lower().compare("mpc") == 0 ) ) // muse-pack
2171 found_file = FALSE;
2172 file_needs_update = FALSE;
2173 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2174 kdDebug() << TUT"Getting status of [" << fi2->fileName() << "]..." << endl;
2175 for ( checked_mm_file = MusicCatalog.begin(); checked_mm_file != MusicCatalog.end(); ++checked_mm_file )
2177 if ( (*checked_mm_file).IsDir()==FALSE )
2179 if ( ( (*checked_mm_file).Folder().compare( dir )==0 ) &&
2180 ( (*checked_mm_file).FileName().compare( fi2->fileName() )==0 ) )
2182 found_file = TRUE;
2183 if( fi2->lastModified().toTime_t() != (*checked_mm_file).ModifiedTime() )
2184 file_needs_update = TRUE;
2185 break;
2189 if( found_file )
2191 if( file_needs_update )
2193 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2195 kdDebug() << TUT"IN catalog, NOT in SYNC..." << endl;
2197 tree_needs_update = TRUE;
2198 // Removing items from MmData's structures posibly invalidates all iterator, pointing at the
2199 // deleted item, so we need to take care of that after the delete...
2200 MusicCatalog.remove( checked_mm_file );
2201 // These few lines should get a pointer to first file inside *dir* - the new ones should be
2202 // added right on top of it
2203 for ( item_to_inset_at = MusicCatalog.begin();
2204 item_to_inset_at != MusicCatalog.end(); ++item_to_inset_at )
2206 if ( ( !(*item_to_inset_at).IsDir() ) && ( (*item_to_inset_at).Folder().compare( dir )==0 ) )
2207 { if( item_to_inset_at!=MusicCatalog.end() ) item_to_inset_at++; break; }
2210 else
2212 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2213 kdDebug() << TUT"IN catalog, IN SYNC" << endl;
2214 loaded_files++;
2215 ++it2;
2216 continue;
2219 else if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2220 kdDebug() << TUT"NOT in catalog, ADDING it..." << endl;
2221 /* Init our storage for this file... */
2222 art = ""; ttl = ""; alb = ""; gen = ""; cmt = "";
2223 using namespace TagLib;
2224 /* Use TagLib's functions to extract meta data and audio properties;
2225 This tells TagLib to read tags, and be as fast as possible */
2226 if( s->dbg() & ( KMK_DBG_FS_OPS + KMK_DBG_OTHER ) )
2227 kdDebug() << TUT"Now checking: [" << d.filePath( fi2->fileName().latin1() ) << "]..." << endl;
2228 TagLib::FileRef f( d.filePath( fi2->fileName().latin1() ), TRUE, TagLib::AudioProperties::Fast );
2229 /* If TagLib found the file to be valid - then add it to the catalog.
2230 Maybe here would be a good place to update some global catalog stats,
2231 like total storage used by the collection we are cataloguining, number
2232 of files in it, average bitrate, highest one, lowest one, biggest file,
2233 smallest file... and anything else someone might consider "interesting"!*/
2234 if( f.file()->isValid() )
2236 TagLib::String s;
2237 s = f.tag()->artist(); art = s.toCString();
2238 s = f.tag()->title(); ttl = s.toCString();
2239 s = f.tag()->album(); alb = s.toCString();
2240 s = f.tag()->genre(); gen = s.toCString();
2241 s = f.tag()->comment(); cmt = s.toCString();
2242 /* Some of the extracted file data is in our storage vars now.
2243 Create a new MmData item, and fill it with what we've read. */
2244 MmData ni = MmData();
2245 ni.setFolder( d.absPath() );
2246 ni.setFileName( fi2->fileName() );
2247 ni.setArtist( art );
2248 ni.setTitle( ttl );
2249 ni.setAlbum( alb );
2250 ni.setGenre( gen );
2251 ni.setComment( cmt );
2252 ni.setYear( f.tag()->year() );
2253 ni.setTrackNum( f.tag()->track() );
2254 ni.setFileSize( fi2->size() );
2255 // qDebug( "To "+QString::number( total_bytes )+" B, adding "+QString::number((Q_ULLONG) fi2->size())+" B." );
2256 total_bytes += (Q_ULLONG) fi2->size();
2257 ni.setModifiedTime( fi2->lastModified().toTime_t() );
2258 /* How about using Qt's functions QFile and QDir to check this?
2259 Not a problem! I've checked it - TagLib gives correct data; in a bunch of mp3s, I
2260 set to 2 of them to have only read acces - TagLib said exactly this! The only thing
2261 left in mind is speed, but... how do I check IT? Pl.Petrov */
2262 ni.setIsReadOnly( f.file()->readOnly() );
2263 ni.setIsDir( FALSE ); // we are adding a file
2264 ni.setLength( f.audioProperties()->length() ); total_play_time += ni.Length();
2265 ni.setChannels( f.audioProperties()->channels() );
2266 ni.setSampleRate( f.audioProperties()->sampleRate() );
2267 ni.setBitRate( f.audioProperties()->bitrate() );
2268 /* Now, after we've filled our new catalog item - lets add it
2269 to the catalog list */
2270 item_to_inset_at = MusicCatalog.insert( item_to_inset_at, ni );
2271 tree_needs_update = TRUE;
2272 /* We said we will keep track of loaded files number - do so */
2273 loaded_files++; total_files++;
2276 /* Go to the next file in "dir-for-scan"... */
2277 ++it2;
2280 // Now its time to remove files, that are in catalog, but not on disk
2281 // Walk on trough all files in *dir*...
2282 for ( checked_mm_file = MusicCatalog.begin(); checked_mm_file != MusicCatalog.end(); ++checked_mm_file )
2284 if ( ( (*checked_mm_file).IsDir()==FALSE ) && ( (*checked_mm_file).Folder().compare( dir )==0 ) )
2286 found_file = FALSE;
2287 QFileInfoListIterator it3( *list2 );
2288 while ( (fi2 = it3.current()) != 0 )
2290 /* If the files aren't with the supported extensions - ignore them;
2291 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
2292 if ( fi2->isReadable() )
2293 // LOSSLESS first
2294 if ( ( fi2->extension(FALSE).lower().compare("wv" ) == 0 ) || // wav-pack
2295 ( fi2->extension(FALSE).lower().compare("tta" ) == 0 ) || // true-audio
2296 ( fi2->extension(FALSE).lower().compare("ape" ) == 0 ) || // monkey's audio
2297 /* Is it correct for a FLAC file to have any other extension than "flac"?
2298 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
2299 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) || // flac
2300 // LOSSY next
2301 ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) || // mp3
2302 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) || // ogg-vorbis
2303 ( fi2->extension(FALSE).lower().compare("spx") == 0 ) || // ogg-speex
2304 ( fi2->extension(FALSE).lower().compare("mpc") == 0 ) ) // muse-pack
2306 if( fi2->fileName().compare( (*checked_mm_file).FileName() )==0 )
2308 // When we find one, which is in catalog - mark it
2309 found_file = TRUE;
2310 break;
2313 /* Go to the next file in "dir-for-scan"... */
2314 ++it3;
2316 // If the file *(*checked_mm_file)* is in catalog, but not in the list of file of *dir*
2317 // - then its for DELETION from catalog
2318 if( found_file==FALSE )
2320 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2321 kdDebug() << TUT"REMOVING [" << (*checked_mm_file).FileName()
2322 << "] from catalog..." << endl;
2323 tree_needs_update = TRUE;
2324 MusicCatalog.remove( checked_mm_file );
2325 checked_mm_file = MusicCatalog.begin();
2330 /* Now here is what we've got so far:
2331 o) new_dir - it is a pointer to the MmData item,
2332 containing the "dir-for-scan" directory info;
2333 o) ptr - a pointer the KListViewItem, which we created and added to the catalog tree;
2334 o) lf - contains the number of files added from all "dir-for-scan" SUBDIRs;
2335 o) loaded_files - contains the number of files added from "dir-for-scan" itself;
2336 If any of the last two in the list above is non-zero... */
2337 if ( loaded_files || lf )
2338 /* ...we must update the MmData item, containing the info for "dir-for-scan" to comply with this:
2339 [ ...if a MmData item holds info for a directory,
2340 its MmFileSize field holds a count of the files contained
2341 in it, !!! NOT !!! including its subdirs. ]
2342 We also update our overall folder counter.*/
2343 { (*checked_mm_dir).setFileSize( loaded_files ); (*checked_mm_dir).setLength( lf ); total_folders++; }
2344 /* If the sum of the last two is zero (0), or... lets put it this way -
2345 if both of them are zeros - then the first two ought to be removed; */
2346 else
2348 MusicCatalog.remove( checked_mm_dir );
2349 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2350 kdDebug() << TUT"REMOVING FOLDER [" << (*checked_mm_dir).FileName() << "] from catalog..." << endl;
2352 /* We are done on this level - return subDlevel to where it was... */
2353 if(s->dbg() & KMK_DBG_FS_OPS )
2354 if(subDlevel != 1) kdDebug() << TUT"done!" << endl;
2355 else kdDebug() << TUT"Finishing MAIN traverse_tree..." << endl;
2356 subDlevel--;
2357 /* And then return the sum of files in "dir-for-scan" and the files in
2358 its subdirectories.*/
2359 return (loaded_files + lf);
2363 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
2365 void kmk::bytes_to_read_by_traverse( const QString& dir )
2367 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
2368 { kmkSmooth->restart(); qApp->processEvents(); }
2369 // kdDebug() << "called with param " << dir << endl;
2370 QDir d ( dir ); d.setFilter( QDir::Dirs | QDir::NoSymLinks );
2371 const QFileInfoList *list = d.entryInfoList(); QFileInfoListIterator it( *list );
2372 QFileInfo *fi = new QFileInfo();
2373 if ( !list->isEmpty() )
2374 while ( (fi = it.current()) != 0 )
2376 if ( (fi->isReadable()) && (fi->isExecutable()) )
2377 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
2379 if ( !fi->isDir() ) kdDebug() << "...and we are trying to do dir op on non-dir!" << endl;
2380 else bytes_to_read_by_traverse( d.filePath( fi->fileName() ) );
2382 ++it;
2384 d.setFilter( QDir::Files | QDir::NoSymLinks ); const QFileInfoList *list2 = d.entryInfoList();
2385 QFileInfoListIterator it2( *list2 ); QFileInfo *fi2;
2386 while ( (fi2 = it2.current()) != 0 )
2388 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
2389 { kmkSmooth->restart(); qApp->processEvents(); }
2390 if ( fi2->isReadable() )
2391 if ( ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) ||
2392 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) ||
2393 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) )
2394 { bytes_to_read += (Q_ULLONG) fi2->size(); files_to_read++; }
2395 ++it2;
2399 /* Some neat code I am really proud of: fast and effective; doesn't tollerate storage errors, though... */
2400 uint kmk::generateListViewSubtreeXML( const KListViewItem *item, QDomDocument doc, QDomElement e, const uint add_lv )
2402 if( item != 0 )
2404 // kdDebug() << "DOMelem <tree_item Name=\"" << item->text(0) <<"\" M=\"" << add_lv << "\" />" << endl;
2406 QDomElement dscr =
2407 doc.createElement( "FolderTreeDescriptor" );
2408 dscr.setAttribute( "FolderName", item->text(0) );
2409 dscr.setAttribute( "GoUpHowMuch", add_lv );
2410 e.appendChild( dscr );
2412 uint levels_added=0;
2413 KListViewItem * some_child = (KListViewItem*) item->firstChild();
2414 while( some_child )
2416 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
2417 some_child = (KListViewItem*) some_child->nextSibling();
2420 return ++levels_added;
2422 else return 0;
2425 void kmk::generateListViewXML( const KListView *list, QDomDocument doc, QDomElement e )
2427 if( list != 0 )
2429 uint levels_added=0;
2430 KListViewItem * some_child = (KListViewItem*) list->firstChild();
2431 while( some_child )
2433 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
2434 some_child = (KListViewItem*) some_child->nextSibling();
2441 * Finds all files in TreeList currentItem and all its subfolders
2442 * and passes them to player_bin with parameter @param act
2443 * NOTE this function respects _kmk_include_subdirs
2444 * NOTE 2: in a rethink of what the function should do,
2445 * the new behaviour is that it writes all the relevant files
2446 * in a .M3U or .PLS playlist file (stored with a randomly generated
2447 * name in /tmp/kmk) and passes it to the player with @p act
2449 void kmk::playerDir( uint act ) // act>=1 - play; act==0 - enqueue
2451 QListViewItem* lv = treeListView->currentItem();
2452 if ( lv != 0 )
2454 // generate some random based filename for the playlist file
2455 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
2456 QFile file( flNm );
2457 if ( file.open( IO_WriteOnly ) )
2459 QTextStream stream( &file );
2460 stream << "#EXTM3U\n";
2461 // determine the folder we use as root and save it in @p d
2462 QString d;
2463 while ( TRUE ) {
2464 d.prepend( lv->text(0) );
2465 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) d.prepend( "/" ); lv = lv->parent(); }
2466 else break;
2468 // do the acctual adding of files to the playlist....
2469 MmDataList::iterator it;
2470 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
2471 if ( _kmk_include_subdirs )
2473 if ( ( !((*it).IsDir()) ) && ( (( (*it).Folder().compare(d)==0 )) || ( (*it).Folder().contains(d+"/",TRUE)==1 ) ) )
2475 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
2476 stream << (*it).Folder()+"/"+(*it).FileName() << "\n";
2479 else
2481 if ( ( !((*it).IsDir()) ) && ( (*it).Folder().compare(d)==0 ) )
2483 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
2484 stream << (*it).Folder()+"/"+(*it).FileName()<<"\n";
2487 file.close();
2489 if (act) player->playPlaylist( flNm );
2490 else player->addPlaylist( flNm );
2491 // file.remove();
2493 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
2494 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
2498 MmData* kmk::findMmData( const QString& folder, const QString& filename )
2500 bool found = FALSE;
2501 MmDataList::iterator it;
2502 for( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
2504 if( ((*it).Folder().compare(folder)==0) && ((*it).FileName().compare(filename)==0) )
2506 found = TRUE;
2507 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2508 kdDebug() << "findMmData: called once. " << endl;
2509 break;
2512 if( found ) return &(*it);
2513 else return 0;
2516 const QString kmk::formatted_string_from_seconds( const Q_ULLONG t )
2518 QString ttime = "";
2519 unsigned short secs, mins, hours, years;
2520 secs = mins = hours = years = 0;
2521 uint days = 0;
2523 if ( t == 0 ) return ttime;
2524 years = t / 30758400;
2525 days = (t - (years * 30758400)) / 86400;
2526 hours = (t - ((years * 30758400) + (days * 86400))) / 3600;
2527 mins = (t - ((years * 30758400) + (days * 86400) + (hours * 3600))) / 60;
2528 secs = t % 60;
2529 if ( years ) if ( years > 1 ) ttime.append( i18n("%1 years").arg(years) );
2530 else ttime.append( i18n("1 year") );
2531 if ( days )
2533 if ( years ) ttime.append(", ");
2534 if ( days > 1 ) ttime.append( i18n("%1 days").arg(days) );
2535 else ttime.append( i18n("1 day") );
2537 if ( (days || years) && (hours || mins || secs) ) ttime.append(", ");
2538 if ( hours )
2540 if ( hours>9 ) ttime.append( QString("%1:").arg(hours) );
2541 else ttime.append( QString("0%1:").arg(hours) );
2543 if ( secs || mins || hours )
2545 if ( mins>9 ) ttime.append( QString("%1:").arg(mins) );
2546 else ttime.append( QString("0%1:").arg(mins) );
2548 if ( secs || mins || hours )
2550 if ( secs>9 ) ttime.append( QString("%1").arg(secs) );
2551 else ttime.append( QString("0%1").arg(secs) );
2553 return ttime;
2556 const bool kmk::catalog_has_dir( const QString & looked_for )
2558 bool found = FALSE;
2559 MmDataList::iterator it;
2561 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2562 kdDebug() << "catalog_has_dir: Now will look for dir [" << looked_for << "]..." << endl;
2563 // QDir gives us paths ending on "/" and in the catalog - folders don't
2564 // end on "/"; so, strip that last slash "/" symbol
2565 QString stripped_looked_for = looked_for;
2566 if ( stripped_looked_for.length()>1 )
2567 stripped_looked_for.setLength( stripped_looked_for.length() - 1 );
2568 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
2570 if ( (*it).IsDir() )
2572 if ( (*it).Folder().compare( stripped_looked_for )==0 )
2574 found = TRUE;
2575 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2576 kdDebug() << "catalog_has_dir: ...found! " << endl;
2577 break;
2582 return found;
2585 void kmk::loadCatalog( const QString & fileName )
2587 QDomDocument doc = QDomDocument::QDomDocument();
2588 QFile file( fileName );
2589 if ( !file.open( IO_ReadOnly | IO_Raw ) ) {
2590 QMessageBox::warning( this, i18n("KDE Music Kataloger"),
2591 i18n("Could not open %1 for reading!").arg(fileName),
2592 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
2593 return;
2595 this->setCaption( i18n("Checking integrity of [%1] - ").arg(fileName) );
2596 this->repaint(); qApp->processEvents();
2597 // sleep( 5 ); kdDebug() << " sleeping..." << endl;
2598 QString xml_parse_err = "test"; int err_ln = 0; int err_cl = 0;
2599 QTime parse_time; parse_time.start();
2600 if ( !doc.setContent( &file, TRUE, &xml_parse_err, &err_ln, &err_cl ) ) {
2601 file.close();
2602 // qWarning( QString::number( *err_ln )+ " " + QString::number( *err_cl ) );
2603 QMessageBox::warning( this, i18n("KDE Music Kataloger"),
2604 i18n("Error parsing catalog file %1 !\n MSG: %2 on line %3, column %4.")
2605 .arg(fileName).arg(xml_parse_err.ascii()).arg(QString::number( err_ln )).arg(QString::number( err_cl )),
2606 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
2607 this->setCaption( savedCaption );
2608 return;
2610 file.close();
2611 if( s->dbg() & KMK_DBG_CATALOG_IO )
2612 kdDebug() << "loadCatalog: parse_file(SAX2 parse): elapsed time: "
2613 << parse_time.elapsed() << " ms." << endl;
2614 parse_time.restart();
2615 clearCatalogData();
2616 this->setCaption( i18n("Loading [%1]...").arg(fileName) );
2617 catalogFileName = fileName;
2618 qApp->processEvents();
2620 /** READ CATALOG VERSION INFO - if none is found - inform, and bail out */
2621 bool process = TRUE;
2622 ulong nodes_to_add = 0;
2623 QDomNodeList ml = doc.elementsByTagName( "KMK_catalog_file_data" ); QDomNode n; QDomElement e; QDomAttr a;
2624 for ( uint i = 0; i<ml.count(); i++)
2626 n = ml.item( i );
2627 if ( n.isElement() )
2628 { e = n.toElement();
2629 QString tx = e.attribute("Version");
2630 if( !tx.isNull() )
2632 if( tx.toInt() > 1 ) KMessageBox::sorry( this, i18n("The catalog [%1] is saved in new format (v.%2),"
2633 "so some data will not be recognized.").arg(fileName).arg(tx.toInt()),i18n("KDE Music Kataloger") );
2634 if( tx.toInt() == 0 ) process = FALSE;
2638 if( s->dbg() & KMK_DBG_CATALOG_IO )
2639 kdDebug() << "loadCatalog: parse_file(new read): catalog version read time: "
2640 << parse_time.elapsed() << " ms." << endl;
2641 parse_time.restart();
2642 if( process )
2644 ml = doc.elementsByTagName( "CatalogFile_Statistics" );
2645 for ( uint i = 0; i<ml.count(); i++)
2647 n = ml.item( i );
2648 if ( n.isElement() )
2649 { e = n.toElement();
2650 QString tx = e.attribute("Total_files_count");
2651 if( !tx.isNull() ) total_files = (Q_ULLONG) tx.toDouble();
2652 tx = e.attribute("Total_bytes_count");
2653 if( !tx.isNull() ) total_bytes = (Q_ULLONG) tx.toDouble();
2654 tx = e.attribute("Total_dirs_count");
2655 if( !tx.isNull() ) total_folders = (Q_ULLONG) tx.toDouble();
2656 tx = e.attribute("Total_secs_count");
2657 if( !tx.isNull() ) total_play_time = (Q_ULLONG) tx.toDouble();
2658 tx = e.attribute("Average_file_size");
2659 if( !tx.isNull() ) average_file_size = (Q_ULLONG) tx.toDouble();
2662 if( s->dbg() & KMK_DBG_CATALOG_IO )
2663 kdDebug() << "loadCatalog: parse_file(new read): catalog stats read time: "
2664 << parse_time.elapsed() << " ms." << endl;
2665 parse_time.restart();
2668 /** READ THE FOLDERS STRUCTURE AUXILARY DATA - version, counters, etc */
2669 /* Currently - IRRELEVANT
2670 ml = doc.elementsByTagName( "CatalogFile_TreeDescription" );
2671 for ( uint i = 0; i<ml.count(); i++)
2673 n = ml.item( i );
2674 if ( n.isAttr() )
2675 { a = n.toAttr();
2676 #ifdef __KMK_DEBUG
2677 kdDebug() << "(kmk): i="<<i<<"; found attr; name: "<< a.name()<<"; value: "<< a.value() << endl;
2678 #endif
2680 if ( n.isElement() )
2681 { e = n.toElement();
2682 #ifdef __KMK_DEBUG
2683 kdDebug() << "(kmk): i="<<i<<"; found elem; tag: "<< e.tagName() << endl;
2684 #endif
2689 /** READ THE FOLDERS STRUCTURE AND RECREATE IT IN KLISTVIEW */
2690 /* if( process )
2692 KListViewItem *CURRENT = 0; bool warned = FALSE;
2693 ml = doc.elementsByTagName( "FolderTreeDescriptor" );
2694 for ( uint i = 0; i<ml.count(); i++)
2696 n = ml.item( i );
2697 if ( n.isElement() )
2698 { e = n.toElement();
2699 QString nm = e.attribute("FolderName");
2700 if( nm.isNull() ) kdDebug() << "READ A NULL FOLDER NAME!" << endl;
2701 QString up = e.attribute("GoUpHowMuch");
2702 if( up.isNull() ) kdDebug() << "READ A NULL GO_UP_HOW_MUCH!"<< endl;
2703 uint t = up.toUInt();
2704 while( (t) && (CURRENT) )
2706 CURRENT = (KListViewItem*)CURRENT->parent();
2707 t--;
2709 if( t && (!warned) ) { KMessageBox::sorry( this,
2710 i18n("The catalog file you are trying to load is messed up. "
2711 "You get this message, so it passed XML sanity checks. That means almost for sure "
2712 "that it has been edited by hand - you are better off if you recreate it via Catalog->New. "
2713 "The scanning is fast enough anyway (around 10GB mp3 files scanned per minute)!\n"
2714 "You have been warned - don't complain if something goes wrong."),
2715 i18n("KDE Music Kataloger") );
2716 warned = TRUE; }
2717 if( s->dbg() & KMK_DBG_OTHER )
2718 if(CURRENT)
2719 kdDebug() << "loadCatalog: " << i << ":(t="<<t<<") adding "<<nm<<" at "<<CURRENT->text(0)<<endl;
2720 else
2721 kdDebug() << "loadCatalog: " << i << ":(t="<<t<<") adding "<<nm<<" at ROOT."<<endl;
2722 if(CURRENT) CURRENT = new KListViewItem( (QListViewItem*) CURRENT, nm );
2723 else CURRENT = new KListViewItem( (QListView*) treeListView, nm );
2724 //kdDebug() << i << ":-------------> "<<CURRENT->text(0)<<"'s parent() is: " << CURRENT->parent() << endl;
2727 if( s->dbg() & KMK_DBG_CATALOG_IO )
2728 kdDebug() << "loadCatalog: parse_file(new read): folder tree reconstruction time: "
2729 << parse_time.elapsed() << " ms." << endl;
2730 parse_time.restart();
2731 qApp->processEvents();
2733 // 10 February 2008 - removed folder tree reconstruction;
2734 // whoever needs it, now should call update_tree_view() instead
2736 /** READ CATALOG ITEMS AUXILARY DATA - objects number, size, etc */
2737 if( process )
2739 ml = doc.elementsByTagName( "CatalogFile_Objects" );
2740 for ( uint i = 0; i<ml.count(); i++)
2742 n = ml.item( i );
2743 if ( n.isElement() )
2744 { e = n.toElement();
2745 QString tx = e.attribute("Total_MObjs_count");
2746 if( !tx.isNull() ) nodes_to_add = tx.toULong();
2749 if( s->dbg() & KMK_DBG_CATALOG_IO )
2750 kdDebug() << "loadCatalog: parse_file(new read): catalog objects count read time: "
2751 << parse_time.elapsed() << " ms." << endl;
2752 parse_time.restart();
2755 // some vars used to convert old dirs data to new - fill in Comment field of MmData
2756 QString base_dir = "";
2757 long dirs_found = 0;
2758 /** READ ACTUAL AUDIO FILE DATA INTO MusicCatalog */
2759 if( process )
2761 ml = doc.elementsByTagName( "MObj" ); MmData node; uint tags_found;
2762 for ( uint i = 0; i<ml.count(); i++)
2764 tags_found = 0;
2765 n = ml.item( i );
2766 if ( n.isElement() )
2767 { e = n.toElement(); QString
2768 tx = e.attribute("Folder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2769 else { node.setFolder( tx ); tags_found++; }
2770 tx = e.attribute("Filename"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2771 else { node.setFileName( tx ); tags_found++; }
2772 tx = e.attribute("Artist"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2773 else { node.setArtist( tx ); tags_found++; }
2774 tx = e.attribute("Title"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2775 else { node.setTitle( tx ); tags_found++; }
2776 tx = e.attribute("Album"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2777 else { node.setAlbum( tx ); tags_found++; }
2778 tx = e.attribute("Genre"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2779 else { node.setGenre( tx ); tags_found++; }
2780 tx = e.attribute("Comment"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2781 else { node.setComment( tx ); tags_found++; }
2782 tx = e.attribute("Year"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2783 else { node.setYear( tx.toInt() ); tags_found++; }
2784 tx = e.attribute("TrackNumber"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2785 else { node.setTrackNum( tx.toInt() ); tags_found++; }
2786 tx = e.attribute("Length"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2787 else { node.setLength( tx.toInt() ); tags_found++; }
2788 tx = e.attribute("Modified"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2789 else { node.setModifiedTime( tx.toInt() ); tags_found++; }
2790 tx = e.attribute("Size"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2791 else { node.setFileSize( tx.toInt() ); tags_found++; }
2792 tx = e.attribute("Channels"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2793 else { node.setChannels( tx.toInt() ); tags_found++; }
2794 tx = e.attribute("BitRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2795 else { node.setBitRate( tx.toInt() ); tags_found++; }
2796 tx = e.attribute("SampleRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2797 else { node.setSampleRate( tx.toInt() ); tags_found++; }
2798 tx = e.attribute("IsReadOnly"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2799 else { node.setIsReadOnly( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2800 tx = e.attribute("IsFolder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2801 else { node.setIsDir( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2803 if ( tags_found )
2805 qApp->processEvents();
2806 if( node.IsDir() )
2807 if( node.Comment().isEmpty() )
2809 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2810 kdDebug() << "loadCatalog: converting old-format folder "
2811 << node.Folder() << "..." << endl;
2812 dirs_found++;
2813 if( dirs_found == 1)
2815 base_dir = node.Folder();
2816 node.setComment( "TREE_BASE" );
2817 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2818 kdDebug() << "loadCatalog: adding folder comment [" << base_dir
2819 << "]; setting it as BASE_DIR..." << endl;
2821 else
2823 if( node.Folder().contains( base_dir ) > 0 )
2825 QString tmpQ = node.Folder();
2826 tmpQ.setLength( node.Folder().length() - ( node.FileName().length() + 1 ) );
2827 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2828 kdDebug() << "loadCatalog: adding folder comment [" << tmpQ
2829 << "] as part of conversion..." << endl;
2830 node.setComment( tmpQ );
2832 else
2834 base_dir = node.Folder();
2835 node.setComment( "TREE_BASE" );
2836 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
2837 kdDebug() << "loadCatalog: adding folder comment [" << base_dir
2838 << "]; setting it as NEW BASE_DIR..." << endl;
2842 MusicCatalog.append( node );
2843 if( s->dbg() & KMK_DBG_OTHER )
2844 kdDebug() << "loadCatalog: parse_file(new read): just added " << i << "-th object;" << endl;
2847 if( s->dbg() & KMK_DBG_CATALOG_IO )
2848 kdDebug() << "loadCatalog: parse_file(new read): node read and add time: "
2849 << parse_time.elapsed() << " ms." << endl;
2850 parse_time.restart();
2853 if( ! process )
2854 KMessageBox::sorry( this, i18n("No catalog markings found in [%1].\n"
2855 "This can happen if the file is corrupt.")
2856 .arg(catalogFileName),i18n("KDE Music Kataloger") );
2857 else {
2858 if( dirs_found ) setCatalogStateAndUpdate( Modified );
2859 else setCatalogStateAndUpdate( Saved );
2860 fileCatalogFileStats->setEnabled( TRUE );
2861 fileCatalogFileClose->setEnabled( TRUE );
2866 #include "kmk.moc"