1 /*
2     SPDX-FileCopyrightText: 1998-2008 Sebastian Trueg <trueg@k3b.org>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "k3bmediacache.h"
7 #include "k3bmediacache_p.h"
8 #include "k3bmedium.h"
9 #include "k3bmedium_p.h"
10 #include "k3bcddb.h"
11 #include "k3bdevicemanager.h"
12 #include "k3bdeviceglobals.h"
13 #include "k3bcore.h"
14 #include "k3b_i18n.h"
15 
16 #include <QDebug>
17 #include <QThread>
18 #include <QMutex>
19 #include <QEvent>
20 #include <QRandomGenerator>
21 
22 #include <KCddb/Client>
23 
24 
25 
DeviceEntry(K3b::MediaCache * c,K3b::Device::Device * dev)26 K3b::MediaCache::DeviceEntry::DeviceEntry( K3b::MediaCache* c, K3b::Device::Device* dev )
27     : medium(dev),
28       blockedId(0),
29       cache(c)
30 {
31     thread = new K3b::MediaCache::PollThread( this );
32     connect( thread, SIGNAL(mediumChanged(K3b::Device::Device*)),
33              c, SLOT(_k_mediumChanged(K3b::Device::Device*)),
34              Qt::QueuedConnection );
35     connect( thread, SIGNAL(checkingMedium(K3b::Device::Device*,QString)),
36              c, SIGNAL(checkingMedium(K3b::Device::Device*,QString)),
37              Qt::QueuedConnection );
38 }
39 
40 
~DeviceEntry()41 K3b::MediaCache::DeviceEntry::~DeviceEntry()
42 {
43     delete thread;
44 }
45 
46 
run()47 void K3b::MediaCache::PollThread::run()
48 {
49     while( m_deviceEntry->blockedId == 0 ) {
50         bool unitReady = m_deviceEntry->medium.device()->testUnitReady();
51         bool mediumCached = ( m_deviceEntry->medium.diskInfo().diskState() != K3b::Device::STATE_NO_MEDIA );
52 
53         //
54         // we only get the other information in case the disk state changed or if we have
55         // no info at all (FIXME: there are drives around that are not able to provide a proper
56         // disk state)
57         //
58         if( m_deviceEntry->medium.diskInfo().diskState() == K3b::Device::STATE_UNKNOWN ||
59             unitReady != mediumCached ) {
60 
61             if( m_deviceEntry->blockedId == 0 )
62                 emit checkingMedium( m_deviceEntry->medium.device(), QString() );
63 
64             //
65             // we block for writing before the update
66             // This is important to make sure we do not overwrite a reset operation
67             //
68             m_deviceEntry->writeMutex.lock();
69 
70             //
71             // The medium has changed. We need to update the information.
72             //
73             K3b::Medium m( m_deviceEntry->medium.device() );
74             m.update();
75 
76             // block the info since it is not valid anymore
77             m_deviceEntry->readMutex.lock();
78 
79             m_deviceEntry->medium = m;
80 
81             // the information is valid. let the info go.
82             m_deviceEntry->readMutex.unlock();
83             m_deviceEntry->writeMutex.unlock();
84 
85             //
86             // inform the media cache about the media change
87             //
88             if( m_deviceEntry->blockedId == 0 )
89                 emit mediumChanged( m_deviceEntry->medium.device() );
90         }
91 
92         if( m_deviceEntry->blockedId == 0 )
93             QThread::sleep( 2 );
94     }
95 }
96 
97 
98 
99 
100 
101 // ////////////////////////////////////////////////////////////////////////////////
102 // MEDIA CACHE IMPL
103 // ////////////////////////////////////////////////////////////////////////////////
104 
105 
106 class K3b::MediaCache::Private
107 {
108 public:
109     QMap<K3b::Device::Device*, DeviceEntry*> deviceMap;
110     KCDDB::Client cddbClient;
111 
112     K3b::MediaCache* q;
113 
114     void _k_mediumChanged( K3b::Device::Device* );
115     void _k_cddbJobFinished( KJob* job );
116 };
117 
118 
119 // called from the device thread which updated the medium
_k_mediumChanged(K3b::Device::Device * dev)120 void K3b::MediaCache::Private::_k_mediumChanged( K3b::Device::Device* dev )
121 {
122     if ( q->medium( dev ).content() & K3b::Medium::ContentAudio ) {
123         K3b::CDDB::CDDBJob* job = K3b::CDDB::CDDBJob::queryCddb( q->medium( dev ) );
124         connect( job, SIGNAL(result(KJob*)),
125                  q, SLOT(_k_cddbJobFinished(KJob*)) );
126         emit q->checkingMedium( dev, i18n( "CDDB Lookup" ) );
127     }
128     else {
129         emit q->mediumChanged( dev );
130     }
131 }
132 
133 
134 // once the cddb job is finished the medium is really updated
_k_cddbJobFinished(KJob * job)135 void K3b::MediaCache::Private::_k_cddbJobFinished( KJob* job )
136 {
137     K3b::CDDB::CDDBJob* cddbJob = dynamic_cast<K3b::CDDB::CDDBJob*>( job );
138     K3b::Medium oldMedium = cddbJob->medium();
139 
140     // make sure the medium did not change during the job
141     if ( oldMedium.sameMedium( q->medium( oldMedium.device() ) ) ) {
142         if ( !job->error() ) {
143             // update it
144             deviceMap[oldMedium.device()]->medium.d->cddbInfo = cddbJob->cddbResult();
145             emit q->mediumCddbChanged( oldMedium.device() );
146         }
147 
148         emit q->mediumChanged( oldMedium.device() );
149     }
150 }
151 
152 
153 
MediaCache(QObject * parent)154 K3b::MediaCache::MediaCache( QObject* parent )
155     : QObject( parent ),
156       d( new Private() )
157 {
158     d->q = this;
159 }
160 
161 
~MediaCache()162 K3b::MediaCache::~MediaCache()
163 {
164     clearDeviceList();
165     delete d;
166 }
167 
168 
blockDevice(K3b::Device::Device * dev)169 int K3b::MediaCache::blockDevice( K3b::Device::Device* dev )
170 {
171     qDebug() << dev->blockDeviceName();
172     DeviceEntry* e = findDeviceEntry( dev );
173     if( e ) {
174         if( e->blockedId )
175             return -1;
176         else {
177             // block the information
178             e->readMutex.lock();
179 
180             // create (hopefully) unique id
181             e->blockedId = QRandomGenerator::global()->bounded(RAND_MAX);
182 
183             // let the info go
184             e->readMutex.unlock();
185 
186             // wait for the thread to stop
187             e->thread->wait();
188 
189             return e->blockedId;
190         }
191     }
192     else
193         return -1;
194 }
195 
196 
unblockDevice(K3b::Device::Device * dev,int id)197 bool K3b::MediaCache::unblockDevice( K3b::Device::Device* dev, int id )
198 {
199     qDebug() << dev->blockDeviceName();
200     DeviceEntry* e = findDeviceEntry( dev );
201     if( e && e->blockedId && e->blockedId == id ) {
202         e->blockedId = 0;
203 
204         e->medium = K3b::Medium( dev );
205 
206         // restart the poll thread
207         e->thread->start();
208 
209         return true;
210     }
211     else
212         return false;
213 }
214 
215 
isBlocked(K3b::Device::Device * dev)216 bool K3b::MediaCache::isBlocked( K3b::Device::Device* dev )
217 {
218     if( DeviceEntry* e = findDeviceEntry( dev ) )
219         return ( e->blockedId != 0 );
220     else
221         return false;
222 }
223 
224 
medium(K3b::Device::Device * dev)225 K3b::Medium K3b::MediaCache::medium( K3b::Device::Device* dev )
226 {
227     if( DeviceEntry* e = findDeviceEntry( dev ) ) {
228         e->readMutex.lock();
229         K3b::Medium m = e->medium;
230         e->readMutex.unlock();
231         return m;
232     }
233     else
234         return K3b::Medium();
235 }
236 
237 
diskInfo(K3b::Device::Device * dev)238 K3b::Device::DiskInfo K3b::MediaCache::diskInfo( K3b::Device::Device* dev )
239 {
240     if( DeviceEntry* e = findDeviceEntry( dev ) ) {
241         e->readMutex.lock();
242         K3b::Device::DiskInfo di = e->medium.diskInfo();
243         e->readMutex.unlock();
244         return di;
245     }
246     else
247         return K3b::Device::DiskInfo();
248 }
249 
250 
toc(K3b::Device::Device * dev)251 K3b::Device::Toc K3b::MediaCache::toc( K3b::Device::Device* dev )
252 {
253     if( DeviceEntry* e = findDeviceEntry( dev ) ) {
254         e->readMutex.lock();
255         K3b::Device::Toc toc = e->medium.toc();
256         e->readMutex.unlock();
257         return toc;
258     }
259     else
260         return K3b::Device::Toc();
261 }
262 
263 
cdText(K3b::Device::Device * dev)264 K3b::Device::CdText K3b::MediaCache::cdText( K3b::Device::Device* dev )
265 {
266     if( DeviceEntry* e = findDeviceEntry( dev ) ) {
267         e->readMutex.lock();
268         K3b::Device::CdText cdt = e->medium.cdText();
269         e->readMutex.unlock();
270         return cdt;
271     }
272     else
273         return K3b::Device::CdText();
274 }
275 
276 
writingSpeeds(K3b::Device::Device * dev)277 QList<int> K3b::MediaCache::writingSpeeds( K3b::Device::Device* dev )
278 {
279     if( DeviceEntry* e = findDeviceEntry( dev ) ) {
280         e->readMutex.lock();
281         QList<int> ws = e->medium.writingSpeeds();
282         e->readMutex.unlock();
283         return ws;
284     }
285     else
286         return QList<int>();
287 }
288 
289 
mediumString(K3b::Device::Device * device,bool useContent)290 QString K3b::MediaCache::mediumString( K3b::Device::Device* device, bool useContent )
291 {
292     if( DeviceEntry* e = findDeviceEntry( device ) ) {
293         return e->medium.shortString( useContent ? Medium::WithContents : Medium::NoStringFlags );
294     }
295     else
296         return QString();
297 }
298 
299 
clearDeviceList()300 void K3b::MediaCache::clearDeviceList()
301 {
302     qDebug();
303 
304     // make all the threads stop
305     for( QMap<K3b::Device::Device*, DeviceEntry*>::iterator it = d->deviceMap.begin();
306          it != d->deviceMap.end(); ++it ) {
307         it.value()->blockedId = 1;
308     }
309 
310     // and remove them
311     for( QMap<K3b::Device::Device*, DeviceEntry*>::iterator it = d->deviceMap.begin();
312          it != d->deviceMap.end(); ++it ) {
313         qDebug() << " waiting for info thread " << it.key()->blockDeviceName() << " to finish";
314         it.value()->thread->wait();
315         delete it.value();
316     }
317 
318     d->deviceMap.clear();
319 }
320 
321 
buildDeviceList(K3b::Device::DeviceManager * dm)322 void K3b::MediaCache::buildDeviceList( K3b::Device::DeviceManager* dm )
323 {
324     // remember blocked ids
325     QMap<K3b::Device::Device*, int> blockedIds;
326     for( QMap<K3b::Device::Device*, DeviceEntry*>::iterator it = d->deviceMap.begin();
327          it != d->deviceMap.end(); ++it )
328         blockedIds.insert( it.key(), it.value()->blockedId );
329 
330     clearDeviceList();
331 
332     QList<K3b::Device::Device *> items(dm->allDevices());
333     for( QList<K3b::Device::Device *>::const_iterator it = items.constBegin();
334          it != items.constEnd(); ++it ) {
335         d->deviceMap.insert( *it, new DeviceEntry( this, *it ) );
336         QMap<K3b::Device::Device*, int>::const_iterator bi_it = blockedIds.constFind( *it );
337         if( bi_it != blockedIds.constEnd() )
338             d->deviceMap[*it]->blockedId = bi_it.value();
339     }
340 
341     // start all the polling threads
342     for( QMap<K3b::Device::Device*, DeviceEntry*>::iterator it = d->deviceMap.begin();
343          it != d->deviceMap.end(); ++it ) {
344         if( !it.value()->blockedId )
345             it.value()->thread->start();
346     }
347 }
348 
349 
findDeviceEntry(K3b::Device::Device * dev)350 K3b::MediaCache::DeviceEntry* K3b::MediaCache::findDeviceEntry( K3b::Device::Device* dev )
351 {
352     QMap<K3b::Device::Device*, DeviceEntry*>::iterator it = d->deviceMap.find( dev );
353     if( it != d->deviceMap.end() )
354         return it.value();
355     else
356         return 0;
357 }
358 
359 
lookupCddb(K3b::Device::Device * dev)360 void K3b::MediaCache::lookupCddb( K3b::Device::Device* dev )
361 {
362     K3b::Medium m = medium( dev );
363     if ( m.content() & K3b::Medium::ContentAudio ) {
364         K3b::CDDB::CDDBJob* job = K3b::CDDB::CDDBJob::queryCddb( m );
365         connect( job, SIGNAL(result(KJob*)),
366                  this, SLOT(_k_cddbJobFinished(KJob*)) );
367         emit checkingMedium( dev, i18n( "CDDB Lookup" ) );
368     }
369 }
370 
371 
resetDevice(K3b::Device::Device * dev)372 void K3b::MediaCache::resetDevice( K3b::Device::Device* dev )
373 {
374     if( DeviceEntry* e = findDeviceEntry( dev ) ) {
375         qDebug() << "Resetting medium in" << dev->blockDeviceName();
376         e->writeMutex.lock();
377         e->readMutex.lock();
378         e->medium.reset();
379         e->readMutex.unlock();
380         e->writeMutex.unlock();
381         // no need to emit mediumChanged here. The poll thread will act on it soon
382     }
383 }
384 
385 #include "moc_k3bmediacache.cpp"
386