1 /****************************************************************************************
2 * Copyright (c) 2007 Maximilian Kossick <maximilian.kossick@googlemail.com> *
3 * Copyright (c) 2008 Jason A. Donenfeld <Jason@zx2c4.com> *
4 * Copyright (c) 2010 Casey Link <unnamedrambler@gmail.com> *
5 * Copyright (c) 2010 Teo Mrnjavac <teo@kde.org> *
6 * *
7 * This program is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU General Public License as published by the Free Software *
9 * Foundation; either version 2 of the License, or (at your option) any later *
10 * version. *
11 * *
12 * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
14 * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License along with *
17 * this program. If not, see <http://www.gnu.org/licenses/>. *
18 ****************************************************************************************/
19
20 #define DEBUG_PREFIX "SqlCollectionLocation"
21
22 #include "SqlCollectionLocation.h"
23
24 #include "MetaTagLib.h" // for getting the uid
25 #include "core/collections/CollectionLocationDelegate.h"
26 #include <core/storage/SqlStorage.h>
27 #include "core/logger/Logger.h"
28 #include "core/support/Components.h"
29 #include "core/support/Debug.h"
30 #include "core/meta/Meta.h"
31 #include "core/meta/support/MetaUtility.h"
32 #include "core/transcoding/TranscodingController.h"
33 #include "core-impl/collections/db/MountPointManager.h"
34 #include "core-impl/collections/db/sql/SqlCollection.h"
35 #include "core-impl/collections/db/sql/SqlMeta.h"
36 #include "transcoding/TranscodingJob.h"
37
38 #include <QDir>
39 #include <QFile>
40 #include <QFileInfo>
41
42 #include <KDiskFreeSpaceInfo>
43 #include <KFileItem>
44 #include <KJob>
45 #include <KIO/DeleteJob>
46 #include <KIO/Job>
47 #include <KConfigGroup>
48 #include <KLocalizedString>
49
50 using namespace Collections;
51
SqlCollectionLocation(SqlCollection * collection)52 SqlCollectionLocation::SqlCollectionLocation( SqlCollection *collection )
53 : CollectionLocation( collection )
54 , m_collection( collection )
55 , m_delegateFactory( 0 )
56 , m_overwriteFiles( false )
57 , m_transferjob( )
58 {
59 //nothing to do
60 }
61
~SqlCollectionLocation()62 SqlCollectionLocation::~SqlCollectionLocation()
63 {
64 //nothing to do
65 delete m_delegateFactory;
66 }
67
68 QString
prettyLocation() const69 SqlCollectionLocation::prettyLocation() const
70 {
71 return i18n( "Local Collection" );
72 }
73
74 QStringList
actualLocation() const75 SqlCollectionLocation::actualLocation() const
76 {
77 return m_collection->mountPointManager()->collectionFolders();
78 }
79
80 bool
isWritable() const81 SqlCollectionLocation::isWritable() const
82 {
83 // TODO: This function is also called when removing files to check
84 // if the tracks can be removed. In such a case we should not check the space
85
86 // The collection is writable if there exists a path that has more than
87 // 500 MB free space.
88 bool path_exists_with_space = false;
89 bool path_exists_writable = false;
90 QStringList folders = actualLocation();
91 foreach( const QString &path, folders )
92 {
93 float used = KDiskFreeSpaceInfo::freeSpaceInfo( path ).used();
94 float total = KDiskFreeSpaceInfo::freeSpaceInfo( path ).size();
95
96 if( total <= 0 ) // protect against div by zero
97 continue; //How did this happen?
98
99 float free_space = total - used;
100 if( free_space >= 500*1000*1000 ) // ~500 megabytes
101 path_exists_with_space = true;
102
103 QFileInfo info( path );
104 if( info.isWritable() )
105 path_exists_writable = true;
106 }
107 return path_exists_with_space && path_exists_writable;
108 }
109
110 bool
isOrganizable() const111 SqlCollectionLocation::isOrganizable() const
112 {
113 return true;
114 }
115
116 bool
remove(const Meta::TrackPtr & track)117 SqlCollectionLocation::remove( const Meta::TrackPtr &track )
118 {
119 DEBUG_BLOCK
120 Q_ASSERT( track );
121
122 if( track->inCollection() &&
123 track->collection()->collectionId() == m_collection->collectionId() )
124 {
125 bool removed;
126 QUrl src = track->playableUrl();
127 if( isGoingToRemoveSources() ) // is organize operation?
128 {
129 SqlCollectionLocation* destinationloc = qobject_cast<SqlCollectionLocation*>( destination() );
130 if( destinationloc )
131 {
132 src = destinationloc->m_originalUrls[track];
133 if( src == track->playableUrl() )
134 return false;
135 }
136 }
137 // we are going to delete it from the database only if is no longer on disk
138 removed = !QFile::exists( src.path() );
139 if( removed )
140 static_cast<Meta::SqlTrack*>(const_cast<Meta::Track*>(track.data()))->remove();
141
142 return removed;
143 }
144 else
145 {
146 debug() << "Remove Failed";
147 return false;
148 }
149 }
150
151 bool
insert(const Meta::TrackPtr & track,const QString & path)152 SqlCollectionLocation::insert( const Meta::TrackPtr &track, const QString &path )
153 {
154 if( !QFile::exists( path ) )
155 {
156 warning() << Q_FUNC_INFO << "file" << path << "does not exist, not inserting into db";
157 return false;
158 }
159
160 // -- the target path
161 SqlRegistry *registry = m_collection->registry();
162 int deviceId = m_collection->mountPointManager()->getIdForUrl( QUrl::fromLocalFile( path ) );
163 QString rpath = m_collection->mountPointManager()->getRelativePath( deviceId, path );
164 int directoryId = registry->getDirectory( QFileInfo( path ).path() );
165
166 // -- the track uid (we can't use the original one from the old collection)
167 Meta::FieldHash fileTags = Meta::Tag::readTags( path );
168 QString uid = fileTags.value( Meta::valUniqueId ).toString();
169 uid = m_collection->generateUidUrl( uid ); // add the right prefix
170
171 // -- the track from the registry
172 Meta::SqlTrackPtr metaTrack;
173 metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrackFromUid( uid ) );
174
175 if( metaTrack )
176 {
177 warning() << "Location is inserting a file with the same uid as an already existing one.";
178 metaTrack->setUrl( deviceId, rpath, directoryId );
179 } else
180 metaTrack = Meta::SqlTrackPtr::staticCast( registry->getTrack( deviceId, rpath, directoryId, uid ) );
181
182 Meta::ConstStatisticsPtr origStats = track->statistics();
183
184 // -- set the values
185 metaTrack->setWriteFile( false ); // no need to write the tags back
186 metaTrack->beginUpdate();
187
188 if( !track->name().isEmpty() )
189 metaTrack->setTitle( track->name() );
190 if( track->album() )
191 metaTrack->setAlbum( track->album()->name() );
192 if( track->artist() )
193 metaTrack->setArtist( track->artist()->name() );
194 if( track->composer() )
195 metaTrack->setComposer( track->composer()->name() );
196 if( track->year() && track->year()->year() > 0 )
197 metaTrack->setYear( track->year()->year() );
198 if( track->genre() )
199 metaTrack->setGenre( track->genre()->name() );
200
201 if( track->bpm() > 0 )
202 metaTrack->setBpm( track->bpm() );
203 if( !track->comment().isEmpty() )
204 metaTrack->setComment( track->comment() );
205
206 if( origStats->score() > 0 )
207 metaTrack->setScore( origStats->score() );
208 if( origStats->rating() > 0 )
209 metaTrack->setRating( origStats->rating() );
210
211 /* These tags change when transcoding. Prefer to read those from file */
212 if( fileTags.value( Meta::valLength, 0 ).toLongLong() > 0 )
213 metaTrack->setLength( fileTags.value( Meta::valLength ).value<qint64>() );
214 else if( track->length() > 0 )
215 metaTrack->setLength( track->length() );
216 // the filesize is updated every time after the
217 // file is changed. Doesn't make sense to set it.
218 if( fileTags.value( Meta::valSamplerate, 0 ).toInt() > 0 )
219 metaTrack->setSampleRate( fileTags.value( Meta::valSamplerate ).toInt() );
220 else if( track->sampleRate() > 0 )
221 metaTrack->setSampleRate( track->sampleRate() );
222 if( fileTags.value( Meta::valBitrate, 0 ).toInt() > 0 )
223 metaTrack->setBitrate( fileTags.value( Meta::valBitrate ).toInt() );
224 else if( track->bitrate() > 0 )
225 metaTrack->setBitrate( track->bitrate() );
226
227 // createDate is already set in Track constructor
228 if( track->modifyDate().isValid() )
229 metaTrack->setModifyDate( track->modifyDate() );
230
231 if( track->trackNumber() > 0 )
232 metaTrack->setTrackNumber( track->trackNumber() );
233 if( track->discNumber() > 0 )
234 metaTrack->setDiscNumber( track->discNumber() );
235
236 if( origStats->lastPlayed().isValid() )
237 metaTrack->setLastPlayed( origStats->lastPlayed() );
238 if( origStats->firstPlayed().isValid() )
239 metaTrack->setFirstPlayed( origStats->firstPlayed() );
240 if( origStats->playCount() > 0 )
241 metaTrack->setPlayCount( origStats->playCount() );
242
243 Meta::ReplayGainTag modes[] = { Meta::ReplayGain_Track_Gain,
244 Meta::ReplayGain_Track_Peak,
245 Meta::ReplayGain_Album_Gain,
246 Meta::ReplayGain_Album_Peak };
247 for( int i=0; i<4; i++ )
248 if( track->replayGain( modes[i] ) != 0 )
249 metaTrack->setReplayGain( modes[i], track->replayGain( modes[i] ) );
250
251 Meta::LabelList labels = track->labels();
252 foreach( Meta::LabelPtr label, labels )
253 metaTrack->addLabel( label );
254
255 if( fileTags.value( Meta::valFormat, int(Amarok::Unknown) ).toInt() != int(Amarok::Unknown) )
256 metaTrack->setType( Amarok::FileType( fileTags.value( Meta::valFormat ).toInt() ) );
257 else if( Amarok::FileTypeSupport::fileType( track->type() ) != Amarok::Unknown )
258 metaTrack->setType( Amarok::FileTypeSupport::fileType( track->type() ) );
259
260 // Used to be updated after changes commit to prevent crash on NULL pointer access
261 // if metaTrack had no album.
262 if( track->album() && metaTrack->album() )
263 {
264 if( track->album()->hasAlbumArtist() && !metaTrack->album()->hasAlbumArtist() )
265 metaTrack->setAlbumArtist( track->album()->albumArtist()->name() );
266
267 if( track->album()->hasImage() && !metaTrack->album()->hasImage() )
268 metaTrack->album()->setImage( track->album()->image() );
269 }
270
271 metaTrack->endUpdate();
272 metaTrack->setWriteFile( true );
273
274 // we have a first shot at the meta data (especially ratings and playcounts from media
275 // collections) but we still need to trigger the collection scanner
276 // to get the album and other meta data correct.
277 // TODO m_collection->directoryWatcher()->delayedIncrementalScan( QFileInfo(url).path() );
278
279 return true;
280 }
281
282 void
showDestinationDialog(const Meta::TrackList & tracks,bool removeSources,const Transcoding::Configuration & configuration)283 SqlCollectionLocation::showDestinationDialog( const Meta::TrackList &tracks,
284 bool removeSources,
285 const Transcoding::Configuration &configuration )
286 {
287 DEBUG_BLOCK
288 setGoingToRemoveSources( removeSources );
289
290 KIO::filesize_t transferSize = 0;
291 foreach( Meta::TrackPtr track, tracks )
292 transferSize += track->filesize();
293
294 const QStringList actual_folders = actualLocation(); // the folders in the collection
295 QStringList available_folders; // the folders which have freespace available
296 foreach(const QString &path, actual_folders)
297 {
298 if( path.isEmpty() )
299 continue;
300 debug() << "Path" << path;
301 KDiskFreeSpaceInfo spaceInfo = KDiskFreeSpaceInfo::freeSpaceInfo( path );
302 if( !spaceInfo.isValid() )
303 continue;
304
305 KIO::filesize_t totalCapacity = spaceInfo.size();
306 KIO::filesize_t used = spaceInfo.used();
307
308 KIO::filesize_t freeSpace = totalCapacity - used;
309
310 debug() << "used:" << used;
311 debug() << "total:" << totalCapacity;
312 debug() << "Free space" << freeSpace;
313 debug() << "transfersize" << transferSize;
314
315 if( totalCapacity <= 0 ) // protect against div by zero
316 continue; //How did this happen?
317
318 QFileInfo info( path );
319
320 // since bad things happen when drives become totally full
321 // we make sure there is at least ~500MB left
322 // finally, ensure the path is writable
323 debug() << ( freeSpace - transferSize );
324 if( ( freeSpace - transferSize ) > 1024*1024*500 && info.isWritable() )
325 available_folders << path;
326 }
327
328 if( available_folders.size() <= 0 )
329 {
330 debug() << "No space available or not writable";
331 CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
332 delegate->notWriteable( this );
333 abort();
334 return;
335 }
336
337 OrganizeCollectionDelegate *delegate = m_delegateFactory->createDelegate();
338 delegate->setTracks( tracks );
339 delegate->setFolders( available_folders );
340 delegate->setIsOrganizing( ( collection() == source()->collection() ) );
341 delegate->setTranscodingConfiguration( configuration );
342 delegate->setCaption( operationText( configuration ) );
343
344 connect( delegate, &OrganizeCollectionDelegate::accepted, this, &SqlCollectionLocation::slotDialogAccepted );
345 connect( delegate, &OrganizeCollectionDelegate::rejected, this, &SqlCollectionLocation::slotDialogRejected );
346 delegate->show();
347 }
348
349 void
slotDialogAccepted()350 SqlCollectionLocation::slotDialogAccepted()
351 {
352 DEBUG_BLOCK
353 sender()->deleteLater();
354 OrganizeCollectionDelegate *ocDelegate = qobject_cast<OrganizeCollectionDelegate*>( sender() );
355 m_destinations = ocDelegate->destinations();
356 m_overwriteFiles = ocDelegate->overwriteDestinations();
357 if( isGoingToRemoveSources() )
358 {
359 CollectionLocationDelegate *delegate = Amarok::Components::collectionLocationDelegate();
360 const bool del = delegate->reallyMove( this, m_destinations.keys() );
361 if( !del )
362 {
363 abort();
364 return;
365 }
366 }
367 slotShowDestinationDialogDone();
368 }
369
370 void
slotDialogRejected()371 SqlCollectionLocation::slotDialogRejected()
372 {
373 DEBUG_BLOCK
374 sender()->deleteLater();
375 abort();
376 }
377
378 void
slotJobFinished(KJob * job)379 SqlCollectionLocation::slotJobFinished( KJob *job )
380 {
381 DEBUG_BLOCK
382
383 Meta::TrackPtr track = m_jobs.value( job );
384 if( job->error() && job->error() != KIO::ERR_FILE_ALREADY_EXIST )
385 {
386 //TODO: proper error handling
387 warning() << "An error occurred when copying a file: " << job->errorString();
388 source()->transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) );
389 m_destinations.remove( track );
390 }
391 else
392 source()->transferSuccessful( track );
393
394 m_jobs.remove( job );
395 job->deleteLater();
396
397 }
398
399 void
slotRemoveJobFinished(KJob * job)400 SqlCollectionLocation::slotRemoveJobFinished( KJob *job )
401 {
402 DEBUG_BLOCK
403 Meta::TrackPtr track = m_removejobs.value( job );
404 if( job->error() )
405 {
406 //TODO: proper error handling
407 warning() << "An error occurred when removing a file: " << job->errorString();
408 }
409
410 // -- remove the track from the database if it's gone
411 if( !QFile(track->playableUrl().path()).exists() )
412 {
413 // Remove the track from the database
414 remove( track );
415
416 //we assume that KIO works correctly...
417 transferSuccessful( track );
418 }
419 else
420 {
421 transferError( track, KIO::buildErrorString( job->error(), job->errorString() ) );
422 }
423
424 m_removejobs.remove( job );
425 job->deleteLater();
426
427 if( !startNextRemoveJob() )
428 {
429 slotRemoveOperationFinished();
430 }
431
432 }
433
slotTransferJobFinished(KJob * job)434 void SqlCollectionLocation::slotTransferJobFinished( KJob* job )
435 {
436 DEBUG_BLOCK
437 if( job->error() )
438 {
439 debug() << job->errorText();
440 }
441 // filter the list of destinations to only include tracks
442 // that were successfully copied
443 foreach( const Meta::TrackPtr &track, m_destinations.keys() )
444 {
445 if( QFile::exists( m_destinations[ track ] ) )
446 insert( track, m_destinations[ track ] );
447 m_originalUrls[track] = track->playableUrl();
448 }
449 debug () << "m_originalUrls" << m_originalUrls;
450 slotCopyOperationFinished();
451 }
452
slotTransferJobAborted()453 void SqlCollectionLocation::slotTransferJobAborted()
454 {
455 DEBUG_BLOCK
456 if( !m_transferjob )
457 return;
458 m_transferjob->kill();
459 // filter the list of destinations to only include tracks
460 // that were successfully copied
461 foreach( const Meta::TrackPtr &track, m_destinations.keys() )
462 {
463 if( QFile::exists( m_destinations[ track ] ) )
464 insert( track, m_destinations[ track ] ); // was already copied, so have to insert it in the db
465 m_originalUrls[track] = track->playableUrl();
466 }
467 abort();
468 }
469
470
471 void
copyUrlsToCollection(const QMap<Meta::TrackPtr,QUrl> & sources,const Transcoding::Configuration & configuration)472 SqlCollectionLocation::copyUrlsToCollection( const QMap<Meta::TrackPtr, QUrl> &sources,
473 const Transcoding::Configuration &configuration )
474 {
475 DEBUG_BLOCK
476 m_sources = sources;
477
478 QString statusBarTxt = operationInProgressText( configuration, sources.count() );
479 m_transferjob = new TransferJob( this, configuration );
480 Amarok::Logger::newProgressOperation( m_transferjob, statusBarTxt, this,
481 &SqlCollectionLocation::slotTransferJobAborted );
482 connect( m_transferjob, &Collections::TransferJob::result,
483 this, &SqlCollectionLocation::slotTransferJobFinished );
484 m_transferjob->start();
485 }
486
487 void
removeUrlsFromCollection(const Meta::TrackList & sources)488 SqlCollectionLocation::removeUrlsFromCollection( const Meta::TrackList &sources )
489 {
490 DEBUG_BLOCK
491
492 m_removetracks = sources;
493
494 if( !startNextRemoveJob() ) //this signal needs to be called no matter what, even if there are no job finishes to call it
495 slotRemoveOperationFinished();
496 }
497
498 void
setOrganizeCollectionDelegateFactory(OrganizeCollectionDelegateFactory * fac)499 SqlCollectionLocation::setOrganizeCollectionDelegateFactory( OrganizeCollectionDelegateFactory *fac )
500 {
501 m_delegateFactory = fac;
502 }
503
startNextJob(const Transcoding::Configuration & configuration)504 bool SqlCollectionLocation::startNextJob( const Transcoding::Configuration &configuration )
505 {
506 DEBUG_BLOCK
507 if( !m_sources.isEmpty() )
508 {
509 Meta::TrackPtr track = m_sources.keys().first();
510 QUrl src = m_sources.take( track );
511
512 QUrl dest = QUrl::fromLocalFile(m_destinations[ track ]);
513 dest.setPath( QDir::cleanPath(dest.path()) );
514 src.setPath( QDir::cleanPath(src.path()) );
515
516 bool hasMoodFile = QFile::exists( moodFile( src ).toLocalFile() );
517 bool isJustCopy = configuration.isJustCopy( track );
518
519 if( isJustCopy )
520 debug() << "copying from " << src << " to " << dest;
521 else
522 debug() << "transcoding from " << src << " to " << dest;
523
524 KFileItem srcInfo( src );
525 if( !srcInfo.isFile() )
526 {
527 warning() << "Source track" << src << "was no file";
528 source()->transferError( track, i18n( "Source track does not exist: %1", src.toDisplayString() ) );
529 return true; // Attempt to copy/move the next item in m_sources
530 }
531
532 QFileInfo destInfo( dest.toLocalFile() );
533 QDir dir = destInfo.dir();
534 if( !dir.exists() )
535 {
536 if( !dir.mkpath( "." ) )
537 {
538 warning() << "Could not create directory " << dir;
539 source()->transferError(track, i18n( "Could not create directory: %1", dir.path() ) );
540 return true; // Attempt to copy/move the next item in m_sources
541 }
542 }
543
544 KIO::JobFlags flags;
545 if( isJustCopy )
546 {
547 flags = KIO::HideProgressInfo;
548 if( m_overwriteFiles )
549 {
550 flags |= KIO::Overwrite;
551 }
552 }
553
554 KJob *job = 0;
555 KJob *moodJob = 0;
556
557 if( src.matches( dest, QUrl::StripTrailingSlash ) )
558 {
559 warning() << "move to itself found: " << destInfo.absoluteFilePath();
560 m_transferjob->slotJobFinished( 0 );
561 if( m_sources.isEmpty() )
562 return false;
563 return true;
564 }
565 else if( isGoingToRemoveSources() && source()->collection() == collection() )
566 {
567 debug() << "moving!";
568 job = KIO::file_move( src, dest, -1, flags );
569 if( hasMoodFile )
570 {
571 QUrl moodSrc = moodFile( src );
572 QUrl moodDest = moodFile( dest );
573 moodJob = KIO::file_move( moodSrc, moodDest, -1, flags );
574 }
575 }
576 else
577 {
578 //later on in the case that remove is called, the file will be deleted because we didn't apply moveByDestination to the track
579 if( isJustCopy )
580 job = KIO::file_copy( src, dest, -1, flags );
581 else
582 {
583 QString destPath = dest.path();
584 destPath.truncate( dest.path().lastIndexOf( QLatin1Char('.') ) + 1 );
585 destPath.append( Amarok::Components::transcodingController()->
586 format( configuration.encoder() )->fileExtension() );
587 dest.setPath( destPath );
588 job = new Transcoding::Job( src, dest, configuration, this );
589 job->start();
590 }
591
592 if( hasMoodFile )
593 {
594 QUrl moodSrc = moodFile( src );
595 QUrl moodDest = moodFile( dest );
596 moodJob = KIO::file_copy( moodSrc, moodDest, -1, flags );
597 }
598 }
599 if( job ) //just to be safe
600 {
601 connect( job, &KJob::result, this, &SqlCollectionLocation::slotJobFinished );
602 connect( job, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished );
603 m_transferjob->addSubjob( job );
604
605 if( moodJob )
606 {
607 connect( moodJob, &KJob::result, m_transferjob, &Collections::TransferJob::slotJobFinished );
608 m_transferjob->addSubjob( moodJob );
609 }
610
611 QString name = track->prettyName();
612 if( track->artist() )
613 name = QStringLiteral( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() );
614
615 if( isJustCopy )
616 m_transferjob->emitInfo( i18n( "Transferring: %1", name ) );
617 else
618 m_transferjob->emitInfo( i18n( "Transcoding: %1", name ) );
619 m_jobs.insert( job, track );
620 return true;
621 }
622 debug() << "JOB NULL OMG!11";
623 }
624 return false;
625 }
626
startNextRemoveJob()627 bool SqlCollectionLocation::startNextRemoveJob()
628 {
629 DEBUG_BLOCK
630 while ( !m_removetracks.isEmpty() )
631 {
632 Meta::TrackPtr track = m_removetracks.takeFirst();
633 // QUrl src = track->playableUrl();
634 QUrl src = track->playableUrl();
635 QUrl srcMoodFile = moodFile( src );
636
637 debug() << "isGoingToRemoveSources() " << isGoingToRemoveSources();
638 if( isGoingToRemoveSources() && destination() ) // is organize operation?
639 {
640 SqlCollectionLocation* destinationloc = dynamic_cast<SqlCollectionLocation*>( destination() );
641
642 // src = destinationloc->m_originalUrls[track];
643 if( destinationloc && src == QUrl::fromUserInput(destinationloc->m_destinations[track]) ) {
644 debug() << "src == dst ("<<src<<")";
645 continue;
646 }
647 }
648
649 src.setPath( QDir::cleanPath(src.path()) );
650 debug() << "deleting " << src;
651 KIO::DeleteJob *job = KIO::del( src, KIO::HideProgressInfo );
652 if( job ) //just to be safe
653 {
654 if( QFile::exists( srcMoodFile.toLocalFile() ) )
655 KIO::del( srcMoodFile, KIO::HideProgressInfo );
656
657 connect( job, &KIO::DeleteJob::result, this, &SqlCollectionLocation::slotRemoveJobFinished );
658 QString name = track->prettyName();
659 if( track->artist() )
660 name = QStringLiteral( "%1 - %2" ).arg( track->artist()->name(), track->prettyName() );
661
662 Amarok::Logger::newProgressOperation( job, i18n( "Removing: %1", name ) );
663 m_removejobs.insert( job, track );
664 return true;
665 }
666 break;
667 }
668 return false;
669 }
670
671 QUrl
moodFile(const QUrl & track) const672 SqlCollectionLocation::moodFile( const QUrl &track ) const
673 {
674 QUrl moodPath = track;
675 QString fileName = moodPath.fileName();
676 moodPath = moodPath.adjusted(QUrl::RemoveFilename);
677 moodPath.setPath(moodPath.path() + '.' + fileName.replace( QRegExp( "(\\.\\w{2,5})$" ), ".mood" ) );
678 return moodPath;
679 }
680
TransferJob(SqlCollectionLocation * location,const Transcoding::Configuration & configuration)681 TransferJob::TransferJob( SqlCollectionLocation * location, const Transcoding::Configuration & configuration )
682 : KCompositeJob( 0 )
683 , m_location( location )
684 , m_killed( false )
685 , m_transcodeFormat( configuration )
686 {
687 setCapabilities( KJob::Killable );
688 debug() << "TransferJob::TransferJob";
689 }
690
addSubjob(KJob * job)691 bool TransferJob::addSubjob( KJob* job )
692 {
693 connect( job, SIGNAL(processedAmount(KJob*, KJob::Unit, qulonglong)),
694 this, SLOT(propagateProcessedAmount(KJob*, KJob::Unit, qulonglong)) );
695 //KCompositeJob::addSubjob doesn't handle progress reporting.
696 return KCompositeJob::addSubjob( job );
697 }
698
emitInfo(const QString & message)699 void TransferJob::emitInfo(const QString& message)
700 {
701 Q_EMIT infoMessage( this, message );
702 }
703
slotResult(KJob * job)704 void TransferJob::slotResult( KJob *job )
705 {
706 // When copying without overwriting some files might already be
707 // there and it is not a reason for stopping entire transfer.
708 if ( job->error() == KIO::ERR_FILE_ALREADY_EXIST )
709 removeSubjob( job );
710 else
711 KCompositeJob::slotResult( job );
712 }
713
start()714 void TransferJob::start()
715 {
716 DEBUG_BLOCK
717 if( m_location == 0 )
718 {
719 setError( 1 );
720 setErrorText( "Location is null!" );
721 emitResult();
722 return;
723 }
724 QTimer::singleShot( 0, this, &TransferJob::doWork );
725 }
726
doWork()727 void TransferJob::doWork()
728 {
729 DEBUG_BLOCK
730 setTotalAmount( KJob::Files, m_location->m_sources.size() );
731 setTotalAmount( KJob::Bytes, m_location->m_sources.size() * 1000 );
732 setProcessedAmount( KJob::Files, 0 );
733 if( !m_location->startNextJob( m_transcodeFormat ) )
734 {
735 if( !hasSubjobs() )
736 emitResult();
737 }
738 }
739
slotJobFinished(KJob * job)740 void TransferJob::slotJobFinished( KJob* job )
741 {
742 DEBUG_BLOCK
743 if( job )
744 removeSubjob( job );
745 if( m_killed )
746 {
747 debug() << "slotJobFinished entered, but it should be killed!";
748 return;
749 }
750 setProcessedAmount( KJob::Files, processedAmount( KJob::Files ) + 1 );
751 emitPercent( processedAmount( KJob::Files ) * 1000, totalAmount( KJob::Bytes ) );
752 debug() << "processed" << processedAmount( KJob::Files ) << " totalAmount" << totalAmount( KJob::Files );
753 if( !m_location->startNextJob( m_transcodeFormat ) )
754 {
755 debug() << "sources empty";
756 // don't quit if there are still subjobs
757 if( !hasSubjobs() )
758 emitResult();
759 else
760 debug() << "have subjobs";
761 }
762 }
763
doKill()764 bool TransferJob::doKill()
765 {
766 DEBUG_BLOCK
767 m_killed = true;
768 foreach( KJob* job, subjobs() )
769 {
770 job->kill();
771 }
772 clearSubjobs();
773 return KJob::doKill();
774 }
775
propagateProcessedAmount(KJob * job,KJob::Unit unit,qulonglong amount)776 void TransferJob::propagateProcessedAmount( KJob *job, KJob::Unit unit, qulonglong amount ) //SLOT
777 {
778 if( unit == KJob::Bytes )
779 {
780 qulonglong currentJobAmount = ( static_cast< qreal >( amount ) / job->totalAmount( KJob::Bytes ) ) * 1000;
781
782 setProcessedAmount( KJob::Bytes, processedAmount( KJob::Files ) * 1000 + currentJobAmount );
783 emitPercent( processedAmount( KJob::Bytes ), totalAmount( KJob::Bytes ) );
784 }
785 }
786
787