1 /*
2 SPDX-FileCopyrightText: 2011 Michal Malek <michalm@jabster.pl>
3 SPDX-FileCopyrightText: 1998-2007 Sebastian Trueg <trueg@k3b.org>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8
9 #include "k3bdiritem.h"
10 #include "k3bdatadoc.h"
11 #include "k3bsessionimportitem.h"
12 #include "k3bfileitem.h"
13 #include "k3bisooptions.h"
14 #include "k3b_i18n.h"
15
16 #include <QDebug>
17 #include <QMimeDatabase>
18
19
DirItem(const QString & name,const ItemFlags & flags)20 K3b::DirItem::DirItem(const QString& name, const ItemFlags& flags)
21 : K3b::DataItem( flags | DIR ),
22 m_size(0),
23 m_followSymlinksSize(0),
24 m_blocks(0),
25 m_followSymlinksBlocks(0),
26 m_files(0),
27 m_dirs(0)
28 {
29 m_k3bName = name;
30 }
31
32
DirItem(const K3b::DirItem & item)33 K3b::DirItem::DirItem( const K3b::DirItem& item )
34 : K3b::DataItem( item ),
35 m_size(0),
36 m_followSymlinksSize(0),
37 m_blocks(0),
38 m_followSymlinksBlocks(0),
39 m_files(0),
40 m_dirs(0),
41 m_localPath( item.m_localPath )
42 {
43 Q_FOREACH( K3b::DataItem* _item, item.children() ) {
44 addDataItem( _item->copy() );
45 }
46 }
47
~DirItem()48 K3b::DirItem::~DirItem()
49 {
50 // delete all children
51 // doing this by hand is much saver than using the
52 // auto-delete feature since some of the items' destructors
53 // may change the list
54 while( !m_children.isEmpty() ) {
55 // it is important to use takeDataItem here to be sure
56 // the size gets updated properly
57 K3b::DataItem* item = m_children.first();
58 takeDataItem( item );
59 delete item;
60 }
61
62 // this has to be done after deleting the children
63 // because the directory itself has a size of 0 in K3b
64 // and all it's files' sizes have already been subtracted
65 take();
66 }
67
68
copy() const69 K3b::DataItem* K3b::DirItem::copy() const
70 {
71 return new K3b::DirItem( *this );
72 }
73
74
getDirItem() const75 K3b::DirItem* K3b::DirItem::getDirItem() const
76 {
77 return const_cast<K3b::DirItem*>(this);
78 }
79
80
addDataItem(K3b::DataItem * item)81 K3b::DirItem* K3b::DirItem::addDataItem( K3b::DataItem* item )
82 {
83 if( canAddDataItem( item ) ) {
84
85 // Detach item from its parent in case it's moved from elsewhere.
86 // It is essential to do this before calling getDoc()->aboutToAddItemToDir()
87 // avoid situation when beginRemoveRows() is called after beginInsertRows()
88 // in DataProjectModel
89 item->take();
90
91 // inform the doc
92 if( DataDoc* doc = getDoc() ) {
93 doc->beginInsertItems( this, m_children.size(), m_children.size() );
94 }
95
96 addDataItemImpl( item );
97
98 if( DataDoc* doc = getDoc() ) {
99 doc->endInsertItems( this, m_children.size()-1, m_children.size()-1 );
100 }
101 }
102
103 return this;
104 }
105
106
addDataItems(const Children & items)107 void K3b::DirItem::addDataItems( const Children& items )
108 {
109 Children newItems;
110 newItems.reserve( items.size() );
111 Q_FOREACH( DataItem* item, items ) {
112 if( canAddDataItem( item ) ) {
113 // Detach item from its parent in case it's moved from elsewhere.
114 // It is essential to do this before calling getDoc()->aboutToAddItemToDir()
115 // avoid situation when beginRemoveRows() is called after beginInsertRows()
116 // in DataProjectModel
117 item->take();
118
119 newItems.push_back( item );
120 }
121 }
122
123 if( !newItems.empty() ) {
124 const int start = m_children.size();
125 const int end = m_children.size() + newItems.size() - 1;
126
127 // inform the doc
128 if( DataDoc* doc = getDoc() ) {
129 doc->beginInsertItems( this, start, end );
130 }
131
132 // pre-alloc space for items
133 m_children.reserve( m_children.size() + newItems.size() );
134
135 Q_FOREACH( DataItem* item, newItems ) {
136 addDataItemImpl( item );
137 }
138
139 if( DataDoc* doc = getDoc() ) {
140 doc->endInsertItems( this, start, end );
141 }
142 }
143 }
144
145
removeDataItems(int start,int count)146 void K3b::DirItem::removeDataItems( int start, int count )
147 {
148 Children takenItems = takeDataItems( start, count );
149 qDeleteAll( takenItems );
150 }
151
152
takeDataItem(K3b::DataItem * item)153 K3b::DataItem* K3b::DirItem::takeDataItem( K3b::DataItem* item )
154 {
155 int i = m_children.lastIndexOf( item );
156 if( i > -1 ) {
157 takeDataItems( i, 1 );
158 return item;
159 } else {
160 return 0;
161 }
162 }
163
164
takeDataItems(int start,int count)165 K3b::DirItem::Children K3b::DirItem::takeDataItems( int start, int count )
166 {
167 Children takenItems;
168
169 if( start >= 0 && count > 0 ) {
170 if( DataDoc* doc = getDoc() ) {
171 doc->beginRemoveItems( this, start, start+count-1 );
172 }
173
174 for( int i = 0; i < count; ++i ) {
175 DataItem* item = m_children.at( start+i );
176 updateSize( item, true );
177 if( item->isDir() )
178 updateFiles( -1*((DirItem*)item)->numFiles(), -1*((DirItem*)item)->numDirs()-1 );
179 else
180 updateFiles( -1, 0 );
181
182 item->setParentDir( 0 );
183
184 // unset OLD_SESSION flag if it was the last child from previous sessions
185 updateOldSessionFlag();
186
187 takenItems.append( item );
188 }
189
190 // filling the gap: move items from after removed range
191 std::copy( m_children.begin()+start+count, m_children.end(),
192 m_children.begin()+start );
193
194 // remove unused space
195 for( int i = 0; i < count; ++i ) {
196 m_children.pop_back();
197 }
198
199 // inform the doc
200 if( DataDoc* doc = getDoc() ) {
201 doc->endRemoveItems( this, start, start+count-1 );
202 }
203
204 Q_FOREACH( DataItem* item, takenItems ) {
205 if( item->isFile() ) {
206 // restore the item imported from an old session
207 if( DataItem* replaceItem = static_cast<FileItem*>(item)->replaceItemFromOldSession() )
208 addDataItem( replaceItem );
209 }
210 }
211 }
212
213 return takenItems;
214 }
215
216
nextSibling() const217 K3b::DataItem* K3b::DirItem::nextSibling() const
218 {
219 if( !m_children.isEmpty() )
220 return m_children.first();
221 else
222 return K3b::DataItem::nextSibling();
223 }
224
225
nextChild(K3b::DataItem * prev) const226 K3b::DataItem* K3b::DirItem::nextChild( K3b::DataItem* prev ) const
227 {
228 // search for prev in children
229 int index = m_children.lastIndexOf( prev );
230 if( index < 0 || index+1 == m_children.count() ) {
231 return 0;
232 }
233 else
234 return m_children[index+1];
235 }
236
237
alreadyInDirectory(const QString & filename) const238 bool K3b::DirItem::alreadyInDirectory( const QString& filename ) const
239 {
240 return (find( filename ) != 0);
241 }
242
243
find(const QString & filename) const244 K3b::DataItem* K3b::DirItem::find( const QString& filename ) const
245 {
246 Q_FOREACH( K3b::DataItem* item, m_children ) {
247 if( item->k3bName() == filename )
248 return item;
249 }
250 return 0;
251 }
252
253
findByPath(const QString & p)254 K3b::DataItem* K3b::DirItem::findByPath( const QString& p )
255 {
256 if( p.isEmpty() || p == "/" )
257 return this;
258
259 QString path = p;
260 if( path.startsWith('/') )
261 path = path.mid(1);
262 int pos = path.indexOf( "/" );
263 if( pos < 0 )
264 return find( path );
265 else {
266 // do it recursively
267 K3b::DataItem* item = find( path.left(pos) );
268 if( item && item->isDir() )
269 return ((K3b::DirItem*)item)->findByPath( path.mid( pos+1 ) );
270 else
271 return 0;
272 }
273 }
274
275
mkdir(const QString & dirPath)276 bool K3b::DirItem::mkdir( const QString& dirPath )
277 {
278 //
279 // An absolute path always starts at the root item
280 //
281 if( dirPath[0] == '/' ) {
282 if( parent() )
283 return parent()->mkdir( dirPath );
284 else
285 return mkdir( dirPath.mid( 1 ) );
286 }
287
288 if( findByPath( dirPath ) )
289 return false;
290
291 QString restPath;
292 QString dirName;
293 int pos = dirPath.indexOf( '/' );
294 if( pos == -1 ) {
295 dirName = dirPath;
296 }
297 else {
298 dirName = dirPath.left( pos );
299 restPath = dirPath.mid( pos+1 );
300 }
301
302 K3b::DataItem* dir = find( dirName );
303 if( !dir ) {
304 dir = new K3b::DirItem( dirName );
305 addDataItem( dir );
306 } else if( !dir->isDir() ) {
307 return false;
308 }
309
310 if( !restPath.isEmpty() )
311 return static_cast<K3b::DirItem*>(dir)->mkdir( restPath );
312
313 return true;
314 }
315
316
itemSize(bool followsylinks) const317 KIO::filesize_t K3b::DirItem::itemSize( bool followsylinks ) const
318 {
319 if( followsylinks )
320 return m_followSymlinksSize;
321 else
322 return m_size;
323 }
324
325
itemBlocks(bool followSymlinks) const326 K3b::Msf K3b::DirItem::itemBlocks( bool followSymlinks ) const
327 {
328 if( followSymlinks )
329 return m_followSymlinksBlocks;
330 else
331 return m_blocks;
332 }
333
334
isSubItem(const DataItem * item) const335 bool K3b::DirItem::isSubItem( const DataItem* item ) const
336 {
337 for( const DirItem* dir = dynamic_cast<const DirItem*>(item); dir != 0; dir = dir->parent() ) {
338 if( dir == this ) {
339 return true;
340 }
341 }
342
343 return false;
344 }
345
346
numFiles() const347 long K3b::DirItem::numFiles() const
348 {
349 return m_files;
350 }
351
352
numDirs() const353 long K3b::DirItem::numDirs() const
354 {
355 return m_dirs;
356 }
357
358
isRemoveable() const359 bool K3b::DirItem::isRemoveable() const
360 {
361 if( !K3b::DataItem::isRemoveable() )
362 return false;
363
364 for( Children::const_iterator it = m_children.constBegin(), end = m_children.constEnd(); it != end; ++it ) {
365 if( !( *it )->isRemoveable() )
366 return false;
367 }
368
369 return true;
370 }
371
372
updateSize(K3b::DataItem * item,bool removed)373 void K3b::DirItem::updateSize( K3b::DataItem* item, bool removed )
374 {
375 if ( !item->isFromOldSession() ) {
376 if( removed ) {
377 m_followSymlinksSize -= item->itemSize( true );
378 m_size -= item->itemSize( false );
379 m_followSymlinksBlocks -= item->itemBlocks( true ).lba();
380 m_blocks -= item->itemBlocks( false ).lba();
381 }
382 else {
383 m_followSymlinksSize += item->itemSize( true );
384 m_size += item->itemSize( false );
385 m_followSymlinksBlocks += item->itemBlocks( true ).lba();
386 m_blocks += item->itemBlocks( false ).lba();
387 }
388 }
389
390 if( parent() )
391 parent()->updateSize( item, removed );
392 }
393
updateFiles(long files,long dirs)394 void K3b::DirItem::updateFiles( long files, long dirs )
395 {
396 m_files += files;
397 m_dirs += dirs;
398 if( parent() )
399 parent()->updateFiles( files, dirs );
400 }
401
402
updateOldSessionFlag()403 void K3b::DirItem::updateOldSessionFlag()
404 {
405 if( flags().testFlag( OLD_SESSION ) ) {
406 for( Children::const_iterator it = m_children.constBegin(), end = m_children.constEnd(); it != end; ++it ) {
407 if( (*it)->isFromOldSession() ) {
408 return;
409 }
410 }
411 setFlags( flags() & ~OLD_SESSION );
412 }
413 }
414
415
writeToCd() const416 bool K3b::DirItem::writeToCd() const
417 {
418 // check if this dir contains items to write
419 Children::const_iterator end( m_children.constEnd() );
420 for( Children::const_iterator it = m_children.constBegin(); it != end; ++it ) {
421 if( (*it)->writeToCd() )
422 return true;
423 }
424 return K3b::DataItem::writeToCd();
425 }
426
427
mimeType() const428 QMimeType K3b::DirItem::mimeType() const
429 {
430 return QMimeDatabase().mimeTypeForName( "inode/directory" );
431 }
432
433
canAddDataItem(DataItem * item) const434 bool K3b::DirItem::canAddDataItem( DataItem* item ) const
435 {
436 // check if we are a subdir of item
437 DirItem* dirItem = dynamic_cast<DirItem*>( item );
438 if( dirItem && dirItem->isSubItem( this ) ) {
439 qDebug() << "(K3b::DirItem) trying to move a dir item down in it's own tree.";
440 return false;
441 } else if( !item || m_children.contains( item ) ) {
442 return false;
443 } else {
444 return true;
445 }
446 }
447
448
addDataItemImpl(DataItem * item)449 void K3b::DirItem::addDataItemImpl( DataItem* item )
450 {
451 if( item->isFile() ) {
452 // do we replace an old item?
453 QString name = item->k3bName();
454 int cnt = 1;
455 while( DataItem* oldItem = find( name ) ) {
456 if( !oldItem->isDir() && oldItem->isFromOldSession() ) {
457 // in this case we remove this item from it's parent and save it in the new one
458 // to be able to recover it
459 oldItem->take();
460 static_cast<SessionImportItem*>(oldItem)->setReplaceItem( static_cast<FileItem*>(item) );
461 static_cast<FileItem*>(item)->setReplacedItemFromOldSession( oldItem );
462 break;
463 }
464 else {
465 //
466 // add a counter to the filename
467 //
468 if( item->k3bName()[item->k3bName().length()-4] == '.' )
469 name = item->k3bName().left( item->k3bName().length()-4 ) + QString::number(cnt++) + item->k3bName().right(4);
470 else
471 name = item->k3bName() + QString::number(cnt++);
472 }
473 }
474 item->setK3bName( name );
475 }
476
477 m_children.append( item );
478 updateSize( item, false );
479 if( item->isDir() )
480 updateFiles( ((DirItem*)item)->numFiles(), ((DirItem*)item)->numDirs()+1 );
481 else
482 updateFiles( 1, 0 );
483
484 item->setParentDir( this );
485
486 // If item is from previous session,flag this directory as such also
487 if( !isFromOldSession() && item->isFromOldSession() ) {
488 setFlags( flags() | OLD_SESSION );
489 }
490 }
491
492
RootItem(K3b::DataDoc & doc)493 K3b::RootItem::RootItem( K3b::DataDoc& doc )
494 : K3b::DirItem( "root" ),
495 m_doc( doc )
496 {
497 }
498
499
~RootItem()500 K3b::RootItem::~RootItem()
501 {
502 }
503
504
getDoc() const505 K3b::DataDoc* K3b::RootItem::getDoc() const
506 {
507 return &m_doc;
508 }
509
510
k3bName() const511 QString K3b::RootItem::k3bName() const
512 {
513 return m_doc.isoOptions().volumeID();
514 }
515
516
setK3bName(const QString & text)517 void K3b::RootItem::setK3bName( const QString& text )
518 {
519 m_doc.setVolumeID( text );
520 }
521