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