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