diff options
Diffstat (limited to 'kdat')
103 files changed, 18747 insertions, 0 deletions
diff --git a/kdat/Archive.cpp b/kdat/Archive.cpp new file mode 100644 index 0000000..0933265 --- /dev/null +++ b/kdat/Archive.cpp @@ -0,0 +1,389 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <assert.h> + +#include <qstringlist.h> + +#include <kdebug.h> + +#include "Archive.h" +#include "Options.h" +#include "TapeManager.h" + +Archive::Archive( Tape* tape, int ctime, const QString & name ) + : _stubbed( FALSE ), + _fptr( 0 ), + _offset( 0 ), + _name( name ), + _tape( tape ) +{ + assert( _tape ); + + _ctime = ctime; +} + +Archive::Archive( Tape* tape, FILE* fptr, int offset ) + : _stubbed( TRUE ), + _tape( tape ) +{ + assert( _tape ); + + _fptr = fptr; + _offset = offset; +} + +Archive::~Archive() +{ +} + +void Archive::read( int version ) +{ + if ( !_stubbed ) { + return; + } + + _stubbed = FALSE; + + fseek( _fptr, _offset, SEEK_SET ); + + // Archive name (4 bytes + n chars). + int ival; + fread( &ival, sizeof( ival ), 1, _fptr ); + + char *buf = new char[ival + 1]; + buf[ival] = '\0'; + fread( buf, sizeof( char ), ival, _fptr ); + _name = buf; + delete [] buf; + + // Archive creation time (4 bytes). + fread( &ival, sizeof( ival ), 1, _fptr ); + _ctime = ival; + + // Archive ending block (4 bytes). + fread( &ival, sizeof( ival ), 1, _fptr ); + _endBlock = ival; + + if ( version > 3 ) { + fread( &ival, sizeof( ival ), 1, _fptr ); + int rc = ival; + int start = 0; + int end = 0; + for ( int ii = 0; ii < rc; ii++ ) { + fread( &ival, sizeof( ival ), 1, _fptr ); + start = ival; + fread( &ival, sizeof( ival ), 1, _fptr ); + end = ival; + _ranges.addRange( start, end ); + } + } + + // Number of immediate children (4 bytes). + fread( &ival, sizeof( ival ), 1, _fptr ); + + //===== Read files ===== + for ( int count = ival; count > 0; count-- ) { + fread( &ival, sizeof( ival ), 1, _fptr ); + addChild( new File( 0, _fptr, ival ) ); + } +} + +void Archive::readAll( int version ) +{ + read( version ); + + QPtrListIterator<File> i( getChildren() ); + for ( ; i.current(); ++i ) { + i.current()->readAll( version ); + } +} + +void Archive::write( FILE* fptr ) +{ + _fptr = fptr; + _offset = ftell( _fptr ); + + int zero = 0; + + // Archive name (4 bytes + n chars). + int ival = 4096; + fwrite( &ival, sizeof( ival ), 1, _fptr ); + char buf[4096]; + memset( buf, 0, 4096 ); + memcpy( buf, _name.ascii(), _name.length() > 4095 ? 4095 : _name.length() ); + fwrite( buf, sizeof( char ), 4096, _fptr ); + + // Archive creation time (4 bytes). + ival = getCTime(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + + // Archive ending block (4 bytes). + ival = getEndBlock(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + + // Child range list. + ival = _ranges.getRanges().count(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + QPtrListIterator<Range> it( _ranges.getRanges() ); + for ( ; it.current(); ++it ) { + ival = it.current()->getStart(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + ival = it.current()->getEnd(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + } + + // Number of immediate children (4 bytes). + ival = getChildren().count(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + + // Fill in file offsets later... + int fileTable = ftell( _fptr ); + for ( ; ival > 0; ival-- ) { + fwrite( &zero, sizeof( zero ), 1, _fptr ); + } + + //===== Write files ===== + QPtrListIterator<File> i( getChildren() ); + int count = 0; + for ( ; i.current(); ++i, count++ ) { + // Fill in the file offset. + int here = ftell( _fptr ); + fseek( _fptr, fileTable + 4*count, SEEK_SET ); + fwrite( &here, sizeof( here ), 1, _fptr ); + fseek( _fptr, here, SEEK_SET ); + + i.current()->write( _fptr ); + } +} + +int Archive::getCTime() +{ + read(); + + return _ctime; +} + +int Archive::getEndBlock() +{ + read(); + + return _endBlock; +} + +QString Archive::getName() +{ + read(); + + return _name; +} + +Tape* Archive::getTape() +{ + return _tape; +} + +const QPtrList<File>& Archive::getChildren() +{ + read(); + + return _children; +} + +const QPtrList<Range>& Archive::getRanges() +{ + read(); + + return _ranges.getRanges(); +} + +void Archive::setEndBlock( int endBlock ) +{ + read(); + + _endBlock = endBlock; + + if ( _fptr ) { + fseek( _fptr, _offset + 4 + 4096 + 4, SEEK_SET ); + fwrite( &_endBlock, sizeof( _endBlock ), 1, _fptr ); + } + + TapeManager::instance()->tapeModified( _tape ); +} + +void Archive::setName( const QString & name ) +{ + read(); + + _name = name; + + if ( _fptr ) { + char buf[4096]; + fseek( _fptr, _offset + 4, SEEK_SET ); + memset( buf, 0, 4096 ); + memcpy( buf, _name.ascii(), _name.length() > 4095 ? 4095 : _name.length() ); + fwrite( buf, sizeof( char ), 4096, _fptr ); + fflush( _fptr ); + } + /* 2002-01-31 LEW */ +#ifdef DEBUG + else { + printf("Archive::setName::_fptr is NULL. %s %d\n", __FILE__, __LINE__); + } +#endif /* DEBUG */ + /* 2002-01-31 LEW */ + + TapeManager::instance()->tapeModified( _tape ); +} + +void Archive::addChild( File* file ) +{ + read(); + + _children.append( file ); +} + +File* Archive::addFile( int size, int mtime, int startRecord, int endRecord, const QString & filename ) +{ + read(); + + QStringList path; + + QString fn( filename ); + int idx = 0; + while ( ( idx = fn.find( '/' ) ) > -1 ) { + path.append( fn.left( idx + 1 ) ); + fn.remove( 0, idx + 1 ); + } + + if ( fn.length() == 0 ) { + fn = path.last(); + path.remove(path.last()); + } + + File* file = 0; + if ( path.count() == 0 ) { + // Top level file/directory. + file = new File( 0, size, mtime, startRecord, endRecord, fn ); + + addChild( file ); + return file; + } + + QString dir = path.first(); + //path.remove(path.first()); + path.remove(path.begin()); + QPtrListIterator<File> i( getChildren() ); + File* parent = 0; + for ( ; i.current() ; ++i ) { + if ( i.current()->getName() == dir ) { + parent = i.current(); + break; + } + } + + if ( parent == 0 ) { + parent = new File( 0, 0, 0, startRecord, endRecord, dir ); + addChild( parent ); + } + + for ( QStringList::Iterator j = path.begin(); + j != path.end(); + ++j ) { + QString dir = *j; + File* pparent = parent; + QPtrListIterator<File> i( pparent->getChildren() ); + for ( parent = 0; i.current() ; ++i ) { + if ( i.current()->getName() == dir ) { + parent = i.current(); + break; + } + } + + if ( parent == 0 ) { + parent = new File( pparent, 0, 0, 0, 0, dir ); + pparent->addChild( parent ); + } + } + + file = new File( parent, size, mtime, startRecord, endRecord, fn ); + parent->addChild( file ); + + return file; +} + +void Archive::calcRanges() +{ + assert( !_stubbed ); + + _ranges.clear(); + + QPtrListIterator<File> it( getChildren() ); + for ( ; it.current(); ++it ) { + it.current()->calcRanges(); + QPtrListIterator<Range> it2( it.current()->getRanges() ); + for ( ; it2.current(); ++it2 ) { + _ranges.addRange( it2.current()->getStart(), it2.current()->getEnd() ); + } + } + + //%%% This is a kludge to cope with a bug that I haven't found yet. + //%%% If there is more than one range, then all of the ranges are merged + //%%% into one big contiguous range. + if ( _ranges.getRanges().count() > 1 ) { + kdDebug() << "Archive::calcRanges() -- extra ranges detected, fixing..." << endl; + QPtrListIterator<Range> iter( _ranges.getRanges() ); + for ( ; iter.current(); ++iter ) { + kdDebug() << "Archive::calcRanges() -- range = " << iter.current() << " to " << iter.current()->getEnd() << endl; + } + int start = _ranges.getRanges().getFirst()->getStart(); + int end = _ranges.getRanges().getLast()->getEnd(); + _ranges.clear(); + _ranges.addRange( start, end ); + } + + /* 2002-01-26 LEW */ + // printf("Ranges of tar files on tape (1):\n"); + /* 2002-01-26 LEW */ + assert( _ranges.getRanges().count() <= 1 ); + + /* 2002-01-26 LEW */ + { +#ifdef DEBUG +#if 0 /* commented out because of compile warnings. Fix 'em if this is needed. */ + Range *ptr; + // printf("Ranges of tar files on tape (2):\n"); + for ( ptr = _ranges.getRanges().first(); + ptr; + ptr = _ranges.getRanges().next() ){ + // printf("Range: start %d, end %d\n", ptr->getStart(), ptr->getEnd()); + } +#endif +#endif /* DEBUG */ + if( _ranges.getRanges().getFirst() == NULL ){ + printf("There aren't any tar files on this tape as far as kdat can tell!\n"); + _endBlock = 0; + return; + } + /* 2002-01-26 LEW */ + + _endBlock = _ranges.getRanges().getFirst()->getEnd() + / ( Options::instance()->getTapeBlockSize() / 512 ); + } +} + diff --git a/kdat/Archive.h b/kdat/Archive.h new file mode 100644 index 0000000..b64ae36 --- /dev/null +++ b/kdat/Archive.h @@ -0,0 +1,184 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _Archive_h_ +#define _Archive_h_ + +#include <stdio.h> + +#include <qptrlist.h> +#include <qstring.h> + +#include "File.h" + +class Tape; + +/** + * @short This class represents a single tar archive. + */ +class Archive { + bool _stubbed; + + int _endBlock; + int _ctime; + FILE* _fptr; + int _offset; + QString _name; + QPtrList<File> _children; + RangeList _ranges; + Tape* _tape; +public: + /** + * Create a new archive. + * + * @param tape The tape containing this archive. + * @param ctime The create time of the archive. + * @param name The name given to this archive by the user. + */ + Archive( Tape* tape, int ctime, const QString & name ); + + /** + * Create a new stubbed instance of an archive. The file pointer and + * offset specify where the actual instance data can be found. The real + * data is read on demand when one of the accessor functions is called. + * + * @param tape The tape containing this archive. + * @param fptr The open index file containing this archive entry. The file + * must be left open so that the archive entry information can + * be read at a later time. + * @param offset The offset that will be seeked to when reading the archive + * entry information. + */ + Archive( Tape* tape, FILE* fptr, int offset ); + + /** + * Destroy the archive entry and all of its children. + */ + ~Archive(); + + /** + * Insure that all of the data fields for this archive entry have been read + * in. If the archive entry is a stub then the actual data is read from the + * index file. If the archive entry is not a stub then no action is taken. + * + * @param version The version of the old tape index. + */ + void read( int version = KDAT_INDEX_FILE_VERSION ); + + /** + * Recursively read the instance data for this archive entry and all of its + * children. This method is used when converting from an older index format. + * + * @param version The version of the old tape index. + */ + void readAll( int version ); + + /** + * Write out the archive entry to the open file. Entries for each of its + * children will also be written. + */ + void write( FILE* fptr ); + + /** + * Get the creation time for this archive. + * + * @return The creation time in seconds since the Epoch. + */ + int getCTime(); + + /** + * Get the last tape block of this archive. + * + * @return The last tape block used by this archive. + */ + int getEndBlock(); + + /** + * Get the name of this archive. + * + * @return The name of this archive. + */ + QString getName(); + + /** + * Get the tape that contains this archive. + * + * @return A pointer to the tape containing this archive. + */ + Tape* getTape(); + + /** + * Get the list of top-level files in this archive. + * + * @return A list of the immediate children of this archive. + */ + const QPtrList<File>& getChildren(); + + /** + * Get the list of ranges of this file and all of its children. + * + * @return A list of ranges. + */ + const QPtrList<Range>& getRanges(); + + /** + * Set the ending tape block for this archive. + * + * @param endBlock The last tape block used by this archive. + */ + void setEndBlock( int endBlock ); + + /** + * Set the name of this archive. + * + * @param name The new archive name. + */ + void setName( const QString & name ); + + /** + * Add a new top level file as a child of this archive. + * + * @param file The file to add. + */ + void addChild( File* file ); + + /** + * Create a new file entry, and add it to the archive. Based on the + * full path name of the file, an appropriate parent is found. The parent + * may be this archive or another file entry. File entries will be created + * on demand if some or all of the file's path does not yet exist in this + * archive. + * + * @param size The size, in bytes, of the file. + * @param mtime The last modification time for the file, in seconds since + * the Epoch. + * @param startRecord The first tar record number of this file. + * @param endRecord The last tar record number of this file. + * @param name The full path name for the file. + * + * @return A pointer to the newly created file entry. + */ + File* addFile( int size, int mtime, int startRecord, int endRecord, const QString & filename ); + + /** + * Recursively calculate the list of ranges for all of the archive's children. + */ + void calcRanges(); +}; + +#endif diff --git a/kdat/ArchiveInfoWidget.cpp b/kdat/ArchiveInfoWidget.cpp new file mode 100644 index 0000000..225d229 --- /dev/null +++ b/kdat/ArchiveInfoWidget.cpp @@ -0,0 +1,145 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <time.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> + +#include <kapplication.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "Archive.h" +#include "ArchiveInfoWidget.h" +#include "Options.h" +#include "Tape.h" +#include "Util.h" +#include <klocale.h> + +#include "ArchiveInfoWidget.moc" + +ArchiveInfoWidget::ArchiveInfoWidget( QWidget* parent, const char* name ) + : QWidget( parent, name ), + _archive( 0 ) +{ + QLabel* lbl1 = new QLabel( i18n( "Archive name:" ), this ); + QLabel* lbl2 = new QLabel( i18n( "Created on:" ), this ); + QLabel* lbl3 = new QLabel( i18n( "Size:" ), this ); + + int max = lbl1->sizeHint().width(); + if ( lbl2->sizeHint().width() > max ) max = lbl2->sizeHint().width(); + if ( lbl3->sizeHint().width() > max ) max = lbl3->sizeHint().width(); + + lbl1->setFixedSize( max, lbl1->sizeHint().height() ); + lbl2->setFixedSize( max, lbl2->sizeHint().height() ); + lbl3->setFixedSize( max, lbl3->sizeHint().height() ); + + _archiveName = new QLineEdit( this ); + _archiveName->setFixedHeight( _archiveName->sizeHint().height() ); + + _ctime = new QLabel( "???", this ); + _ctime->setFixedHeight( _ctime->sizeHint().height() ); + + _size = new QLabel( "???", this ); + _size->setFixedHeight( _size->sizeHint().height() ); + + _apply = new KPushButton( KStdGuiItem::apply(), this ); + _apply->setFixedSize( 80, _apply->sizeHint().height() ); + _apply->setEnabled( FALSE ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 4, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addWidget( lbl1 ); + l1_1->addWidget( _archiveName, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addWidget( lbl2 ); + l1_2->addWidget( _ctime ); + + QHBoxLayout* l1_3 = new QHBoxLayout(); + l1->addLayout( l1_3 ); + l1_3->addWidget( lbl3 ); + l1_3->addWidget( _size ); + + l1->addStretch( 1 ); + + QHBoxLayout* l1_4 = new QHBoxLayout(); + l1->addLayout( l1_4 ); + l1_4->addStretch( 1 ); + l1_4->addWidget( _apply ); + + connect( _archiveName, SIGNAL( textChanged( const QString& ) ), this, SLOT( slotTextChanged( const QString& ) ) ); + connect( _apply , SIGNAL( clicked() ) , this, SLOT( slotApply() ) ); +} + +ArchiveInfoWidget::~ArchiveInfoWidget() +{ +} + +void ArchiveInfoWidget::setArchive( Archive* archive ) +{ + _archive = archive; + + if ( !_archive ) { + return; + } + + _archiveName->setText( _archive->getName() ); + + QString tmp; + time_t tm = _archive->getCTime(); + tmp = ctime( &tm ); + tmp = tmp.stripWhiteSpace(); + _ctime->setText( tmp ); + + int used = _archive->getEndBlock(); + int blockSize = Options::instance()->getTapeBlockSize(); + if ( blockSize < 1024 ) { + used /= 1024 / blockSize; + } else if ( blockSize > 1024 ) { + used *= blockSize / 1024; + } + _size->setText( Util::kbytesToString( used ) ); +} + +void ArchiveInfoWidget::slotTextChanged( const QString& text ) +{ + if ( !_archive ) { + return; + } + + _apply->setEnabled( _archive->getName() != text ); +} + +void ArchiveInfoWidget::slotApply() +{ + if ( !_archive ) { + return; + } + + if ( _archive->getName() != _archiveName->text() ) { + _archive->setName( _archiveName->text() ); + } + + _apply->setEnabled( FALSE ); +} diff --git a/kdat/ArchiveInfoWidget.h b/kdat/ArchiveInfoWidget.h new file mode 100644 index 0000000..83d7bab --- /dev/null +++ b/kdat/ArchiveInfoWidget.h @@ -0,0 +1,62 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _ArchiveInfoWidget_h_ +#define _ArchiveInfoWidget_h_ + +#include <qwidget.h> + +class QLabel; +class QLineEdit; +class QPushButton; + +class Archive; + +/** + * @short Display/edit information about an archive index. + */ +class ArchiveInfoWidget : public QWidget { + Q_OBJECT + Archive* _archive; + QLineEdit* _archiveName; + QLabel* _ctime; + QLabel* _size; + QPushButton* _apply; +private slots: + void slotTextChanged( const QString& text ); + void slotApply(); +public: + /** + * Create a new archive info widget. + */ + ArchiveInfoWidget( QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the archive info widget. + */ + ~ArchiveInfoWidget(); + + /** + * Change the archive index that the widget displays/edits. + * + * @param archive The new archive index to display/edit. + */ + void setArchive( Archive* archive ); +}; + +#endif diff --git a/kdat/BackupDlg.cpp b/kdat/BackupDlg.cpp new file mode 100644 index 0000000..1f237ab --- /dev/null +++ b/kdat/BackupDlg.cpp @@ -0,0 +1,432 @@ +// $Id$ +// +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <assert.h> +#include <stdio.h> +#include <time.h> +#include <unistd.h> +#include <sys/errno.h> +#include <sys/stat.h> + +#include <qdir.h> +#include <qlabel.h> +#include <qlayout.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include <kapplication.h> +#include <kprocess.h> +#include <kmessagebox.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "Archive.h" +#include "BackupDlg.h" +#include "LoggerWidget.h" +#include "Options.h" +#include "Tape.h" +#include "TapeDrive.h" +#include "TarParser.h" +#include "Util.h" +#include <klocale.h> + +#include "BackupDlg.moc" + +BackupDlg::BackupDlg( const QString & archiveName, const QString & workingDir, const QStringList& files, bool oneFilesystem, bool incremental, + const QString & snapshot, bool removeSnapshot, int archiveSize, Tape* tape, + QWidget* parent, const char* name ) + : QDialog( parent, name, TRUE ), + _proc( NULL ), + _tarParser( NULL ), + _archiveName( archiveName ), + _workingDir( workingDir ), + _files(files), + _oneFilesystem( oneFilesystem ), + _incremental( incremental ), + _snapshot( snapshot ), + _removeSnapshot( removeSnapshot ), + _archiveSize( archiveSize ), + _tape( tape ), + _totalKBytes( 0.0 ), + _totalRecords( 0 ), + _startTime( 0 ), + _archive( NULL ), + _aborted( FALSE ), + _numFiles( 0 ), + _fileSize( -1 ), + _fileMTime( -1 ), + _fileStartRecord( -1 ) +{ + // Copy the list of files to archive. + + setCaption( i18n( "KDat: Backup" ) ); + setIconText( i18n( "KDat: Backup" ) ); + + resize( 515, 300 ); + + /* 2002-01-26 LEW: "Time remaining" was cut off in mid-"g" + so we'll provide that plus some space beyond it. */ + // const int labelWidth = 96; + const int labelWidth = 110; + + QFrame* f1 = new QFrame( this ); + f1->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + QFrame* f2 = new QFrame( this ); + f2->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + QLabel* lbl1 = new QLabel( i18n( "Elapsed time:" ), f1 ); + lbl1->setFixedSize( labelWidth, lbl1->sizeHint().height() ); + + _elapsedTime = new QLabel( i18n( "00:00:00" ), f1 ); + _elapsedTime->setFixedHeight( _elapsedTime->sizeHint().height() ); + + QLabel* lbl2 = new QLabel( i18n( "Time remaining:" ), f2 ); + lbl2->setFixedSize( labelWidth, lbl2->sizeHint().height() ); + + _timeRemaining = new QLabel( i18n( "00:00:00" ), f2 ); + _timeRemaining->setFixedHeight( _timeRemaining->sizeHint().height() ); + + QLabel* lbl3 = new QLabel( i18n( "Total KB:" ), f1 ); + lbl3->setFixedSize( labelWidth, lbl3->sizeHint().height() ); + + QLabel* totalKbytes = new QLabel( Util::kbytesToString( archiveSize ), f1 ); + totalKbytes->setFixedHeight( totalKbytes->sizeHint().height() ); + + QLabel* lbl4 = new QLabel( i18n( "KB written:" ), f2 ); + lbl4->setFixedSize( labelWidth, lbl4->sizeHint().height() ); + + _kbytesWritten = new QLabel( i18n( "0KB" ), f2 ); + _kbytesWritten->setFixedHeight( _kbytesWritten->sizeHint().height() ); + + QLabel* lbl5 = new QLabel( i18n( "Transfer rate:" ), f1 ); + lbl5->setFixedSize( labelWidth, lbl5->sizeHint().height() ); + + _transferRate = new QLabel( i18n( "0KB/min" ), f1 ); + _transferRate->setFixedHeight( _transferRate->sizeHint().height() ); + + QLabel* lbl6 = new QLabel( i18n( "Files:" ), f2 ); + lbl6->setFixedSize( labelWidth, lbl6->sizeHint().height() ); + + _fileCount = new QLabel( i18n( "0" ), f2 ); + _fileCount->setFixedHeight( _fileCount->sizeHint().height() ); + + _log = new LoggerWidget( i18n( "Backup log:" ), this ); + + _ok = new KPushButton( KStdGuiItem::ok(), this ); + _ok->setFixedSize( 80, _ok->sizeHint().height() ); + connect( _ok, SIGNAL( clicked() ), this, SLOT( slotOK() ) ); + _ok->setEnabled( FALSE ); + + _save = new QPushButton( i18n( "Save Log..." ), this ); + _save->setFixedSize( 80, _save->sizeHint().height() ); + connect( _save, SIGNAL( clicked() ), _log, SLOT( save() ) ); + _save->setEnabled( FALSE ); + + _abort = new KPushButton( KStdGuiItem::cancel(), this ); + _abort->setFixedSize( 80, _abort->sizeHint().height() ); + connect( _abort, SIGNAL( clicked() ), this, SLOT( slotAbort() ) ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 8, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addStrut( 3 * lbl1->height() + 16 ); + l1_1->addWidget( f1 ); + l1_1->addWidget( f2 ); + + QVBoxLayout* l1_1_1 = new QVBoxLayout( f1, 4, 4 ); + + QHBoxLayout* l1_1_1_1 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_1 ); + l1_1_1_1->addWidget( lbl1 ); + l1_1_1_1->addWidget( _elapsedTime, 1 ); + + QHBoxLayout* l1_1_1_2 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_2 ); + l1_1_1_2->addWidget( lbl3 ); + l1_1_1_2->addWidget( totalKbytes, 1 ); + + QHBoxLayout* l1_1_1_3 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_3 ); + l1_1_1_3->addWidget( lbl5 ); + l1_1_1_3->addWidget( _transferRate, 1 ); + + QVBoxLayout* l1_1_2 = new QVBoxLayout( f2, 4, 4 ); + + QHBoxLayout* l1_1_2_1 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_1 ); + l1_1_2_1->addWidget( lbl2 ); + l1_1_2_1->addWidget( _timeRemaining, 1 ); + + QHBoxLayout* l1_1_2_2 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_2 ); + l1_1_2_2->addWidget( lbl4 ); + l1_1_2_2->addWidget( _kbytesWritten, 1 ); + + QHBoxLayout* l1_1_2_3 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_3 ); + l1_1_2_3->addWidget( lbl6 ); + l1_1_2_3->addWidget( _fileCount, 1 ); + + l1->addWidget( _log, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addStretch( 1 ); + l1_2->addWidget( _ok ); + l1_2->addWidget( _save ); + l1_2->addWidget( _abort ); +} + +BackupDlg::~BackupDlg() +{ + delete _tarParser; +} + +void BackupDlg::show() +{ + bool bGoOn = false; + + _archive = new Archive( _tape, time( NULL ), _archiveName.utf8() ); + + chdir( QFile::encodeName(_workingDir) ); + + if ( _removeSnapshot ) { + unlink( QFile::encodeName(_snapshot) ); + } + + _tarParser = new TarParser(); + connect( _tarParser, SIGNAL( sigEntry( const QString &, int, int, int ) ), this, SLOT( slotEntry( const QString &, int, int, int ) ) ); + + _proc = new KProcess(); + *_proc << Options::instance()->getTarCommand(); + if ( _oneFilesystem ) { + *_proc << "-l"; + } + if ( _incremental ) { + *_proc << "-g" << _snapshot; + } + *_proc << "-Spcf" << "-"; + + // Append the list of files to archive. + if ( _files.count() == 1 && _files.first() == "." ) { + dev_t device = 0; + struct stat info; + if ( _oneFilesystem ) { + if ( lstat( ".", &info ) == 0 ) { + device = info.st_dev; + } + } + + // Backup all files in current working directory. + QDir dir; + //roland + //QStringList::Iterator i = dir.entryList( QDir::All, QDir::Name | QDir::DirsFirst ).begin(); + QStringList FilesList = dir.entryList( QDir::All, QDir::Name | QDir::DirsFirst ); + QStringList::Iterator i = FilesList.begin(); + //roland + for ( ; !(*i).isNull() ; ++i ) { + if ( *i != "." && *i != ".." ) { + if ( _oneFilesystem ) { + if ( lstat( QFile::encodeName(*i), &info ) == 0 ) + { + if ( info.st_dev == device ) { + *_proc << *i; + bGoOn = true; + } + } + } else { + *_proc << *i; + bGoOn = true; + } + } + } + } else { + // Backup listed files only. + /* 2002-01-28 LEW */ + // printf("Fixing to list the files/dirs to be dumped:\n"); + /* 2002-01-28 LEW */ + for ( QStringList::Iterator it = _files.begin(); + it != _files.end(); + ++it ) { + /* 2002-01-28 LEW */ + // printf("tar argument: \"%s\"\n", (*it).latin1()); + /* 2002-01-28 LEW */ + *_proc << *it; + bGoOn = true; + } + } + + if (bGoOn == false) { + KMessageBox::information(this, i18n("No files to back up. Aborting.")); + slotAbort(); + return; + } + + connect( _proc, SIGNAL( processExited( KProcess* ) ), this, SLOT( slotProcessExited( KProcess* ) ) ); + connect( _proc, SIGNAL( receivedStdout( KProcess*, char*, int ) ), this, SLOT( slotStdout( KProcess*, char*, int ) ) ); + + startTimer( 1000 ); + + _proc->start( KProcess::NotifyOnExit, KProcess::Stdout ); + + QDialog::show(); +} + +void BackupDlg::slotProcessExited( KProcess* ) +{ + updateStats(); + + _archive->setEndBlock( _totalRecords / ( Options::instance()->getTapeBlockSize() / 512 ) ); + + if ( _fileName.length() > 0 ) { + _archive->addFile( _fileSize, _fileMTime, _fileStartRecord, _totalRecords, _fileName ); + _fileName = QString::null; + } + + TapeDrive::instance()->close(); + TapeDrive::instance()->open(); + + killTimers(); + delete _proc; + + _tape->addChild( _archive ); + + _ok->setEnabled( TRUE ); + _ok->setDefault( TRUE ); + _save->setEnabled( TRUE ); + _abort->setEnabled( FALSE ); +} + +// the KProcess passes the arguments to tar, and tar's output is piped here. +// The output is shown to _tarParser->slotData(), which figures out which files +// are going to tape and their file parameters, and saves these data into +// kdat's archive. The raw data are then written to tape with write(). +// 2002-01-27 LEW +void BackupDlg::slotStdout( KProcess*, char* buf, int len ) +{ + // Don't start throughput timer until the first block of data is written. + if ( _startTime == 0 ) { + _startTime = time( NULL ); + } + + /* 2002-01-26 LEW */ + // printf("got a line from tar, length %d bytes...\n", len); + /* 2002-01-26 LEW */ + + // Pass the data through the tar parser to extract the file info. + _tarParser->slotData( buf, len ); + + _totalKBytes += (float)len / 1024.0; + assert( len % 512 == 0 ); + _totalRecords += len / 512; + if ( TapeDrive::instance()->write( buf, len ) < len ) { + _log->append( i18n( "*** Write failed, giving up." ) ); + + if ( _proc ) { + _proc->kill(); + } + + slotProcessExited( 0 ); + } +} + +void BackupDlg::slotEntry( const QString& name, int size, int mtime, int record ) +{ + if ( _fileName.length() > 0 ) { + _archive->addFile( _fileSize, _fileMTime, _fileStartRecord, record, _fileName ); + } + + /* 2002-01-28 LEW */ + // printf("BackupDlg::slotEntry called with \"%s\", %d bytes\n", name.latin1(), size); + /* 2002-01-28 LEW */ + + _fileName = name; + _fileSize = size; + _fileMTime = mtime; + _fileStartRecord = record; + + QString tmp; + tmp.setNum( ++_numFiles ); + _fileCount->setText( tmp ); + _log->append( name ); +} + +void BackupDlg::slotOK() +{ + if ( _aborted ) { + reject(); + } else { + accept(); + } +} + +void BackupDlg::slotAbort() +{ + killTimers(); + if ( _proc ) { + _proc->kill(); + delete _proc; + } + delete _archive; + _aborted = TRUE; + + _ok->setEnabled( TRUE ); + _ok->setDefault( TRUE ); + _save->setEnabled( TRUE ); + _abort->setEnabled( FALSE ); +} + +void BackupDlg::timerEvent( QTimerEvent* ) +{ + updateStats(); +} + +void BackupDlg::updateStats() +{ + if ( _startTime == 0 ) { + return; + } + + QString str; + + int elapsed = time( NULL ) - _startTime; + str = QString::fromUtf8( QCString().sprintf( i18n( "%02d:%02d:%02d" ).utf8().data(), elapsed / 3600, elapsed / 60 % 60, elapsed % 60 ) ); + _elapsedTime->setText( str ); + + int remain = 0; + if ( (int)_totalKBytes > 0 ) { + remain = (int)(( (float)_archiveSize - _totalKBytes ) * (float)elapsed / _totalKBytes); + } + if ( remain < 0 ) { + remain = 0; + } + str = QString::fromUtf8( QCString().sprintf( i18n( "%02d:%02d:%02d" ).utf8().data(), remain / 3600, remain / 60 % 60, remain % 60 ) ); + _timeRemaining->setText( str ); + + str = Util::kbytesToString( (int)_totalKBytes ); + _kbytesWritten->setText( str ); + + if ( elapsed > 0 ) { + str = i18n( "%1/min" ).arg(Util::kbytesToString( (int)_totalKBytes *60 / elapsed ) ); + _transferRate->setText( str ); + } +} diff --git a/kdat/BackupDlg.h b/kdat/BackupDlg.h new file mode 100644 index 0000000..d15f5c4 --- /dev/null +++ b/kdat/BackupDlg.h @@ -0,0 +1,108 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _BackupDlg_h_ +#define _BackupDlg_h_ + +#include <qdialog.h> + +class QLabel; +class QPushButton; + +class KProcess; + +class Archive; +class File; +class LoggerWidget; +class Tape; +class TarParser; + +/** + * @short Status dialog for backing up files. + */ +class BackupDlg : public QDialog { + Q_OBJECT + KProcess* _proc; + TarParser* _tarParser; + QString _archiveName; + QString _workingDir; + QStringList _files; + bool _oneFilesystem; + bool _incremental; + QString _snapshot; + bool _removeSnapshot; + int _archiveSize; + Tape* _tape; + float _totalKBytes; + uint _totalRecords; + QLabel* _elapsedTime; + QLabel* _timeRemaining; + QLabel* _kbytesWritten; + QLabel* _transferRate; + QLabel* _fileCount; + LoggerWidget* _log; + QPushButton* _ok; + QPushButton* _save; + QPushButton* _abort; + int _startTime; + Archive* _archive; + bool _aborted; + int _numFiles; + + int _fileSize; + int _fileMTime; + int _fileStartRecord; + QString _fileName; + + void updateStats(); +private slots: + void slotProcessExited( KProcess* proc ); + void slotStdout( KProcess* proc, char* buf, int len ); + void slotOK(); + void slotAbort(); + void slotEntry( const QString& name, int size, int mtime, int record ); +protected: + void show(); + void timerEvent( QTimerEvent* e ); +public: + /** + * Create a backup dialog. + * + * @param archiveName The name for the new archive. + * @param workingDir The directory to backup from. + * @param files The list of files to backup. + * @param oneFilesystem TRUE means do not follow symbolic links across filesystems. + * @param incremental TRUE mean do a GNU listed incremental backup. + * @param snapshot The name of the snapshot file for an incremental backup. + * @param removeSnapshot Remove the snapshot before backing up. + * @param archiveSize The estimate size of the archive in kilobytes. + * @param tape The tape index to add the archive to. + * @param parent The parent widget for this dialog. + * @param name The name of this widget. + */ + BackupDlg( const QString & archiveName, const QString & workingDir, const QStringList& files, bool oneFilesystem, bool incremental, + const QString & snapshot, bool removeSnapshot, int archiveSize, Tape* tape, + QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the backup dialog. + */ + ~BackupDlg(); +}; + +#endif diff --git a/kdat/BackupOptDlg.cpp b/kdat/BackupOptDlg.cpp new file mode 100644 index 0000000..de4622a --- /dev/null +++ b/kdat/BackupOptDlg.cpp @@ -0,0 +1,100 @@ +// $Id$ +// +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <qlayout.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include <kapplication.h> + +#include "BackupOptDlg.h" +#include "BackupProfileWidget.h" +#include <klocale.h> + +#include "BackupOptDlg.moc" + +BackupOptDlg::BackupOptDlg( BackupProfile* backupProfile, QWidget* parent, const char* name ) + : QDialog( parent, name, TRUE ) +{ + setIconText( i18n( "KDat: Backup Options" ) ); + setCaption( i18n( "KDat: Backup Options" ) ); + + resize( 400, 300 ); + + _profile = new BackupProfileWidget( this ); + _profile->setBackupProfile( backupProfile ); + + KPushButton* ok = new KPushButton( KStdGuiItem::ok(), this ); + ok->setFixedSize( 80, ok->sizeHint().height() ); + connect( ok, SIGNAL( clicked() ), this, SLOT( accept() ) ); + KPushButton* cancel = new KPushButton( KStdGuiItem::cancel(), this ); + cancel->setFixedSize( 80, ok->sizeHint().height() ); + connect( cancel, SIGNAL( clicked() ), this, SLOT( reject() ) ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 8, 4 ); + l1->addWidget( _profile, 1 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addStretch( 1 ); + l1_1->addWidget( ok ); + l1_1->addWidget( cancel ); + + ok->setDefault( TRUE ); +} + +BackupOptDlg::~BackupOptDlg() +{ +} + +QString BackupOptDlg::getArchiveName() +{ + return _profile->getArchiveName(); +} + +QString BackupOptDlg::getWorkingDirectory() +{ + return _profile->getWorkingDirectory(); +} + +const QStringList& BackupOptDlg::getRelativeFiles() +{ + return _profile->getRelativeFiles(); +} + +bool BackupOptDlg::isOneFilesystem() +{ + return _profile->isOneFilesystem(); +} + +bool BackupOptDlg::isIncremental() +{ + return _profile->isIncremental(); +} + +QString BackupOptDlg::getSnapshotFile() +{ + return _profile->getSnapshotFile(); +} + +bool BackupOptDlg::getRemoveSnapshot() +{ + return _profile->getRemoveSnapshot(); +} diff --git a/kdat/BackupOptDlg.h b/kdat/BackupOptDlg.h new file mode 100644 index 0000000..f76a7da --- /dev/null +++ b/kdat/BackupOptDlg.h @@ -0,0 +1,99 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _BackupOptDlg_h_ +#define _BackupOptDlg_h_ + +#include <qdialog.h> + +class BackupProfile; +class BackupProfileWidget; + +/** + * @short Display/edit the parameters for a backup operation. + */ +class BackupOptDlg : public QDialog { + Q_OBJECT + BackupProfileWidget* _profile; +public: + /** + * Create a backup options dialog. + * + * @param backupProfile The backup profile. + */ + BackupOptDlg( BackupProfile* backupProfile, QWidget* parent=0, const char* name=0 ); + + /** + * Destroy the backup options dialog. + */ + ~BackupOptDlg(); + + /** + * Query the name of the archive. + * + * @return The name of the new archive. + */ + QString getArchiveName(); + + /** + * Query the working directory for the tar command. + * + * @return The working directory. + */ + QString getWorkingDirectory(); + + /** + * Query the list of files to backup, relative to the working directory. + * + * @return The file list. + */ + const QStringList& getRelativeFiles(); + + /** + * Query whether or not to cross filesystem boundaries when performing the + * backup. + * + * @return TRUE if the backup is restricted to a single filesystem, FALSE + * if the backup can cross filesystem boundaries. + */ + bool isOneFilesystem(); + + /** + * Query whether this is to be a GNU incremental backup. + * + * @return TRUE if incremental, otherwise FALSE. + */ + bool isIncremental(); + + /** + * Query the name of the snapshot file to use for an incremental backup. + * + * @return The name of the snapshot file. + */ + QString getSnapshotFile(); + + /** + * Query whether to remove the snapshot file before beginning an + * incremental backup. This has the effect of performing a full backup. + * + * @return TRUE if the snapshot file should be removed, otherwise FALSE. + */ + bool getRemoveSnapshot(); +}; + +#endif diff --git a/kdat/BackupProfile.cpp b/kdat/BackupProfile.cpp new file mode 100644 index 0000000..2116005 --- /dev/null +++ b/kdat/BackupProfile.cpp @@ -0,0 +1,293 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <qfile.h> + +#include <kstandarddirs.h> + +#include "BackupProfile.h" +#include "BackupProfileManager.h" +#include "Util.h" + +BackupProfile::BackupProfile() +{ +} + +BackupProfile::BackupProfile( const QString & name ) + : _name( name ), + _lastSavedName( name ) +{ + load(); +} + +BackupProfile::~BackupProfile() +{ +} + +void BackupProfile::load() +{ + QString filename = locateLocal( "appdata", _lastSavedName + ".bp"); + + FILE* fptr = fopen( QFile::encodeName(filename), "r" ); + if ( !fptr ) { + return; + } + + char buf[4096]; + + fgets( buf, 4096, fptr ); + int version = 0; + sscanf( buf, "%d", &version ); + + fgets( buf, 4096, fptr ); + _name = buf; + _name.truncate( _name.length() - 1 ); + + fgets( buf, 4096, fptr ); + _archiveName = buf; + _archiveName.truncate( _archiveName.length() - 1 ); + + fgets( buf, 4096, fptr ); + _workingDirectory = buf; + _workingDirectory.truncate( _workingDirectory.length() - 1 ); + + _absoluteFiles.clear(); + fgets( buf, 4096, fptr ); + int filecount = 0; + sscanf( buf, "%d", &filecount ); + for ( int i = 0; i < filecount; i++ ) { + fgets( buf, 4096, fptr ); + QString filename = buf; + filename.truncate( filename.length() - 1 ); + _absoluteFiles.append( filename ); + } + + calcRelativeFiles(); + + fgets( buf, 4096, fptr ); + int tmpBool = 0; + sscanf( buf, "%d", &tmpBool ); + _oneFilesystem = tmpBool; + + fgets( buf, 4096, fptr ); + sscanf( buf, "%d", &tmpBool ); + _incremental = tmpBool; + + fgets( buf, 4096, fptr ); + _snapshotFile = buf; + _snapshotFile.truncate( _snapshotFile.length() - 1 ); + + fgets( buf, 4096, fptr ); + sscanf( buf, "%d", &tmpBool ); + _removeSnapshot = tmpBool; + + fclose( fptr ); +} + +void BackupProfile::save() +{ + QString filename = locateLocal( "appdata", _lastSavedName + ".bp"); + bool null_name_p = _lastSavedName.isEmpty(); + + _lastSavedName = _name.copy(); + + if( TRUE == null_name_p ){ + return; + } + + unlink( QFile::encodeName(filename) ); + + FILE* fptr = fopen( QFile::encodeName(filename), "w" ); + if ( !fptr ) { + return; + } + + fprintf( fptr, "%d\n", 1 ); // Version + fprintf( fptr, "%s\n", _name.utf8().data() ); + fprintf( fptr, "%s\n", _archiveName.utf8().data() ); + fprintf( fptr, "%s\n", _workingDirectory.utf8().data() ); + fprintf( fptr, "%d\n", _absoluteFiles.count() ); + for ( QStringList::Iterator it = _absoluteFiles.begin(); + it != _absoluteFiles.end(); + ++it ) + fprintf( fptr, "%s\n", (*it).utf8().data() ); + fprintf( fptr, "%d\n", _oneFilesystem ); + fprintf( fptr, "%d\n", _incremental ); + fprintf( fptr, "%s\n", _snapshotFile.utf8().data() ); + fprintf( fptr, "%d\n", _removeSnapshot ); + + fclose( fptr ); +} + +QString BackupProfile::getName() +{ + return _name; +} + +QString BackupProfile::getArchiveName() +{ + return _archiveName; +} + +QString BackupProfile::getWorkingDirectory() +{ + return _workingDirectory; + + // LEW: is this fix necessary? +#ifdef LEW + if ( FALSE == _workingDirectory.isEmpty() ) { + return _workingDirectory; + } else { + return 0; + } +#endif +} + +const QStringList& BackupProfile::getRelativeFiles() +{ + return _relativeFiles; +} + +const QStringList& BackupProfile::getAbsoluteFiles() +{ + return _absoluteFiles; +} + +bool BackupProfile::isOneFilesystem() +{ + return _oneFilesystem; +} + +bool BackupProfile::isIncremental() +{ + return _incremental; +} + +QString BackupProfile::getSnapshotFile() +{ + return _snapshotFile; +} + +bool BackupProfile::getRemoveSnapshot() +{ + return _removeSnapshot; +} + +void BackupProfile::setName( const QString & name ) +{ + _name = name; + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::setArchiveName( const QString & archiveName ) +{ + _archiveName = archiveName; + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::setWorkingDirectory( const QString & workingDirectory ) +{ + _workingDirectory = workingDirectory; + + // Regenerate the list of relative files. + calcRelativeFiles(); + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::setAbsoluteFiles( const QStringList& files ) +{ + _absoluteFiles = files; + + // Make sure working directory is still valid. + QStringList::Iterator it = _absoluteFiles.begin(); + for ( ; + it != _absoluteFiles.end(); + ++it ) + { + if ( *it != _workingDirectory ) { + // Working directory is not a prefix for this file. + break; + } + } + + if ( it != _absoluteFiles.end() ) { + // Pick another working directory. + _workingDirectory = Util::longestCommonPath( _absoluteFiles ); + } + + // Regenerate the list of relative files. + calcRelativeFiles(); + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::setOneFilesystem( bool oneFilesystem ) +{ + _oneFilesystem = oneFilesystem; + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::setIncremental( bool incremental ) +{ + _incremental = incremental; + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::setSnapshotFile( const QString & snapshotFile ) +{ + _snapshotFile = snapshotFile; + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::setRemoveSnapshot( bool removeSnapshot ) +{ + _removeSnapshot = removeSnapshot; + + BackupProfileManager::instance()->backupProfileModified( this ); +} + +void BackupProfile::calcRelativeFiles() +{ + _relativeFiles.clear(); + int remove = _workingDirectory.length(); + if ( remove > 1 ) { + remove++; + } + + for ( QStringList::Iterator it = _absoluteFiles.begin(); + it != _absoluteFiles.end(); + ++it ) + { + QString fn = *it; + fn.remove( 0, remove ); + if ( fn.isEmpty() ) { + fn = "."; + } + _relativeFiles.append( fn ); + } +} diff --git a/kdat/BackupProfile.h b/kdat/BackupProfile.h new file mode 100644 index 0000000..2438fc5 --- /dev/null +++ b/kdat/BackupProfile.h @@ -0,0 +1,198 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _BackupProfile_h_ +#define _BackupProfile_h_ + +#include <qstring.h> +#include <qstrlist.h> + +/** + * @short This class stores all the information necessary to perform a backup. + */ +class BackupProfile { + QString _name; + QString _lastSavedName; + QString _archiveName; + QString _workingDirectory; + QStringList _relativeFiles; + QStringList _absoluteFiles; + bool _oneFilesystem; + bool _incremental; + QString _snapshotFile; + bool _removeSnapshot; + + void calcRelativeFiles(); +public: + /** + * Create a new backup profile. + */ + BackupProfile(); + + /** + * Load the named backup profile from disk. + * + * @param name The name of the backup profile. + */ + BackupProfile( const QString & name ); + + /** + * Destroy the backup profile. + */ + ~BackupProfile(); + + /** + * Load the backup profile from disk. + */ + void load(); + + /** + * Save the backup profile to disk. If the backup profile was renamed, + * the old backup profile will be removed. + */ + void save(); + + /** + * Query the name of this backup profile. + * + * @return The name of this profile. + */ + QString getName(); + + /** + * Query the name of the archive. + * + * @return The name of the new archive. + */ + QString getArchiveName(); + + /** + * Query the working directory for the tar command. + * + * @return The working directory. + */ + QString getWorkingDirectory(); + + /** + * Query the list of files to backup, relative to the working directory. + * + * @return The file list. + */ + const QStringList& getRelativeFiles(); + + /** + * Query the list of files to backup, with their full paths. + * + * @return The file list. + */ + const QStringList& getAbsoluteFiles(); + + /** + * Query whether or not to cross filesystem boundaries when performing the + * backup. + * + * @return TRUE if the backup is restricted to a single filesystem, FALSE + * if the backup can cross filesystem boundaries. + */ + bool isOneFilesystem(); + + /** + * Query whether this is to be a GNU incremental backup. + * + * @return TRUE if incremental, otherwise FALSE. + */ + bool isIncremental(); + + /** + * Query the name of the snapshot file to use for an incremental backup. + * + * @return The name of the snapshot file. + */ + QString getSnapshotFile(); + + /** + * Query whether to remove the snapshot file before beginning an + * incremental backup. This has the effect of performing a full backup. + * + * @return TRUE if the snapshot file should be removed, otherwise FALSE. + */ + bool getRemoveSnapshot(); + + /** + * Set the name of this backup profile. + * + * @param name The name of this profile. + */ + void setName( const QString & name ); + + /** + * Set the name of the archive. + * + * @param archiveName The name of the new archive. + */ + void setArchiveName( const QString & archiveName ); + + /** + * Set the working directory for the tar command. + * + * @param workingDirectory The working directory. + */ + void setWorkingDirectory( const QString & workingDirectory ); + + /** + * Set the list of files to backup, with their full paths. + * + * @param files The file list. + */ + void setAbsoluteFiles( const QStringList& files ); + + /** + * Set whether or not to cross filesystem boundaries when performing the + * backup. + * + * @param oneFilesystem TRUE if the backup is restricted to a single + * filesystem, FALSE if the backup can cross + * filesystem boundaries. + */ + void setOneFilesystem( bool oneFilesystem ); + + /** + * Set whether this is to be a GNU incremental backup. + * + * @param incremental TRUE if incremental, otherwise FALSE. + */ + void setIncremental( bool incremental ); + + /** + * Set the name of the snapshot file to use for an incremental backup. + * + * @param snapshotFile The name of the snapshot file. + */ + void setSnapshotFile( const QString & snapshotFile ); + + /** + * Set whether to remove the snapshot file before beginning an + * incremental backup. This has the effect of performing a full backup. + * + * @param removeSnapshot TRUE if the snapshot file should be removed, + * otherwise FALSE. + */ + void setRemoveSnapshot( bool removeSnapshot ); +}; + +#endif diff --git a/kdat/BackupProfileInfoWidget.cpp b/kdat/BackupProfileInfoWidget.cpp new file mode 100644 index 0000000..e294c04 --- /dev/null +++ b/kdat/BackupProfileInfoWidget.cpp @@ -0,0 +1,209 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdlib.h> +#include <time.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> + +#include <kapplication.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "BackupProfile.h" +#include "BackupProfileInfoWidget.h" +#include "BackupProfileWidget.h" +#include "KDatMainWindow.h" +#include "Options.h" +#include <klocale.h> + +#include "BackupProfileInfoWidget.moc" + +BackupProfileInfoWidget::BackupProfileInfoWidget( QWidget* parent, const char* name ) + : QWidget( parent, name ), + _backupProfile( 0 ) +{ + QLabel* lbl1 = new QLabel( i18n( "Backup profile name:" ), this ); + + int max = lbl1->sizeHint().width(); + + lbl1->setFixedSize( max, lbl1->sizeHint().height() ); + + _name = new QLineEdit( this ); + _name->setFixedHeight( _name->sizeHint().height() ); + + _profile = new BackupProfileWidget( this ); + + QPushButton* getSelection = new QPushButton( i18n( "Files >>" ), this ); + getSelection->setFixedSize( 80, getSelection->sizeHint().height() ); + + QPushButton* setSelection = new QPushButton( i18n( "<< Files" ), this ); + setSelection->setFixedSize( 80, setSelection->sizeHint().height() ); + + _apply = new KPushButton( KStdGuiItem::apply(), this ); + _apply->setFixedSize( 80, _apply->sizeHint().height() ); + _apply->setEnabled( FALSE ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 4, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addWidget( lbl1 ); + l1_1->addWidget( _name, 1 ); + + l1->addWidget( _profile, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addWidget( setSelection ); + l1_2->addWidget( getSelection ); + l1_2->addStretch( 1 ); + l1_2->addWidget( _apply ); + + connect( setSelection, SIGNAL( clicked() ) , this, SLOT( slotSetSelection() ) ); + connect( getSelection, SIGNAL( clicked() ) , this, SLOT( slotGetSelection() ) ); + connect( _name , SIGNAL( textChanged( const QString & ) ), this, SLOT( slotTextChanged( const QString & ) ) ); + connect( _profile , SIGNAL( sigSomethingChanged() ) , this, SLOT( slotSomethingChanged() ) ); + connect( _apply , SIGNAL( clicked() ) , this, SLOT( slotApply() ) ); +} + +BackupProfileInfoWidget::~BackupProfileInfoWidget() +{ +} + +void BackupProfileInfoWidget::setBackupProfile( BackupProfile* backupProfile ) +{ + _backupProfile = backupProfile; + + if ( !_backupProfile ) { + return; + } + + _name->setText( _backupProfile->getName() ); + + _profile->setBackupProfile( _backupProfile ); +} + +bool BackupProfileInfoWidget::isModified() +{ + if ( _backupProfile->getName() != _name->text() ) { + return TRUE; + } + + if ( _profile->getArchiveName() != _backupProfile->getArchiveName() ) { + return TRUE; + } + + QString one = _backupProfile->getWorkingDirectory(); + QString two = _profile->getWorkingDirectory(); + // 7/31/01: this breaks + // if ( _profile->getWorkingDirectory() != _backupProfile->getWorkingDirectory() ) { + if( one != two ){ + return TRUE; + } + + if ( _profile->getAbsoluteFiles().count() != _backupProfile->getAbsoluteFiles().count() ) { + return TRUE; + } + + QStringList list1 = _profile->getAbsoluteFiles(); + QStringList list2 = _backupProfile->getAbsoluteFiles(); + QStringList::Iterator i = list1.begin(); + QStringList::Iterator j = list2.begin(); + for ( ; i != list1.end(); ++i ) { + for ( ; j != list2.end(); ++j ) { + if ( *i == *j ) { + break; + } + } + if ( j == list2.end() ) { + // Could not find i.current() in j => lists are not equal. + return TRUE; + } + } + + if ( _profile->isOneFilesystem() != _backupProfile->isOneFilesystem() ) { + return TRUE; + } + + if ( _profile->isIncremental() != _backupProfile->isIncremental() ) { + return TRUE; + } + + if ( _profile->getSnapshotFile() != _backupProfile->getSnapshotFile() ) { + return TRUE; + } + + if ( _profile->getRemoveSnapshot() != _backupProfile->getRemoveSnapshot() ) { + return TRUE; + } + + return FALSE; +} + +void BackupProfileInfoWidget::slotTextChanged( const QString & ) +{ + if ( !_backupProfile ) { + return; + } + + _apply->setEnabled( isModified() ); +} + +void BackupProfileInfoWidget::slotSomethingChanged() +{ + if ( !_backupProfile ) { + return; + } + + _apply->setEnabled( isModified() ); +} + +void BackupProfileInfoWidget::slotApply() +{ + if ( !_backupProfile ) { + return; + } + + _backupProfile->setName( _name->text() ); + _backupProfile->setArchiveName( _profile->getArchiveName() ); + _backupProfile->setWorkingDirectory( _profile->getWorkingDirectory() ); + _backupProfile->setAbsoluteFiles( _profile->getAbsoluteFiles() ); + _backupProfile->setOneFilesystem( _profile->isOneFilesystem() ); + _backupProfile->setIncremental( _profile->isIncremental() ); + _backupProfile->setSnapshotFile( _profile->getSnapshotFile() ); + _backupProfile->setRemoveSnapshot( _profile->getRemoveSnapshot() ); + + _backupProfile->save(); + + _apply->setEnabled( FALSE ); +} + +void BackupProfileInfoWidget::slotSetSelection() +{ + KDatMainWindow::getInstance()->setBackupFiles( _profile->getAbsoluteFiles() ); +} + +void BackupProfileInfoWidget::slotGetSelection() +{ + QStringList files; + KDatMainWindow::getInstance()->getBackupFiles( files ); + _profile->setAbsoluteFiles( files ); +} diff --git a/kdat/BackupProfileInfoWidget.h b/kdat/BackupProfileInfoWidget.h new file mode 100644 index 0000000..413cf63 --- /dev/null +++ b/kdat/BackupProfileInfoWidget.h @@ -0,0 +1,62 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _BackupProfileInfoWidget_h_ +#define _BackupProfileInfoWidget_h_ + +#include <qwidget.h> + +class BackupProfileWidget; + +/** + * @short Display/edit information about a backup profile. + */ +class BackupProfileInfoWidget : public QWidget { + Q_OBJECT + BackupProfile* _backupProfile; + QLineEdit* _name; + BackupProfileWidget* _profile; + QPushButton* _apply; + + bool isModified(); +private slots: + void slotTextChanged( const QString & text ); + void slotSomethingChanged(); + void slotApply(); + void slotSetSelection(); + void slotGetSelection(); +public: + /** + * Create a new backup profile info widget. + */ + BackupProfileInfoWidget( QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the backup profile widget. + */ + ~BackupProfileInfoWidget(); + + /** + * Change the backup profile that the widget displays/edits. + * + * @param backupProfile The new backup profile to display/edit. + */ + void setBackupProfile( BackupProfile* backupProfile ); +}; + +#endif diff --git a/kdat/BackupProfileManager.cpp b/kdat/BackupProfileManager.cpp new file mode 100644 index 0000000..7fc2411 --- /dev/null +++ b/kdat/BackupProfileManager.cpp @@ -0,0 +1,112 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdlib.h> +#include <unistd.h> + +#include <qdir.h> +#include <qregexp.h> + +#include <kglobal.h> +#include <kstandarddirs.h> + +#include "BackupProfileManager.h" + +#include "BackupProfileManager.moc" + +BackupProfileManager::BackupProfileManager() +{ + _backupProfiles.setAutoDelete( TRUE ); + + // Get a list of all available backup profiles. + QStringList relList; + (void) KGlobal::dirs()->findAllResources( "appdata", "*.bp", false, true, relList); + + for(QStringList::Iterator it = relList.begin(); + it != relList.end(); + it++) + { + QString fn = *it; + // Strip extension + _backupProfileNames.append( fn.left( fn.length() - 3 ) ); + } +} + +BackupProfileManager::~BackupProfileManager() +{ +} + +BackupProfileManager* BackupProfileManager::_instance = 0; + +BackupProfileManager* BackupProfileManager::instance() +{ + if ( _instance == 0 ) { + _instance = new BackupProfileManager(); + } + + return _instance; +} + +const QStringList& BackupProfileManager::getBackupProfileNames() +{ + return _backupProfileNames; +} + +BackupProfile* BackupProfileManager::findBackupProfile( const QString & name ) +{ + BackupProfile* backupProfile = _backupProfiles[ name ]; + + if ( !backupProfile ) { + backupProfile = new BackupProfile( name ); + _backupProfiles.insert( backupProfile->getName(), backupProfile ); + } + + return backupProfile; +} + +void BackupProfileManager::addBackupProfile( BackupProfile* backupProfile ) +{ + BackupProfile* old = _backupProfiles[ backupProfile->getName() ]; + if ( old ) { + removeBackupProfile( old ); + } + + _backupProfileNames.append( backupProfile->getName() ); + _backupProfiles.insert( backupProfile->getName(), backupProfile ); + + emit sigBackupProfileAdded( backupProfile ); +} + +void BackupProfileManager::removeBackupProfile( BackupProfile* backupProfile ) +{ + emit sigBackupProfileRemoved( backupProfile ); + + // Remove the index file. + QString filename = locateLocal( "appdata", + QString(backupProfile->getName()) + ".bp"); + + unlink( QFile::encodeName(filename) ); + + _backupProfileNames.remove( backupProfile->getName() ); + _backupProfiles.remove( backupProfile->getName() ); +} + +void BackupProfileManager::backupProfileModified( BackupProfile* backupProfile ) +{ + emit sigBackupProfileModified( backupProfile ); +} diff --git a/kdat/BackupProfileManager.h b/kdat/BackupProfileManager.h new file mode 100644 index 0000000..fa6939e --- /dev/null +++ b/kdat/BackupProfileManager.h @@ -0,0 +1,120 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _BackupProfileManager_h_ +#define _BackupProfileManager_h_ + +#include <qdict.h> +#include <qobject.h> + +#include "BackupProfile.h" + +/** + * @short Control access to the set of backup profiles. + * + * Each user has a set of backup profiles that are stored under + * <TT>$HOME/.kde/share/apps/kdat/</TT>. (Changed from $HOME/.kdat in KDE1.) + * This class provides a single point of access for reading and writing + * these backup profiles. + * + * Other objects can register to be notified when a backup profile is added or + * removed, and when a backup profile is modified. + * + * The BackupProfileManager follows the Singleton pattern. + */ +class BackupProfileManager : public QObject { + Q_OBJECT + + static BackupProfileManager* _instance; + + QDict<BackupProfile> _backupProfiles; + QStringList _backupProfileNames; + + BackupProfileManager(); +public: + ~BackupProfileManager(); + + /** + * All access to the BackupProfileManager goes through this method. + * + * @return a pointer to the single instance of the BackupProfileManager. + */ + static BackupProfileManager* instance(); + + /** + * Get the list of all known backup profiles. + * + * @return a QStringList containing the backup profile names. + */ + const QStringList& getBackupProfileNames(); + + /** + * Retrieve the named backup profile. + * + * @param name The name of the backup profile. + * @return A pointer to the backup profile. + */ + BackupProfile* findBackupProfile( const QString & name ); + + /** + * Add a new backup profile. + * + * @param backupProfile A pointer to the new backup profile. + */ + void addBackupProfile( BackupProfile* backupProfile ); + + /** + * Remove a backup profile. The backup profile is removed from memory and + * from disk. A signal is emitted before the profile is actually removed. + * + * @param backupProfile A pointer to the backup profile to remove. + */ + void removeBackupProfile( BackupProfile* backupProfile ); + + /** + * Notify anyone who cares that the backup profile has been modified. + * + * @param backupProfile A pointer to the backup profile that was modified. + */ + void backupProfileModified( BackupProfile* backupProfile ); +signals: + /** + * Emitted after a new backup profile is created. + * + * @param backupProfile A pointer to the new backup profile. + */ + void sigBackupProfileAdded( BackupProfile* backupProfile ); + + /** + * Emitted before a backup profile is destroyed. This signal is emitted + * immediately before the backup profile is deleted. + * + * @param backupProfile A pointer to the backup profile that is about to + * be destroyed. + */ + void sigBackupProfileRemoved( BackupProfile* backupProfile ); + + /** + * Emitted after a backup profile has been changed in some way. + * + * @param backupProfile A pointer to the backup profile that has been modified. + */ + void sigBackupProfileModified( BackupProfile* backupProfile ); +}; + +#endif diff --git a/kdat/BackupProfileWidget.cpp b/kdat/BackupProfileWidget.cpp new file mode 100644 index 0000000..0b4dfa0 --- /dev/null +++ b/kdat/BackupProfileWidget.cpp @@ -0,0 +1,259 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <qcheckbox.h> +#include <qcombobox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistbox.h> +#include <qpushbutton.h> + +#include <kapplication.h> + +#include "BackupProfile.h" +#include "BackupProfileWidget.h" +#include "Util.h" +#include <klocale.h> + +#include "BackupProfileWidget.moc" + +BackupProfileWidget::BackupProfileWidget( QWidget* parent, const char* name ) + : KTabCtl( parent, name ) +{ + QWidget* one = new QWidget( this ); + addTab( one, i18n( "Backup" ) ); + + QLabel* lbl1 = new QLabel( i18n( "Archive name:" ), one ); + lbl1->setFixedSize( lbl1->sizeHint() ); + + _archiveName = new QLineEdit( one ); + _archiveName->setFixedHeight( _archiveName->sizeHint().height() ); + + QLabel* lbl2 = new QLabel( i18n( "Working folder:" ), one ); + lbl2->setFixedSize( lbl2->sizeHint() ); + + _workingDir = new QComboBox( FALSE, one ); + _workingDir->setFixedHeight( _workingDir->sizeHint().height() ); + + QLabel* lbl3 = new QLabel( i18n( "Backup files:" ), one ); + lbl3->setFixedHeight( lbl3->sizeHint().height() ); + + _files = new QListBox( one ); + + QWidget* two = new QWidget( this ); + addTab( two, i18n( "Tar Options" ) ); + + _oneFilesystem = new QCheckBox( i18n( "Stay on one filesystem" ), two ); + _oneFilesystem->setFixedHeight( _oneFilesystem->sizeHint().height() ); + + _incremental = new QCheckBox( i18n( "GNU listed incremental" ), two ); + _incremental->setFixedHeight( _incremental->sizeHint().height() ); + connect( _incremental, SIGNAL( toggled( bool ) ), this, SLOT( slotIncrementalToggled( bool ) ) ); + + _snapshotLabel = new QLabel( i18n( "Snapshot file:" ), two ); + _snapshotLabel->setFixedSize( _snapshotLabel->sizeHint() ); + + _snapshotFile = new QLineEdit( two ); + _snapshotFile->setFixedHeight( _snapshotFile->sizeHint().height() ); + + _removeSnapshot = new QCheckBox( i18n( "Remove snapshot file before backup" ), two ); + _removeSnapshot->setFixedHeight( _removeSnapshot->sizeHint().height() ); + + slotIncrementalToggled( FALSE ); + + QVBoxLayout* l1 = new QVBoxLayout( one, 8, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addWidget( lbl1 ); + l1_1->addWidget( _archiveName, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addWidget( lbl2 ); + l1_2->addWidget( _workingDir, 1 ); + + l1->addWidget( lbl3 ); + l1->addWidget( _files, 1 ); + + QVBoxLayout* l2 = new QVBoxLayout( two, 8, 4 ); + l2->addWidget( _oneFilesystem ); + l2->addWidget( _incremental ); + + QHBoxLayout* l2_1 = new QHBoxLayout(); + l2->addLayout( l2_1 ); + l2_1->addSpacing( 20 ); + l2_1->addWidget( _snapshotLabel ); + l2_1->addWidget( _snapshotFile, 1 ); + + QHBoxLayout* l2_2 = new QHBoxLayout(); + l2->addLayout( l2_2 ); + l2_2->addSpacing( 20 ); + l2_2->addWidget( _removeSnapshot ); + + l2->addStretch( 1 ); + + connect( _archiveName , SIGNAL( textChanged( const QString & ) ), this, SLOT( slotTextChanged( const QString & ) ) ); + connect( _workingDir , SIGNAL( activated( const QString & ) ) , this, SLOT( slotWorkingDirActivated( const QString & ) ) ); + connect( _oneFilesystem , SIGNAL( toggled( bool ) ) , this, SLOT( slotToggled( bool ) ) ); + connect( _incremental , SIGNAL( toggled( bool ) ) , this, SLOT( slotIncrementalToggled( bool ) ) ); + connect( _snapshotFile , SIGNAL( textChanged( const QString & ) ), this, SLOT( slotTextChanged( const QString & ) ) ); + connect( _removeSnapshot, SIGNAL( toggled( bool ) ) , this, SLOT( slotToggled( bool ) ) ); +} + +BackupProfileWidget::~BackupProfileWidget() +{ +} + +void BackupProfileWidget::slotTextChanged( const QString & ) +{ + emit sigSomethingChanged(); +} + +void BackupProfileWidget::slotToggled( bool ) +{ + emit sigSomethingChanged(); +} + +void BackupProfileWidget::slotIncrementalToggled( bool set ) +{ + _snapshotLabel->setEnabled( set ); + _snapshotFile->setEnabled( set ); + _removeSnapshot->setEnabled( set ); + + emit sigSomethingChanged(); +} + +void BackupProfileWidget::slotWorkingDirActivated( const QString & text ) +{ + while ( FALSE == _relativeFiles.isEmpty() ) { + QString my_first = _relativeFiles.first(); + _relativeFiles.remove( my_first ); + } + _files->clear(); + + QStringList::Iterator i = _absoluteFiles.begin(); + int remove = text.length(); + if ( remove > 1 ) { + remove++; + } + for ( ; i != _absoluteFiles.end(); ++i ) { + QString fn = *i; + fn.remove( 0, remove ); + if ( fn.isEmpty() ) { + fn = "."; + } + _files->insertItem( fn ); + _relativeFiles.append( fn ); + } + + emit sigSomethingChanged(); +} + +void BackupProfileWidget::setBackupProfile( BackupProfile* backupProfile ) +{ + // Set the archive name. + _archiveName->setText( backupProfile->getArchiveName() ); + + setAbsoluteFiles( backupProfile->getAbsoluteFiles() ); + + if ( !backupProfile->getWorkingDirectory().isNull() ) { + for ( int ii = 0; ii < _workingDir->count(); ii++ ) { + QString one = _workingDir->text( ii ); + QString two = backupProfile->getWorkingDirectory(); + // if ( _workingDir->text( ii ) == backupProfile->getWorkingDirectory() ) { + if( one == two ){ + _workingDir->setCurrentItem( ii ); + break; + } + } + } + + // slotWorkingDirActivated( _workingDir->currentText() ); + QString one = _workingDir->currentText(); + slotWorkingDirActivated( one ); + + _oneFilesystem->setChecked( backupProfile->isOneFilesystem() ); + _incremental->setChecked( backupProfile->isIncremental() ); + _snapshotFile->setText( backupProfile->getSnapshotFile() ); + _removeSnapshot->setChecked( backupProfile->getRemoveSnapshot() ); + + slotIncrementalToggled( backupProfile->isIncremental() ); +} + +void BackupProfileWidget::setAbsoluteFiles( const QStringList& files ) +{ + // Copy the string list. + _absoluteFiles = files; + + QString prefix = Util::longestCommonPath( files ); + + _workingDir->clear(); + for ( int pos = prefix.length(); pos > 0; pos = prefix.findRev( '/', pos - 1 ) ) { + _workingDir->insertItem( prefix.left( pos ) ); + } + _workingDir->insertItem( "/" ); + _workingDir->setCurrentItem( 0 ); + + slotWorkingDirActivated( _workingDir->currentText() ); +} + +QString BackupProfileWidget::getArchiveName() +{ + return _archiveName->text(); +} + +QString BackupProfileWidget::getWorkingDirectory() +{ + if ( _workingDir->count() > 0 ) { + return _workingDir->currentText(); + } else { + return 0; + } +} + +const QStringList& BackupProfileWidget::getRelativeFiles() +{ + return _relativeFiles; +} + +const QStringList& BackupProfileWidget::getAbsoluteFiles() +{ + return _absoluteFiles; +} + +bool BackupProfileWidget::isOneFilesystem() +{ + return _oneFilesystem->isChecked(); +} + +bool BackupProfileWidget::isIncremental() +{ + return _incremental->isChecked(); +} + +QString BackupProfileWidget::getSnapshotFile() +{ + return _snapshotFile->text(); +} + +bool BackupProfileWidget::getRemoveSnapshot() +{ + return _removeSnapshot->isChecked(); +} diff --git a/kdat/BackupProfileWidget.h b/kdat/BackupProfileWidget.h new file mode 100644 index 0000000..08826b9 --- /dev/null +++ b/kdat/BackupProfileWidget.h @@ -0,0 +1,148 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _BackupProfileWidget_h_ +#define _BackupProfileWidget_h_ + +#include <ktabctl.h> +#include <qstrlist.h> + +class QCheckBox; +class QComboBox; +class QLabel; +class QLineEdit; +class QListBox; + +class BackupProfile; + +/** + * @short Display/edit the parameters for a backup operation. + */ +class BackupProfileWidget : public KTabCtl { + Q_OBJECT + QLineEdit* _archiveName; + QComboBox* _workingDir; + QListBox* _files; + QCheckBox* _oneFilesystem; + QCheckBox* _incremental; + QLabel* _snapshotLabel; + QLineEdit* _snapshotFile; + QCheckBox* _removeSnapshot; + QStringList _absoluteFiles; + QStringList _relativeFiles; +private slots: + void slotTextChanged( const QString & text ); + void slotIncrementalToggled( bool set ); + void slotToggled( bool set ); + void slotWorkingDirActivated( const QString & text ); +public: + /** + * Create a backup profile widget. + * + * @param archiveName The default name for the new archive. + * @param files The list of files to be archived. + * @param parent The parent widget of this dialog. + * @param name The widget name of this dialog. + */ + BackupProfileWidget( QWidget* parent=0, const char* name=0 ); + + /** + * Destroy the backup profile widget. + */ + ~BackupProfileWidget(); + + /** + * Set the parameters for the backup profile. + * + * @param backupProfile The backup profile to display/edit. + */ + void setBackupProfile( BackupProfile* backupProfile ); + + /** + * Set the list of files for the backup profile, with full paths. + * + * @param files The list. + */ + void setAbsoluteFiles( const QStringList& files ); + + /** + * Query the name of the archive. + * + * @return The name of the new archive. + */ + QString getArchiveName(); + + /** + * Query the working directory for the tar command. + * + * @return The working directory. + */ + QString getWorkingDirectory(); + + /** + * Query the list of files to backup, relative to the working directory. + * + * @return The file list. + */ + const QStringList& getRelativeFiles(); + + /** + * Query the list of files to backup, with full paths. + * + * @return The file list. + */ + const QStringList& getAbsoluteFiles(); + + /** + * Query whether or not to cross filesystem boundaries when performing the + * backup. + * + * @return TRUE if the backup is restricted to a single filesystem, FALSE + * if the backup can cross filesystem boundaries. + */ + bool isOneFilesystem(); + + /** + * Query whether this is to be a GNU incremental backup. + * + * @return TRUE if incremental, otherwise FALSE. + */ + bool isIncremental(); + + /** + * Query the name of the snapshot file to use for an incremental backup. + * + * @return The name of the snapshot file. + */ + QString getSnapshotFile(); + + /** + * Query whether to remove the snapshot file before beginning an + * incremental backup. This has the effect of performing a full backup. + * + * @return TRUE if the snapshot file should be removed, otherwise FALSE. + */ + bool getRemoveSnapshot(); +signals: + /** + * Emitted whenever the user changes anything. + */ + void sigSomethingChanged(); +}; + +#endif diff --git a/kdat/CREDITS b/kdat/CREDITS new file mode 100644 index 0000000..21089f7 --- /dev/null +++ b/kdat/CREDITS @@ -0,0 +1,45 @@ +CREDITS + +2002-04-23 + +Bas Blender <cybersurfer at euronet.nl> offered to make a splash screen. + +2002-01-24 + +Frank Pieczynski <pieczy at knuut.de> provided improved blue icons. + +2002-01-23 + +Roland Gigler <rolandg at onlinehome.de> provided a fix for the +filename corruption on the screen display when a tape is restored. + +2002-01-20 + +Frank Pieczynski <pieczy at knuut.de> provided a workaround for +loading icons in high-color themes when repeated questions to +kde-devel yielded no useful advice. + +2001-11-25 + +All versions of kdat prior to KDE-2.x were developed and/or +supported by Sean Vyain (svyain@mail.tds.net). Starting with +KDE-2.x, Larry Widman (kdat@cardiothink.com) volunteered +to serve as maintainer because the package was about to be +dropped for lack of a maintainer. + +It is unclear what happened to Sean. One email correspondent +reported that he had been killed in a car crash. The KDE +coordinator said that multiple attempts to contact him by email +had been unsuccessful. + +Anyone who has information about Sean Vyain, who has made many +contributions to the Open Source movement, would be appreciated +and may be directed to Larry Widman (email address above). + +Re legal stuff. The copyright to the kdat source code up to +when maintainership changed is owned by Sean Vyain. If he is +in fact deceased, the copyright ownership presumably passes to +his estate. Any representative of that estate should feel free +to contact the current kdat maintainer in case licensing issues +should arise in the future. + diff --git a/kdat/ErrorHandler.cpp b/kdat/ErrorHandler.cpp new file mode 100644 index 0000000..7a60295 --- /dev/null +++ b/kdat/ErrorHandler.cpp @@ -0,0 +1,114 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdio.h> +#include <signal.h> +#include <stdlib.h> +#include <kglobal.h> +#include <qmessagebox.h> +#include <klocale.h> +//#include <kstandarddirs.h> +//#include <kapplication.h> + +void error_handler(int err_sig); + +/* include the following in main(): + signal(SIGHUP, error_handler); + signal(SIGINT, error_handler); + signal(SIGFPE, error_handler); + signal(SIGSEGV, error_handler); + signal(SIGTERM, error_handler); + + printf("Error_handler installed\n"); +*/ + +// from /usr/include/bits/signum.h: +// #define SIGHUP 1 /* Hangup (POSIX). */ +// #define SIGINT 2 /* Interrupt (ANSI). */ +// #define SIGFPE 8 /* Floating-point exception (ANSI). */ +// #define SIGSEGV 11 /* Segmentation violation (ANSI). */ +// #define SIGTERM 15 /* Termination (ANSI). */ + +void error_handler(int err_sig) +{ + QString base1a, base1b, base2a, base2b, msg1, msg2, msg3; + int i; + + base1a = i18n( " caught.\nExit the program from File->Quit or do " + "\"kill -9 <pid>\" if you like.\n" ); + base1b = base1a; // deep copy + + base2a = i18n( "You can dump core by selecting the \"Abort\" button.\n" + "Please notify the maintainer (see Help->About KDat)." ); + base2b = base2a; // deep copy + + msg1 = base1a.append(base2a); + msg2 = base1b.append("It is strongly recommended to do this!\n"); + msg2 = msg2.append(base2b); + msg3 = i18n( "An Error Signal was Received" ); + + switch (err_sig) { + case SIGHUP: + fprintf(stderr, "kdat: SIGHUP signal (\"Hangup (POSIX)\") caught\n"); + i = QMessageBox::critical( (QWidget *)NULL, + msg3, + i18n( "SIGHUP signal (\"Hangup (POSIX)\")" ).append( msg1 ), + QMessageBox::Ignore, QMessageBox::Abort, QMessageBox::NoButton); + if( i == QMessageBox::Abort ){ abort(); } + break; + case SIGINT: + fprintf(stderr, "kdat: SIGINT signal (\"Interrupt (ANSI)\") caught\n"); + i = QMessageBox::critical( (QWidget *)NULL, + msg3, + i18n( "SIGINT signal (\"Interrupt (ANSI)\")" ).append( msg1 ), + QMessageBox::Ignore, QMessageBox::Abort, QMessageBox::NoButton); + if( i == QMessageBox::Abort ){ abort(); } + break; + case SIGFPE: + fprintf(stderr, "kdat: SIGFPE signal (\"Floating-point exception (ANSI)\") caught\n"); + i = QMessageBox::critical( (QWidget *)NULL, + msg3, + i18n("SIGFPE signal (\"Floating-point exception (ANSI)\")").append (msg2), + QMessageBox::Ignore, QMessageBox::Abort, QMessageBox::NoButton); + if( i == QMessageBox::Abort ){ abort(); } + break; + case SIGSEGV: + fprintf(stderr, "kdat: SIGSEGV signal (\"Segmentation violation (ANSI)\") caught\n"); + i = QMessageBox::critical( (QWidget *)NULL, + msg3, + i18n( "SIGSEGV signal (\"Segmentation violation (ANSI)\")").append(msg2), + /* button1, button2 */ + QMessageBox::Ignore, QMessageBox::Abort, QMessageBox::NoButton); + if( i == QMessageBox::Abort ){ abort(); } + break; + case SIGTERM: + fprintf(stderr, "kdat: SIGTERM signal (\"Termination (ANSI)\") caught\n"); + i = QMessageBox::critical( (QWidget *)NULL, + msg3, + i18n( "SIGTERM signal (\"Termination (ANSI)\")").append(msg1), + QMessageBox::Ignore, QMessageBox::Abort, QMessageBox::NoButton); + if( i == QMessageBox::Abort ){ abort(); } + break; + } + + // Deinstall the signal handlers + // signal(SIGHUP, SIG_DFL); + // signal(SIGINT, SIG_DFL); + // signal(SIGFPE, SIG_DFL); + // signal(SIGSEGV, SIG_DFL); + // signal(SIGTERM, SIG_DFL); +} diff --git a/kdat/File.cpp b/kdat/File.cpp new file mode 100644 index 0000000..7fc8f97 --- /dev/null +++ b/kdat/File.cpp @@ -0,0 +1,287 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <assert.h> +#include <values.h> + +#include "File.h" + +File::File( File* parent, int size, int mtime, int startRecord, int endRecord, const QString & name ) + : _stubbed( FALSE ), + _name( name ), + _parent( parent ) +{ + assert( endRecord >= startRecord ); + + _union._data._size = size; + _union._data._mtime = mtime; + _union._data._startRecord = startRecord; + _union._data._endRecord = endRecord; +} + +File::File( File* parent, FILE* fptr, int offset ) + : _stubbed( TRUE ), + _parent( parent ) +{ + _union._stub._fptr = fptr; + _union._stub._offset = offset; +} + +File::~File() +{ + while ( _children.first() ) { + delete _children.first(); + _children.removeFirst(); + } +} + +void File::read( int version ) +{ + if ( !_stubbed ) { + return; + } + + _stubbed = FALSE; + + FILE* fptr = _union._stub._fptr; + + fseek( fptr, _union._stub._offset, SEEK_SET ); + + // File name (4 bytes + n chars). + int ival; + fread( &ival, sizeof( ival ), 1, fptr ); + + char * buf = new char[ival+1]; + buf[ival] = '\0'; + fread( buf, sizeof( char ), ival, fptr ); + _name = buf; + + delete [] buf; + + // File size (4 bytes). + fread( &ival, sizeof( ival ), 1, fptr ); + _union._data._size = ival; + + // File modification time (4 bytes). + fread( &ival, sizeof( ival ), 1, fptr ); + _union._data._mtime = ival; + + // Start record number. + fread( &ival, sizeof( ival ), 1, fptr ); + _union._data._startRecord = ival; + + // End record number. + fread( &ival, sizeof( ival ), 1, fptr ); + _union._data._endRecord = ival; + + //%%% This is a kludge to cope with some screwed up tape indexes. + //%%% Hopefully the file with the zero end record is *always* at + //%%% the end of the archive. + if ( ( _union._data._endRecord <= 0 ) && ( _union._data._startRecord != _union._data._endRecord ) ) { + _union._data._endRecord = MAXINT; + } + + if ( version > 3 ) { + fread( &ival, sizeof( ival ), 1, fptr ); + int rc = ival; + int start = 0; + int end = 0; + for ( int ii = 0; ii < rc; ii++ ) { + fread( &ival, sizeof( ival ), 1, fptr ); + start = ival; + fread( &ival, sizeof( ival ), 1, fptr ); + end = ival; + _ranges.addRange( start, end ); + } + } + + //===== Read files ===== + fread( &ival, sizeof( ival ), 1, fptr ); + for ( int count = ival; count > 0; count-- ) { + fread( &ival, sizeof( ival ), 1, fptr ); + addChild( new File( this, fptr, ival ) ); + } +} + +void File::readAll( int version ) +{ + read( version ); + + QPtrListIterator<File> i( getChildren() ); + for ( ; i.current(); ++i ) { + i.current()->readAll( version ); + } +} + +void File::write( FILE* fptr ) +{ + int zero = 0; + + // File name (4 bytes + n chars). + int ival = getName().length(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + fwrite( getName().ascii(), sizeof( char ), ival, fptr ); + + // File size (4 bytes). + ival = getSize(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + + // File modification time (4 bytes). + ival = getMTime(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + + // Start record number. + ival = getStartRecord(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + + // End record number. + ival = getEndRecord(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + + // Child range list. + ival = _ranges.getRanges().count(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + QPtrListIterator<Range> it( _ranges.getRanges() ); + for ( ; it.current(); ++it ) { + ival = it.current()->getStart(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + ival = it.current()->getEnd(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + } + + // Number of immediate children (4 bytes). + ival = getChildren().count(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + + // Fill in file offsets later... + int fileTable = ftell( fptr ); + for ( ; ival > 0; ival-- ) { + fwrite( &zero, sizeof( zero ), 1, fptr ); + } + + //===== Write files ===== + ival = _children.count(); + fwrite( &ival, sizeof( ival ), 1, fptr ); + + QPtrListIterator<File> i( _children ); + int count = 0; + for ( ; i.current(); ++i, count++ ) { + // Fill in the file offset. + int here = ftell( fptr ); + fseek( fptr, fileTable + 4*count, SEEK_SET ); + fwrite( &here, sizeof( here ), 1, fptr ); + fseek( fptr, here, SEEK_SET ); + + i.current()->write( fptr ); + } +} + +bool File::isDirectory() +{ + read(); + + return _name[ _name.length() - 1 ] == '/'; +} + +int File::getSize() +{ + read(); + + return _union._data._size; +} + +int File::getMTime() +{ + read(); + + return _union._data._mtime; +} + +int File::getStartRecord() +{ + read(); + + return _union._data._startRecord; +} + +int File::getEndRecord() +{ + read(); + + return _union._data._endRecord; +} + +QString File::getName() +{ + read(); + + return _name; +} + +QString File::getFullPathName() +{ + QString tmp = _name.copy(); + for ( File* parent = getParent(); parent; parent = parent->getParent() ) { + tmp.prepend( parent->getName() ); + } + + return tmp; +} + +File* File::getParent() +{ + return _parent; +} + +const QPtrList<File>& File::getChildren() +{ + read(); + + return _children; +} + +const QPtrList<Range>& File::getRanges() +{ + read(); + + return _ranges.getRanges(); +} + +void File::addChild( File* file ) +{ + read(); + + _children.append( file ); +} + +void File::calcRanges() +{ + assert( !_stubbed ); + + _ranges.clear(); + _ranges.addRange( getStartRecord(), getEndRecord() ); + + QPtrListIterator<File> it( getChildren() ); + for ( ; it.current(); ++it ) { + it.current()->calcRanges(); + QPtrListIterator<Range> it2( it.current()->getRanges() ); + for ( ; it2.current(); ++it2 ) { + _ranges.addRange( it2.current()->getStart(), it2.current()->getEnd() ); + } + } +} diff --git a/kdat/File.h b/kdat/File.h new file mode 100644 index 0000000..0dda512 --- /dev/null +++ b/kdat/File.h @@ -0,0 +1,201 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef File_H +#define File_H + +#include <stdio.h> + +#include <qptrlist.h> +#include <qstring.h> + +#include "Range.h" +#include "kdat.h" + +/** + * @short This class represents a single file or directory in a tar file. + */ +class File { + bool _stubbed; + union { + struct { + int _size; + int _mtime; + int _startRecord; + int _endRecord; + } _data; + struct { + FILE* _fptr; + int _offset; + } _stub; + } _union; + QString _name; + File* _parent; + QPtrList<File> _children; + RangeList _ranges; +public: + /** + * Create a new file entry. + * + * @param parent The directory file entry that contains this file. + * @param size The size of the file in bytes. + * @param mtime The last modification time of the file in seconds since + * the Epoch. + * @param startRecord The first tar record number in the file. + * @param endRecord The last tar record number in the file. + * @param name The file name. If the file name ends with a '/' then it + * is assumed to be a directory name. Only the last part of + * the file's path name should be passed in. The rest of the + * path is determined by this file entry's ancestors. + */ + File( File* parent, int size, int mtime, int startRecord, int endRecord, const QString & name ); + + /** + * Create a new stubbed instance of a file entry. The file pointer and + * offset specify where the actual instance data can be found. The real + * data is read on demand when one of the accessor functions is called. + * + * @param parent The directory file entry that contains this file. + * @param fptr The open index file containing this file entry. The file + * must be left open so that the file entry information can + * be read at a later time. + * @param offset The offset that will be seeked to when reading the file + * entry information. + */ + File( File* parent, FILE* fptr, int offset ); + + /** + * Destroy the file entry and all of its children. + */ + ~File(); + + /** + * Insure that all of the data fields for this file entry have been read + * in. If the file entry is a stub then the actual data is read from the + * index file. If the file entry is not a stub then no action is taken. + * + * @param version The version of the old tape index. + */ + void read( int version = KDAT_INDEX_FILE_VERSION ); + + /** + * Recursively read the instance for this file entry and all of it's + * children. This method is used when converting from an older index format. + * + * @param version The version of the old tape index. + */ + void readAll( int version ); + + /** + * Write out the file entry to the open file. Entries for each of its + * children will also be written. + */ + void write( FILE* fptr ); + + /** + * Determine whether this file entry represents a directory. If the file + * name ends in a '/' then it is assumed that it is a directory. + * + * @return TRUE if the file represents a directory, or FALSE if it is an + * ordinary file. + */ + bool isDirectory(); + + /** + * Get the size of the file. + * + * @return The size, in bytes, of the file. + */ + int getSize(); + + /** + * Get the last modification time for the file. + * + * @ return The last time the file was modified, in seconds since the Epoch. + */ + int getMTime(); + + /** + * Get the tar record number for the file. This is the number of 512-byte + * tar blocks that must be read before getting to this file. + * + * @return The tar record number for the file. + */ + int getStartRecord(); + + /** + * Get the tar record number that is just past the end of the file. This + * number minus the start record gives the number of 512-byte tar blocks + * that this file occupies in the archive. + * + * @return The last tar record number for the file. + */ + int getEndRecord(); + + /** + * Get the name of this file. Only the last component of the full path + * name is returned. + * + * @return The file's name. + */ + QString getName(); + + /** + * Get the full path name of the file. + * + * @return The full path to the file. + */ + QString getFullPathName(); + + /** + * Get the file entry's parent. A NULL parent indicates that this is one + * of (possibly) many top level directories within the tar archive. + * + * @return A pointer to the file entry that contains this file entry. + */ + File* getParent(); + + /** + * Get the children of this file entry. A normal file will never have any + * children. A directory may or may not have children. + * + * @return A list of the immediate children of this file entry. + */ + const QPtrList<File>& getChildren(); + + /** + * Get the list of ranges of this file and all of its children. + * + * @return A list of ranges. + */ + const QPtrList<Range>& getRanges(); + + /** + * Add a new file entry as a child of this file entry. + * + * @param file The file to add. + */ + void addChild( File* file ); + + /** + * Recursively calculate the list of ranges for all of the file's children. + */ + void calcRanges(); +}; + +#endif diff --git a/kdat/FileInfoWidget.cpp b/kdat/FileInfoWidget.cpp new file mode 100644 index 0000000..4a2a986 --- /dev/null +++ b/kdat/FileInfoWidget.cpp @@ -0,0 +1,173 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <pwd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <grp.h> +#include <time.h> + +#include <qdatetime.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qfile.h> + +#include <kapplication.h> +#include <kglobal.h> +#include <klocale.h> + +#include "FileInfoWidget.h" +#include "Util.h" + +#include "FileInfoWidget.moc" + +FileInfoWidget::FileInfoWidget( QWidget* parent, const char* name ) + : QWidget( parent, name ) +{ + QLabel* lbl1 = new QLabel( i18n( "File name:" ), this ); + QLabel* lbl2 = new QLabel( i18n( "Created on:" ), this ); + QLabel* lbl3 = new QLabel( i18n( "Last modified:" ), this ); + QLabel* lbl4 = new QLabel( i18n( "Last accessed:" ), this ); + QLabel* lbl5 = new QLabel( i18n( "Size:" ), this ); + QLabel* lbl6 = new QLabel( i18n( "Owner:" ), this ); + QLabel* lbl7 = new QLabel( i18n( "Group:" ), this ); + + int max = lbl1->sizeHint().width(); + if ( lbl2->sizeHint().width() > max ) max = lbl2->sizeHint().width(); + if ( lbl3->sizeHint().width() > max ) max = lbl3->sizeHint().width(); + if ( lbl4->sizeHint().width() > max ) max = lbl4->sizeHint().width(); + if ( lbl5->sizeHint().width() > max ) max = lbl5->sizeHint().width(); + if ( lbl6->sizeHint().width() > max ) max = lbl6->sizeHint().width(); + if ( lbl7->sizeHint().width() > max ) max = lbl7->sizeHint().width(); + + lbl1->setFixedSize( max, lbl1->sizeHint().height() ); + lbl2->setFixedSize( max, lbl2->sizeHint().height() ); + lbl3->setFixedSize( max, lbl3->sizeHint().height() ); + lbl4->setFixedSize( max, lbl4->sizeHint().height() ); + lbl5->setFixedSize( max, lbl5->sizeHint().height() ); + lbl6->setFixedSize( max, lbl6->sizeHint().height() ); + lbl7->setFixedSize( max, lbl7->sizeHint().height() ); + + _fileName = new QLabel( "???", this ); + _fileName->setFixedHeight( _fileName->sizeHint().height() ); + + _ctime = new QLabel( "???", this ); + _ctime->setFixedHeight( _ctime->sizeHint().height() ); + + _mtime = new QLabel( "???", this ); + _mtime->setFixedHeight( _mtime->sizeHint().height() ); + + _atime = new QLabel( "???", this ); + _atime->setFixedHeight( _atime->sizeHint().height() ); + + _size = new QLabel( "???", this ); + _size->setFixedHeight( _size->sizeHint().height() ); + + _owner = new QLabel( "???", this ); + _owner->setFixedHeight( _owner->sizeHint().height() ); + + _group = new QLabel( "???", this ); + _group->setFixedHeight( _group->sizeHint().height() ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 4, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addWidget( lbl1 ); + l1_1->addWidget( _fileName, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addWidget( lbl2 ); + l1_2->addWidget( _ctime, 1 ); + + QHBoxLayout* l1_3 = new QHBoxLayout(); + l1->addLayout( l1_3 ); + l1_3->addWidget( lbl3 ); + l1_3->addWidget( _mtime, 1 ); + + QHBoxLayout* l1_4 = new QHBoxLayout(); + l1->addLayout( l1_4 ); + l1_4->addWidget( lbl4 ); + l1_4->addWidget( _atime, 1 ); + + QHBoxLayout* l1_5 = new QHBoxLayout(); + l1->addLayout( l1_5 ); + l1_5->addWidget( lbl5 ); + l1_5->addWidget( _size, 1 ); + + QHBoxLayout* l1_6 = new QHBoxLayout(); + l1->addLayout( l1_6 ); + l1_6->addWidget( lbl6 ); + l1_6->addWidget( _owner, 1 ); + + QHBoxLayout* l1_7 = new QHBoxLayout(); + l1->addLayout( l1_7 ); + l1_7->addWidget( lbl7 ); + l1_7->addWidget( _group, 1 ); + + l1->addStretch( 1 ); +} + +FileInfoWidget::~FileInfoWidget() +{ +} + +void FileInfoWidget::setFile( const QString & name ) +{ + if ( name.isNull() ) { + return; + } + + _fileName->setText( name ); + + struct stat info; + lstat( QFile::encodeName(name), &info ); + + QString tmp; + QDateTime datetime; + + datetime.setTime_t(info.st_ctime); + _ctime->setText( KGlobal::locale()->formatDateTime(datetime, false) ); + + datetime.setTime_t(info.st_mtime); + _mtime->setText( KGlobal::locale()->formatDateTime(datetime, false) ); + + datetime.setTime_t(info.st_atime); + _atime->setText( KGlobal::locale()->formatDateTime(datetime, false) ); + + _size->setText( Util::bytesToString( info.st_size ) ); + + struct passwd* p; + p = getpwuid( info.st_uid ); + if ( p ) { + tmp = p->pw_name; + } else { + tmp.setNum( info.st_uid ); + } + _owner->setText( tmp ); + + struct group* g; + g = getgrgid( info.st_gid ); + if ( g ) { + tmp = g->gr_name; + } else { + tmp.setNum( info.st_gid ); + } + _group->setText( tmp ); +} diff --git a/kdat/FileInfoWidget.h b/kdat/FileInfoWidget.h new file mode 100644 index 0000000..c763886 --- /dev/null +++ b/kdat/FileInfoWidget.h @@ -0,0 +1,58 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _FileInfoWidget_h_ +#define _FileInfoWidget_h_ + +#include <qwidget.h> + +class QLabel; + +/** + * @short Display information about a local file. + */ +class FileInfoWidget : public QWidget { + Q_OBJECT + QLabel* _fileName; + QLabel* _ctime; + QLabel* _mtime; + QLabel* _atime; + QLabel* _size; + QLabel* _owner; + QLabel* _group; +public: + /** + * Create a new file info widget. + * + * @param parent The parent widget. + * @param name The name of this widget. + */ + FileInfoWidget( QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the file info widget. + */ + ~FileInfoWidget(); + + /** + * Display the information for the given file. + */ + void setFile( const QString & name ); +}; + +#endif diff --git a/kdat/FormatOptDlg.cpp b/kdat/FormatOptDlg.cpp new file mode 100644 index 0000000..8ed79f9 --- /dev/null +++ b/kdat/FormatOptDlg.cpp @@ -0,0 +1,142 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdlib.h> + +#include <qcombobox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include <kapplication.h> +#include <kglobal.h> +#include <klocale.h> + +#include "Options.h" +#include "FormatOptDlg.h" + +#include "FormatOptDlg.moc" + +FormatOptDlg::FormatOptDlg( const QString & def, QWidget* parent, const char* name ) + : QDialog( parent, name, TRUE ) +{ + setIconText( i18n( "KDat: Format Options" ) ); + setCaption( i18n( "KDat: Format Options" ) ); + + QLabel* lbl1 = new QLabel( i18n( "Tape name:" ), this ); + QLabel* lbl2 = new QLabel( i18n( "Tape size:" ), this ); + + int max = lbl1->sizeHint().width(); + if ( lbl2->sizeHint().width() > max ) max = lbl2->sizeHint().width(); + + lbl1->setFixedSize( max, lbl1->sizeHint().height() ); + lbl2->setFixedSize( max, lbl2->sizeHint().height() ); + + _entry = new QLineEdit( this ); + _entry->setText( def ); + _entry->setFixedHeight( _entry->sizeHint().height() ); + + _tapeSize = new QLineEdit( this ); + _tapeSize->setFixedSize( 48, _tapeSize->sizeHint().height() ); + + _tapeSizeUnits = new QComboBox( this ); + _tapeSizeUnits->setFixedSize( 48, _tapeSizeUnits->sizeHint().height() ); + _tapeSizeUnits->insertItem( "MB" ); + _tapeSizeUnits->insertItem( "GB" ); + + KPushButton* ok = new KPushButton( KStdGuiItem::ok(), this ); + ok->setFixedSize( 80, ok->sizeHint().height() ); + KPushButton* cancel = new KPushButton( KStdGuiItem::cancel(), this ); + cancel->setFixedSize( 80, cancel->sizeHint().height() ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 8, 4 ); + QHBoxLayout* l2 = new QHBoxLayout(); + QHBoxLayout* l3 = new QHBoxLayout(); + QHBoxLayout* l4 = new QHBoxLayout(); + + l1->addLayout( l2, 0 ); + l1->addLayout( l3, 0 ); + l1->addStretch( 1 ); + + l1->addLayout( l4, 0 ); + + l2->addWidget( lbl1, 0 ); + l2->addWidget( _entry, 1 ); + + l3->addWidget( lbl2 ); + l3->addWidget( _tapeSize ); + l3->addWidget( _tapeSizeUnits ); + l3->addStretch( 1 ); + + l4->addStretch( 1 ); + l4->addWidget( ok, 0 ); + l4->addWidget( cancel, 0 ); + + resize( 400, 120 ); + + _entry->setFocus(); + _entry->selectAll(); + + connect( _entry, SIGNAL( returnPressed() ), this, SLOT( okClicked() ) ); + connect( ok , SIGNAL( clicked() ) , this, SLOT( okClicked() ) ); + connect( cancel, SIGNAL( clicked() ) , this, SLOT( reject() ) ); + + int size = Options::instance()->getDefaultTapeSize(); + if ( ( size >= 1024*1024 ) && ( size % ( 1024*1024 ) == 0 ) ) { + // GB + size /= 1024*1024; + _tapeSizeUnits->setCurrentItem( 1 ); + } else { + // MB + size /= 1024; + _tapeSizeUnits->setCurrentItem( 0 ); + } + _tapeSize->setText( KGlobal::locale()->formatNumber(size, 0) ); +} + +FormatOptDlg::~FormatOptDlg() +{ +} + +void FormatOptDlg::okClicked() +{ + _name = _entry->text(); + + _size = (int)KGlobal::locale()->readNumber( _tapeSize->text() ); + if ( _tapeSizeUnits->currentItem() == 0 ) { + // MB + _size *= 1024; + } else { + // GB + _size *= 1024*1024; + } + + accept(); +} + +QString FormatOptDlg::getName() +{ + return _name; +} + +int FormatOptDlg::getSize() +{ + return _size; +} diff --git a/kdat/FormatOptDlg.h b/kdat/FormatOptDlg.h new file mode 100644 index 0000000..b4c5540 --- /dev/null +++ b/kdat/FormatOptDlg.h @@ -0,0 +1,70 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _FormatOptDlg_h_ +#define _FormatOptDlg_h_ + +#include <qdialog.h> +#include <qstring.h> + +class QComboBox; +class QLineEdit; + +/** + * @short Display/edit options for formatting a tape. + */ +class FormatOptDlg : public QDialog { + Q_OBJECT + QString _name; + int _size; + QLineEdit* _entry; + QLineEdit* _tapeSize; + QComboBox* _tapeSizeUnits; +private slots: + void okClicked(); +public: + /** + * Create a new format options dialog. + * + * @param def The default value for the tape name. + * @param parent The parent widget for the dialog. + * @param name The name for the dialog. + */ + FormatOptDlg( const QString & def, QWidget* parent=0, const char* name=0 ); + + /** + * Destroy the format options dialog. + */ + ~FormatOptDlg(); + + /** + * Get the name for the new tape. + * + * @return The name for the tape being formatted. + */ + QString getName(); + + /** + * Get the capacity of the new tape. + * + * @return The size of the tape. + */ + int getSize(); +}; + +#endif diff --git a/kdat/ImageCache.cpp b/kdat/ImageCache.cpp new file mode 100644 index 0000000..016fc61 --- /dev/null +++ b/kdat/ImageCache.cpp @@ -0,0 +1,137 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <qpixmap.h> +#include <kglobal.h> +#include <kiconloader.h> + +#include "ImageCache.h" + +ImageCache::ImageCache() +{ + KIconLoader *l = KGlobal::iconLoader(); + /* 2002-01-24 FP */ + // _archive = new QPixmap(l->iconPath("package", KIcon::Toolbar)); + _archive = new QPixmap(l->iconPath("tar", KIcon::Small)); + /* 2002-01-24 FP */ + _backup = new QPixmap(l->iconPath("kdat_backup", KIcon::Toolbar)); + _file = new QPixmap(l->iconPath("mime_empty", KIcon::Small)); + _folderClosed = new QPixmap(l->iconPath("folder_blue", KIcon::Small)); + _folderOpen = new QPixmap(l->iconPath("folder_blue_open", KIcon::Small)); + _restore = new QPixmap(l->iconPath("kdat_restore", KIcon::Toolbar)); + _selectAll = new QPixmap(l->iconPath("kdat_select_all", KIcon::Toolbar)); + _selectNone = new QPixmap(l->iconPath("kdat_select_none", KIcon::Toolbar)); + _selectSome = new QPixmap(l->iconPath("kdat_select_some", KIcon::Toolbar)); + // 2002-01-28 FP + // _tape = new QPixmap(l->iconPath("kdat_archive", KIcon::Toolbar)); + _tape = new QPixmap(l->iconPath("kdat", KIcon::Small)); + // 2002-01-28 FP + _tapeMounted = new QPixmap(l->iconPath("kdat_mounted", KIcon::Toolbar)); + _tapeUnmounted = new QPixmap(l->iconPath("kdat_unmounted", KIcon::Toolbar)); + _verify = new QPixmap(l->iconPath("kdat_verify", KIcon::Toolbar)); +} + +ImageCache::~ImageCache() +{ + delete _archive; + delete _backup; + delete _file; + delete _folderClosed; + delete _folderOpen; + delete _restore; + delete _tape; + delete _tapeMounted; + delete _tapeUnmounted; + delete _verify; +} + +ImageCache* ImageCache::_instance = 0; + +ImageCache* ImageCache::instance() +{ + if ( _instance == 0 ) { + _instance = new ImageCache(); + } + + return _instance; +} + +const QPixmap* ImageCache::getArchive() +{ + return _archive; +} + +const QPixmap* ImageCache::getBackup() +{ + return _backup; +} + +const QPixmap* ImageCache::getFile() +{ + return _file; +} + +const QPixmap* ImageCache::getFolderClosed() +{ + return _folderClosed; +} + +const QPixmap* ImageCache::getFolderOpen() +{ + return _folderOpen; +} + +const QPixmap* ImageCache::getRestore() +{ + return _restore; +} + +const QPixmap* ImageCache::getSelectAll() +{ + return _selectAll; +} + +const QPixmap* ImageCache::getSelectNone() +{ + return _selectNone; +} + +const QPixmap* ImageCache::getSelectSome() +{ + return _selectSome; +} + +const QPixmap* ImageCache::getTape() +{ + return _tape; +} + +const QPixmap* ImageCache::getTapeMounted() +{ + return _tapeMounted; +} + +const QPixmap* ImageCache::getTapeUnmounted() +{ + return _tapeUnmounted; +} + +const QPixmap* ImageCache::getVerify() +{ + return _verify; +} diff --git a/kdat/ImageCache.h b/kdat/ImageCache.h new file mode 100644 index 0000000..b9bf59b --- /dev/null +++ b/kdat/ImageCache.h @@ -0,0 +1,150 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _ImageCache_h_ +#define _ImageCache_h_ + +class QPixmap; + +/** + * @short A container for commonly used icons. + */ +class ImageCache { + static ImageCache* _instance; + + QPixmap* _archive; + QPixmap* _backup; + QPixmap* _file; + QPixmap* _folderClosed; + QPixmap* _folderOpen; + QPixmap* _restore; + QPixmap* _selectAll; + QPixmap* _selectNone; + QPixmap* _selectSome; + QPixmap* _tape; + QPixmap* _tapeMounted; + QPixmap* _tapeUnmounted; + QPixmap* _verify; + + ImageCache(); +public: + /** + * Destroy the image cache and free the icons. + */ + ~ImageCache(); + + /** + * This method is an accessor for the single instance of the image cache. + * + * @return A pointer to the single instance of the image cache. + */ + static ImageCache* instance(); + + /** + * Get the tree-node icon used for archives. + * + * @return A pointer to the icon. + */ + const QPixmap* getArchive(); + + /** + * Get the toolbar icon used for the backup action. + * + * @return A pointer to the icon. + */ + const QPixmap* getBackup(); + + /** + * Get the tree-node icon used for files. + * + * @return A pointer to the icon. + */ + const QPixmap* getFile(); + + /** + * Get the tree-node icon used for a closed folder. + * + * @return A pointer to the icon. + */ + const QPixmap* getFolderClosed(); + + /** + * Get the tree-node icon used for an open folder. + * + * @return A pointer to the icon. + */ + const QPixmap* getFolderOpen(); + + /** + * Get the toolbar icon used for the restore action. + * + * @return A pointer to the icon. + */ + const QPixmap* getRestore(); + + /** + * Get the tree-node icon used when all subnodes are selected. + * + * @return A pointer to the icon. + */ + const QPixmap* getSelectAll(); + + /** + * Get the tree-node icon used when no subnodes are selected. + * + * @return A pointer to the icon. + */ + const QPixmap* getSelectNone(); + + /** + * Get the tree-node icon used when some subnodes are selected. + * + * @return A pointer to the icon. + */ + const QPixmap* getSelectSome(); + + /** + * Get the tree-node icon used for tape indexes. + * + * @return A pointer to the icon. + */ + const QPixmap* getTape(); + + /** + * Get the tree-node/toolbar icon used for a mounted tape drive. + * + * @return A pointer to the icon. + */ + const QPixmap* getTapeMounted(); + + /** + * Get the tree-node/toolbar icon used for an unmounted tape drive. + * + * @return A pointer to the icon. + */ + const QPixmap* getTapeUnmounted(); + + /** + * Get the toolbar icon used for the verify action. + * + * @return A pointer to the icon. + */ + const QPixmap* getVerify(); +}; + +#endif diff --git a/kdat/IndexDlg.cpp b/kdat/IndexDlg.cpp new file mode 100644 index 0000000..decea36 --- /dev/null +++ b/kdat/IndexDlg.cpp @@ -0,0 +1,355 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include <kapplication.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include "LoggerWidget.h" +#include "Options.h" +#include "IndexDlg.h" +#include "Tape.h" +#include "TapeDrive.h" +#include "TapeManager.h" +#include "TarParser.h" +#include "Util.h" + +#include "IndexDlg.moc" + +IndexDlg::IndexDlg( Tape* tape, QWidget* parent, const char* name ) + : QDialog( parent, name, TRUE ), + _tarParser( NULL ), + _tape( tape ), + _archive( NULL ), + _totalKBytes( 0.0 ), + _archiveCount( 0 ), + _fileCount( 0 ), + _totalFileCount( 0 ), + _aborted( FALSE ), + _numFiles( 0 ), + _fileSize( -1 ), + _fileMTime( -1 ), + _fileStartRecord( -1 ) +{ + setCaption( i18n( "KDat: Index" ) ); + setIconText( i18n( "KDat: Index" ) ); + + resize( 500, 300 ); + + const int labelWidth = 96; + + QFrame* f1 = new QFrame( this ); + f1->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + QFrame* f2 = new QFrame( this ); + f2->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + QLabel* lbl1 = new QLabel( i18n( "Elapsed time:" ), f1 ); + lbl1->setFixedSize( labelWidth, lbl1->sizeHint().height() ); + + _elapsedTime = new QLabel( i18n( "00:00:00" ), f1 ); + _elapsedTime->setFixedHeight( _elapsedTime->sizeHint().height() ); + + QLabel* lbl2 = new QLabel( i18n( "Archives:" ), f2 ); + lbl2->setFixedSize( labelWidth, lbl2->sizeHint().height() ); + + _archives = new QLabel( i18n( "0" ), f2 ); + _archives->setFixedHeight( _archives->sizeHint().height() ); + + QLabel* lbl3 = new QLabel( i18n( "KB read:" ), f1 ); + lbl3->setFixedSize( labelWidth, lbl3->sizeHint().height() ); + + _kbytesRead = new QLabel( i18n( "0KB" ), f1 ); + _kbytesRead->setFixedHeight( _kbytesRead->sizeHint().height() ); + + QLabel* lbl4 = new QLabel( i18n( "Files:" ), f2 ); + lbl4->setFixedSize( labelWidth, lbl4->sizeHint().height() ); + + _files = new QLabel( i18n( "0" ), f2 ); + _files->setFixedHeight( _kbytesRead->sizeHint().height() ); + + QLabel* lbl5 = new QLabel( i18n( "Transfer rate:" ), f1 ); + lbl5->setFixedSize( labelWidth, lbl5->sizeHint().height() ); + + _transferRate = new QLabel( i18n( "0KB/min" ), f1 ); + _transferRate->setFixedHeight( _transferRate->sizeHint().height() ); + + QLabel* lbl6 = new QLabel( i18n( "Total files:" ), f2 ); + lbl6->setFixedSize( labelWidth, lbl6->sizeHint().height() ); + + _totalFiles = new QLabel( i18n( "0" ), f2 ); + _totalFiles->setFixedHeight( _totalFiles->sizeHint().height() ); + + _log = new LoggerWidget( i18n( "Index log:" ), this ); + + _ok = new KPushButton( KStdGuiItem::ok(), this ); + _ok->setFixedSize( 80, _ok->sizeHint().height() ); + connect( _ok, SIGNAL( clicked() ), this, SLOT( slotOK() ) ); + _ok->setEnabled( FALSE ); + + _save = new QPushButton( i18n( "Save Log..." ), this ); + _save->setFixedSize( 80, _save->sizeHint().height() ); + connect( _save, SIGNAL( clicked() ), _log, SLOT( save() ) ); + _save->setEnabled( FALSE ); + + _abort = new QPushButton( i18n( "Abort" ), this ); + _abort->setFixedSize( 80, _abort->sizeHint().height() ); + connect( _abort, SIGNAL( clicked() ), this, SLOT( slotAbort() ) ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 8, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addStrut( 3 * lbl1->height() + 16 ); + l1_1->addWidget( f1 ); + l1_1->addWidget( f2 ); + + QVBoxLayout* l1_1_1 = new QVBoxLayout( f1, 4, 4 ); + + QHBoxLayout* l1_1_1_1 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_1 ); + l1_1_1_1->addWidget( lbl1 ); + l1_1_1_1->addWidget( _elapsedTime, 1 ); + + QHBoxLayout* l1_1_1_2 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_2 ); + l1_1_1_2->addWidget( lbl3 ); + l1_1_1_2->addWidget( _kbytesRead, 1 ); + + QHBoxLayout* l1_1_1_3 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_3 ); + l1_1_1_3->addWidget( lbl5 ); + l1_1_1_3->addWidget( _transferRate, 1 ); + + QVBoxLayout* l1_1_2 = new QVBoxLayout( f2, 4, 4 ); + + QHBoxLayout* l1_1_2_1 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_1 ); + l1_1_2_1->addWidget( lbl2 ); + l1_1_2_1->addWidget( _archives, 1 ); + + QHBoxLayout* l1_1_2_2 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_2 ); + l1_1_2_2->addWidget( lbl4 ); + l1_1_2_2->addWidget( _files, 1 ); + + QHBoxLayout* l1_1_2_3 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_3 ); + l1_1_2_3->addWidget( lbl6 ); + l1_1_2_3->addWidget( _totalFiles, 1 ); + + l1->addWidget( _log, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addStretch( 1 ); + l1_2->addWidget( _ok ); + l1_2->addWidget( _save ); + l1_2->addWidget( _abort ); +} + +IndexDlg::~IndexDlg() +{ + delete _tarParser; +} + +void IndexDlg::show() +{ + startTimer( 100 ); + + QDialog::show(); +} + +void IndexDlg::slotEntry( const QString & name, int size, int mtime, int record ) +{ + if ( !_fileName.isEmpty() ) { + _archive->addFile( _fileSize, _fileMTime, _fileStartRecord, record, _fileName ); + } + + _fileName = name; + _fileSize = size; + _fileMTime = mtime; + _fileStartRecord = record; + + /* 2002-01-28 LEW */ + // printf("IndexDlg::slotEntry called with \"%s\", %d bytes\n", name.latin1(), size); + /* 2002-01-28 LEW */ + + QString tmp; + tmp.setNum( ++_numFiles ); + _files->setText( tmp ); + _totalFileCount++; + tmp.setNum( _totalFileCount ); + _totalFiles->setText( tmp ); + + _log->append( name ); +} + +void IndexDlg::slotOK() +{ + if ( _aborted ) { + reject(); + } else { + accept(); + } +} + +void IndexDlg::slotAbort() +{ + killTimers(); + _aborted = TRUE; +} + +void IndexDlg::timerEvent( QTimerEvent* ) +{ + killTimers(); + + // Rewind tape. + _log->append( i18n( "Rewinding tape." ) ); + if ( !TapeDrive::instance()->rewind() ) { + KMessageBox::error( this, i18n( "Cannot rewind tape. Indexing aborted." )); + _ok->setEnabled( TRUE ); + _save->setEnabled( TRUE ); + _abort->setEnabled( FALSE ); + _log->append( i18n( "Cannot rewind tape." ) ); + return; + } + + // Skip tape ID. + if ( !TapeDrive::instance()->nextFile( 1 ) ) { + KMessageBox::error( this, i18n( "Failed to skip tape ID. Indexing aborted." )); + _ok->setEnabled( TRUE ); + _save->setEnabled( TRUE ); + _abort->setEnabled( FALSE ); + _log->append( i18n( "Failed to skip tape ID." ) ); + return; + } + + _startTime = time( NULL ); + + int oldElapsed = 0; + QString msg; + int count; + char *buf = new char[Options::instance()->getTapeBlockSize()]; + bool atLeastOneGoodRead = TRUE; + while ( ( !_aborted ) && ( atLeastOneGoodRead ) ) { + atLeastOneGoodRead = FALSE; + _archive = NULL; + + delete _tarParser; + _tarParser = new TarParser(); + connect( _tarParser, SIGNAL( sigEntry( const QString &, int, int, int ) ), this, SLOT( slotEntry( const QString &, int, int, int ) ) ); + + int endBlock = 0; + int leftover = 0; + + while ( ( count = TapeDrive::instance()->read( buf, Options::instance()->getTapeBlockSize() ) ) > 0 ) { + // Is this the first block of the archive? + if ( !atLeastOneGoodRead ) { + atLeastOneGoodRead = TRUE; + _archiveCount++; + msg.setNum( _archiveCount ); + _archives->setText( msg ); + msg = i18n( "Indexing archive %1." ).arg( _archiveCount ); + _log->append( msg ); + + msg = i18n( "Archive %1" ).arg( _archiveCount ); + _archive = new Archive( _tape, 0, msg ); + _fileName = QString::null; + _fileSize = -1; + _fileMTime = -1; + _fileStartRecord = -1; + _numFiles = 0; + } + + leftover += count; + endBlock += leftover / Options::instance()->getTapeBlockSize(); + leftover %= Options::instance()->getTapeBlockSize(); + + _tarParser->slotData( buf, count ); + _totalKBytes += (float)count / 1024.0; + + // Update stats. + int elapsed = time( NULL ) - _startTime; + if ( elapsed > oldElapsed ) + { + updateStats(); + KApplication::kApplication()->processEvents(); + if ( _aborted ) { + break; + } + oldElapsed = elapsed; + } + } + if ( _aborted ) { + break; + } + if ( _archive ) { + _archive->setEndBlock( endBlock ); + + if ( _fileName.length() > 0 ) { + _archive->addFile( _fileSize, _fileMTime, _fileStartRecord, _archive->getEndBlock() * ( Options::instance()->getTapeBlockSize() / 512 ), _fileName ); + } + + _tape->addChild( _archive ); + } + } + + delete [] buf; + + updateStats(); + + TapeDrive::instance()->rewind(); + + if ( !_aborted ) { + _tape->setName( i18n( "Reindexed Tape" ) ); + _tape->setSize( Options::instance()->getDefaultTapeSize() ); + } + + _ok->setEnabled( TRUE ); + _ok->setDefault( TRUE ); + _save->setEnabled( TRUE ); + _abort->setEnabled( FALSE ); +} + +void IndexDlg::updateStats() +{ + QString str; + int elapsed = time( NULL ) - _startTime; + + str = QString::fromUtf8( QCString().sprintf( i18n( "%02d:%02d:%02d" ).utf8(), elapsed / 3600, elapsed / 60 % 60, elapsed % 60 ) ); + _elapsedTime->setText( str ); + + str = Util::kbytesToString( (int)_totalKBytes ); + _kbytesRead->setText( str ); + + if ( elapsed > 0 ) { + str = i18n( "%1/min" ).arg(Util::kbytesToString( (int)_totalKBytes * 60 / elapsed ) ); + _transferRate->setText( str ); + } +} diff --git a/kdat/IndexDlg.h b/kdat/IndexDlg.h new file mode 100644 index 0000000..e624cf5 --- /dev/null +++ b/kdat/IndexDlg.h @@ -0,0 +1,92 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _IndexDlg_h_ +#define _IndexDlg_h_ + +#include <qdialog.h> +#include <qptrlist.h> + +#include "Range.h" + +class QLabel; +class QPushButton; + +class Archive; +class File; +class LoggerWidget; +class Tape; +class TarParser; + +/** + * @short Status dialog for recreating a tape index. + */ +class IndexDlg : public QDialog { + Q_OBJECT + TarParser* _tarParser; + Tape* _tape; + Archive* _archive; + QString _leftover; + QLabel* _elapsedTime; + QLabel* _kbytesRead; + QLabel* _transferRate; + QLabel* _archives; + QLabel* _files; + QLabel* _totalFiles; + LoggerWidget* _log; + QPushButton* _ok; + QPushButton* _save; + QPushButton* _abort; + int _startTime; + float _totalKBytes; + int _archiveCount; + int _fileCount; + int _totalFileCount; + bool _aborted; + int _numFiles; + + int _fileSize; + int _fileMTime; + int _fileStartRecord; + QString _fileName; + + void updateStats(); +private slots: + void slotOK(); + void slotAbort(); + void slotEntry( const QString & name, int size, int mtime, int record ); +protected: + void show(); + void timerEvent( QTimerEvent* e ); +public: + /** + * Create a new tape index dialog. + * + * @param tape A pointer to the empty tape index to fill in. + * @param parent The parent widget for this dialog. + * @param name The name of this dialog. + */ + IndexDlg( Tape* tape, QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the tape index dialog. + */ + ~IndexDlg(); +}; + +#endif diff --git a/kdat/InfoShellWidget.cpp b/kdat/InfoShellWidget.cpp new file mode 100644 index 0000000..e7c3189 --- /dev/null +++ b/kdat/InfoShellWidget.cpp @@ -0,0 +1,43 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <qobjectlist.h> +#include <qobjectdict.h> + +#include "InfoShellWidget.h" + +#include "InfoShellWidget.moc" + +InfoShellWidget::InfoShellWidget( QWidget* parent, const char* name ) + : QWidget( parent, name ) +{ +} + +InfoShellWidget::~InfoShellWidget() +{ +} + +void InfoShellWidget::resizeEvent( QResizeEvent* ) +{ + if ( children() ) { + QObjectListIt i( *children() ); + for ( ; i.current(); ++i ) { + ((QWidget*)i.current())->resize( size() ); + } + } +} diff --git a/kdat/InfoShellWidget.h b/kdat/InfoShellWidget.h new file mode 100644 index 0000000..c1379ee --- /dev/null +++ b/kdat/InfoShellWidget.h @@ -0,0 +1,49 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _InfoShellWidget_h_ +#define _InfoShellWidget_h_ + +#include <qwidget.h> + +/** + * @short A suitable parent for the info widgets. + */ +class InfoShellWidget : public QWidget { + Q_OBJECT +public: + /** + * Create a new info shell widget. + * + * @param parent The parent widget. + * @param name The name of this widget. + */ + InfoShellWidget( QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the info shell widget. + */ + ~InfoShellWidget(); +protected: + /** + * Resize all children to fill all of the available space within the shell. + */ + virtual void resizeEvent( QResizeEvent* e ); +}; + +#endif diff --git a/kdat/KDat.kdoc b/kdat/KDat.kdoc new file mode 100644 index 0000000..7297e64 --- /dev/null +++ b/kdat/KDat.kdoc @@ -0,0 +1,643 @@ +. +Archive=Archive.html +Archive=Archive.html +Archive::_startBlock=Archive.html#_startBlock +Archive::_endBlock=Archive.html#_endBlock +Archive::_timeStamp=Archive.html#_timeStamp +Archive::_name=Archive.html#_name +Archive::Archive=Archive.html#Archive +Archive::~Archive=Archive.html#~Archive +Archive::timeStamp=Archive.html#timeStamp +Archive::startBlock=Archive.html#startBlock +Archive::endBlock=Archive.html#endBlock +Archive::name=Archive.html#name +Archive::files=Archive.html#files +Archive::children=Archive.html#children +Archive::setEndBlock=Archive.html#setEndBlock +Archive::setName=Archive.html#setName +Archive::addFile=Archive.html#addFile +ArchiveInfoDlg=ArchiveInfoDlg.html +ArchiveInfoDlg=ArchiveInfoDlg.html +ArchiveInfoDlg::_tape=ArchiveInfoDlg.html#_tape +ArchiveInfoDlg::_archive=ArchiveInfoDlg.html#_archive +ArchiveInfoDlg::_archiveName=ArchiveInfoDlg.html#_archiveName +ArchiveInfoDlg::slotOK=ArchiveInfoDlg.html#slotOK +ArchiveInfoDlg::slotApply=ArchiveInfoDlg.html#slotApply +ArchiveInfoDlg::slotCancel=ArchiveInfoDlg.html#slotCancel +ArchiveInfoDlg::ArchiveInfoDlg=ArchiveInfoDlg.html#ArchiveInfoDlg +ArchiveInfoDlg::~ArchiveInfoDlg=ArchiveInfoDlg.html#~ArchiveInfoDlg +BackupDlg=BackupDlg.html +BackupDlg=BackupDlg.html +BackupDlg::_proc=BackupDlg.html#_proc +BackupDlg::_tarParser=BackupDlg.html#_tarParser +BackupDlg::_archiveName=BackupDlg.html#_archiveName +BackupDlg::_workingDir=BackupDlg.html#_workingDir +BackupDlg::_file=BackupDlg.html#_file +BackupDlg::_oneFileSystem=BackupDlg.html#_oneFileSystem +BackupDlg::_incremental=BackupDlg.html#_incremental +BackupDlg::_snapshot=BackupDlg.html#_snapshot +BackupDlg::_archiveSize=BackupDlg.html#_archiveSize +BackupDlg::_tape=BackupDlg.html#_tape +BackupDlg::_totalKBytes=BackupDlg.html#_totalKBytes +BackupDlg::_elapsedTime=BackupDlg.html#_elapsedTime +BackupDlg::_timeRemaining=BackupDlg.html#_timeRemaining +BackupDlg::_kbytesWritten=BackupDlg.html#_kbytesWritten +BackupDlg::_transferRate=BackupDlg.html#_transferRate +BackupDlg::_files=BackupDlg.html#_files +BackupDlg::_log=BackupDlg.html#_log +BackupDlg::_ok=BackupDlg.html#_ok +BackupDlg::_save=BackupDlg.html#_save +BackupDlg::_abort=BackupDlg.html#_abort +BackupDlg::_startTime=BackupDlg.html#_startTime +BackupDlg::_archive=BackupDlg.html#_archive +BackupDlg::_aborted=BackupDlg.html#_aborted +BackupDlg::toString=BackupDlg.html#toString +BackupDlg::slotProcessExited=BackupDlg.html#slotProcessExited +BackupDlg::slotStdout=BackupDlg.html#slotStdout +BackupDlg::slotOK=BackupDlg.html#slotOK +BackupDlg::slotAbort=BackupDlg.html#slotAbort +BackupDlg::slotEntry=BackupDlg.html#slotEntry +BackupDlg::show=BackupDlg.html#show +BackupDlg::timerEvent=BackupDlg.html#timerEvent +BackupDlg::BackupDlg=BackupDlg.html#BackupDlg +BackupDlg::~BackupDlg=BackupDlg.html#~BackupDlg +BackupOptDlg=BackupOptDlg.html +BackupOptDlg=BackupOptDlg.html +BackupOptDlg::_archiveName=BackupOptDlg.html#_archiveName +BackupOptDlg::_sourceFile=BackupOptDlg.html#_sourceFile +BackupOptDlg::_workingDir=BackupOptDlg.html#_workingDir +BackupOptDlg::_oneFilesystem=BackupOptDlg.html#_oneFilesystem +BackupOptDlg::_listedIncremental=BackupOptDlg.html#_listedIncremental +BackupOptDlg::_group=BackupOptDlg.html#_group +BackupOptDlg::_snapshotLabel=BackupOptDlg.html#_snapshotLabel +BackupOptDlg::_snapshotFile=BackupOptDlg.html#_snapshotFile +BackupOptDlg::_removeSnapshot=BackupOptDlg.html#_removeSnapshot +BackupOptDlg::incrementalToggled=BackupOptDlg.html#incrementalToggled +BackupOptDlg::BackupOptDlg=BackupOptDlg.html#BackupOptDlg +BackupOptDlg::~BackupOptDlg=BackupOptDlg.html#~BackupOptDlg +BackupOptDlg::archiveName=BackupOptDlg.html#archiveName +BackupOptDlg::file=BackupOptDlg.html#file +BackupOptDlg::workingDir=BackupOptDlg.html#workingDir +BackupOptDlg::oneFilesystem=BackupOptDlg.html#oneFilesystem +BackupOptDlg::incremental=BackupOptDlg.html#incremental +BackupOptDlg::snapshotFile=BackupOptDlg.html#snapshotFile +BackupOptDlg::removeSnapshot=BackupOptDlg.html#removeSnapshot +File=File.html +File=File.html +File::_size=File.html#_size +File::_mtime=File.html#_mtime +File::_record=File.html#_record +File::_name=File.html#_name +File::File=File.html#File +File::~File=File.html#~File +File::size=File.html#size +File::mtime=File.html#mtime +File::record=File.html#record +File::name=File.html#name +File::children=File.html#children +IndexDlg=IndexDlg.html +IndexDlg=IndexDlg.html +IndexDlg::_tarParser=IndexDlg.html#_tarParser +IndexDlg::_tape=IndexDlg.html#_tape +IndexDlg::_archive=IndexDlg.html#_archive +IndexDlg::_leftover=IndexDlg.html#_leftover +IndexDlg::_elapsedTime=IndexDlg.html#_elapsedTime +IndexDlg::_kbytesRead=IndexDlg.html#_kbytesRead +IndexDlg::_transferRate=IndexDlg.html#_transferRate +IndexDlg::_archives=IndexDlg.html#_archives +IndexDlg::_files=IndexDlg.html#_files +IndexDlg::_totalFiles=IndexDlg.html#_totalFiles +IndexDlg::_log=IndexDlg.html#_log +IndexDlg::_ok=IndexDlg.html#_ok +IndexDlg::_save=IndexDlg.html#_save +IndexDlg::_abort=IndexDlg.html#_abort +IndexDlg::_startTime=IndexDlg.html#_startTime +IndexDlg::_totalKBytes=IndexDlg.html#_totalKBytes +IndexDlg::_archiveCount=IndexDlg.html#_archiveCount +IndexDlg::_fileCount=IndexDlg.html#_fileCount +IndexDlg::_totalFileCount=IndexDlg.html#_totalFileCount +IndexDlg::_aborted=IndexDlg.html#_aborted +IndexDlg::updateStats=IndexDlg.html#updateStats +IndexDlg::toString=IndexDlg.html#toString +IndexDlg::slotOK=IndexDlg.html#slotOK +IndexDlg::slotAbort=IndexDlg.html#slotAbort +IndexDlg::slotEntry=IndexDlg.html#slotEntry +IndexDlg::show=IndexDlg.html#show +IndexDlg::timerEvent=IndexDlg.html#timerEvent +IndexDlg::IndexDlg=IndexDlg.html#IndexDlg +IndexDlg::~IndexDlg=IndexDlg.html#~IndexDlg +KDat=KDat.html +KDat=KDat.html +KDat::_menu=KDat.html#_menu +KDat::_fileMenu=KDat.html#_fileMenu +KDat::_editMenu=KDat.html#_editMenu +KDat::_toolbar=KDat.html#_toolbar +KDat::_statusBar=KDat.html#_statusBar +KDat::_localTree=KDat.html#_localTree +KDat::_tapeTree=KDat.html#_tapeTree +KDat::_folderOpen=KDat.html#_folderOpen +KDat::_folderClosed=KDat.html#_folderClosed +KDat::_file=KDat.html#_file +KDat::_archive=KDat.html#_archive +KDat::_online=KDat.html#_online +KDat::_offline=KDat.html#_offline +KDat::_tapePresent=KDat.html#_tapePresent +KDat::_tape=KDat.html#_tape +KDat::_instance=KDat.html#_instance +KDat::setTapePresent=KDat.html#setTapePresent +KDat::fillTapeTree=KDat.html#fillTapeTree +KDat::insertNode=KDat.html#insertNode +KDat::indexIndex=KDat.html#indexIndex +KDat::calcBackupSize=KDat.html#calcBackupSize +KDat::KDat=KDat.html#KDat +KDat::localExpanding=KDat.html#localExpanding +KDat::localExpanded=KDat.html#localExpanded +KDat::localCollapsed=KDat.html#localCollapsed +KDat::localSelected=KDat.html#localSelected +KDat::tapeExpanding=KDat.html#tapeExpanding +KDat::tapeExpanded=KDat.html#tapeExpanded +KDat::tapeCollapsed=KDat.html#tapeCollapsed +KDat::tapeSelected=KDat.html#tapeSelected +KDat::fileBackup=KDat.html#fileBackup +KDat::fileRestore=KDat.html#fileRestore +KDat::fileVerify=KDat.html#fileVerify +KDat::fileMountTape=KDat.html#fileMountTape +KDat::fileIndexTape=KDat.html#fileIndexTape +KDat::fileFormatTape=KDat.html#fileFormatTape +KDat::fileQuit=KDat.html#fileQuit +KDat::editTapeInfo=KDat.html#editTapeInfo +KDat::editPreferences=KDat.html#editPreferences +KDat::help=KDat.html#help +KDat::slotTapeDevice=KDat.html#slotTapeDevice +KDat::~KDat=KDat.html#~KDat +KDat::instance=KDat.html#instance +KDat::status=KDat.html#status +TapeDrive=TapeDrive.html +TapeDrive=TapeDrive.html +TapeDrive::_fd=TapeDrive.html#_fd +TapeDrive::_readOnly=TapeDrive.html#_readOnly +TapeDrive::_tape=TapeDrive.html#_tape +TapeDrive::_writeBuf=TapeDrive.html#_writeBuf +TapeDrive::_readBuf=TapeDrive.html#_readBuf +TapeDrive::_writeBufIdx=TapeDrive.html#_writeBufIdx +TapeDrive::_readBufIdx=TapeDrive.html#_readBufIdx +TapeDrive::_instance=TapeDrive.html#_instance +TapeDrive::TapeDrive=TapeDrive.html#TapeDrive +TapeDrive::flush=TapeDrive.html#flush +TapeDrive::instance=TapeDrive.html#instance +TapeDrive::isReadOnly=TapeDrive.html#isReadOnly +TapeDrive::isTapePresent=TapeDrive.html#isTapePresent +TapeDrive::ejectTape=TapeDrive.html#ejectTape +TapeDrive::eraseTape=TapeDrive.html#eraseTape +TapeDrive::rewind=TapeDrive.html#rewind +TapeDrive::readHeader=TapeDrive.html#readHeader +TapeDrive::getBlock=TapeDrive.html#getBlock +TapeDrive::gotoBlock=TapeDrive.html#gotoBlock +TapeDrive::nextFile=TapeDrive.html#nextFile +TapeDrive::prevFile=TapeDrive.html#prevFile +TapeDrive::nextRecord=TapeDrive.html#nextRecord +TapeDrive::prevRecord=TapeDrive.html#prevRecord +TapeDrive::endOfTape=TapeDrive.html#endOfTape +TapeDrive::open=TapeDrive.html#open +TapeDrive::close=TapeDrive.html#close +TapeDrive::read=TapeDrive.html#read +TapeDrive::write=TapeDrive.html#write +TapeDrive::writeEOF=TapeDrive.html#writeEOF +TapeDrive::seek=TapeDrive.html#seek +TapeDrive::pastEOF=TapeDrive.html#pastEOF +TapeDrive::lock=TapeDrive.html#lock +TapeDrive::unlock=TapeDrive.html#unlock +TapeDrive::setBlockSize=TapeDrive.html#setBlockSize +TapeDrive::slotTapeBlockSize=TapeDrive.html#slotTapeBlockSize +TapeDrive::status=TapeDrive.html#status +Logger=Logger.html +Logger=Logger.html +Logger::_mle=Logger.html#_mle +Logger::Logger=Logger.html#Logger +Logger::~Logger=Logger.html#~Logger +Logger::append=Logger.html#append +Logger::save=Logger.html#save +Node=Node.html +Node=Node.html +Node::_type=Node.html#_type +Node::_archive=Node.html#_archive +Node::_file=Node.html#_file +Node::=Node.html# +Node::Node=Node.html#Node +Node::Node=Node.html#Node +Node::Node=Node.html#Node +Node::type=Node.html#type +Node::archive=Node.html#archive +Node::file=Node.html#file +Options=Options.html +Options=Options.html +Options::_config=Options.html#_config +Options::_instance=Options.html#_instance +Options::Options=Options.html#Options +Options::instance=Options.html#instance +Options::sync=Options.html#sync +Options::getDefaultTapeSize=Options.html#getDefaultTapeSize +Options::getTapeBlockSize=Options.html#getTapeBlockSize +Options::getTapeDevice=Options.html#getTapeDevice +Options::getTarCommand=Options.html#getTarCommand +Options::getRmtCommand=Options.html#getRmtCommand +Options::getLockOnMount=Options.html#getLockOnMount +Options::getEjectOnUnmount=Options.html#getEjectOnUnmount +Options::setDefaultTapeSize=Options.html#setDefaultTapeSize +Options::setTapeBlockSize=Options.html#setTapeBlockSize +Options::setTapeDevice=Options.html#setTapeDevice +Options::setTarCommand=Options.html#setTarCommand +Options::setRmtCommand=Options.html#setRmtCommand +Options::setLockOnMount=Options.html#setLockOnMount +Options::setEjectOnUnmount=Options.html#setEjectOnUnmount +Options::sigDefaultTapeSize=Options.html#sigDefaultTapeSize +Options::sigTapeBlockSize=Options.html#sigTapeBlockSize +Options::sigTapeDevice=Options.html#sigTapeDevice +Options::sigTarCommand=Options.html#sigTarCommand +Options::sigRmtCommand=Options.html#sigRmtCommand +Options::sigLockOnMount=Options.html#sigLockOnMount +Options::sigEjectOnUnmount=Options.html#sigEjectOnUnmount +OptionsDlg=OptionsDlg.html +OptionsDlg=OptionsDlg.html +OptionsDlg::_defaultTapeSize=OptionsDlg.html#_defaultTapeSize +OptionsDlg::_defaultTapeSizeUnits=OptionsDlg.html#_defaultTapeSizeUnits +OptionsDlg::_tapeBlockSize=OptionsDlg.html#_tapeBlockSize +OptionsDlg::_tapeDevice=OptionsDlg.html#_tapeDevice +OptionsDlg::_tarCommand=OptionsDlg.html#_tarCommand +OptionsDlg::_lockOnMount=OptionsDlg.html#_lockOnMount +OptionsDlg::_ejectOnUnmount=OptionsDlg.html#_ejectOnUnmount +OptionsDlg::OptionsDlg=OptionsDlg.html#OptionsDlg +OptionsDlg::~OptionsDlg=OptionsDlg.html#~OptionsDlg +OptionsDlg::slotOK=OptionsDlg.html#slotOK +OptionsDlg::slotApply=OptionsDlg.html#slotApply +OptionsDlg::slotCancel=OptionsDlg.html#slotCancel +Range=Range.html +Range=Range.html +Range::Range=Range.html#Range +Range::start=Range.html#start +Range::end=Range.html#end +RestoreDlg=RestoreDlg.html +RestoreDlg=RestoreDlg.html +RestoreDlg::_proc=RestoreDlg.html#_proc +RestoreDlg::_workingDir=RestoreDlg.html#_workingDir +RestoreDlg::_startBlock=RestoreDlg.html#_startBlock +RestoreDlg::_leftover=RestoreDlg.html#_leftover +RestoreDlg::_elapsedTime=RestoreDlg.html#_elapsedTime +RestoreDlg::_timeRemaining=RestoreDlg.html#_timeRemaining +RestoreDlg::_kbytesRead=RestoreDlg.html#_kbytesRead +RestoreDlg::_transferRate=RestoreDlg.html#_transferRate +RestoreDlg::_files=RestoreDlg.html#_files +RestoreDlg::_log=RestoreDlg.html#_log +RestoreDlg::_ok=RestoreDlg.html#_ok +RestoreDlg::_save=RestoreDlg.html#_save +RestoreDlg::_abort=RestoreDlg.html#_abort +RestoreDlg::_startTime=RestoreDlg.html#_startTime +RestoreDlg::_totalKBytes=RestoreDlg.html#_totalKBytes +RestoreDlg::_fileCount=RestoreDlg.html#_fileCount +RestoreDlg::_archiveSize=RestoreDlg.html#_archiveSize +RestoreDlg::_wroteStdin=RestoreDlg.html#_wroteStdin +RestoreDlg::_aborted=RestoreDlg.html#_aborted +RestoreDlg::_done=RestoreDlg.html#_done +RestoreDlg::toString=RestoreDlg.html#toString +RestoreDlg::slotProcessExited=RestoreDlg.html#slotProcessExited +RestoreDlg::slotStdout=RestoreDlg.html#slotStdout +RestoreDlg::slotWroteStdin=RestoreDlg.html#slotWroteStdin +RestoreDlg::slotOK=RestoreDlg.html#slotOK +RestoreDlg::slotAbort=RestoreDlg.html#slotAbort +RestoreDlg::show=RestoreDlg.html#show +RestoreDlg::timerEvent=RestoreDlg.html#timerEvent +RestoreDlg::RestoreDlg=RestoreDlg.html#RestoreDlg +RestoreDlg::~RestoreDlg=RestoreDlg.html#~RestoreDlg +RestoreOptDlg=RestoreOptDlg.html +RestoreOptDlg=RestoreOptDlg.html +RestoreOptDlg::_workingDir=RestoreOptDlg.html#_workingDir +RestoreOptDlg::_entry=RestoreOptDlg.html#_entry +RestoreOptDlg::okClicked=RestoreOptDlg.html#okClicked +RestoreOptDlg::RestoreOptDlg=RestoreOptDlg.html#RestoreOptDlg +RestoreOptDlg::~RestoreOptDlg=RestoreOptDlg.html#~RestoreOptDlg +RestoreOptDlg::workingDir=RestoreOptDlg.html#workingDir +Tape=Tape.html +Tape=Tape.html +Tape::_tapeID=Tape.html#_tapeID +Tape::_createTime=Tape.html#_createTime +Tape::_modTime=Tape.html#_modTime +Tape::_tapeName=Tape.html#_tapeName +Tape::_tapeSize=Tape.html#_tapeSize +Tape::Tape=Tape.html#Tape +Tape::~Tape=Tape.html#~Tape +Tape::formatTape=Tape.html#formatTape +Tape::writeIndex=Tape.html#writeIndex +Tape::tapeName=Tape.html#tapeName +Tape::tapeID=Tape.html#tapeID +Tape::createTime=Tape.html#createTime +Tape::modTime=Tape.html#modTime +Tape::tapeSize=Tape.html#tapeSize +Tape::numArchives=Tape.html#numArchives +Tape::addArchive=Tape.html#addArchive +Tape::setTapeName=Tape.html#setTapeName +Tape::setTapeSize=Tape.html#setTapeSize +Tape::archives=Tape.html#archives +Tape::status=Tape.html#status +TapeDriveIF=TapeDriveIF.html +TapeDriveIF=TapeDriveIF.html +TapeDriveIF::flush=TapeDriveIF.html#flush +TapeDriveIF::nextRecord=TapeDriveIF.html#nextRecord +TapeDriveIF::pastEOF=TapeDriveIF.html#pastEOF +TapeDriveIF::prevFile=TapeDriveIF.html#prevFile +TapeDriveIF::prevRecord=TapeDriveIF.html#prevRecord +TapeDriveIF::setBlockSize=TapeDriveIF.html#setBlockSize +TapeDriveIF::close=TapeDriveIF.html#close +TapeDriveIF::eject=TapeDriveIF.html#eject +TapeDriveIF::getBlock=TapeDriveIF.html#getBlock +TapeDriveIF::isReadOnly=TapeDriveIF.html#isReadOnly +TapeDriveIF::isTapePresent=TapeDriveIF.html#isTapePresent +TapeDriveIF::lock=TapeDriveIF.html#lock +TapeDriveIF::nextFile=TapeDriveIF.html#nextFile +TapeDriveIF::open=TapeDriveIF.html#open +TapeDriveIF::read=TapeDriveIF.html#read +TapeDriveIF::readHeader=TapeDriveIF.html#readHeader +TapeDriveIF::rewind=TapeDriveIF.html#rewind +TapeDriveIF::seek=TapeDriveIF.html#seek +TapeDriveIF::unlock=TapeDriveIF.html#unlock +TapeDriveIF::write=TapeDriveIF.html#write +TapeDrive=TapeDrive.html +TapeDrive=TapeDrive.html +TapeDrive::_tapeDrive=TapeDrive.html#_tapeDrive +TapeDrive::_instance=TapeDrive.html#_instance +TapeDrive::TapeDrive=TapeDrive.html#TapeDrive +TapeDrive::flush=TapeDrive.html#flush +TapeDrive::nextRecord=TapeDrive.html#nextRecord +TapeDrive::pastEOF=TapeDrive.html#pastEOF +TapeDrive::prevFile=TapeDrive.html#prevFile +TapeDrive::prevRecord=TapeDrive.html#prevRecord +TapeDrive::setBlockSize=TapeDrive.html#setBlockSize +TapeDrive::instance=TapeDrive.html#instance +TapeDrive::close=TapeDrive.html#close +TapeDrive::eject=TapeDrive.html#eject +TapeDrive::getBlock=TapeDrive.html#getBlock +TapeDrive::isReadOnly=TapeDrive.html#isReadOnly +TapeDrive::isTapePresent=TapeDrive.html#isTapePresent +TapeDrive::lock=TapeDrive.html#lock +TapeDrive::nextFile=TapeDrive.html#nextFile +TapeDrive::open=TapeDrive.html#open +TapeDrive::read=TapeDrive.html#read +TapeDrive::readHeader=TapeDrive.html#readHeader +TapeDrive::rewind=TapeDrive.html#rewind +TapeDrive::seek=TapeDrive.html#seek +TapeDrive::unlock=TapeDrive.html#unlock +TapeDrive::write=TapeDrive.html#write +TapeDrive::slotTapeBlockSize=TapeDrive.html#slotTapeBlockSize +TapeDrive::slotStatus=TapeDrive.html#slotStatus +TapeDrive::status=TapeDrive.html#status +TapeInfoDlg=TapeInfoDlg.html +TapeInfoDlg=TapeInfoDlg.html +TapeInfoDlg::_tape=TapeInfoDlg.html#_tape +TapeInfoDlg::_tapeName=TapeInfoDlg.html#_tapeName +TapeInfoDlg::_tapeSize=TapeInfoDlg.html#_tapeSize +TapeInfoDlg::_tapeSizeUnits=TapeInfoDlg.html#_tapeSizeUnits +TapeInfoDlg::_archives=TapeInfoDlg.html#_archives +TapeInfoDlg::toString=TapeInfoDlg.html#toString +TapeInfoDlg::slotOK=TapeInfoDlg.html#slotOK +TapeInfoDlg::slotApply=TapeInfoDlg.html#slotApply +TapeInfoDlg::slotCancel=TapeInfoDlg.html#slotCancel +TapeInfoDlg::slotArchiveInfo=TapeInfoDlg.html#slotArchiveInfo +TapeInfoDlg::TapeInfoDlg=TapeInfoDlg.html#TapeInfoDlg +TapeInfoDlg::~TapeInfoDlg=TapeInfoDlg.html#~TapeInfoDlg +TapeNameDlg=TapeNameDlg.html +TapeNameDlg=TapeNameDlg.html +TapeNameDlg::_name=TapeNameDlg.html#_name +TapeNameDlg::_size=TapeNameDlg.html#_size +TapeNameDlg::_entry=TapeNameDlg.html#_entry +TapeNameDlg::_tapeSize=TapeNameDlg.html#_tapeSize +TapeNameDlg::_tapeSizeUnits=TapeNameDlg.html#_tapeSizeUnits +TapeNameDlg::okClicked=TapeNameDlg.html#okClicked +TapeNameDlg::TapeNameDlg=TapeNameDlg.html#TapeNameDlg +TapeNameDlg::~TapeNameDlg=TapeNameDlg.html#~TapeNameDlg +TapeNameDlg::name=TapeNameDlg.html#name +TapeNameDlg::size=TapeNameDlg.html#size +TarParser=TarParser.html +TarParser=TarParser.html +TarParser::_bufIdx=TarParser.html#_bufIdx +TarParser::_blocksToSkip=TarParser.html#_blocksToSkip +TarParser::_record=TarParser.html#_record +TarParser::_longname=TarParser.html#_longname +TarParser::_archnameIdx=TarParser.html#_archnameIdx +TarParser::parseOctal=TarParser.html#parseOctal +TarParser::parseTarBlock=TarParser.html#parseTarBlock +TarParser::TarParser=TarParser.html#TarParser +TarParser::slotData=TarParser.html#slotData +TarParser::sigEntry=TarParser.html#sigEntry +VerifyDlg=VerifyDlg.html +VerifyDlg=VerifyDlg.html +VerifyDlg::_proc=VerifyDlg.html#_proc +VerifyDlg::_workingDir=VerifyDlg.html#_workingDir +VerifyDlg::_startBlock=VerifyDlg.html#_startBlock +VerifyDlg::_leftover=VerifyDlg.html#_leftover +VerifyDlg::_elapsedTime=VerifyDlg.html#_elapsedTime +VerifyDlg::_timeRemaining=VerifyDlg.html#_timeRemaining +VerifyDlg::_kbytesRead=VerifyDlg.html#_kbytesRead +VerifyDlg::_transferRate=VerifyDlg.html#_transferRate +VerifyDlg::_files=VerifyDlg.html#_files +VerifyDlg::_log=VerifyDlg.html#_log +VerifyDlg::_ok=VerifyDlg.html#_ok +VerifyDlg::_save=VerifyDlg.html#_save +VerifyDlg::_abort=VerifyDlg.html#_abort +VerifyDlg::_startTime=VerifyDlg.html#_startTime +VerifyDlg::_totalKBytes=VerifyDlg.html#_totalKBytes +VerifyDlg::_fileCount=VerifyDlg.html#_fileCount +VerifyDlg::_archiveSize=VerifyDlg.html#_archiveSize +VerifyDlg::_wroteStdin=VerifyDlg.html#_wroteStdin +VerifyDlg::_aborted=VerifyDlg.html#_aborted +VerifyDlg::_done=VerifyDlg.html#_done +VerifyDlg::updateStats=VerifyDlg.html#updateStats +VerifyDlg::toString=VerifyDlg.html#toString +VerifyDlg::slotProcessExited=VerifyDlg.html#slotProcessExited +VerifyDlg::slotStdout=VerifyDlg.html#slotStdout +VerifyDlg::slotWroteStdin=VerifyDlg.html#slotWroteStdin +VerifyDlg::slotOK=VerifyDlg.html#slotOK +VerifyDlg::slotAbort=VerifyDlg.html#slotAbort +VerifyDlg::show=VerifyDlg.html#show +VerifyDlg::timerEvent=VerifyDlg.html#timerEvent +VerifyDlg::VerifyDlg=VerifyDlg.html#VerifyDlg +VerifyDlg::~VerifyDlg=VerifyDlg.html#~VerifyDlg +VerifyOptDlg=VerifyOptDlg.html +VerifyOptDlg=VerifyOptDlg.html +VerifyOptDlg::_workingDir=VerifyOptDlg.html#_workingDir +VerifyOptDlg::_entry=VerifyOptDlg.html#_entry +VerifyOptDlg::okClicked=VerifyOptDlg.html#okClicked +VerifyOptDlg::VerifyOptDlg=VerifyOptDlg.html#VerifyOptDlg +VerifyOptDlg::~VerifyOptDlg=VerifyOptDlg.html#~VerifyOptDlg +VerifyOptDlg::workingDir=VerifyOptDlg.html#workingDir +KTreeViewItem=KTreeViewItem.html +KTreeViewItem=KTreeViewItem.html +KTreeViewItem::KTreeView=KTreeViewItem.html#KTreeView +KTreeViewItem::KTreeViewItem=KTreeViewItem.html#KTreeViewItem +KTreeViewItem::KTreeViewItem=KTreeViewItem.html#KTreeViewItem +KTreeViewItem::~KTreeViewItem=KTreeViewItem.html#~KTreeViewItem +KTreeViewItem::appendChild=KTreeViewItem.html#appendChild +KTreeViewItem::childAt=KTreeViewItem.html#childAt +KTreeViewItem::childCount=KTreeViewItem.html#childCount +KTreeViewItem::childIndex=KTreeViewItem.html#childIndex +KTreeViewItem::expandButtonClicked=KTreeViewItem.html#expandButtonClicked +KTreeViewItem::getChild=KTreeViewItem.html#getChild +KTreeViewItem::getParent=KTreeViewItem.html#getParent +KTreeViewItem::getPixmap=KTreeViewItem.html#getPixmap +KTreeViewItem::getSibling=KTreeViewItem.html#getSibling +KTreeViewItem::getText=KTreeViewItem.html#getText +KTreeViewItem::hasChild=KTreeViewItem.html#hasChild +KTreeViewItem::hasParent=KTreeViewItem.html#hasParent +KTreeViewItem::hasSibling=KTreeViewItem.html#hasSibling +KTreeViewItem::insertChild=KTreeViewItem.html#insertChild +KTreeViewItem::isExpanded=KTreeViewItem.html#isExpanded +KTreeViewItem::isVisible=KTreeViewItem.html#isVisible +KTreeViewItem::removeChild=KTreeViewItem.html#removeChild +KTreeViewItem::setDelayedExpanding=KTreeViewItem.html#setDelayedExpanding +KTreeViewItem::setDeleteChildren=KTreeViewItem.html#setDeleteChildren +KTreeViewItem::setDrawExpandButton=KTreeViewItem.html#setDrawExpandButton +KTreeViewItem::setDrawText=KTreeViewItem.html#setDrawText +KTreeViewItem::setDrawTree=KTreeViewItem.html#setDrawTree +KTreeViewItem::setExpanded=KTreeViewItem.html#setExpanded +KTreeViewItem::setPixmap=KTreeViewItem.html#setPixmap +KTreeViewItem::setText=KTreeViewItem.html#setText +KTreeViewItem::boundingRect=KTreeViewItem.html#boundingRect +KTreeViewItem::height=KTreeViewItem.html#height +KTreeViewItem::height=KTreeViewItem.html#height +KTreeViewItem::paint=KTreeViewItem.html#paint +KTreeViewItem::paintExpandButton=KTreeViewItem.html#paintExpandButton +KTreeViewItem::paintHighlight=KTreeViewItem.html#paintHighlight +KTreeViewItem::paintText=KTreeViewItem.html#paintText +KTreeViewItem::paintTree=KTreeViewItem.html#paintTree +KTreeViewItem::synchNumChildren=KTreeViewItem.html#synchNumChildren +KTreeViewItem::textBoundingRect=KTreeViewItem.html#textBoundingRect +KTreeViewItem::width=KTreeViewItem.html#width +KTreeViewItem::width=KTreeViewItem.html#width +KTreeViewItem::owner=KTreeViewItem.html#owner +KTreeViewItem::numChildren=KTreeViewItem.html#numChildren +KTreeViewItem::doExpandButton=KTreeViewItem.html#doExpandButton +KTreeViewItem::expanded=KTreeViewItem.html#expanded +KTreeViewItem::delayedExpanding=KTreeViewItem.html#delayedExpanding +KTreeViewItem::doTree=KTreeViewItem.html#doTree +KTreeViewItem::doText=KTreeViewItem.html#doText +KTreeViewItem::paint=KTreeViewItem.html#paint +KTreeViewItem::parent=KTreeViewItem.html#parent +KTreeViewItem::sibling=KTreeViewItem.html#sibling +KTreeViewItem::pixmap=KTreeViewItem.html#pixmap +KTreeViewItem::text=KTreeViewItem.html#text +KTreeViewItem::deleteChildren=KTreeViewItem.html#deleteChildren +KTreeView=KTreeView.html +KTreeView=KTreeView.html +KTreeView::KTreeViewItem=KTreeView.html#KTreeViewItem +KTreeView::KTreeView=KTreeView.html#KTreeView +KTreeView::~KTreeView=KTreeView.html#~KTreeView +KTreeView::appendChildItem=KTreeView.html#appendChildItem +KTreeView::appendChildItem=KTreeView.html#appendChildItem +KTreeView::appendChildItem=KTreeView.html#appendChildItem +KTreeView::appendChildItem=KTreeView.html#appendChildItem +KTreeView::autoBottomScrollBar=KTreeView.html#autoBottomScrollBar +KTreeView::autoScrollBar=KTreeView.html#autoScrollBar +KTreeView::autoUpdate=KTreeView.html#autoUpdate +KTreeView::bottomScrollBar=KTreeView.html#bottomScrollBar +KTreeView::changeItem=KTreeView.html#changeItem +KTreeView::changeItem=KTreeView.html#changeItem +KTreeView::clear=KTreeView.html#clear +KTreeView::count=KTreeView.html#count +KTreeView::currentItem=KTreeView.html#currentItem +KTreeView::collapseItem=KTreeView.html#collapseItem +KTreeView::expandItem=KTreeView.html#expandItem +KTreeView::expandLevel=KTreeView.html#expandLevel +KTreeView::expandOrCollapseItem=KTreeView.html#expandOrCollapseItem +KTreeView::forEveryItem=KTreeView.html#forEveryItem +KTreeView::forEveryVisibleItem=KTreeView.html#forEveryVisibleItem +KTreeView::*getCurrentItem=KTreeView.html#*getCurrentItem +KTreeView::indentSpacing=KTreeView.html#indentSpacing +KTreeView::insertItem=KTreeView.html#insertItem +KTreeView::insertItem=KTreeView.html#insertItem +KTreeView::insertItem=KTreeView.html#insertItem +KTreeView::insertItem=KTreeView.html#insertItem +KTreeView::itemAt=KTreeView.html#itemAt +KTreeView::itemAt=KTreeView.html#itemAt +KTreeView::itemRow=KTreeView.html#itemRow +KTreeView::itemPath=KTreeView.html#itemPath +KTreeView::join=KTreeView.html#join +KTreeView::join=KTreeView.html#join +KTreeView::lowerItem=KTreeView.html#lowerItem +KTreeView::lowerItem=KTreeView.html#lowerItem +KTreeView::raiseItem=KTreeView.html#raiseItem +KTreeView::raiseItem=KTreeView.html#raiseItem +KTreeView::removeItem=KTreeView.html#removeItem +KTreeView::removeItem=KTreeView.html#removeItem +KTreeView::scrollBar=KTreeView.html#scrollBar +KTreeView::setAutoUpdate=KTreeView.html#setAutoUpdate +KTreeView::setBottomScrollBar=KTreeView.html#setBottomScrollBar +KTreeView::setCurrentItem=KTreeView.html#setCurrentItem +KTreeView::setExpandButtonDrawing=KTreeView.html#setExpandButtonDrawing +KTreeView::setExpandLevel=KTreeView.html#setExpandLevel +KTreeView::setIndentSpacing=KTreeView.html#setIndentSpacing +KTreeView::setScrollBar=KTreeView.html#setScrollBar +KTreeView::setShowItemText=KTreeView.html#setShowItemText +KTreeView::setSmoothScrolling=KTreeView.html#setSmoothScrolling +KTreeView::setTreeDrawing=KTreeView.html#setTreeDrawing +KTreeView::showItemText=KTreeView.html#showItemText +KTreeView::smoothScrolling=KTreeView.html#smoothScrolling +KTreeView::split=KTreeView.html#split +KTreeView::split=KTreeView.html#split +KTreeView::takeItem=KTreeView.html#takeItem +KTreeView::takeItem=KTreeView.html#takeItem +KTreeView::treeDrawing=KTreeView.html#treeDrawing +KTreeView::visibleCount=KTreeView.html#visibleCount +KTreeView::collapsed=KTreeView.html#collapsed +KTreeView::expanded=KTreeView.html#expanded +KTreeView::expanding=KTreeView.html#expanding +KTreeView::highlighted=KTreeView.html#highlighted +KTreeView::selected=KTreeView.html#selected +KTreeView::appendChildItem=KTreeView.html#appendChildItem +KTreeView::cellHeight=KTreeView.html#cellHeight +KTreeView::cellWidth=KTreeView.html#cellWidth +KTreeView::changeItem=KTreeView.html#changeItem +KTreeView::collapseSubTree=KTreeView.html#collapseSubTree +KTreeView::countItem=KTreeView.html#countItem +KTreeView::expandOrCollapse=KTreeView.html#expandOrCollapse +KTreeView::expandSubTree=KTreeView.html#expandSubTree +KTreeView::fixChildren=KTreeView.html#fixChildren +KTreeView::focusInEvent=KTreeView.html#focusInEvent +KTreeView::forEveryItem=KTreeView.html#forEveryItem +KTreeView::forEveryVisibleItem=KTreeView.html#forEveryVisibleItem +KTreeView::getMaxItemWidth=KTreeView.html#getMaxItemWidth +KTreeView::indentation=KTreeView.html#indentation +KTreeView::insertItem=KTreeView.html#insertItem +KTreeView::itemPath=KTreeView.html#itemPath +KTreeView::join=KTreeView.html#join +KTreeView::keyPressEvent=KTreeView.html#keyPressEvent +KTreeView::level=KTreeView.html#level +KTreeView::lowerItem=KTreeView.html#lowerItem +KTreeView::mouseDoubleClickEvent=KTreeView.html#mouseDoubleClickEvent +KTreeView::mouseMoveEvent=KTreeView.html#mouseMoveEvent +KTreeView::mousePressEvent=KTreeView.html#mousePressEvent +KTreeView::mouseReleaseEvent=KTreeView.html#mouseReleaseEvent +KTreeView::paintCell=KTreeView.html#paintCell +KTreeView::raiseItem=KTreeView.html#raiseItem +KTreeView::recursiveFind=KTreeView.html#recursiveFind +KTreeView::setItemExpanded=KTreeView.html#setItemExpanded +KTreeView::setItemExpandButtonDrawing=KTreeView.html#setItemExpandButtonDrawing +KTreeView::setItemShowText=KTreeView.html#setItemShowText +KTreeView::setItemTreeDrawing=KTreeView.html#setItemTreeDrawing +KTreeView::split=KTreeView.html#split +KTreeView::takeItem=KTreeView.html#takeItem +KTreeView::updateCellWidth=KTreeView.html#updateCellWidth +KTreeView::updateVisibleItems=KTreeView.html#updateVisibleItems +KTreeView::updateVisibleItemRec=KTreeView.html#updateVisibleItemRec +KTreeView::treeRoot=KTreeView.html#treeRoot +KTreeView::clearing=KTreeView.html#clearing +KTreeView::current=KTreeView.html#current +KTreeView::drawExpandButton=KTreeView.html#drawExpandButton +KTreeView::drawTree=KTreeView.html#drawTree +KTreeView::expansion=KTreeView.html#expansion +KTreeView::goingDown=KTreeView.html#goingDown +KTreeView::itemIndent=KTreeView.html#itemIndent +KTreeView::maxItemWidth=KTreeView.html#maxItemWidth +KTreeView::showText=KTreeView.html#showText +KTreeView::visibleItems=KTreeView.html#visibleItems +KTreeView::x/yOffset=KTreeView.html#x/yOffset +KTreeView::start_rubberband=KTreeView.html#start_rubberband +KTreeView::end_rubberband=KTreeView.html#end_rubberband +KTreeView::move_rubberband=KTreeView.html#move_rubberband diff --git a/kdat/KDatMainWindow.cpp b/kdat/KDatMainWindow.cpp new file mode 100644 index 0000000..925ad92 --- /dev/null +++ b/kdat/KDatMainWindow.cpp @@ -0,0 +1,1381 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <assert.h> +#include <time.h> +#include <unistd.h> +#include <sys/stat.h> +#include <stdlib.h> + +#include <qdatetime.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qkeycode.h> +#include <qlayout.h> +#include <qtextstream.h> + +#include <kapplication.h> +#include <kiconloader.h> +#include <kmenubar.h> +#include <kpopupmenu.h> +#include <ktoolbar.h> +#include <kglobal.h> +#include <kconfig.h> +#include <kmessagebox.h> +#include <kstringhandler.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "ArchiveInfoWidget.h" +#include "BackupDlg.h" +#include "BackupOptDlg.h" +#include "BackupProfile.h" +#include "BackupProfileInfoWidget.h" +#include "BackupProfileManager.h" +#include "FileInfoWidget.h" +#include "FormatOptDlg.h" +#include "ImageCache.h" +#include "IndexDlg.h" +#include "InfoShellWidget.h" +#include "KDatMainWindow.h" +#include "Node.h" +#include "Options.h" +#include "OptionsDlg.h" +#include "Range.h" +#include "Tape.h" +#include "TapeDrive.h" +#include "TapeFileInfoWidget.h" +#include "TapeInfoWidget.h" +#include "TapeManager.h" +#include "Util.h" +#include "VerifyDlg.h" +#include "VerifyOptDlg.h" +#include "kdat.h" +#include "ktreeview.h" +#include <klocale.h> +#include <qsplitter.h> +#include <kstatusbar.h> + +#include "KDatMainWindow.moc" + +KDatMainWindow* KDatMainWindow::_instance = 0; + +KDatMainWindow* KDatMainWindow::getInstance() +{ + if ( _instance == 0 ) { + _instance = new KDatMainWindow(); + } + return _instance; +} + +#define KDAT_HORIZONTAL_LAYOUT +KDatMainWindow::KDatMainWindow() + : KMainWindow(0), _destroyed( FALSE ) +{ +#ifdef KDAT_HORIZONTAL_LAYOUT /* 2002-01-20 LEW */ + resize( 600, 600 ); /* was 600 by 400 */ +#else + resize( 600, 600 ); +#endif /* KDAT_HORIZONTAL_LAYOUT */ + + setIconText( i18n( "KDat: <no tape>" ) ); + setCaption( i18n( "KDat: <no tape>" ) ); + + // Create object popup menus. + _tapeDriveMenu = new QPopupMenu(); + _tapeDriveMenu->insertItem( i18n( "Mount Tape" ) , this, SLOT( fileMountTape() ) ); + _tapeDriveMenu->insertItem( i18n( "Recreate Tape Index" ), this, SLOT( fileIndexTape() ) ); + _tapeDriveMenu->insertSeparator(); + _tapeDriveMenu->insertItem( i18n( "Format Tape..." ), this, SLOT( fileFormatTape() ) ); + + _archiveMenu = new QPopupMenu(); + _archiveMenu->insertItem( i18n( "Delete Archive" ), this, SLOT( fileDeleteArchive() ) ); + + _mountedArchiveMenu = new QPopupMenu(); + _mountedArchiveMenu->insertItem( i18n( "Verify..." ) , this, SLOT( fileVerify() ) ); + _mountedArchiveMenu->insertItem( i18n( "Restore..." ) , this, SLOT( fileRestore() ) ); + _mountedArchiveMenu->insertSeparator(); + _mountedArchiveMenu->insertItem( i18n( "Delete Archive" ), this, SLOT( fileDeleteArchive() ) ); + + _mountedTapeFileMenu = new QPopupMenu(); + _mountedTapeFileMenu->insertItem( i18n( "Verify..." ) , this, SLOT( fileVerify() ) ); + _mountedTapeFileMenu->insertItem( i18n( "Restore..." ), this, SLOT( fileRestore() ) ); + + _localFileMenu = new QPopupMenu(); + _localFileMenu->insertItem( i18n( "Backup..." ), this, SLOT( fileBackup() ) ); + + _tapeMenu = new QPopupMenu(); + _tapeMenu->insertItem( i18n( "Delete Tape Index" ), this, SLOT( fileDeleteIndex() ) ); + + _backupProfileRootMenu = new QPopupMenu(); + _backupProfileRootMenu->insertItem( i18n( "Create Backup Profile" ), this, SLOT( fileNewBackupProfile() ) ); + + _backupProfileMenu = new QPopupMenu(); + _backupProfileMenu->insertItem( i18n( "Backup..." ), this, SLOT( fileBackup() ) ); + _backupProfileMenu->insertSeparator(); + _backupProfileMenu->insertItem( i18n( "Delete Backup Profile" ), this, SLOT( fileDeleteBackupProfile() ) ); + + _fileMenu = new QPopupMenu; + _fileMenu->insertItem( i18n( "Backup..." ) , this, SLOT( fileBackup() ) ); + _fileMenu->insertItem( i18n( "Restore..." ) , this, SLOT( fileRestore() ) ); + _fileMenu->insertItem( i18n( "Verify..." ) , this, SLOT( fileVerify() ) ); + _fileMenu->insertItem( i18n( "Mount Tape" ) , this, SLOT( fileMountTape() ) ); + _fileMenu->insertItem( i18n( "Recreate Tape Index" ) , this, SLOT( fileIndexTape() ) ); + _fileMenu->insertItem( i18n( "Create Backup Profile" ), this, SLOT( fileNewBackupProfile() ) ); + _fileMenu->insertSeparator(); + _fileMenu->insertItem( i18n( "Delete Archive" ) , this, SLOT( fileDeleteArchive() ) ); + _fileMenu->insertItem( i18n( "Delete Index" ) , this, SLOT( fileDeleteIndex() ) ); + _fileMenu->insertItem( i18n( "Delete Backup Profile" ), this, SLOT( fileDeleteBackupProfile() ) ); + _fileMenu->insertItem( i18n( "Format Tape..." ) , this, SLOT( fileFormatTape() ) ); + _fileMenu->insertSeparator(); + _fileMenu->insertItem( SmallIcon("exit"), i18n( "&Quit" ) , this, SLOT( fileQuit() ), CTRL + Key_Q ); + + _editMenu = new QPopupMenu; + _editMenu->insertItem( SmallIcon("configure"), i18n( "Configure KDat..." ) , this, SLOT( editPreferences() ) ); + + _menu = new KMenuBar( this ); + _menu->insertItem( i18n( "&File" ), _fileMenu ); + _menu->insertItem( i18n( "&Settings" ), _editMenu ); + _menu->insertSeparator(); + QString about = i18n( "KDat Version %1\n\nKDat is a tar-based tape archiver.\n\nCopyright (c) 1998-2000 Sean Vyain\nCopyright (c) 2001-2002 Lawrence Widman\nkdat@cardiothink.com" ).arg( KDAT_VERSION ); + _menu->insertItem( i18n( "&Help" ), helpMenu( about ) ); + + _toolbar = new KToolBar( this ); + + _toolbar->insertButton( *ImageCache::instance()->getTapeUnmounted(), 0, SIGNAL( clicked( int ) ), this, SLOT( fileMountTape() ), TRUE, i18n( "Mount/unmount tape" ) ); + + _toolbar->insertSeparator(); + + _toolbar->insertButton( *ImageCache::instance()->getBackup() , 1, SIGNAL( clicked( int ) ), this, SLOT( fileBackup() ) , TRUE, i18n( "Backup" ) ); + _toolbar->setButtonIconSet( 1, BarIconSet("kdat_backup")); + _toolbar->insertButton( *ImageCache::instance()->getRestore(), 2, SIGNAL( clicked( int ) ), this, SLOT( fileRestore() ), TRUE, i18n( "Restore" ) ); + _toolbar->setButtonIconSet( 2, BarIconSet("kdat_restore")); + _toolbar->insertButton( *ImageCache::instance()->getVerify() , 3, SIGNAL( clicked( int ) ), this, SLOT( fileVerify() ) , TRUE, i18n( "Verify" ) ); + _toolbar->setButtonIconSet( 3, BarIconSet("kdat_verify")); + addToolBar( _toolbar ); + + _statusBar = new KStatusBar( this ); + _statusBar->insertItem( i18n( "Ready." ), 0 ); + +#ifdef KDAT_HORIZONTAL_LAYOUT /* 2002-01-20 LEW */ + _panner = new QSplitter( QSplitter::Horizontal, this, "panner"); +#else + _panner = new QSplitter( QSplitter::Vertical, this, "panner"); +#endif /* KDAT_HORIZONTAL_LAYOUT */ + + // Create info viewers. + InfoShellWidget* infoShell = new InfoShellWidget( _panner ); + _tapeInfo = new TapeInfoWidget( infoShell ); + _archiveInfo = new ArchiveInfoWidget( infoShell ); + _backupProfileInfo = new BackupProfileInfoWidget( infoShell ); + _tapeFileInfo = new TapeFileInfoWidget( infoShell ); + _fileInfo = new FileInfoWidget( infoShell ); + + // Now set up the tree + _tree = new KTreeView( _panner ); + _tree->setExpandButtonDrawing( TRUE ); + +#ifdef KDAT_HORIZONTAL_LAYOUT /* 2002-01-20 LEW */ + _tree->setMinimumWidth( 300 ); + _panner->moveToFirst( _tree ); +#else + _tree->setMinimumHeight( 300 ); +#endif /* KDAT_HORIZONTAL_LAYOUT */ + + connect( _tree, SIGNAL( expanding( KTreeViewItem*, bool& ) ), this, SLOT( localExpanding( KTreeViewItem*, bool& ) ) ); + connect( _tree, SIGNAL( expanded( int ) ), this, SLOT( localExpanded( int ) ) ); + connect( _tree, SIGNAL( collapsed( int ) ), this, SLOT( localCollapsed( int ) ) ); + connect( _tree, SIGNAL( selected( int ) ), this, SLOT( localSelected( int ) ) ); + connect( _tree, SIGNAL( highlighted( int ) ), this, SLOT( localHighlighted( int ) ) ); + connect( _tree, SIGNAL( popupMenu( int, const QPoint& ) ), this, SLOT( localPopupMenu( int, const QPoint& ) ) ); + + setCentralWidget( _panner ); + + _tree->insertItem( _tapeDriveNode = new TapeDriveNode() ); + _tree->insertItem( _rootNode = new RootNode() ); + _tree->insertItem( _backupProfileRootNode = new BackupProfileRootNode() ); + _tree->insertItem( new TapeIndexRootNode() ); + + connect( TapeDrive::instance(), SIGNAL( sigStatus( const QString & ) ), this, SLOT( status( const QString & ) ) ); + + setTapePresent( FALSE ); + + connect( Options::instance(), SIGNAL( sigTapeDevice() ), this, SLOT( slotTapeDevice() ) ); + + connect( TapeManager::instance(), SIGNAL( sigTapeMounted() ) , this, SLOT( slotTapeMounted() ) ); + connect( TapeManager::instance(), SIGNAL( sigTapeUnmounted() ), this, SLOT( slotTapeUnmounted() ) ); + + configureUI( 0 ); +} + +KDatMainWindow::~KDatMainWindow() +{ + _destroyed = TRUE; + + if ( Options::instance()->getLockOnMount() ) { + TapeDrive::instance()->unlock(); + } + delete _tapeDriveMenu; + delete _archiveMenu; + delete _mountedArchiveMenu; + delete _mountedTapeFileMenu; + delete _localFileMenu; + delete _tapeMenu; + delete _backupProfileRootMenu; + delete _backupProfileMenu; + +} + +void KDatMainWindow::popupTapeDriveMenu( const QPoint& p ) +{ + // Configure menu before popping up. + if ( TapeManager::instance()->getMountedTape() ) { + _tapeDriveMenu->changeItem( i18n( "Unmount Tape" ), _tapeDriveMenu->idAt( 0 ) ); + _tapeDriveMenu->setItemEnabled( _tapeDriveMenu->idAt( 1 ), TRUE ); + } else { + _tapeDriveMenu->changeItem( i18n( "Mount Tape" ), _tapeDriveMenu->idAt( 0 ) ); + _tapeDriveMenu->setItemEnabled( _tapeDriveMenu->idAt( 1 ), FALSE ); + } + + _tapeDriveMenu->popup( p ); +} + +void KDatMainWindow::popupArchiveMenu( const QPoint& p ) +{ + _archiveMenu->popup( p ); +} + +void KDatMainWindow::popupMountedArchiveMenu( const QPoint& p ) +{ + _mountedArchiveMenu->popup( p ); +} + +void KDatMainWindow::popupMountedTapeFileMenu( const QPoint& p ) +{ + _mountedTapeFileMenu->popup( p ); +} + +void KDatMainWindow::popupLocalFileMenu( const QPoint& p ) +{ + // Configure menu before popping up. + _localFileMenu->setItemEnabled( _localFileMenu->idAt( 0 ), ( TapeManager::instance()->getMountedTape() != 0 ) && ( !TapeDrive::instance()->isReadOnly() ) ); + + _localFileMenu->popup( p ); +} + +void KDatMainWindow::popupTapeMenu( const QPoint& p ) +{ + _tapeMenu->popup( p ); +} + +void KDatMainWindow::popupBackupProfileRootMenu( const QPoint& p ) +{ + _backupProfileRootMenu->popup( p ); +} + +void KDatMainWindow::popupBackupProfileMenu( const QPoint& p ) +{ + // Configure menu before popping up. + _backupProfileMenu->setItemEnabled( _backupProfileMenu->idAt( 0 ), ( TapeManager::instance()->getMountedTape() != 0 ) && ( !TapeDrive::instance()->isReadOnly() ) ); + + _backupProfileMenu->popup( p ); +} + +void KDatMainWindow::hideInfo() +{ + _archiveInfo->hide(); + _backupProfileInfo->hide(); + _tapeInfo->hide(); + _tapeFileInfo->hide(); + _fileInfo->hide(); +} + +void KDatMainWindow::showTapeInfo( Tape* tape ) +{ + assert( tape ); + + hideInfo(); + + _tapeInfo->setTape( tape ); + _tapeInfo->show(); +} + +void KDatMainWindow::showArchiveInfo( Archive* archive ) +{ + assert( archive ); + + hideInfo(); + + _archiveInfo->setArchive( archive ); + _archiveInfo->show(); +} + +void KDatMainWindow::showBackupProfileInfo( BackupProfile* backupProfile ) +{ + assert( backupProfile ); + + hideInfo(); + + _backupProfileInfo->setBackupProfile( backupProfile ); + _backupProfileInfo->show(); +} + +void KDatMainWindow::showTapeFileInfo( File* file ) +{ + assert( file ); + + hideInfo(); + + _tapeFileInfo->setFile( file ); + _tapeFileInfo->show(); +} + +void KDatMainWindow::showFileInfo( const QString & name ) +{ + assert( !name.isNull() ); + + hideInfo(); + + _fileInfo->setFile( name ); + _fileInfo->show(); +} + +void KDatMainWindow::localExpanding( KTreeViewItem* item, bool& allow ) +{ + ((Node*)item)->expanding( allow ); +} + +void KDatMainWindow::localExpanded( int index ) +{ + ((Node*)_tree->itemAt( index ))->expanded(); +} + +void KDatMainWindow::localCollapsed( int index ) +{ + ((Node*)_tree->itemAt( index ))->collapsed(); +} + +void KDatMainWindow::localSelected( int index ) +{ + Node* item = (Node*)_tree->itemAt( index ); + + /* 2002-01-30 LEW: RG and I don't like the behavior in which + the subtree expands or contracts when a directory is selected + or unselected. + so make the tree look the same when we are done.... + The goal is to redraw the icon to the left of the item. + There's gotta be a better way to do this. */ + if ( item->isExpanded() ) { + _tree->collapseItem( index ); + _tree->expandItem( index ); // 2002-01-30 LEW + } else { + _tree->expandItem( index ); + _tree->collapseItem( index ); // 2002-01-30 LEW + } + // repaint(); // this doesn't work 2002-01-30 LEW + /* 2002-01-30 LEW: RG and I don't like this behavior */ +} + +void KDatMainWindow::localHighlighted( int index ) +{ + Node* item = (Node*)_tree->itemAt( index ); + + if ( item ) { + item->selected(); + } + + configureUI( TapeManager::instance()->getMountedTape() ); +} + +void KDatMainWindow::localPopupMenu( int index, const QPoint& p ) +{ + Node* item = (Node*)_tree->itemAt( index ); + item->popupMenu( p ); +} + +void KDatMainWindow::fileBackup() +{ + BackupProfile backupProfile; + + /* 2002-01-28 LEW */ + // To the i18n translator: I apologize for this long text on the last day to submit + // i18n changes. But, the program isn't very usable without this information. Thanks. LEW + // (later today): now the program works correctly! Why? Beats me! + QString msg = i18n("KDat will dump your files properly to tape, but may not be able\n" + "to restore them. To restore your files by hand, you need to know\n" + "the name of the *non-rewinding* version of your tape device %1.\n" + ).arg(Options::instance()->getTapeDevice()); + QString msg2 = i18n("For example, if your device is /dev/st0, the non-rewinding version\n" + "is /dev/nst0. If your device name doesn't look like that, type\n" + "\"ls -l %2\" in a terminal window to see the real name of your\n" + "tape drive. Substitute that name for /dev/nst0 below.\n" + "Open a terminal window and type the following:\n" + " tar tfv /dev/nst0; tar tfv /dev/nst0\n" + " tar xfv /dev/nst0\n" + "The third call to \"tar\" will retrieve your data into your\n" + "current directory. Please let us know if this happens to you!\n" + " - KDat Maintenance Team\n" + ).arg(Options::instance()->getTapeDevice()); + msg = msg.append(msg2); + KMessageBox::sorry( this, msg); + /* 2002-01-28 LEW */ + + if ( ( _tree->getCurrentItem() ) && ( ((Node*)_tree->getCurrentItem())->isType( Node::BackupProfileNodeType ) ) ) { + BackupProfile* bp = ((BackupProfileNode*)_tree->getCurrentItem())->getBackupProfile(); + backupProfile.setArchiveName( bp->getArchiveName() ); + backupProfile.setWorkingDirectory( bp->getWorkingDirectory() ); + backupProfile.setAbsoluteFiles( bp->getAbsoluteFiles() ); + backupProfile.setOneFilesystem( bp->isOneFilesystem() ); + backupProfile.setIncremental( bp->isIncremental() ); + backupProfile.setSnapshotFile( bp->getSnapshotFile() ); + backupProfile.setRemoveSnapshot( bp->getRemoveSnapshot() ); + } else { + QString name; + name = i18n( "Archive created on %1" ).arg( KGlobal::locale()->formatDate(QDate::currentDate(), true) ); + name = name.stripWhiteSpace(); + + QStringList files; + getBackupFiles( files ); + + backupProfile.setArchiveName( name ); + backupProfile.setAbsoluteFiles( files ); + backupProfile.setOneFilesystem( TRUE ); + backupProfile.setIncremental( FALSE ); + backupProfile.setSnapshotFile( "snapshot" ); + backupProfile.setRemoveSnapshot( FALSE ); + } + + int ret = 0; + BackupOptDlg dlg( &backupProfile, this ); + if ( dlg.exec() == QDialog::Accepted ) { + // Begin backup. + status( i18n( "Performing backup..." ) ); + int size = calcBackupSize( dlg.getWorkingDirectory(), dlg.isOneFilesystem(), dlg.getRelativeFiles(), dlg.isIncremental(), dlg.getSnapshotFile(), dlg.getRemoveSnapshot() ); + + // Check to see whether user aborted the backup + if ( size < 0) { + status( i18n( "Backup canceled." ) ); + return; + } + + // Make sure the archive will fit on the tape. + int tapeSize = 1; + QPtrListIterator<Archive> i( TapeManager::instance()->getMountedTape()->getChildren() ); + if ( i.toLast() != NULL ) { + tapeSize = i.current()->getEndBlock(); + tapeSize = (int)((float)tapeSize / 1024.0 * (float)Options::instance()->getTapeBlockSize()) + 1; + } + if ( tapeSize + size >= TapeManager::instance()->getMountedTape()->getSize() ) { + // Warn user that tape is probably too short. + QString msg; + msg = i18n( "WARNING: The estimated archive size is %1 KB but " + "the tape has only %2 KB of space!\n" + "Back up anyway?" ) + .arg(KGlobal::locale()->formatNumber(size, 0)) + .arg(KGlobal::locale()->formatNumber(TapeManager::instance()->getMountedTape()->getSize() - tapeSize, 0 )); + int result = KMessageBox::warningContinueCancel( this, + msg, i18n("Backup"), i18n("Backup") ); + if ( result != KMessageBox::Continue) { + status( i18n( "Backup canceled." ) ); + return; + } + } + + if ( TapeDrive::instance()->getFile() < 0 ) { + // Rewind tape. + status( i18n( "Rewinding tape..." ) ); + if ( !TapeDrive::instance()->rewind() ) { + KMessageBox::error( this, + i18n("Cannot rewind tape.\nBackup aborted."), + i18n("Backup Error")); + status( i18n( "Backup aborted." ) ); + return; + } + } + + // Go to end of tape. + status( i18n( "Skipping to end of tape..." ) ); + if ( !TapeDrive::instance()->nextFile( TapeManager::instance()->getMountedTape()->getChildren().count() + 1 - TapeDrive::instance()->getFile() ) ) { + KMessageBox::error( this, + i18n("Cannot get to end of tape.\nBackup aborted."), + i18n("Backup Error")); + status( i18n( "Backup aborted." ) ); + return; + } + + status( i18n( "Backup in progress..." ) ); + BackupDlg backupDlg( dlg.getArchiveName(), dlg.getWorkingDirectory(), dlg.getRelativeFiles(), dlg.isOneFilesystem(), dlg.isIncremental(), dlg.getSnapshotFile(), dlg.getRemoveSnapshot(), size, TapeManager::instance()->getMountedTape(), this ); + ret = backupDlg.exec(); + +// status( i18n( "Rewinding tape..." ) ); +// TapeDrive::instance()->rewind(); +// if ( !TapeDrive::instance()->prevFile( 1 ) ) { +// status( i18n( "Rewinding tape..." ) ); +// TapeDrive::instance()->rewind(); +// } + } + + // All done. + if ( ret == QDialog::Accepted ) { + status( i18n( "Backup complete." ) ); + } else { + status( i18n( "Backup aborted." ) ); + } +} + +void KDatMainWindow::fileRestore() +{ + doVerify( TRUE ); +} + +void KDatMainWindow::fileVerify() +{ + doVerify( FALSE ); +} + +void KDatMainWindow::doVerify( bool restore ) +{ + RangeableNode* rangeableNode = 0; + MountedArchiveNode* archiveNode = 0; + QStringList files; + Archive* archive = 0; + // Check for marked files first. + for ( int i = _tapeDriveNode->childCount() - 1; i >= 0; i-- ) { + if ( ( ((SelectableNode*)_tapeDriveNode->childAt( i ))->isSelected() ) || + ( ((SelectableNode*)_tapeDriveNode->childAt( i ))->hasSelectedChildren() )) { + archiveNode = (MountedArchiveNode*)_tapeDriveNode->childAt( i ); + archive = archiveNode->getArchive(); + /* 2002-01-30 LEW */ +#ifdef DEBUG + printf("KDatMainWindow::doVerify: %d node of %s: ", i, + (SelectableNode*)_tapeDriveNode->getText().latin1() ); + if( ((SelectableNode*)_tapeDriveNode->childAt( i ))->isSelected() ){ + printf("is completely selected\n"); + } else { + printf("is partially selected\n"); + } +#endif /* DEBUG */ + /* 2002-01-30 LEW */ + break; + } + } + + if ( !archiveNode ) { + if ( ((Node*)_tree->getCurrentItem())->isType( Node::RangeableNodeType ) ) { + rangeableNode = (RangeableNode*)_tree->getCurrentItem(); + Node* parent = rangeableNode; + for ( ; !parent->isType( Node::MountedArchiveNodeType ); parent = (Node*)parent->getParent() ); + assert( parent ); + archive = ((MountedArchiveNode*)parent)->getArchive(); + } + } + + assert( archive ); + QPtrListIterator<Archive> it( archive->getTape()->getChildren() ); + int fileno; + for ( fileno = 1; ( it.current() ) && ( it.current() != archive ); ++it, fileno++ ); + + assert( archiveNode || rangeableNode ); + assert( fileno > -1 ); + + RangeList ranges; + if ( rangeableNode ) { + QPtrListIterator<Range> it( rangeableNode->getRanges() ); + for ( ; it.current(); ++it ) { + ranges.addRange( it.current()->getStart(), it.current()->getEnd() ); + } + if ( rangeableNode->isType( Node::MountedArchiveNodeType ) ) { + // Make sure the mounted archive node has populated its children. + archiveNode = (MountedArchiveNode*)rangeableNode; + if ( archiveNode->childCount() == 0 ) { + bool dummy = TRUE; + archiveNode->expanding( dummy ); + } + + for ( int i = rangeableNode->childCount() - 1; i >= 0; i-- ) { + if ( ((Node*)rangeableNode->childAt( i ))->isType( Node::MountedTapeDirectoryNodeType ) ) { + files.append( ((MountedTapeDirectoryNode*)rangeableNode->childAt( i ))->getFullPath() ); + } else if ( ((Node*)rangeableNode->childAt( i ))->isType( Node::MountedTapeFileNodeType ) ) { + files.append( ((MountedTapeFileNode*)rangeableNode->childAt( i ))->getFullPath() ); + } else { + assert( FALSE ); + } + } + } else if ( rangeableNode->isType( Node::MountedTapeDirectoryNodeType ) ) { + files.append( ((MountedTapeDirectoryNode*)rangeableNode)->getFullPath() ); + } else if ( rangeableNode->isType( Node::MountedTapeFileNodeType ) ) { + files.append( ((MountedTapeFileNode*)rangeableNode)->getFullPath() ); + } else { + assert( FALSE ); + } + } else { + // Compile a list of files to verify/restore. + QPtrStack<RangeableNode> stack; + + // Make sure the mounted archive node has populated its children. + if ( archiveNode->childCount() == 0 ) { + bool dummy = TRUE; + archiveNode->expanding( dummy ); + } + + for ( int i = archiveNode->childCount() - 1; i >= 0; i-- ) { + stack.push( (RangeableNode*)archiveNode->childAt( i ) ); + } + RangeableNode* sel = 0; + while ( stack.count() > 0 ) { + sel = stack.pop(); + if ( sel->isSelected() ) { + QPtrListIterator<Range> it( sel->getRanges() ); + for ( ; it.current(); ++it ) { + ranges.addRange( it.current()->getStart(), it.current()->getEnd() ); + } + + if ( sel->isType( Node::MountedTapeDirectoryNodeType ) ) { + files.append( ((MountedTapeDirectoryNode*)sel)->getFullPath() ); + } else if ( sel->isType( Node::MountedTapeFileNodeType ) ) { + files.append( ((MountedTapeFileNode*)sel)->getFullPath() ); + } else { + assert( FALSE ); + } + } else if ( sel->hasSelectedChildren() ) { + for ( int i = sel->childCount() - 1; i >= 0; i-- ) { + stack.push( (RangeableNode*)sel->childAt( i ) ); + } + } + } + } + + char buf[1024]; + VerifyOptDlg dlg( getcwd( buf, 1024 ), files, restore, this ); + if ( dlg.exec() == QDialog::Accepted ) { + if ( restore ) { + status( i18n( "Restore in progress..." ) ); + } else { + status( i18n( "Verify in progress..." ) ); + } + VerifyDlg verifyDlg( dlg.getWorkingDirectory(), fileno, ranges, restore, this ); + int ret = verifyDlg.exec(); + + if ( ret == QDialog::Accepted ) { + if ( restore ) { + status( i18n( "Restore complete." ) ); + } else { + status( i18n( "Verify complete." ) ); + } + } else { + if ( restore ) { + status( i18n( "Restore aborted." ) ); + } else { + status( i18n( "Verify aborted." ) ); + } + } + } +} + +void KDatMainWindow::fileMountTape() +{ + static QString msg; + + // construct helpful error message (same as in fileFormatTape()) + msg = i18n("There appears to be no tape in the drive %1. Please\n" + "check \"Edit->Preferences\" to make sure the\n" + "correct device is selected as the tape drive (e.g.\n" + "/dev/st0). If you hear the tape drive moving, wait\n" + "until it stops and then try mounting it again.") + .arg(Options::instance()->getTapeDevice()); + + if ( !TapeManager::instance()->getMountedTape() ) { + if ( Options::instance()->getLoadOnMount() ) { + if ( !TapeDrive::instance()->load() ) { + KMessageBox::sorry( this, msg); + return; + } + } + + if ( TapeDrive::instance()->isTapePresent() ) { + setTapePresent( TRUE ); + } else { + KMessageBox::sorry( this, msg); + } + } else { + setTapePresent( FALSE ); + } +} + +void KDatMainWindow::fileIndexTape() +{ + int result = KMessageBox::warningContinueCancel( this, + i18n( "The current tape index will be overwritten, continue?" ), + i18n( "Index Tape"), i18n("Overwrite")); + if ( result == KMessageBox::Continue) { + TapeManager::instance()->getMountedTape()->clear(); + IndexDlg dlg( TapeManager::instance()->getMountedTape(), this ); + if ( dlg.exec() == QDialog::Accepted ) { + QString title; + title = i18n( "KDat: %1" ).arg( TapeManager::instance()->getMountedTape()->getName() ); + setCaption( title ); + setIconText( title ); + + status( i18n( "Index complete." ) ); + } else { + status( i18n( "Index aborted." ) ); + } + } +} + +void KDatMainWindow::fileDeleteArchive() +{ + Node* sel = (Node*)_tree->getCurrentItem(); + if ( ( !sel ) || ( !sel->isType( Node::ArchiveNodeType ) && !sel->isType( Node::MountedArchiveNodeType ) ) ) { + KMessageBox::sorry( this, i18n( "No archive is selected.\n" + "In order to delete an archive, the archive to be deleted " + "must be selected in the tree first." )); + return; + } + + // Find the selected archive and see if it has any archives after it. + Archive* archive = 0; + if ( sel->isType( Node::ArchiveNodeType ) ) { + archive = ((ArchiveNode*)sel)->getArchive(); + } else if ( sel->isType( Node::MountedArchiveNodeType ) ) { + archive = ((MountedArchiveNode*)sel)->getArchive(); + } + assert( archive ); + + Tape* tape = archive->getTape(); + QPtrListIterator<Archive> i( tape->getChildren() ); + for ( ; i.current(); ++i ) { + if ( i.current() == archive ) { + break; + } + } + assert( i.current() ); + + ++i; + if ( i.current() ) { + // There are other archives after this one on the tape. + QString list; + for ( ; i.current(); ++i ) { + list.append( "\n " ); + list.append( i.current()->getName() ); + } + + QString msg = + i18n( "An archive cannot be removed from the middle of the tape. If\nthe archive '%1' is deleted then\nthe following archives will also be deleted:\n%2\n\nDelete all listed archives?" ).arg(archive->getName()).arg(list); + int result = KMessageBox::warningContinueCancel( this, + msg, i18n("Delete Archive"), i18n("Delete All")); + if (result == KMessageBox::Continue) { + tape->removeChild( archive ); + emit status( i18n( "Archives deleted." ) ); + if ( _tree->getCurrentItem() ) { + ((Node*)_tree->getCurrentItem())->selected(); + } + configureUI( TapeManager::instance()->getMountedTape() ); + } + } else { + // This is the last (known) archive on the tape. + QString msg = + i18n( "Really delete the archive '%1'?" ).arg(archive->getName()); + int result = KMessageBox::warningContinueCancel( this, + msg, i18n("Delete Archive"), i18n("Delete")); + if (result == KMessageBox::Continue) { + tape->removeChild( archive ); + emit status( i18n( "Archive deleted." ) ); + if ( _tree->getCurrentItem() ) { + ((Node*)_tree->getCurrentItem())->selected(); + } + configureUI( TapeManager::instance()->getMountedTape() ); + } + } +} + +void KDatMainWindow::fileDeleteIndex() +{ + Node* sel = (Node*)_tree->getCurrentItem(); + if ( ( !sel ) || ( !sel->isType( Node::TapeNodeType ) ) ) { + KMessageBox::sorry( this, i18n( "No tape index is selected.\n" + "In order to delete a tape index, the tape index to be deleted " + "must be selected in the tree first." )); + return; + } + + Tape* tape = ((TapeNode*)sel)->getTape(); + assert( tape ); + if ( tape == TapeManager::instance()->getMountedTape() ) { + KMessageBox::sorry( this, i18n( "Tape is still mounted. " + "The index for a mounted tape cannot be deleted.\n" + "Unmount the tape and try again." )); + return; + } + + QString msg = + i18n( "Really delete the index for '%1'?" ).arg(tape->getName()); + int result = KMessageBox::warningContinueCancel( this, + msg, i18n("Delete Tape Index"), i18n("Delete")); + if (result == KMessageBox::Continue) { + TapeManager::instance()->removeTape( tape ); + emit status( i18n( "Tape index deleted." ) ); + if ( _tree->getCurrentItem() ) { + ((Node*)_tree->getCurrentItem())->selected(); + } + configureUI( TapeManager::instance()->getMountedTape() ); + } +} + +void KDatMainWindow::fileFormatTape() +{ + static QString msg; + + // construct helpful error message (same as in fileMountTape()) + msg = i18n("There appears to be no tape in the drive %1. Please\n" + "check \"Edit->Preferences\" to make sure the\n" + "correct device is selected as the tape drive (e.g.\n" + "/dev/st0). If you hear the tape drive moving, wait\n" + "until it stops and then try mounting it again.") + .arg(Options::instance()->getTapeDevice()); + + if ( !TapeDrive::instance()->isTapePresent() ) { + KMessageBox::sorry( this, msg ); + return; + } + + if ( TapeDrive::instance()->isReadOnly() ) { + KMessageBox::sorry( this, i18n( "The tape in the drive is write protected.\nPlease disable write protection and try again." )); + return; + } + + int result = KMessageBox::warningContinueCancel( this, + i18n( "All data currently on the tape will be lost.\n" + "Are you sure you want to continue?" ), + i18n("Format Tape"), i18n("Format")); + if (result == KMessageBox::Continue ) { + QString name; + name = i18n( "Tape created on %1" ).arg( KGlobal::locale()->formatDate(QDate::currentDate(), true) ); + FormatOptDlg dlg( name.stripWhiteSpace(), this ); + if ( dlg.exec() != QDialog::Accepted ) { + return; + } + + // Delete old index file. + if ( TapeManager::instance()->getMountedTape() ) { + TapeManager::instance()->removeTape( TapeManager::instance()->getMountedTape() ); + TapeManager::instance()->unmountTape(); + } + + Tape* tape = new Tape(); + tape->setName( dlg.getName() ); + tape->setSize( dlg.getSize() ); + + status( i18n( "Formatting tape..." ) ); + tape->format(); + TapeManager::instance()->addTape( tape ); + status( i18n( "Format complete." ) ); + + setTapePresent( FALSE, FALSE ); + setTapePresent( TRUE, FALSE ); + } +} + +void KDatMainWindow::fileNewBackupProfile() +{ + BackupProfile* backupProfile = new BackupProfile(); + + // Pick a unique name. + QString name; + for ( int i = 1; ; i++ ) { + name = i18n( "Backup Profile %1").arg( i ); + QStringList list = BackupProfileManager::instance()->getBackupProfileNames(); + QStringList::Iterator it = list.begin(); + for ( ; it != list.end(); ++it ) { + if ( name == *it ) { + break; + } + } + if ( it == list.end() ) { + // Name is unique. + break; + } + } + + QStringList files; + getBackupFiles( files ); + + backupProfile->setName( name ); + backupProfile->setArchiveName( i18n( "Archive" ) ); + backupProfile->setWorkingDirectory( Util::longestCommonPath( files ) ); + if ( !backupProfile->getWorkingDirectory() ) { + backupProfile->setWorkingDirectory( "/" ); + } + backupProfile->setAbsoluteFiles( files ); + backupProfile->setOneFilesystem( TRUE ); + backupProfile->setIncremental( FALSE ); + backupProfile->setSnapshotFile( "snapshot" ); + backupProfile->setRemoveSnapshot( FALSE ); + backupProfile->save(); + + BackupProfileManager::instance()->addBackupProfile( backupProfile ); + + _backupProfileRootNode->setSelected( backupProfile ); +} + +void KDatMainWindow::fileDeleteBackupProfile() +{ + Node* sel = (Node*)_tree->getCurrentItem(); + if ( ( !sel ) || ( !sel->isType( Node::BackupProfileNodeType ) ) ) { + KMessageBox::sorry( this, i18n( "In order to delete a backup profile, the backup profile to be deleted " + "must be selected in the tree first." )); + return; + } + + BackupProfile* backupProfile = ((BackupProfileNode*)sel)->getBackupProfile(); + assert( backupProfile ); + + QString msg = + i18n("Really delete backup profile '%1'?").arg(backupProfile->getName()); + int result = KMessageBox::warningContinueCancel( this, + msg, i18n("Delete Backup Profile"), i18n("Delete")); + if (result == KMessageBox::Continue) { + BackupProfileManager::instance()->removeBackupProfile( backupProfile ); + emit status( i18n( "Backup profile deleted." ) ); + if ( _tree->getCurrentItem() ) { + ((Node*)_tree->getCurrentItem())->selected(); + } + configureUI( TapeManager::instance()->getMountedTape() ); + } +} + +void KDatMainWindow::fileQuit() +{ + KApplication::kApplication()->quit(); +} + +void KDatMainWindow::editPreferences() +{ + OptionsDlg dlg( this ); + dlg.exec(); +} + +void KDatMainWindow::help() +{ + KApplication::kApplication()->invokeHelp( ); +} + +void KDatMainWindow::setTapePresent( bool tapePresent, bool eject ) +{ + if ( TapeManager::instance()->getMountedTape() ) { + if ( !tapePresent ) { + TapeManager::instance()->unmountTape(); + + if ( ( eject ) && ( Options::instance()->getLockOnMount() ) ) { + TapeDrive::instance()->unlock(); + } + + if ( ( eject ) && ( Options::instance()->getEjectOnUnmount() ) ) { + TapeDrive::instance()->eject(); + } + + status( i18n( "Tape unmounted." ) ); + } + } else { + if ( tapePresent ) { + status( i18n( "Reading tape header..." ) ); + Tape* tape = TapeDrive::instance()->readHeader(); + + if ( tape ) { + TapeManager::instance()->mountTape( tape ); + } else { + if ( TapeDrive::instance()->isReadOnly() ) { + KMessageBox::sorry( this, i18n( "This tape has not been formatted by KDat." )); + } else { + int result = KMessageBox::questionYesNo( this, + i18n( "This tape has not been formatted by KDat.\n\nWould you like to format it now?" ), QString::null, i18n("Format"), i18n("Do Not Format")); + if (result == KMessageBox::Yes) { + fileFormatTape(); + return; + } + } + } + + if ( Options::instance()->getLockOnMount() ) { + TapeDrive::instance()->lock(); + } + + status( i18n( "Tape mounted." ) ); + } + } +} + +void KDatMainWindow::status( const QString & msg ) +{ + _statusBar->changeItem( msg, 0 ); + KApplication::kApplication()->processEvents(); +} + +void KDatMainWindow::show() +{ + KMainWindow::show(); + + hideInfo(); +} + +// 2002-01-21 LEW: returns backup size in KB, or -1 if user chose +// to abort the backup. +int KDatMainWindow::calcBackupSize( const QString& workingDir, bool local, const QStringList& files, + bool incremental, const QString& snapshot, bool removeSnapshot ) +{ + int return_value; + + chdir( QFile::encodeName(workingDir) ); + + bool useSnapshot = !snapshot.isEmpty() && !removeSnapshot; + + int tmp = 0; + if ( incremental && !removeSnapshot ) { + QFile snap( snapshot ); + if ( snap.exists() && snap.open( IO_ReadOnly ) ) { + QTextStream t( &snap ); + t >> tmp; + } else { + useSnapshot = FALSE; + } + } + QDateTime mtime; + mtime.setTime_t( tmp ); + + int size = 0; + + /* 2002-01-24 LEW: start of backup-cancel dialog */ + create_backup_dialog(); + stop_flag = FALSE; // initialize the flag that tells us whether use + // cancelled the backup via the dialog. + /* 2002-01-24 LEW: end of backup-cancel dialog */ + + QStringList filesTmp = files; + QStringList::Iterator ii = filesTmp.begin(); + for ( ; ii != filesTmp.end(); ++ii ) { + + // Is this a normal file, or a directory? + QFileInfo finfo( *ii ); + if ( !finfo.isDir() ) { + if ( ( !useSnapshot ) || ( finfo.lastModified() > mtime ) ) { + size += finfo.size() / 512; + if ( finfo.size() % 512 ) { + size++; + } + } + continue; + } + + QPtrStack<QString> dirStack; + dirStack.push( new QString( *ii ) ); + + // If stay on one file system, get device of starting directory. + dev_t device = 0; + struct stat info; + if ( local ) { + if ( lstat( QFile::encodeName(*ii), &info ) != 0 ) { + device = 0; + } else { + device = info.st_dev; + } + } + + QString msg; + // msg.truncate( 4095 ); + QDir dir; + const QFileInfoList* infoList; + while ( !dirStack.isEmpty() ) { + if( stop_flag == TRUE ) break; + QString* path = dirStack.pop(); + msg = i18n("Estimating backup size: %1, %2" ) + .arg(Util::kbytesToString( size / 2 )) + .arg(KStringHandler::csqueeze(*path, 60)); + status( msg ); + KApplication::kApplication()->processEvents(); + dir.setPath( *path ); + infoList = dir.entryInfoList( QDir::Hidden | QDir::Files | QDir::Dirs, 0 ); + if ( infoList ) { + QFileInfoListIterator i( *infoList ); + for ( ; i.current(); ++i ) { + if ( ( i.current()->fileName() != "." ) && ( i.current()->fileName() != ".." ) ) { + size++; + if ( i.current()->isSymLink() ) { + } else if ( i.current()->isDir() ) { + if ( local ) { + if ( lstat( QFile::encodeName(i.current()->absFilePath()), &info ) == 0 ) { + if ( device == info.st_dev ) { + dirStack.push( new QString( i.current()->absFilePath() ) ); + } + } + } else { + dirStack.push( new QString( i.current()->absFilePath() ) ); + } + } else if ( ( !useSnapshot ) || ( i.current()->lastModified() > mtime ) ) { + size += i.current()->size() / 512; + if ( i.current()->size() % 512 ) { + size++; + } + } + } + } + } + } + } + + delete _backupdialog; // we're done, so zap this widget + + // Convert size in tar blocks to size in kbytes. + return_value = size / 2; + + if( stop_flag == TRUE ) return_value = -1; + + return return_value; +} + +void KDatMainWindow::getBackupFiles( QStringList& files ) +{ + if ( !_rootNode->isSelected() && !_rootNode->hasSelectedChildren() ) { + // Backup the entire subtree. + if ( ( _tree->getCurrentItem() ) && ( ((Node*)_tree->getCurrentItem())->isType( Node::ArchiveableNodeType ) ) ) { + files.append( ((ArchiveableNode*)_tree->getCurrentItem())->getFullPath() ); + } + } else { + // Compile a list of files to backup. + QPtrStack<ArchiveableNode> stack; + stack.push( _rootNode ); + ArchiveableNode* sel = 0; + while ( stack.count() > 0 ) { + sel = stack.pop(); + if ( sel->isSelected() ) { + files.append( sel->getFullPath() ); + } else if ( sel->hasSelectedChildren() ) { + for ( int i = sel->childCount() - 1; i >= 0; i-- ) { + stack.push( (ArchiveableNode*)sel->childAt( i ) ); + } + } + } + } +} + +void KDatMainWindow::setBackupFiles( const QStringList& files ) +{ + _rootNode->setSelected( FALSE ); + + QString tmp; + QStringList filesTmp = files; + QStringList::Iterator it = filesTmp.begin(); + for ( ; it != filesTmp.end(); ++it ) { + ArchiveableNode* n = _rootNode; + while ( n ) { + if ( n->getFullPath() == *it ) { + n->setSelected( TRUE ); + n = 0; + } else { + if ( n->childCount() == 0 ) { + bool dummy = TRUE; + n->expanding( dummy ); + } + int i; + for ( i = n->childCount() - 1; i >=0; i-- ) { + tmp = ((ArchiveableNode*)n->childAt( i ))->getFullPath(); + if ( tmp == (*it).left( tmp.length() ) ) { + n = (ArchiveableNode*)n->childAt( i ); + break; + } + } + if ( i < 0 ) { + n = 0; + } + } + } + } +} + +void KDatMainWindow::slotTapeDevice() +{ + setTapePresent( FALSE ); +} + +void KDatMainWindow::slotTapeMounted() +{ + configureUI( TapeManager::instance()->getMountedTape() ); +} + +void KDatMainWindow::slotTapeUnmounted() +{ + configureUI( 0 ); +} + +void KDatMainWindow::configureUI( Tape* tape ) +{ + if ( _destroyed ) { + return; + } + + Node* sel = (Node*)_tree->getCurrentItem(); + + // Title bar + if ( tape ) { + QString title; + title = i18n( "KDat: %1" ).arg(tape->getName()); + setCaption( title ); + setIconText( title ); + } else { + setCaption( i18n( "KDat: <no tape>" ) ); + setIconText( i18n( "KDat: <no tape >" ) ); + } + + // Backup + bool canBackup = ( tape ) && ( !TapeDrive::instance()->isReadOnly() ) && + ( ( ( sel ) && ( sel->isType( Node::ArchiveableNodeType ) || sel->isType( Node::BackupProfileNodeType ) ) ) || + ( _rootNode->isSelected() || _rootNode->hasSelectedChildren() ) ); + _toolbar->setItemEnabled( 1, canBackup ); + _fileMenu->setItemEnabled( _fileMenu->idAt( 0 ), canBackup ); + + // Restore/verify + bool canRestore = ( tape ) && ( sel ) && sel->isType( Node::RangeableNodeType ); + if ( !canRestore ) { + for ( int i = _tapeDriveNode->childCount() - 1; i >= 0; i-- ) { + if ( ( ((SelectableNode*)_tapeDriveNode->childAt( i ))->isSelected() ) || + ( ((SelectableNode*)_tapeDriveNode->childAt( i ))->hasSelectedChildren() )) { + canRestore = TRUE; + break; + } + } + } + if ( canRestore ) { + // 2002-01-30 LEW: check Node *sel because we can have canRestore==true + // even if sel==NULL when a child is selected (see loop above). + if( sel != (Node *)0x0 ) + { + for ( Node* parent = (Node*)sel->getParent(); + ( parent ) && ( parent->getParent() ); + parent = (Node*)parent->getParent() ) { + if ( parent->isType( Node::TapeNodeType ) ) { + canRestore = FALSE; + } + } + } + // 2002-01-30 LEW: see if we ever get here... + else + { + printf("Found sel==0x0 in %s %d\n", __FILE__, __LINE__); + } + // 2002-01-30 LEW + + } + _toolbar->setItemEnabled( 2, canRestore ); + _toolbar->setItemEnabled( 3, canRestore ); + _fileMenu->setItemEnabled( _fileMenu->idAt( 1 ), canRestore ); + _fileMenu->setItemEnabled( _fileMenu->idAt( 2 ), canRestore ); + + // Mount/unmount tape + if ( tape ) { + _toolbar->setButtonPixmap( 0, *ImageCache::instance()->getTapeMounted() ); + _fileMenu->changeItem( i18n( "Unmount Tape" ), _fileMenu->idAt( 3 ) ); + } else { + _toolbar->setButtonPixmap( 0, *ImageCache::instance()->getTapeUnmounted() ); + _fileMenu->changeItem( i18n( "Mount Tape" ), _fileMenu->idAt( 3 ) ); + } + + // Index tape + _fileMenu->setItemEnabled( _fileMenu->idAt( 4 ), tape != NULL ); + + // Delete archive + _fileMenu->setItemEnabled( _fileMenu->idAt( 7 ), ( sel ) && sel->isType( Node::ArchiveNodeType ) ); + + // Delete index + _fileMenu->setItemEnabled( _fileMenu->idAt( 8 ), ( sel ) && sel->isType( Node::TapeNodeType ) ); + + // Delete backup profile + _fileMenu->setItemEnabled( _fileMenu->idAt( 9 ), ( sel ) && sel->isType( Node::BackupProfileNodeType ) ); + + // Format tape + _fileMenu->setItemEnabled( _fileMenu->idAt( 10 ), TapeManager::instance()->getMountedTape() && !TapeDrive::instance()->isReadOnly() ); +} + +void KDatMainWindow::readProperties( KConfig* config ) +{ + QValueList<int> sizes; + sizes << config->readNumEntry( "panner", 50 ); + _panner->setSizes( sizes ); +} + +void KDatMainWindow::saveProperties( KConfig* config ) +{ + config->writeEntry( "panner", _panner->sizes().first()); +} + +// Create the dialog that lets user cancel estimation of backup size +void KDatMainWindow::create_backup_dialog() +{ + /* 2002-01-24 LEW: start of backup-cancel dialog */ + static QString stop_button_text, stop_button_caption; + stop_button_text = + i18n("Click \"CANCEL\" to stop the backup process.\n" + "For example, you may quickly see that the size of\n" + "the files you selected will exceed the size of the\n" + "backup tape, and may then decide to stop and remove\n" + "some files from your list of files to backup.\n\n" + "Click \"Continue\" to remove this message while\n" + "continuing the backup."); + stop_button_caption = i18n("Stop estimating backup size"); + + // gotta put a callback in here. Probably need to create a + // dialog object 2002-01-22 LEW + _backupdialog = new KDialog( this, "backupdialog", false ); + _backupdialog->setCaption( stop_button_caption ); + // _backupdialog->resize(370,200); /* 2002-01-28 LEW */ + _backupdialog->resize(370,260); + + _lbl = new QLabel( stop_button_text, _backupdialog ); + // _lbl->setGeometry( QRect( 30, 20, 350, 140 ) ); /* 2002-01-28 LEW */ + _lbl->setGeometry( QRect( 30, 20, 350, 200 ) ); + + _cancel = new KPushButton( KStdGuiItem::cancel(), _backupdialog ); + _cancel->setFixedSize( 80, _cancel->sizeHint().height() ); + _cancel->setEnabled( TRUE ); + /* 2002-01-24 LEW: looks like we can't increase the button width + to accomodate a wider message :( */ + // _cancel->setGeometry( QRect( 50, 170, 0, 0 ) ); /* 2002-01-28 LEW */ + _cancel->setGeometry( QRect( 50, 230, 0, 0 ) ); + connect( _cancel, SIGNAL( clicked() ), this, SLOT( backupCancel() ) ); + + _continue = new KPushButton( KStdGuiItem::cont(), _backupdialog ); + _continue->setFixedSize( 80, _continue->sizeHint().height() ); + _continue->setEnabled( TRUE ); + _continue->setDefault( TRUE ); + // _continue->setGeometry( QRect( 200, 170, 0, 0 ) ); /* 2002-01-28 LEW */ + _continue->setGeometry( QRect( 200, 230, 0, 0 ) ); + connect( _continue, SIGNAL( clicked() ), this, SLOT( backupContinue() ) ); + + _backupdialog->show(); +} + +// stop calculating the backup size and hide the dialog screen +void KDatMainWindow::backupCancel() +{ + stop_flag = TRUE; + _backupdialog->hide(); +} + +// continue calculating the backup size and hide the dialog screen +void KDatMainWindow::backupContinue() +{ + _backupdialog->hide(); +} diff --git a/kdat/KDatMainWindow.h b/kdat/KDatMainWindow.h new file mode 100644 index 0000000..33e6732 --- /dev/null +++ b/kdat/KDatMainWindow.h @@ -0,0 +1,285 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef KDat_h +#define KDat_h + +#include <kmainwindow.h> +#include <kdialog.h> +#include <qpushbutton.h> +#include <qlabel.h> + +class KMenuBar; +class QSplitter; +class KStatusBar; +class KToolBar; +class KTreeView; +class KTreeViewItem; + +class QPixmap; +class QPopupMenu; + +class Archive; +class ArchiveInfoWidget; +class BackupProfile; +class BackupProfileInfoWidget; +class BackupProfileRootNode; +class File; +class FileInfoWidget; +class Node; +class RootNode; +class Tape; +class TapeDriveNode; +class TapeFileInfoWidget; +class TapeInfoWidget; + +/** + * @short The KDat main window. Everything happens from here. + */ +class KDatMainWindow : public KMainWindow { + Q_OBJECT + +private: + bool _destroyed; + KMenuBar* _menu; + QPopupMenu* _fileMenu; + QPopupMenu* _editMenu; + KToolBar* _toolbar; + KStatusBar* _statusBar; + QSplitter* _panner; + KTreeView* _tree; + QPopupMenu* _tapeDriveMenu; + QPopupMenu* _archiveMenu; + QPopupMenu* _mountedArchiveMenu; + QPopupMenu* _mountedTapeFileMenu; + QPopupMenu* _localFileMenu; + QPopupMenu* _tapeMenu; + QPopupMenu* _backupProfileRootMenu; + QPopupMenu* _backupProfileMenu; + ArchiveInfoWidget* _archiveInfo; + BackupProfileInfoWidget* _backupProfileInfo; + TapeFileInfoWidget* _tapeFileInfo; + TapeInfoWidget* _tapeInfo; + FileInfoWidget* _fileInfo; + RootNode* _rootNode; + TapeDriveNode* _tapeDriveNode; + BackupProfileRootNode* _backupProfileRootNode; + + static KDatMainWindow* _instance; + + void doVerify( bool restore = FALSE ); + void setTapePresent( bool tapePresent, bool eject = TRUE ); + int calcBackupSize( const QString& workingDir, bool local, const QStringList& files, + bool incremental, const QString& snapshot, bool removeSnapshot ); + + KDatMainWindow(); + + // 2002-01-24 LEW + // used in calcBackupSize() to see whether user cancelled the on-going + // backup to tape. + KDialog* _backupdialog; + QPushButton* _cancel; + QPushButton* _continue; + QLabel* _lbl; + int stop_flag; + void create_backup_dialog(); + // 2002-01-24 LEW + +private slots: + void localExpanding( KTreeViewItem* item, bool& allow ); + void localExpanded( int index ); + void localCollapsed( int index ); + void localSelected( int index ); + void localHighlighted( int index ); + void localPopupMenu( int index, const QPoint& p ); + + void fileBackup(); + void fileRestore(); + void fileVerify(); + void fileMountTape(); + void fileIndexTape(); + void fileDeleteArchive(); + void fileDeleteIndex(); + void fileFormatTape(); + void fileNewBackupProfile(); + void fileDeleteBackupProfile(); + void fileQuit(); + void editPreferences(); + void help(); + + void slotTapeDevice(); + void slotTapeMounted(); + void slotTapeUnmounted(); + + // 2002-01-24 LEW + void backupCancel(); + void backupContinue(); + // 2002-01-24 LEW + +protected: + virtual void readProperties( KConfig* config ); + virtual void saveProperties( KConfig* config ); + +public: + /** + * Destroy the KDat main window. + */ + ~KDatMainWindow(); + + /** + * Get a reference to the single instance of the KDat main window. + * + * @return A pointer to the KDat main window. + */ + static KDatMainWindow* getInstance(); + + /** + * Display the tape drive node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupTapeDriveMenu( const QPoint& p ); + + /** + * Display the archive node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupArchiveMenu( const QPoint& p ); + + /** + * Display the mounted archive node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupMountedArchiveMenu( const QPoint& p ); + + /** + * Display the mounted tape file node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupMountedTapeFileMenu( const QPoint& p ); + + /** + * Display the local file node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupLocalFileMenu( const QPoint& p ); + + /** + * Display the tape index node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupTapeMenu( const QPoint& p ); + + /** + * Display the backup profile root node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupBackupProfileRootMenu( const QPoint& p ); + + /** + * Display the backup profile node popup menu. + * + * @param p The upper left corner of the menu. + */ + void popupBackupProfileMenu( const QPoint& p ); + + /** + * Hide all of the info viewers. + */ + void hideInfo(); + + /** + * Display the tape info widget for the given tape. + * + * @param tape The tape index to display. + */ + void showTapeInfo( Tape* tape ); + + /** + * Display the archive info widget for the given archive. + * + * @param archive The archive to display. + */ + void showArchiveInfo( Archive* archive ); + + /** + * Display the tape file info widget for the given file. + * + * @param file The file to display. + */ + void showTapeFileInfo( File* file ); + + /** + * Display the backup profile info widget for the given backup profile. + * + * @param backupProfile The backup profile to display. + */ + void showBackupProfileInfo( BackupProfile* backupProfile ); + + /** + * Display the local file info widget for the given file. + * + * @param file The full path name of the file to display. + */ + void showFileInfo( const QString & name ); + + /** + * Make sure that the user interface is consistent for the given mounted + * tape. This method mostly just enables or disables GUI objects based on + * the currently mounted tape, and selections. + * + * @param tape The currently mounted tape. May be NULL. + */ + void configureUI( Tape* tape ); + + /** + * Get a list of all the files selected for backup. + * + * @param files This list will be filled with the selected files. + */ + void getBackupFiles( QStringList& files ); + + /** + * Set the list of all the files selected for backup. + * + * @param files This list will become the selection. + */ + void setBackupFiles( const QStringList& files ); + +public slots: + /** + * Display a text message in the status bar. + * + * @param msg The message to display. + */ + void status( const QString & msg ); + + /** + * Initialize the KDat main window before displaying. + */ + virtual void show(); + +}; + +#endif diff --git a/kdat/LoggerWidget.cpp b/kdat/LoggerWidget.cpp new file mode 100644 index 0000000..ac0bdc5 --- /dev/null +++ b/kdat/LoggerWidget.cpp @@ -0,0 +1,79 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <qlabel.h> +#include <qlayout.h> +#include <qmultilineedit.h> + +#include <kapplication.h> +#include <kfiledialog.h> + +#include "LoggerWidget.h" +#include <klocale.h> +#include <kmessagebox.h> + +#include "LoggerWidget.moc" + +LoggerWidget::LoggerWidget( const QString & title, QWidget* parent, const char* name ) + : QWidget( parent, name ) +{ + QLabel* lbl1 = new QLabel( title, this ); + lbl1->setFixedHeight( lbl1->sizeHint().height() ); + + _mle = new QMultiLineEdit( this ); + _mle->setReadOnly( TRUE ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 0, 4 ); + l1->addWidget( lbl1 ); + l1->addWidget( _mle, 1 ); +} + +LoggerWidget::~LoggerWidget() +{ +} + +void LoggerWidget::append( const QString & text ) +{ + _mle->append( text ); + _mle->setCursorPosition( _mle->numLines(), 0 ); +} + +void LoggerWidget::save() +{ + QString file = KFileDialog::getSaveFileName( QString::null, QString::null, this ); + if ( !file.isNull() ) { + QFile f( file ); + if ( f.exists() ) { + int result = KMessageBox::warningContinueCancel( this, + i18n( "Log file exists, overwrite?" ), + i18n( "KDat: Save Log" ), + i18n("&Overwrite")); + if (result != KMessageBox::Continue) + return; + } + if ( f.open( IO_WriteOnly ) ) { + QCString line; + for ( int i = 0; i < _mle->numLines(); i++ ) { + line = _mle->textLine( i ).utf8(); + f.writeBlock( line, line.length() ); + f.writeBlock( "\n", 1 ); + } + f.close(); + } + } +} diff --git a/kdat/LoggerWidget.h b/kdat/LoggerWidget.h new file mode 100644 index 0000000..76afb78 --- /dev/null +++ b/kdat/LoggerWidget.h @@ -0,0 +1,62 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _LoggerWidget_h_ +#define _LoggerWidget_h_ + +#include <qwidget.h> + +class QMultiLineEdit; + +/** + * @short A titled logging widget with a save option. + */ +class LoggerWidget : public QWidget { + Q_OBJECT + QMultiLineEdit* _mle; +public: + /** + * Create a titled logging widget + * + * @param title The title text displayed above the logging window. + * @param parent The parent widget. + * @param name The name of this widget. + */ + LoggerWidget( const QString & title, QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the logging widget. + */ + ~LoggerWidget(); + + /** + * Append the given text to the end of the logging widget, and position the + * viewable area at the end of the text. + * + * @param text The text to append. + */ + void append( const QString & text ); +public slots: + /** + * Prompt the user for a file name, and save the contents of the log to + * that file. + */ + void save(); +}; + +#endif diff --git a/kdat/Makefile.am b/kdat/Makefile.am new file mode 100644 index 0000000..846ce4c --- /dev/null +++ b/kdat/Makefile.am @@ -0,0 +1,110 @@ +# KDE tags expanded automatically by am_edit - $Revision$ + +# this 10 paths are KDE specific. Use them: +# kde_htmldir Where your docs should go to. (contains lang subdirs) +# kde_appsdir Where your application file (.desktop) should go to. +# kde_icondir Where your icon should go to. +# kde_minidir Where your mini icon should go to. +# kde_datadir Where you install application data. (Use a subdir) +# kde_locale Where translation files should go to.(contains lang subdirs) +# kde_cgidir Where cgi-bin executables should go to. +# kde_confdir Where config files should go to. +# kde_mimedir Where mimetypes should go to. +# kde_toolbardir Where general toolbar icons should go to. +# kde_wallpaperdir Where general wallpapers should go to. + +# just set the variable +xdg_apps_DATA = kdat.desktop + +# set the include path for X, qt and KDE +INCLUDES= $(all_includes) +# claim, which subdirectories you want to install +SUBDIRS = pics + +####### This part is very kdat specific +# you can add here more. This one gets installed +bin_PROGRAMS = kdat + +# Which sources should be compiled for kdat. +kdat_SOURCES = \ + Archive.cpp \ + ArchiveInfoWidget.cpp \ + BackupDlg.cpp \ + BackupOptDlg.cpp \ + BackupProfile.cpp \ + BackupProfileInfoWidget.cpp \ + BackupProfileManager.cpp \ + BackupProfileWidget.cpp \ + ErrorHandler.cpp \ + File.cpp \ + FileInfoWidget.cpp \ + ImageCache.cpp \ + IndexDlg.cpp \ + InfoShellWidget.cpp \ + KDatMainWindow.cpp \ + LoggerWidget.cpp \ + Node.cpp \ + Options.cpp \ + OptionsDlg.cpp \ + OptionsDlgWidget.ui \ + Range.cpp \ + Tape.cpp \ + TapeDrive.cpp \ + TapeFileInfoWidget.cpp \ + TapeInfoWidget.cpp \ + TapeManager.cpp \ + FormatOptDlg.cpp \ + TarParser.cpp \ + Util.cpp \ + VerifyDlg.cpp \ + VerifyOptDlg.cpp \ + ktreeview.cpp \ + main.cpp + + +# the library search path +kdat_LDFLAGS = $(all_libraries) $(KDE_RPATH) + +# the libraries to link against. Be aware of the order. First the libraries, +# that depend on the following ones. +kdat_LDADD = $(LIB_KFILE) + +# this option you can leave out. Just, if you use "make dist", you need it +noinst_HEADERS = \ + Archive.h \ + ArchiveInfoWidget.h \ + BackupDlg.h \ + BackupOptDlg.h \ + BackupProfile.h \ + BackupProfileInfoWidget.h \ + BackupProfileManager.h \ + BackupProfileWidget.h \ + File.h \ + FileInfoWidget.h \ + ImageCache.h \ + IndexDlg.h \ + InfoShellWidget.h \ + KDatMainWindow.h \ + LoggerWidget.h \ + Node.h \ + Options.h \ + OptionsDlg.h \ + Range.h \ + Tape.h \ + TapeDrive.h \ + TapeFileInfoWidget.h \ + TapeInfoWidget.h \ + TapeManager.h \ + FormatOptDlg.h \ + TarParser.h \ + Util.h \ + VerifyDlg.h \ + VerifyOptDlg.h \ + kdat.h \ + ktreeview.h + +# just to make sure, automake makes them +METASOURCES = AUTO + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kdat.pot diff --git a/kdat/Node.cpp b/kdat/Node.cpp new file mode 100644 index 0000000..828ad69 --- /dev/null +++ b/kdat/Node.cpp @@ -0,0 +1,1585 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <assert.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <qdir.h> +#include <qpainter.h> +#include <qstyle.h> + +#include <kapplication.h> +#include <kdebug.h> + +#include "Archive.h" +#include "BackupProfile.h" +#include "BackupProfileManager.h" +#include "ImageCache.h" +#include "KDatMainWindow.h" +#include "Node.h" +#include "Range.h" +#include "Tape.h" +#include "TapeManager.h" +#include <klocale.h> + +#include "Node.moc" + +Node::Node( int type, const QString & text, const QPixmap& pixmap ) + : KTreeViewItem( text, pixmap ), + _type( type ) +{ +} + +Node::~Node() +{ +} + +void Node::insertNode( Node* child ) +{ + static Node* lastParent = 0; + static uint lastDirIndex = 0; + + if ( lastParent != this ) { + // Recompute lastDirIndex. + for ( lastDirIndex = 0; + ( lastDirIndex < childCount() ) && ( ((Node*)childAt( lastDirIndex ))->getType() == TapeDirectoryNodeType ); + lastDirIndex++ ); + lastParent = this; + } + + int min, mid, max, smin, smax; + if ( ( child->getType() == TapeDirectoryNodeType ) || ( child->getType() == MountedTapeDirectoryNodeType ) ) { + min = 0; + max = lastDirIndex - 1; + lastDirIndex++; + } else { + min = lastDirIndex; + max = childCount() - 1; + } + + if ( min > max ) min = max; + + smin = min; + smax = max; + //printf( "min = %d, max = %d\n", min, max ); + + mid = min; + while ( min < max ) { + mid = ( max - min ) / 2 + min; + //mid = ( min + max ) / 2; + if ( child->getText().compare( childAt( mid )->getText() ) < 0 ) { + max = mid - 1; + } else { + min = mid + 1; + } + } + + if ( childCount() ) { + // KLUDGE! + mid -= 5; + if ( mid < smin ) mid = smin; + if ( ((Node*)childAt( mid ))->getType() != child->getType() ) mid++; + for ( ; ( mid <= smax ) && ( child->getText().compare( childAt( mid )->getText() ) > 0 ); mid++ ); + //printf( "index = %d, text = '%s'\n", mid, child->getText().data() ); + insertChild( mid, child ); + } else { + //printf( "index = %d, text = '%s'\n", 0, child->getText().data() ); + insertChild( 0, child ); + } +} + +int Node::getType() +{ + return _type; +} + +bool Node::isType( int type ) +{ + return type == NodeType; +} + +void Node::expanding( bool expand ) +{ + expand = TRUE; +} + +void Node::expanded() +{ +} + +void Node::collapsed() +{ +} + +void Node::selected() +{ + KDatMainWindow::getInstance()->hideInfo(); +} + +void Node::popupMenu( const QPoint& ) +{ +} + +TapeNode::TapeNode( Tape* tape ) + : Node( TapeNodeType, tape->getName(), *ImageCache::instance()->getTape() ), + _tape( tape ) +{ + setDelayedExpanding( TRUE ); +} + +Tape* TapeNode::getTape() +{ + return _tape; +} + +bool TapeNode::validate() +{ + bool changed = _tape->getName() != getText(); + if ( changed ) { + setText( _tape->getName() ); + } + + if ( ( childCount() == 0 ) && ( !isExpanded() ) ) { + return changed; + } + + // Add/update existing archives. + QPtrListIterator<Archive> i( _tape->getChildren() ); + ArchiveNode* n; + for ( ; i.current(); ++i ) { + n = findArchiveNode( i.current() ); + if ( n ) { + if ( n->validate() ) { + bool select = ( owner->getCurrentItem() == n ); + removeChild( n ); + insertNode( n ); + if ( select ) { + owner->setCurrentItem( owner->itemRow( n ) ); + } + } + } else { + insertNode( new ArchiveNode( i.current() ) ); + } + } + + // Remove deleted archives. + Archive* a; + for ( uint j = 0; j < childCount(); ) { + a = ((ArchiveNode*)childAt( j ))->getArchive(); + for ( i.toFirst(); i.current(); ++i ) { + if ( i.current() == a ) { + break; + } + } + if ( !i.current() ) { + removeChild( childAt( j ) ); + } else { + j++; + } + } + + return changed; +} + +bool TapeNode::isType( int type ) +{ + if ( type == TapeNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void TapeNode::expanding( bool expand ) +{ + expand = TRUE; + + if ( childCount() > 0 ) { + // We already have the children added + return; + } + + QPtrListIterator<Archive> f( _tape->getChildren() ); + for ( ; f.current(); ++f ) { + insertNode( new ArchiveNode( f.current() ) ); + } +} + +void TapeNode::selected() +{ + KDatMainWindow::getInstance()->showTapeInfo( _tape ); +} + +void TapeNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupTapeMenu( p ); +} + +ArchiveNode* TapeNode::findArchiveNode( Archive* archive ) +{ + ArchiveNode* n = 0; + for ( uint i = 0; i < childCount(); i++ ) { + if ( ((ArchiveNode*)childAt( i ))->getArchive() == archive ) { + n = (ArchiveNode*)childAt( i ); + break; + } + } + + return n; +} + +ArchiveNode::ArchiveNode( Archive* archive ) + : Node( ArchiveNodeType, archive->getName(), *ImageCache::instance()->getArchive() ), + _archive( archive ) +{ + setDelayedExpanding( TRUE ); +} + +Archive* ArchiveNode::getArchive() +{ + return _archive; +} + +bool ArchiveNode::validate() +{ + bool changed = _archive->getName() != getText(); + if ( changed ) { + setText( _archive->getName() ); + } + + return changed; +} + +bool ArchiveNode::isType( int type ) +{ + if ( type == ArchiveNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void ArchiveNode::expanding( bool expand ) +{ + expand = TRUE; + + if ( childCount() > 0 ) { + // We already have the children added. + return; + } + + QPtrListIterator<File> f( _archive->getChildren() ); + for ( ; f.current(); ++f ) { + if ( f.current()->getName()[f.current()->getName().length()-1] == '/' ) { + insertNode( new TapeDirectoryNode( f.current() ) ); + } else { + insertNode( new TapeFileNode( f.current() ) ); + } + } +} + +void ArchiveNode::selected() +{ + KDatMainWindow::getInstance()->showArchiveInfo( _archive ); +} + +void ArchiveNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupArchiveMenu( p ); +} + +TapeDirectoryNode::TapeDirectoryNode( File* file ) + : Node( TapeDirectoryNodeType, "", *ImageCache::instance()->getFolderClosed() ), + _file( file ) +{ + setDelayedExpanding( TRUE ); + + int len = _file->getName().length(); + int idx1 = _file->getName().findRev( '/', len - 2 ); + if ( idx1 < 0 ) { + setText( _file->getName().left( len - 1 ) ); + } else { + setText( _file->getName().mid( idx1 + 1, len - idx1 - 2 ) ); + } +} + +File* TapeDirectoryNode::getFile() +{ + return _file; +} + +bool TapeDirectoryNode::isType( int type ) +{ + if ( type == TapeDirectoryNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void TapeDirectoryNode::expanding( bool expand ) +{ + expand = TRUE; + + if ( childCount() > 0 ) { + // We already have the children added. + return; + } + + QPtrListIterator<File> f( _file->getChildren() ); + for ( ; f.current(); ++f ) { + if ( f.current()->getName()[f.current()->getName().length()-1] == '/' ) { + insertNode( new TapeDirectoryNode( f.current() ) ); + } else { + insertNode( new TapeFileNode( f.current() ) ); + } + } +} + +void TapeDirectoryNode::expanded() +{ + setPixmap( *ImageCache::instance()->getFolderOpen() ); +} + +void TapeDirectoryNode::collapsed() +{ + setPixmap( *ImageCache::instance()->getFolderClosed() ); +} + +void TapeDirectoryNode::selected() +{ + KDatMainWindow::getInstance()->showTapeFileInfo( _file ); +} + +TapeFileNode::TapeFileNode( File* file ) + : Node( TapeFileNodeType, file->getName(), *ImageCache::instance()->getFile() ), + _file( file ) +{ + int len = _file->getName().length(); + int idx1 = _file->getName().findRev( '/', len - 1 ); + if ( idx1 < 0 ) { + setText( _file->getName().left( len ) ); + } else { + setText( _file->getName().mid( idx1 + 1, len - idx1 - 1 ) ); + } +} + +File* TapeFileNode::getFile() +{ + return _file; +} + +bool TapeFileNode::isType( int type ) +{ + if ( type == TapeFileNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void TapeFileNode::selected() +{ + KDatMainWindow::getInstance()->showTapeFileInfo( _file ); +} + +SelectableNode::SelectableNode( int type, const QString & text, const QPixmap& pixmap, int state ) + : Node( type, text, pixmap ), + _state( state ) +{ +} + +void SelectableNode::doUpdateState() +{ + bool oneSelected = FALSE; + bool hasSelected = FALSE; + bool allSelected = TRUE; + for ( uint i = 0; i < childCount(); i++ ) { + switch ( ((SelectableNode*)childAt( i ))->_state ) { + case SelAll: + oneSelected = TRUE; + hasSelected = TRUE; + break; + + case SelSome: + hasSelected = TRUE; + allSelected = FALSE; + break; + + case SelNone: + allSelected = FALSE; + break; + } + } + + if ( allSelected ) { + _state = SelAll; + } else if ( oneSelected || hasSelected ) { + _state = SelSome; + } else { + _state = SelNone; + } + + if ( ( getParent() ) && ( getParent()->getParent() ) && ( ((Node*)getParent())->isType( SelectableNodeType ) ) ) { + ((SelectableNode*)getParent())->doUpdateState(); + } +} + +void SelectableNode::doSetSelected( bool select ) +{ + // All my children get the same selection state. + for ( uint i = 0; i < childCount(); i++ ) { + if ( select ) { + if ( !((SelectableNode*)childAt( i ))->isSelected() ) { + ((SelectableNode*)childAt( i ))->doSetSelected( TRUE ); + } + } else { + if ( ((SelectableNode*)childAt( i ))->isSelected() || ((SelectableNode*)childAt( i ))->hasSelectedChildren() ) { + ((SelectableNode*)childAt( i ))->doSetSelected( FALSE ); + } + } + } + + if ( select ) { + _state = SelAll; + } else { + _state = SelNone; + } +} + +const QPixmap* SelectableNode::getSelectPixmap() const +{ + switch ( _state ) { + case SelAll: + return ImageCache::instance()->getSelectAll(); + + case SelNone: + return ImageCache::instance()->getSelectNone(); + + case SelSome: + return ImageCache::instance()->getSelectSome(); + + default: + return 0; + } +} + +bool SelectableNode::mousePressEvent( const QPoint& point ) +{ + if ( _selectRect.contains( point ) ) { + switch ( _state ) { + case SelAll: + setSelected( FALSE ); + break; + + case SelNone: + setSelected( TRUE ); + break; + + case SelSome: + setSelected( TRUE ); + break; + } + return TRUE; + } else { + return FALSE; + } +} + +void SelectableNode::paint( QPainter* p, int indent, const QColorGroup& cg, + bool highlighted ) const +{ + assert(getParent() != 0); /* can't paint root item */ + + p->save(); + p->setPen(cg.text()); + p->setBackgroundColor(cg.base()); + + int cellHeight = height(p->fontMetrics()); + + if (doTree) { + paintTree(p, indent, cellHeight); + } + + /* + * If this item has at least one child and expand button drawing is + * enabled, set the rect for the expand button for later mouse press + * bounds checking, and draw the button. + */ + if (doExpandButton && (child || delayedExpanding)) { + paintExpandButton(p, indent, cellHeight); + } + + const QPixmap* pm = getSelectPixmap(); + p->drawPixmap( indent, ( height() - pm->height() ) / 2, *pm ); + _selectRect.setRect( indent, ( height() - pm->height() ) / 2, 12, 12 ); + + // now draw the item pixmap and text, if applicable + int pixmapX = indent + 4 + 12; + int pixmapY = (cellHeight - pixmap.height()) / 2; + p->drawPixmap(pixmapX, pixmapY, pixmap); + + if (doText) { + paintText(p, indent, cellHeight, cg, highlighted); + } + p->restore(); +} + +void SelectableNode::paintText( QPainter* p, int indent, int cellHeight, + const QColorGroup& cg, bool highlighted ) const +{ + int textX = indent + 12 + 4 + pixmap.width() + 4; + int textY = cellHeight - ((cellHeight - p->fontMetrics().ascent() - + p->fontMetrics().leading()) / 2); + if (highlighted) { + paintHighlight(p, indent, cg, owner->hasFocus(), + (Qt::GUIStyle)owner->style().styleHint(QStyle::SH_GUIStyle)); // Qt3 doesn't make this easy ;) + p->setPen(cg.base()); + p->setBackgroundColor(cg.text()); + } + else { + p->setPen(cg.text()); + p->setBackgroundColor(cg.base()); + } + p->drawText(textX, textY, text); +} + +int SelectableNode::width( int indent, const QFontMetrics& fm ) const +{ + int maxWidth = pixmap.width(); + maxWidth += (4 + 12 + 4 + fm.width(text)); + return maxWidth == 0 ? -1 : indent + maxWidth + 3; +} + +QRect SelectableNode::textBoundingRect(int indent) const +{ + const QFontMetrics& fm = owner->fontMetrics(); + int cellHeight = height(fm); + int rectX = indent + 12 + 4 + pixmap.width() + 3; + int rectY = (cellHeight - fm.ascent() - fm.leading()) / 2 + 2; + int rectW = fm.width(text) + 1; + int rectH = fm.ascent() + fm.leading(); + return QRect(rectX, rectY, rectW, rectH); +} + +bool SelectableNode::isType( int type ) +{ + if ( type == SelectableNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +bool SelectableNode::isSelected() +{ + return _state == SelAll; +} + +bool SelectableNode::hasSelectedChildren() +{ + return _state == SelAll || _state == SelSome; +} + +void SelectableNode::setSelected( bool select ) +{ + doSetSelected( select ); + + if ( ( getParent() ) && ( getParent()->getParent() ) && ( ((Node*)getParent())->isType( SelectableNodeType ) ) ) { + ((SelectableNode*)getParent())->doUpdateState(); + } + + owner->repaint(); + + KDatMainWindow::getInstance()->configureUI( TapeManager::instance()->getMountedTape() ); +} + +RangeableNode::RangeableNode( int type, const QString & text, const QPixmap& pixmap, int state ) + : SelectableNode( type, text, pixmap, state ) +{ +} + +bool RangeableNode::isType( int type ) +{ + if ( type == RangeableNodeType ) { + return TRUE; + } else { + return SelectableNode::isType( type ); + } +} + +MountedArchiveNode::MountedArchiveNode( Archive* archive ) + : RangeableNode( MountedArchiveNodeType, archive->getName(), *ImageCache::instance()->getArchive(), SelNone ), + _archive( archive ) +{ + setDelayedExpanding( TRUE ); +} + +Archive* MountedArchiveNode::getArchive() +{ + return _archive; +} + +const QPtrList<Range>& MountedArchiveNode::getRanges() +{ + return _archive->getRanges(); +} + +bool MountedArchiveNode::validate() +{ + bool changed = _archive->getName() != getText(); + if ( changed ) { + setText( _archive->getName() ); + } + + return changed; +} + +void MountedArchiveNode::setSelected( bool select ) +{ + if ( select ) { + // Deselect all other archives. + Node* parent = (Node*)getParent(); + for ( int i = parent->childCount() - 1; i >= 0; i-- ) { + if ( parent->childAt( i ) != this ) { + ((SelectableNode*)parent->childAt( i ))->setSelected( FALSE ); + } + } + } + + SelectableNode::setSelected( select ); +} + +bool MountedArchiveNode::isType( int type ) +{ + if ( type == MountedArchiveNodeType ) { + return TRUE; + } else { + return RangeableNode::isType( type ); + } +} + +void MountedArchiveNode::expanding( bool expand ) +{ + expand = TRUE; + + if ( childCount() > 0 ) { + // We already have the children added. + return; + } + + QPtrListIterator<File> f( _archive->getChildren() ); + for ( ; f.current(); ++f ) { + if ( f.current()->getName()[f.current()->getName().length()-1] == '/' ) { + insertNode( new MountedTapeDirectoryNode( f.current(), _state == SelSome ? SelNone : _state ) ); + } else { + insertNode( new MountedTapeFileNode( f.current(), _state == SelSome ? SelNone : _state ) ); + } + } +} + +void MountedArchiveNode::selected() +{ + KDatMainWindow::getInstance()->showArchiveInfo( _archive ); +} + +void MountedArchiveNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupMountedArchiveMenu( p ); +} + +MountedTapeDirectoryNode::MountedTapeDirectoryNode( File* file, int state ) + : RangeableNode( MountedTapeDirectoryNodeType, "", *ImageCache::instance()->getFolderClosed(), state ), + _file( file ) +{ + assert( _file > (File*)0x1 ); + + setDelayedExpanding( TRUE ); + + int len = _file->getName().length(); + int idx1 = _file->getName().findRev( '/', len - 2 ); + if ( idx1 < 0 ) { + setText( _file->getName().left( len - 1 ) ); + } else { + setText( _file->getName().mid( idx1 + 1, len - idx1 - 2 ) ); + } +} + +File* MountedTapeDirectoryNode::getFile() +{ + return _file; +} + +QString MountedTapeDirectoryNode::getFullPath() +{ + if ( _fullPath.length() == 0 ) { + _fullPath = getText() + "/"; + for ( Node* parent = (Node*)getParent(); !parent->isType( MountedArchiveNodeType ); parent = (Node*)parent->getParent() ) { + _fullPath.prepend( "/" ); + _fullPath.prepend( parent->getText() ); + } + } + + return _fullPath; +} + +const QPtrList<Range>& MountedTapeDirectoryNode::getRanges() +{ + return _file->getRanges(); +} + +void MountedTapeDirectoryNode::setSelected( bool select ) +{ + if ( select ) { + // Deselect all other archives. + Node* parent = (Node*)getParent(); + Node* arcNode = 0; + while ( !parent->isType( TapeDriveNodeType ) ) { + if ( parent->isType( MountedArchiveNodeType ) ) { + arcNode = parent; + } + parent = (Node*)parent->getParent(); + } + for ( int i = parent->childCount() - 1; i >= 0; i-- ) { + if ( parent->childAt( i ) != arcNode ) { + ((SelectableNode*)parent->childAt( i ))->setSelected( FALSE ); + } + } + } + + SelectableNode::setSelected( select ); +} + +bool MountedTapeDirectoryNode::isType( int type ) +{ + if ( type == MountedTapeDirectoryNodeType ) { + return TRUE; + } else { + return RangeableNode::isType( type ); + } +} + +void MountedTapeDirectoryNode::expanding( bool expand ) +{ + expand = TRUE; + + if ( childCount() > 0 ) { + // We already have the children added. + return; + } + + QPtrListIterator<File> f( _file->getChildren() ); + for ( ; f.current(); ++f ) { + if ( f.current()->getName()[f.current()->getName().length()-1] == '/' ) { + insertNode( new MountedTapeDirectoryNode( f.current(), _state == SelSome ? SelNone : _state ) ); + } else { + insertNode( new MountedTapeFileNode( f.current(), _state == SelSome ? SelNone : _state ) ); + } + } +} + +void MountedTapeDirectoryNode::expanded() +{ + setPixmap( *ImageCache::instance()->getFolderOpen() ); +} + +void MountedTapeDirectoryNode::collapsed() +{ + setPixmap( *ImageCache::instance()->getFolderClosed() ); +} + +void MountedTapeDirectoryNode::selected() +{ + KDatMainWindow::getInstance()->showTapeFileInfo( _file ); +} + +void MountedTapeDirectoryNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupMountedTapeFileMenu( p ); +} + +MountedTapeFileNode::MountedTapeFileNode( File* file, int state ) + : RangeableNode( MountedTapeFileNodeType, file->getName(), *ImageCache::instance()->getFile(), state ), + _file( file ) +{ + int len = _file->getName().length(); + int idx1 = _file->getName().findRev( '/', len - 1 ); + if ( idx1 < 0 ) { + setText( _file->getName().left( len ) ); + } else { + setText( _file->getName().mid( idx1 + 1, len - idx1 - 1 ) ); + } +} + +File* MountedTapeFileNode::getFile() +{ + return _file; +} + +QString MountedTapeFileNode::getFullPath() +{ + if ( _fullPath.length() == 0 ) { + _fullPath = getText(); + for ( Node* parent = (Node*)getParent(); !parent->isType( MountedArchiveNodeType ); parent = (Node*)parent->getParent() ) { + _fullPath.prepend( "/" ); + _fullPath.prepend( parent->getText() ); + } + } + + return _fullPath; +} + +const QPtrList<Range>& MountedTapeFileNode::getRanges() +{ + return _file->getRanges(); +} + +void MountedTapeFileNode::setSelected( bool select ) +{ + if ( select ) { + // Deselect all other archives. + Node* parent = (Node*)getParent(); + Node* arcNode = 0; + while ( !parent->isType( TapeDriveNodeType ) ) { + if ( parent->isType( MountedArchiveNodeType ) ) { + arcNode = parent; + } + parent = (Node*)parent->getParent(); + } + for ( int i = parent->childCount() - 1; i >= 0; i-- ) { + if ( parent->childAt( i ) != arcNode ) { + ((SelectableNode*)parent->childAt( i ))->setSelected( FALSE ); + } + } + } + + SelectableNode::setSelected( select ); +} + +bool MountedTapeFileNode::isType( int type ) +{ + if ( type == MountedTapeFileNodeType ) { + return TRUE; + } else { + return RangeableNode::isType( type ); + } +} + +void MountedTapeFileNode::selected() +{ + KDatMainWindow::getInstance()->showTapeFileInfo( _file ); +} + +void MountedTapeFileNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupMountedTapeFileMenu( p ); +} + +ArchiveableNode::ArchiveableNode( int type, const QString & text, const QPixmap& pixmap, int state ) + : SelectableNode( type, text, pixmap, state ) +{ +} + +bool ArchiveableNode::isType( int type ) +{ + if ( type == ArchiveableNodeType ) { + return TRUE; + } else { + return SelectableNode::isType( type ); + } +} + +RootNode::RootNode() + : ArchiveableNode( RootNodeType, "/", *ImageCache::instance()->getFolderClosed(), SelNone ), + _mtime( -1 ) +{ + setDelayedExpanding( TRUE ); +} + +QString RootNode::getFullPath() +{ + if ( _fullPath.length() == 0 ) { + _fullPath = "/"; + } + + return _fullPath; +} + +bool RootNode::isType( int type ) +{ + if ( type == RootNodeType ) { + return TRUE; + } else { + return ArchiveableNode::isType( type ); + } +} + +void RootNode::expanding( bool expand ) +{ + expand = TRUE; + + // If we already have some children, check to see if the directory has been modified. + if ( childCount() > 0 ) { + struct stat statinfo; + if ( stat( "/", &statinfo ) < 0 ) { + printf( "Can't stat '/'\n" ); + expand = FALSE; + return; + } + + if ( statinfo.st_mtime == _mtime ) { + // Directory has not been modified. + return; + } + + _mtime = statinfo.st_mtime; + + // Remove all the children. + Node* n; + while ( ( n = (Node*)getChild() ) ) { + removeChild( n ); + delete n; + } + } else { + struct stat statinfo; + if ( stat( "/", &statinfo ) < 0 ) { + printf( "Can't stat '/'\n" ); + expand = FALSE; + return; + } + + _mtime = statinfo.st_mtime; + } + + QDir dir( "/" ); + if ( dir.exists() ) { + // Make sure we can read the directory. + if ( !dir.isReadable() ) { + //KMsgBox::message( this, i18n( "KDat: Error" ), i18n( "The directory cannot be read." ), KMsgBox::EXCLAMATION ); + return; + } + + // Fill in the child's children. + const QFileInfoList* list = dir.entryInfoList( QDir::Hidden | QDir::Files | QDir::Dirs, QDir::Name | QDir::DirsFirst ); + if ( list ) { + QFileInfoListIterator it( *list ); + for ( ; it.current(); ++it ) { + if ( ( it.current()->fileName() != "." ) && ( it.current()->fileName() != ".." ) ) { + if ( it.current()->isDir() ) { + appendChild( new DirectoryNode( it.current()->fileName(), _state == SelSome ? SelNone : _state ) ); + } else { + appendChild( new FileNode( it.current()->fileName(), _state == SelSome ? SelNone : _state ) ); + } + } + } + } + } +} + +void RootNode::expanded() +{ + setPixmap( *ImageCache::instance()->getFolderOpen() ); +} + +void RootNode::collapsed() +{ + setPixmap( *ImageCache::instance()->getFolderClosed() ); +} + +void RootNode::selected() +{ + KDatMainWindow::getInstance()->showFileInfo( "/" ); +} + +void RootNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupLocalFileMenu( p ); +} + +DirectoryNode::DirectoryNode( const QString & text, int state ) + : ArchiveableNode( DirectoryNodeType, text, *ImageCache::instance()->getFolderClosed(), state ), + _mtime( -1 ) +{ + setDelayedExpanding( TRUE ); +} + +QString DirectoryNode::getFullPath() +{ + if ( _fullPath.isEmpty() ) { + _fullPath = ((ArchiveableNode*)getParent())->getFullPath() + getText() + "/"; + } + + return _fullPath; +} + +bool DirectoryNode::isType( int type ) +{ + if ( type == DirectoryNodeType ) { + return TRUE; + } else { + return ArchiveableNode::isType( type ); + } +} + +void DirectoryNode::expanding( bool expand ) +{ + expand = TRUE; + + // Construct the full path. + QString path; + Node* n; + for ( n = this; n->getType() != RootNodeType; n = (Node*)n->getParent() ) { + path.prepend( "/" ); + path.prepend( n->getText() ); + } + path.prepend( "/" ); + + // If we already have some children, check to see if the directory has been modified. + if ( childCount() > 0 ) { + struct stat statinfo; + if ( stat( QFile::encodeName(path), &statinfo ) < 0 ) { + kdError() << "Can't stat " << path << endl; + expand = FALSE; + return; + } + + if ( statinfo.st_mtime == _mtime ) { + // Directory has not been modified. + return; + } + + _mtime = statinfo.st_mtime; + + // Remove all the children. + Node* n; + while ( ( n = (Node*)getChild() ) ) { + removeChild( n ); + delete n; + } + } else { + struct stat statinfo; + if ( stat( QFile::encodeName(path), &statinfo ) < 0 ) { + kdError() << "Can't stat " << path << endl; + expand = FALSE; + return; + } + + _mtime = statinfo.st_mtime; + } + + QDir dir( path ); + if ( dir.exists() ) { + // Make sure we can read the directory. + if ( !dir.isReadable() ) { + //KMsgBox::message( this, i18n( "KDat: Error" ), i18n( "The directory cannot be read." ), KMsgBox::EXCLAMATION ); + return; + } + + // Fill in the child's children. + const QFileInfoList* list = dir.entryInfoList( QDir::Hidden | QDir::Files | QDir::Dirs, QDir::Name | QDir::DirsFirst ); + if ( list ) { + QFileInfoListIterator it( *list ); + for ( ; it.current(); ++it ) { + if ( ( it.current()->fileName() != "." ) && ( it.current()->fileName() != ".." ) ) { + if ( it.current()->isDir() ) { + appendChild( new DirectoryNode( it.current()->fileName(), _state == SelSome ? SelNone : _state ) ); + } else { + appendChild( new FileNode( it.current()->fileName(), _state == SelSome ? SelNone : _state ) ); + } + } + } + } + } +} + +void DirectoryNode::expanded() +{ + setPixmap( *ImageCache::instance()->getFolderOpen() ); +} + +void DirectoryNode::collapsed() +{ + setPixmap( *ImageCache::instance()->getFolderClosed() ); +} + +void DirectoryNode::selected() +{ + // Construct the full path. + QString path; + Node* n; + for ( n = this; n->getType() != RootNodeType; n = (Node*)n->getParent() ) { + path.prepend( "/" ); + path.prepend( n->getText() ); + } + path.prepend( "/" ); + path = path.left( path.length() - 1 ); + + KDatMainWindow::getInstance()->showFileInfo( path ); +} + +void DirectoryNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupLocalFileMenu( p ); +} + +FileNode::FileNode( const QString & text, int state ) + : ArchiveableNode( FileNodeType, text, *ImageCache::instance()->getFile(), state ) +{ +} + +QString FileNode::getFullPath() +{ + if ( _fullPath.isEmpty() ) { + _fullPath = ((ArchiveableNode*)getParent())->getFullPath() + getText(); + } + + return _fullPath; +} + +bool FileNode::isType( int type ) +{ + if ( type == FileNodeType ) { + return TRUE; + } else { + return ArchiveableNode::isType( type ); + } +} + +void FileNode::selected() +{ + // Construct the full path. + QString path; + Node* n; + for ( n = this; n->getType() != RootNodeType; n = (Node*)n->getParent() ) { + path.prepend( "/" ); + path.prepend( n->getText() ); + } + path.prepend( "/" ); + path = path.left( path.length() - 1 ); + + KDatMainWindow::getInstance()->showFileInfo( path ); +} + +void FileNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupLocalFileMenu( p ); +} + +TapeDriveNode::TapeDriveNode() + : Node( TapeDriveNodeType, i18n( "<no tape>" ), *ImageCache::instance()->getTapeUnmounted() ) +{ + setDelayedExpanding( TRUE ); + + connect( TapeManager::instance(), SIGNAL( sigTapeMounted() ) , this, SLOT( slotTapeMounted() ) ); + connect( TapeManager::instance(), SIGNAL( sigTapeUnmounted() ) , this, SLOT( slotTapeUnmounted() ) ); + connect( TapeManager::instance(), SIGNAL( sigTapeModified( Tape* ) ), this, SLOT( slotTapeModified( Tape* ) ) ); +} + +bool TapeDriveNode::isType( int type ) +{ + if ( type == TapeDriveNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void TapeDriveNode::expanding( bool expand ) +{ + if ( !TapeManager::instance()->getMountedTape() ) { + expand = FALSE; + return; + } + + expand = TRUE; + + if ( childCount() > 0 ) { + // We already have the children added + return; + } + + QPtrListIterator<Archive> f( TapeManager::instance()->getMountedTape()->getChildren() ); + for ( ; f.current(); ++f ) { + insertNode( new MountedArchiveNode( f.current() ) ); + } +} + +void TapeDriveNode::selected() +{ + if ( TapeManager::instance()->getMountedTape() ) { + KDatMainWindow::getInstance()->showTapeInfo( TapeManager::instance()->getMountedTape() ); + } else { + KDatMainWindow::getInstance()->hideInfo(); + } +} + +void TapeDriveNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupTapeDriveMenu( p ); +} + +void TapeDriveNode::slotTapeMounted() +{ + setPixmap( *ImageCache::instance()->getTapeMounted() ); + setText( TapeManager::instance()->getMountedTape()->getName() ); + + if ( this == owner->getCurrentItem() ) { + KDatMainWindow::getInstance()->showTapeInfo( TapeManager::instance()->getMountedTape() ); + } + + if ( isExpanded() ) { + bool dummy = TRUE; + expanding( dummy ); + } +} + +void TapeDriveNode::slotTapeUnmounted() +{ + setPixmap( *ImageCache::instance()->getTapeUnmounted() ); + setText( i18n( "<no tape>" ) ); + + // Remove all the children. + Node* n; + while ( ( n = (Node*)getChild() ) ) { + Node::removeChild( n ); + delete n; + } + + if ( this == owner->getCurrentItem() ) { + KDatMainWindow::getInstance()->hideInfo(); + } +} + +void TapeDriveNode::slotTapeModified( Tape* tape ) +{ + if ( TapeManager::instance()->getMountedTape() != tape ) { + return; + } + + setText( tape->getName() ); + + if ( owner->getCurrentItem() == this ) { + KDatMainWindow::getInstance()->showTapeInfo( TapeManager::instance()->getMountedTape() ); + } + + if ( ( childCount() == 0 ) && ( !isExpanded() ) ) { + return; + } + + // Add/update existing archives. + QPtrListIterator<Archive> i( tape->getChildren() ); + MountedArchiveNode* n; + for ( ; i.current(); ++i ) { + n = findArchiveNode( i.current() ); + if ( n ) { + if ( n->validate() ) { + bool select = ( owner->getCurrentItem() == n ); + Node::removeChild( n ); + insertNode( n ); + if ( select ) { + owner->setCurrentItem( owner->itemRow( n ) ); + } + } + } else { + insertNode( new MountedArchiveNode( i.current() ) ); + } + } + + // Remove deleted archives. + Archive* a; + for ( uint j = 0; j < childCount(); ) { + a = ((MountedArchiveNode*)childAt( j ))->getArchive(); + for ( i.toFirst(); i.current(); ++i ) { + if ( i.current() == a ) { + break; + } + } + if ( !i.current() ) { + Node::removeChild( childAt( j ) ); + } else { + j++; + } + } +} + +MountedArchiveNode* TapeDriveNode::findArchiveNode( Archive* archive ) +{ + MountedArchiveNode* n = 0; + for ( uint i = 0; i < childCount(); i++ ) { + if ( ((MountedArchiveNode*)childAt( i ))->getArchive() == archive ) { + n = (MountedArchiveNode*)childAt( i ); + break; + } + } + + return n; +} + +TapeIndexRootNode::TapeIndexRootNode() + : Node( TapeIndexRootNodeType, i18n( "Tape Indexes" ), *ImageCache::instance()->getFolderClosed() ) +{ + setDelayedExpanding( TRUE ); + + connect( TapeManager::instance(), SIGNAL( sigTapeAdded( Tape* ) ) , this, SLOT( slotTapeAdded( Tape* ) ) ); + connect( TapeManager::instance(), SIGNAL( sigTapeRemoved( Tape* ) ) , this, SLOT( slotTapeRemoved( Tape* ) ) ); + connect( TapeManager::instance(), SIGNAL( sigTapeModified( Tape* ) ), this, SLOT( slotTapeModified( Tape* ) ) ); +} + +bool TapeIndexRootNode::isType( int type ) +{ + if ( type == TapeIndexRootNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void TapeIndexRootNode::expanding( bool expand ) +{ + expand = TRUE; + + if ( childCount() > 0 ) { + return; + } + + QStringList list = TapeManager::instance()->getTapeIDs(); + QStringList::Iterator i = list.begin(); + for ( ; i != list.end(); ++i ) { + Tape* tape = TapeManager::instance()->findTape( *i ); + if ( tape ) { + insertNode( new TapeNode( tape ) ); + } + } +} + +void TapeIndexRootNode::expanded() +{ + setPixmap( *ImageCache::instance()->getFolderOpen() ); +} + +void TapeIndexRootNode::collapsed() +{ + setPixmap( *ImageCache::instance()->getFolderClosed() ); +} + +void TapeIndexRootNode::slotTapeAdded( Tape* tape ) +{ + if ( ( childCount() > 0 ) || ( isExpanded() ) ) { + insertNode( new TapeNode( tape ) ); + } +} + +void TapeIndexRootNode::slotTapeRemoved( Tape* tape ) +{ + if ( ( childCount() > 0 ) || ( isExpanded() ) ) { + Node* n = findTapeNode( tape ); + if ( n ) { + Node::removeChild( n ); + delete n; + } + } +} + +void TapeIndexRootNode::slotTapeModified( Tape* tape ) +{ + TapeNode* n = findTapeNode( tape ); + if ( !n ) { + return; + } + + if ( n->validate() ) { + bool select = ( owner->getCurrentItem() == n ); + Node::removeChild( n ); + insertNode( n ); + if ( select ) { + owner->setCurrentItem( owner->itemRow( n ) ); + } + } +} + +TapeNode* TapeIndexRootNode::findTapeNode( Tape* tape ) +{ + TapeNode* n = 0; + for ( uint i = 0; i < childCount(); i++ ) { + n = (TapeNode*)childAt( i ); + if ( n->getTape() == tape ) { + return n; + } + } + + return 0; +} + +BackupProfileNode::BackupProfileNode( BackupProfile* backupProfile ) + : Node( BackupProfileNodeType, backupProfile->getName(), *ImageCache::instance()->getFile() ), + _backupProfile( backupProfile ) +{ +} + +BackupProfile* BackupProfileNode::getBackupProfile() +{ + return _backupProfile; +} + +bool BackupProfileNode::validate() +{ + bool changed = _backupProfile->getName() != getText(); + if ( changed ) { + setText( _backupProfile->getName() ); + } + + return changed; +} + +bool BackupProfileNode::isType( int type ) +{ + if ( type == BackupProfileNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void BackupProfileNode::selected() +{ + KDatMainWindow::getInstance()->showBackupProfileInfo( _backupProfile ); +} + +void BackupProfileNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupBackupProfileMenu( p ); +} + +BackupProfileRootNode::BackupProfileRootNode() + : Node( BackupProfileRootNodeType, i18n( "Backup Profiles" ), *ImageCache::instance()->getFolderClosed() ) +{ + setDelayedExpanding( TRUE ); + + connect( BackupProfileManager::instance(), SIGNAL( sigBackupProfileAdded( BackupProfile* ) ) , + this, SLOT( slotBackupProfileAdded( BackupProfile* ) ) ); + connect( BackupProfileManager::instance(), SIGNAL( sigBackupProfileRemoved( BackupProfile* ) ) , + this, SLOT( slotBackupProfileRemoved( BackupProfile* ) ) ); + connect( BackupProfileManager::instance(), SIGNAL( sigBackupProfileModified( BackupProfile* ) ), + this, SLOT( slotBackupProfileModified( BackupProfile* ) ) ); +} + +void BackupProfileRootNode::setSelected( BackupProfile* pBackupProfile ) +{ + setExpanded( TRUE ); + bool dummy = TRUE; + expanding( dummy ); + expanded(); + + BackupProfileNode* pNode = findBackupProfileNode( pBackupProfile ); + if ( pNode ) { + owner->setCurrentItem( owner->itemRow( pNode ) ); + pNode->selected(); + } + + KDatMainWindow::getInstance()->configureUI( TapeManager::instance()->getMountedTape() ); +} + +bool BackupProfileRootNode::isType( int type ) +{ + if ( type == BackupProfileRootNodeType ) { + return TRUE; + } else { + return Node::isType( type ); + } +} + +void BackupProfileRootNode::expanding( bool expand ) +{ + expand = TRUE; + + if ( childCount() > 0 ) { + return; + } + + QStringList list = BackupProfileManager::instance()->getBackupProfileNames(); + QStringList::Iterator i = list.begin(); + for ( ; i != list.end(); ++i ) { + BackupProfile* backupProfile = BackupProfileManager::instance()->findBackupProfile( *i ); + if ( backupProfile ) { + insertNode( new BackupProfileNode( backupProfile ) ); + } + } +} + +void BackupProfileRootNode::expanded() +{ + setPixmap( *ImageCache::instance()->getFolderOpen() ); +} + +void BackupProfileRootNode::collapsed() +{ + setPixmap( *ImageCache::instance()->getFolderClosed() ); +} + +void BackupProfileRootNode::popupMenu( const QPoint& p ) +{ + KDatMainWindow::getInstance()->popupBackupProfileRootMenu( p ); +} + +void BackupProfileRootNode::slotBackupProfileAdded( BackupProfile* backupProfile ) +{ + if ( ( childCount() > 0 ) || ( isExpanded() ) ) { + insertNode( new BackupProfileNode( backupProfile ) ); + } +} + +void BackupProfileRootNode::slotBackupProfileRemoved( BackupProfile* backupProfile ) +{ + if ( ( childCount() > 0 ) || ( isExpanded() ) ) { + Node* n = findBackupProfileNode( backupProfile ); + assert( n ); + Node::removeChild( n ); + delete n; + } +} + +void BackupProfileRootNode::slotBackupProfileModified( BackupProfile* backupProfile ) +{ + BackupProfileNode* n = findBackupProfileNode( backupProfile ); + if ( !n ) { + return; + } + + if ( n->validate() ) { + bool select = ( owner->getCurrentItem() == n ); + Node::removeChild( n ); + insertNode( n ); + if ( select ) { + owner->setCurrentItem( owner->itemRow( n ) ); + } + } +} + +BackupProfileNode* BackupProfileRootNode::findBackupProfileNode( BackupProfile* backupProfile ) +{ + BackupProfileNode* n = 0; + for ( uint i = 0; i < childCount(); i++ ) { + n = (BackupProfileNode*)childAt( i ); + if ( n->getBackupProfile() == backupProfile ) { + return n; + } + } + + return 0; +} diff --git a/kdat/Node.h b/kdat/Node.h new file mode 100644 index 0000000..84dfea8 --- /dev/null +++ b/kdat/Node.h @@ -0,0 +1,1051 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _Node_h_ +#define _Node_h_ + +#include <qobject.h> + +#include "ktreeview.h" + +class Archive; +class BackupProfile; +class File; +class RangeList; +class Tape; + +/** + * @short The base class for all tree nodes in KDat. + */ +class Node : public KTreeViewItem { + int _type; +protected: + void insertNode( Node* child ); +public: + enum { + ArchiveableNodeType, + ArchiveNodeType, + BackupProfileNodeType, + BackupProfileRootNodeType, + DirectoryNodeType, + FileNodeType, + MountedArchiveNodeType, + MountedTapeDirectoryNodeType, + MountedTapeFileNodeType, + NodeType, + RangeableNodeType, + RootNodeType, + SelectableNodeType, + TapeNodeType, + TapeDirectoryNodeType, + TapeDriveNodeType, + TapeFileNodeType, + TapeIndexRootNodeType + }; + + /** + * Create a new tree node. + * + * @param type Should be one of the enums. + * @param text Text label for the node. + * @param pixmap A pixmap to display to the left of the text. + */ + Node( int type, const QString & text, const QPixmap& pixmap ); + + /** + * There must be a virtual destructor in the base class so that all the + * destructors in the inherited classes get called. + */ + virtual ~Node(); + + /** + * Get the node's type. + * + * @return The type of this node. This information can be used to safely + * downcast a Node pointer. + */ + int getType(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * This method is called immediately before the node is to be expanded. + * This method can be used to fill in the children of this node, or + * prevent the node from expanding. The default implementation does + * nothing. + * + * @param expand The method sets this to TRUE to allow the node to be + * expanded or FALSE to prevent the node from being expanded. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * This method is called immediately after the node has been expanded. + * The default implementation does nothing. + */ + virtual void expanded(); + + /** + * This method is called immediately after the node has been collapsed. + * The default implementation does nothing. + */ + virtual void collapsed(); + + /** + * This method is called immediately after the node has been selected. + * The default implementation does nothing. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& ); +}; + +class ArchiveNode; + +/** + * @short This node represents the root of a tape index. + */ +class TapeNode : public Node { + Tape* _tape; + + ArchiveNode* findArchiveNode( Archive* archive ); +public: + /** + * Create a tape node. + * + * @param tape A pointer to the tape index that this node represents. + */ + TapeNode( Tape* tape ); + + /** + * Get the tape index associated with this node. + * + * @return A pointer to the tape index. + */ + Tape* getTape(); + + /** + * Make sure that the displayed information matches the tape index. This + * method recurses through child nodes. + * + * @return TRUE if the node's text has changed. + */ + bool validate(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each archive in the tape index. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This node represents a single archive within a tape. + */ +class ArchiveNode : public Node { + Archive* _archive; +public: + /** + * Create an archive node. + * + * @param archive A pointer to the archive that this node represents. + */ + ArchiveNode( Archive* archive ); + + /** + * Get the archive associated with this node. + * + * @return A pointer to the archive. + */ + Archive* getArchive(); + + /** + * Make sure that the displayed information matches the tape index. This + * method recurses through child nodes. + * + * @return TRUE if the node's text has changed. + */ + bool validate(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each top-level file/directory contained in the + * archive. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This node represents a single directory within an archive. + */ +class TapeDirectoryNode : public Node { + File* _file; +public: + /** + * Create a tape directory node. + * + * @param file A pointer to the file that this node represents. + */ + TapeDirectoryNode( File* file ); + + /** + * Get the file associated with this node. + * + * @return A pointer to the file. + */ + File* getFile(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each file contained in this directory. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * Change the node's pixmap to an open folder. + */ + virtual void expanded(); + + /** + * Change the node's pixmap to a closed folder. + */ + virtual void collapsed(); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); +}; + +/** + * @short This node represents a single file within an archive. + */ +class TapeFileNode : public Node { + File* _file; +public: + /** + * Create a tape file node. + * + * @param file A pointer to the file that this node represents. + */ + TapeFileNode( File* file ); + + /** + * Get the file associated with this node. + * + * @return A pointer to the file. + */ + File* getFile(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); +}; + +/** + * @short This class defines an interface for managing multiple selection of tree nodes. + */ +class SelectableNode : public Node { +protected: + mutable QRect _selectRect; + enum { SelAll, SelNone, SelSome }; + int _state; + + void doUpdateState(); + void doSetSelected( bool select ); + const QPixmap* getSelectPixmap() const; + + virtual bool mousePressEvent( const QPoint& point ); + virtual void paint( QPainter* p, int indent, + const QColorGroup& cg, bool highlighted ) const; + virtual void paintText( QPainter* p, int indent, int cellHeight, + const QColorGroup& cg, bool highlighted ) const; + virtual int width( int indent, const QFontMetrics& fm ) const; + virtual QRect textBoundingRect( int indent ) const; +public: + /** + * Create a selectable node. + * + * @param type Should be one of the enums. + * @param text Text label for the node. + * @param pixmap A pixmap to display to the left of the text. + * @param state The initial selection state of the node. + */ + SelectableNode( int type, const QString & text, const QPixmap& pixmap, int state ); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Determine whether the node and all of its children are selected. + * + * @return TRUE if the node is selected, otherwise FALSE. + */ + bool isSelected(); + + /** + * Determine whether the node has at least one selected descendent. + * + * @return TRUE if at least one descendent is selected. + */ + bool hasSelectedChildren(); + + /** + * Select/deselect this node and all of its children. + * + * @param selected TRUE means the node will be selected, FALSE means the + * node will be deselected. + */ + virtual void setSelected( bool selected ); +}; + +/** + * @short This class defines an interface for retrieving a list of selected tape blocks. + */ +class RangeableNode : public SelectableNode { +public: + /** + * Create a new rangeable node. + * + * @param type Should be one of the enums. + * @param text Text label for the node. + * @param pixmap A pixmap to display to the left of the text. + * @param state The initial selection state of the node. + */ + RangeableNode( int type, const QString & text, const QPixmap& pixmap, int state ); + + /** + * Get a list of all the selected ranges for the subtree rooted at this + * node. + */ + virtual const QPtrList<Range>& getRanges() = 0; + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); +}; + +/** + * @short This node represents a single archive within a mounted tape. + */ +class MountedArchiveNode : public RangeableNode { + Archive* _archive; +public: + /** + * Create an mounted archive node. + * + * @param archive A pointer to the archive that this node represents. + */ + MountedArchiveNode( Archive* archive ); + + /** + * Get the archive associated with this node. + * + * @return A pointer to the archive. + */ + Archive* getArchive(); + + /** + * Make sure that the displayed information matches the tape index. This + * method recurses through child nodes. + * + * @return TRUE if the node's text has changed. + */ + bool validate(); + + /** + * Get a list of all the selected ranges for the subtree rooted at this + * node. + */ + virtual const QPtrList<Range>& getRanges(); + + /** + * Select/deselect this node and all of its children. + * + * @param selected TRUE means the node will be selected, FALSE means the + * node will be deselected. + */ + virtual void setSelected( bool selected ); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each top-level file/directory contained in the + * archive. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This node represents a single directory within a mounted archive. + */ +class MountedTapeDirectoryNode : public RangeableNode { + File* _file; + QString _fullPath; +public: + /** + * Create a tape directory node. + * + * @param file A pointer to the file that this node represents. + * @param state The initial selection state of the node. + */ + MountedTapeDirectoryNode( File* file, int state ); + + /** + * Get the file associated with this node. + * + * @return A pointer to the file. + */ + File* getFile(); + + /** + * Return the "fully" qualified path name of this node. It will probably + * NOT begin with a '/'. + * + * @return The full path name of the directory. + */ + QString getFullPath(); + + /** + * Get a list of all the selected ranges for the subtree rooted at this + * node. + */ + virtual const QPtrList<Range>& getRanges(); + + /** + * Select/deselect this node and all of its children. + * + * @param selected TRUE means the node will be selected, FALSE means the + * node will be deselected. + */ + virtual void setSelected( bool selected ); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each file contained in this directory. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * Change the node's pixmap to an open folder. + */ + virtual void expanded(); + + /** + * Change the node's pixmap to a closed folder. + */ + virtual void collapsed(); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This node represents a single file within a mounted archive. + */ +class MountedTapeFileNode : public RangeableNode { + File* _file; + QString _fullPath; +public: + /** + * Create a tape file node. + * + * @param file A pointer to the file that this node represents. + * @param state The initial selection state of the node. + */ + MountedTapeFileNode( File* file, int state ); + + /** + * Get the file associated with this node. + * + * @return A pointer to the file. + */ + File* getFile(); + + /** + * Return the "fully" qualified path name of this node. It will probably + * NOT begin with a '/'. + * + * @return The full path name of the file. + */ + QString getFullPath(); + + /** + * Get a list of all the selected ranges for the subtree rooted at this + * node. + */ + virtual const QPtrList<Range>& getRanges(); + + /** + * Select/deselect this node and all of its children. + * + * @param selected TRUE means the node will be selected, FALSE means the + * node will be deselected. + */ + virtual void setSelected( bool selected ); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This defines an interface for archiveable nodes. + */ +class ArchiveableNode : public SelectableNode { +protected: + QString _fullPath; +public: + /** + * Create a new archiveable node. + * + * @param type Should be one of the enums. + * @param text Text label for the node. + * @param pixmap A pixmap to display to the left of the text. + * @param state The initial selection state of the node. + */ + ArchiveableNode( int type, const QString & text, const QPixmap& pixmap, int state ); + + /** + * Compute the full path name of this node. Nodes representing + * directories end with a '/'. + * + * @return The full path of the file that this node represents. + */ + virtual QString getFullPath() = 0; + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); +}; + +/** + * @short This node represents the root directory of the local filesystem. + */ +class RootNode : public ArchiveableNode { + int _mtime; +public: + /** + * Create a root directory node. + */ + RootNode(); + + /** + * Compute the full path name of this node. Nodes representing + * directories end with a '/'. + * + * @return The full path of the file that this node represents. + */ + virtual QString getFullPath(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each file contained in this directory. If the + * directory has been modified then all of the child nodes are recreated. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * Change the node's pixmap to an open folder. + */ + virtual void expanded(); + + /** + * Change the node's pixmap to a closed folder. + */ + virtual void collapsed(); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This node represents a directory on the local filesystem. + */ +class DirectoryNode : public ArchiveableNode { + int _mtime; +public: + /** + * Create a directory node. + * + * @param text The name of the directory. The full path is determined by + * traversing the tree to the root node. + * @param state The initial selection state of the node. + */ + DirectoryNode( const QString & text, int state ); + + /** + * Compute the full path name of this node. Nodes representing + * directories end with a '/'. + * + * @return The full path of the file that this node represents. + */ + virtual QString getFullPath(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each file contained in this directory. If the + * directory has been modified then all of the child nodes are recreated. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * Change the node's pixmap to an open folder. + */ + virtual void expanded(); + + /** + * Change the node's pixmap to a closed folder. + */ + virtual void collapsed(); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This node represents a file on the local filesystem. + */ +class FileNode : public ArchiveableNode { +public: + /** + * Create a file node. + * + * @param text The name of the file. The full path is determined by + * traversing the tree to the root node. + * @param state The initial selection state of the node. + */ + FileNode( const QString & text, int state ); + + /** + * Compute the full path name of this node. Nodes representing + * directories end with a '/'. + * + * @return The full path of the file that this node represents. + */ + virtual QString getFullPath(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +}; + +/** + * @short This node represents a tape drive. + */ +class TapeDriveNode : public QObject, public Node { + Q_OBJECT + + MountedArchiveNode* findArchiveNode( Archive* archive ); +public: + /** + * Create a tape drive node. + */ + TapeDriveNode(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each archive in the tape index. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& p ); +public slots: + /** + * This slot is called when the tape is mounted. + */ + void slotTapeMounted(); + + /** + * This slot is called when the tape is unmounted. + */ + void slotTapeUnmounted(); + + /** + * Locate the child associated with the modified tape index, and make sure + * that the displayed information (including all children) is updated. + * + * @param tape A pointer to the tape index that was modified. + */ + void slotTapeModified( Tape* tape ); +}; + +/** + * @short This node represents the root of the tape index subtree. + */ +class TapeIndexRootNode : public QObject, public Node { + Q_OBJECT + + TapeNode* findTapeNode( Tape* tape ); +public: + /** + * Create a tape index root. + */ + TapeIndexRootNode(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each tape index. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * Change the node's pixmap to an open folder. + */ + virtual void expanded(); + + /** + * Change the node's pixmap to a closed folder. + */ + virtual void collapsed(); +public slots: + /** + * Add a new child node for the new tape index. + * + * @param tape A pointer to the new tape index. + */ + void slotTapeAdded( Tape* tape ); + + /** + * Remove the child associated with the tape index. + * + * @param tape A pointer to the removed tape index. + */ + void slotTapeRemoved( Tape* tape ); + + /** + * Locate the child associated with the modified tape index, and make sure + * that the displayed information (including all children) is updated. + * + * @param tape A pointer to the tape index that was modified. + */ + void slotTapeModified( Tape* tape ); +}; + +/** + * @short This node represents a backup profile. + */ +class BackupProfileNode : public Node { + BackupProfile* _backupProfile; +public: + /** + * Create a backup profile node. + * + * @param backupProfile A pointer to the backup profile that this node represents. + */ + BackupProfileNode( BackupProfile* backupProfile ); + + /** + * Get the backup profile associated with this node. + * + * @return A pointer to the backup profile. + */ + BackupProfile* getBackupProfile(); + + /** + * Make sure that the displayed information matches the backup profile. + * + * @return TRUE if the node's text has changed. + */ + bool validate(); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * This method is called immediately after the node has been selected. + */ + virtual void selected(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& ); +}; + +/** + * @short This node represents the root of the backup profile subtree. + */ +class BackupProfileRootNode : public QObject, public Node { + Q_OBJECT + + BackupProfileNode* findBackupProfileNode( BackupProfile* backupProfile ); +public: + /** + * Create a backup profile root. + */ + BackupProfileRootNode(); + + /** + * Select the node associated with the given backu profile. + * + * @param pBackupProfile The backup profile to select. + */ + void setSelected( BackupProfile* pBackupProfile ); + + /** + * Determine whether the node is an instance of the given node type. + * + * @param type The type to compare against. + */ + virtual bool isType( int type ); + + /** + * Create child nodes for each tape index. + * + * @param expand This will always be set to TRUE. + */ + virtual void expanding( bool expand=TRUE ); + + /** + * Change the node's pixmap to an open folder. + */ + virtual void expanded(); + + /** + * Change the node's pixmap to a closed folder. + */ + virtual void collapsed(); + + /** + * This method is called when the user right-clicks the mouse over the + * node. + */ + virtual void popupMenu( const QPoint& ); +public slots: + /** + * Add a new child node for the new backup profile. + * + * @param tape A pointer to the new backup profile. + */ + void slotBackupProfileAdded( BackupProfile* backupProfile ); + + /** + * Remove the child associated with the backup profile. + * + * @param tape A pointer to the removed backup profile. + */ + void slotBackupProfileRemoved( BackupProfile* backupProfile ); + + /** + * Locate the child associated with the modified backup profile, and make + * sure that the displayed information is updated. + * + * @param tape A pointer to the backup profile that was modified. + */ + void slotBackupProfileModified( BackupProfile* backupProfile ); +}; + +#endif diff --git a/kdat/Options.cpp b/kdat/Options.cpp new file mode 100644 index 0000000..71a784e --- /dev/null +++ b/kdat/Options.cpp @@ -0,0 +1,165 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <kapplication.h> +#include <kconfig.h> + +#include "Options.h" + +#include "Options.moc" + +Options* Options::_instance = 0; + +Options::Options() +{ + _config = KApplication::kApplication()->config(); + + _defaultTapeSize = _config->readNumEntry( "defaultTapeSize", 2*1024*1024 ); + _tapeBlockSize = _config->readNumEntry( "tapeBlockSize", 20*512 ); // Use tar default. + _tapeDevice = _config->readEntry( "tapeDevice", "/dev/tape" ).copy(); + _tarCommand = _config->readPathEntry( "tarCommand", "tar" ).copy(); + _loadOnMount = _config->readNumEntry( "loadOnMount", FALSE ); + _lockOnMount = _config->readNumEntry( "lockOnMount", FALSE ); + _ejectOnUnmount = _config->readNumEntry( "ejectOnUnmount", FALSE ); + _variableBlockSize = _config->readNumEntry( "variableBlockSize", FALSE ); +} + +Options* Options::instance() +{ + if ( _instance == 0 ) { + _instance = new Options(); + } + return _instance; +} + +void Options::sync() +{ + _config->sync(); +} + +int Options::getDefaultTapeSize() +{ + return _defaultTapeSize; +} + +int Options::getTapeBlockSize() +{ + return _tapeBlockSize; +} + +QString Options::getTapeDevice() +{ + return _tapeDevice; +} + +QString Options::getTarCommand() +{ + return _tarCommand; +} + +bool Options::getLoadOnMount() +{ + return _loadOnMount; +} + +bool Options::getLockOnMount() +{ + return _lockOnMount; +} + +bool Options::getEjectOnUnmount() +{ + return _ejectOnUnmount; +} + +bool Options::getVariableBlockSize() +{ + return _variableBlockSize; +} + +void Options::setDefaultTapeSize( int kbytes ) +{ + if ( kbytes != getDefaultTapeSize() ) { + _defaultTapeSize = kbytes; + _config->writeEntry( "defaultTapeSize", _defaultTapeSize ); + emit sigDefaultTapeSize(); + } +} + +void Options::setTapeBlockSize( int bytes ) +{ + if ( bytes != getTapeBlockSize() ) { + _tapeBlockSize = bytes; + _config->writeEntry( "tapeBlockSize", _tapeBlockSize ); + emit sigTapeBlockSize(); + } +} + +void Options::setTapeDevice( const QString & str ) +{ + if ( getTapeDevice() != str ) { + _tapeDevice = str; + _config->writeEntry( "tapeDevice", _tapeDevice ); + emit sigTapeDevice(); + } +} + +void Options::setTarCommand( const QString & str ) +{ + if ( getTarCommand() != str ) { + _tarCommand = str; + _config->writePathEntry( "tarCommand", _tarCommand ); + emit sigTarCommand(); + } +} + +void Options::setLoadOnMount( bool b ) +{ + if ( getLoadOnMount() != b ) { + _loadOnMount = b; + _config->writeEntry( "loadOnMount", _loadOnMount ); + emit sigLoadOnMount(); + } +} + +void Options::setLockOnMount( bool b ) +{ + if ( getLockOnMount() != b ) { + _lockOnMount = b; + _config->writeEntry( "lockOnMount", _lockOnMount ); + emit sigLockOnMount(); + } +} + +void Options::setEjectOnUnmount( bool b ) +{ + if ( getEjectOnUnmount() != b ) { + _ejectOnUnmount = b; + _config->writeEntry( "ejectOnUnmount", _ejectOnUnmount ); + emit sigEjectOnUnmount(); + } +} + +void Options::setVariableBlockSize( bool b ) +{ + if ( getVariableBlockSize() != b ) { + _variableBlockSize = b; + _config->writeEntry( "variableBlockSize", _variableBlockSize ); + emit sigVariableBlockSize(); + } +} diff --git a/kdat/Options.h b/kdat/Options.h new file mode 100644 index 0000000..4f65795 --- /dev/null +++ b/kdat/Options.h @@ -0,0 +1,211 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _Options_h_ +#define _Options_h_ + +#include <qobject.h> + +class KConfig; + +/** + * @short The central repository for user preferences. + */ +class Options : public QObject { + Q_OBJECT + KConfig* _config; + int _defaultTapeSize; + int _tapeBlockSize; + QString _tapeDevice; + QString _tarCommand; + bool _loadOnMount; + bool _lockOnMount; + bool _ejectOnUnmount; + bool _variableBlockSize; + + static Options* _instance; + + Options(); +public: + /** + * Get a reference to the single instance of the Options object. + * + * @return A pointer to the options object. + */ + static Options* instance(); + + /** + * Save the current user preference settings. + */ + void sync(); + + /** + * Get the default size to use when formatting a tape. + * + * @return The default size in kilobytes of a tape. + */ + int getDefaultTapeSize(); + + /** + * The size of a tape block can be different from the size of a tar block. + * Tar blocks are always 512 bytes long. Be careful! + * + * @return the size in bytes of a single tape block + */ + int getTapeBlockSize(); + + /** + * Get the full path to the tape device. + * + * @return The tape device path. + */ + QString getTapeDevice(); + + /** + * Get the full path to the tar command. + * + * @return The path to the tar command. + */ + QString getTarCommand(); + + /** + * Get whether to load the tape before attempting to mount it. + * + * @return TRUE if the tape should be loaded. + */ + bool getLoadOnMount(); + + /** + * Get whether to lock the tape drive when a tape is mounted. + * + * @return TRUE if the tape drive should be locked, otherwise FALSE. + */ + bool getLockOnMount(); + + /** + * Get whether to automatically eject the tape when it is unmounted. + * + * @return TRUE if the tape should be ejected, otherwise FALSE. + */ + bool getEjectOnUnmount(); + + /** + * Get whether the tape drive supports variable block sizes. + * + * @return TRUE if the tape drive can handle the MTSETBLK command. + */ + bool getVariableBlockSize(); + + /** + * Set the default size for new tapes. + * + * @param kbytes The size in kilobytes. + */ + void setDefaultTapeSize( int kbytes ); + + /** + * Set the size of tape block. + * + * @param bytes The size in bytes of one tape block. + */ + void setTapeBlockSize( int bytes ); + + /** + * Set the path to the tape device. + * + * @param str The full path to the tape device. + */ + void setTapeDevice( const QString & str ); + + /** + * Set the path to the tar command. + * + * @param str The full path to the tar command. + */ + void setTarCommand( const QString & str ); + + /** + * Set whether to load the tape before attempting to mount it. + * + * @param b TRUE if the tape should be loaded. + */ + void setLoadOnMount( bool b ); + + /** + * Set whether to lock the tape drive whenever a tape is mounted. + * + * @param b TRUE means lock the drive, FALSE means don't. + */ + void setLockOnMount( bool b ); + + /** + * Set whether to eject the tape drive whenever a tape is unmounted. + * + * @param b TRUE means eject the tape, FALSE means don't. + */ + void setEjectOnUnmount( bool b ); + + /** + * Set whether the tape drive can support variable block sizes. + * + * @param b TRUE means the tape drive understands MTSETBLK. + */ + void setVariableBlockSize( bool b ); +signals: + /** + * Emitted when the default tape size changes. + */ + void sigDefaultTapeSize(); + + /** + * Emitted when the tape block size changes. + */ + void sigTapeBlockSize(); + + /** + * Emitted when the path to the tape device changes. + */ + void sigTapeDevice(); + + /** + * Emitted when the path to the tar command changes. + */ + void sigTarCommand(); + + /** + * Emitted when the load on mount feature is enabled/disabled. + */ + void sigLoadOnMount(); + + /** + * Emitted when the lock on mount feature is enabled/disabled. + */ + void sigLockOnMount(); + + /** + * Emitted when the eject on umount feature is enabled/disabled. + */ + void sigEjectOnUnmount(); + + /** + * Emitted when the variable block size feature is enabled/disabled. + */ + void sigVariableBlockSize(); +}; + +#endif diff --git a/kdat/OptionsDlg.cpp b/kdat/OptionsDlg.cpp new file mode 100644 index 0000000..ba07773 --- /dev/null +++ b/kdat/OptionsDlg.cpp @@ -0,0 +1,126 @@ +// $Id$ +// +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdlib.h> + +#include "Options.h" +#include "OptionsDlg.h" +#include "OptionsDlgWidget.h" + +#include <qcheckbox.h> + +#include <kcombobox.h> +#include <klineedit.h> +#include <knuminput.h> +#include <kapplication.h> +#include <klocale.h> +#include <kglobal.h> + +#include "OptionsDlg.moc" +OptionsDlg::OptionsDlg( QWidget* parent, const char* name ) + : KDialogBase( Swallow, i18n ("Options"), Ok | Apply | Cancel, + Ok, parent, name, true, true ), apply (0) +{ + _baseWidget = new OptionsDlgWidget ( 0 ); + setMainWidget (_baseWidget); + + connect( _baseWidget, SIGNAL( valueChanged () ), this, SLOT( slotChanged() ) ); + + connect( this, SIGNAL( okClicked () ), this, SLOT( slotOK() ) ); + connect( this, SIGNAL( applyClicked() ), this, SLOT( slotApply() ) ); + connect( this, SIGNAL( cancelClicked() ), this, SLOT( slotCancel() ) ); + + int size = Options::instance()->getDefaultTapeSize(); + if ( ( size >= 1024*1024 ) && ( size % ( 1024*1024 ) == 0 ) ) { + // GB + size /= 1024*1024; + _baseWidget->_defaultTapeSizeUnits->setCurrentItem( 1 ); + } else { + // MB + size /= 1024; + _baseWidget->_defaultTapeSizeUnits->setCurrentItem( 0 ); + } + _baseWidget->_defaultTapeSize->setValue( size ); + + _baseWidget->_tapeBlockSize->setValue( Options::instance()->getTapeBlockSize() ); + + _baseWidget->_tapeDevice->setText( Options::instance()->getTapeDevice() ); + _baseWidget->_tarCommand->setText( Options::instance()->getTarCommand() ); + _baseWidget->_loadOnMount->setChecked( Options::instance()->getLoadOnMount() ); + _baseWidget->_lockOnMount->setChecked( Options::instance()->getLockOnMount() ); + _baseWidget->_ejectOnUnmount->setChecked( Options::instance()->getEjectOnUnmount() ); + _baseWidget->_variableBlockSize->setChecked( Options::instance()->getVariableBlockSize() ); + + enableButtonApply ( false ); + configChanged = false; +} + +OptionsDlg::~OptionsDlg() +{ +} + +void OptionsDlg::slotChanged() +{ + enableButtonApply ( true ); + configChanged = true; +} + +void OptionsDlg::slotOK() +{ + if( configChanged ) + slotApply(); + accept(); +} + +void OptionsDlg::slotApply() +{ + int size = _baseWidget->_defaultTapeSize->value(); + if ( _baseWidget->_defaultTapeSizeUnits->currentItem() == 0 ) { + // MB + size *= 1024; + } else { + // GB + size *= 1024*1024; + } + Options::instance()->setDefaultTapeSize( size ); + + Options::instance()->setTapeBlockSize( _baseWidget->_tapeBlockSize->value() ); + + Options::instance()->setTapeDevice( _baseWidget->_tapeDevice->text() ); + + Options::instance()->setTarCommand( _baseWidget->_tarCommand->text() ); + + Options::instance()->setLoadOnMount( _baseWidget->_loadOnMount->isChecked() ); + + Options::instance()->setLockOnMount( _baseWidget->_lockOnMount->isChecked() ); + + Options::instance()->setEjectOnUnmount( _baseWidget->_ejectOnUnmount->isChecked() ); + + Options::instance()->setVariableBlockSize( _baseWidget->_variableBlockSize->isChecked() ); + + Options::instance()->sync(); + enableButtonApply( false ); + configChanged = false; +} + +void OptionsDlg::slotCancel() +{ + reject(); +} diff --git a/kdat/OptionsDlg.h b/kdat/OptionsDlg.h new file mode 100644 index 0000000..9e62fd7 --- /dev/null +++ b/kdat/OptionsDlg.h @@ -0,0 +1,56 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _OptionsDlg_h_ +#define _OptionsDlg_h_ + +#include <kdialogbase.h> + +class QPushButton; +class OptionsDlgWidget; + +/** + * @short Display/edit user preferences. + */ +class OptionsDlg : public KDialogBase { + Q_OBJECT +private slots: + void slotOK(); + void slotApply(); + void slotCancel(); + void slotChanged(); +public: + /** + * Create a new options dialog. + * + * @param parent The parent widget of the dialog. + * @param name The name of the dialog. + */ + OptionsDlg( QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the options dialog. + */ + ~OptionsDlg(); +private: + bool configChanged; + OptionsDlgWidget *_baseWidget; + QPushButton* apply; +}; + +#endif diff --git a/kdat/OptionsDlgWidget.ui b/kdat/OptionsDlgWidget.ui new file mode 100644 index 0000000..5ace94d --- /dev/null +++ b/kdat/OptionsDlgWidget.ui @@ -0,0 +1,385 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>OptionsDlgWidget</class> +<author>Michael Pyne</author> +<widget class="QWidget"> + <property name="name"> + <cstring>OptionsDlgWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>325</width> + <height>332</height> + </rect> + </property> + <property name="caption"> + <string>Options Widget</string> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KIntSpinBox" row="0" column="1"> + <property name="name"> + <cstring>_defaultTapeSize</cstring> + </property> + <property name="minimumSize"> + <size> + <width>96</width> + <height>0</height> + </size> + </property> + <property name="maxValue"> + <number>4096</number> + </property> + <property name="whatsThis" stdset="0"> + <string>This setting determines the capacity that KDat assumes your backup tapes to be. This is used when formatting the tapes.</string> + </property> + </widget> + <widget class="KIntSpinBox" row="1" column="1"> + <property name="name"> + <cstring>_tapeBlockSize</cstring> + </property> + <property name="minimumSize"> + <size> + <width>96</width> + <height>0</height> + </size> + </property> + <property name="maxValue"> + <number>262144</number> + </property> + <property name="whatsThis" stdset="0"> + <string>Tape drives read and write data in individual blocks. This setting controls the size of each block, and should be set to your tape drive's block size. For floppy tape drives this should be set to <b>10240</b> bytes.</string> + </property> + </widget> + <widget class="KComboBox" row="0" column="2"> + <item> + <property name="text"> + <string>MB</string> + </property> + </item> + <item> + <property name="text"> + <string>GB</string> + </property> + </item> + <property name="name"> + <cstring>_defaultTapeSizeUnits</cstring> + </property> + <property name="toolTip" stdset="0"> + <string></string> + </property> + <property name="whatsThis" stdset="0"> + <string>This option chooses whether the default tape size to the left is in megabytes (MB) or gigabytes (GB).</string> + </property> + </widget> + <widget class="QLabel" row="1" column="2"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>bytes</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Tape block size:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>_tapeBlockSize</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Default tape size:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>_defaultTapeSize</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton" row="1" column="2"> + <property name="name"> + <cstring>browseTarCommand</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="toolTip" stdset="0"> + <string>Browse for the tar command.</string> + </property> + </widget> + <widget class="KLineEdit" row="0" column="1"> + <property name="name"> + <cstring>_tapeDevice</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>The location in the filesystem of the <em>non-rewinding</em> tape device. The default is <b>/dev/tape</b>.</string> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>textLabel5</cstring> + </property> + <property name="text"> + <string>Tar command:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>_tarCommand</cstring> + </property> + </widget> + <widget class="KPushButton" row="0" column="2"> + <property name="name"> + <cstring>browseTapeDevice</cstring> + </property> + <property name="text"> + <string>...</string> + </property> + <property name="toolTip" stdset="0"> + <string>Browse for the tape device.</string> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1"> + <property name="name"> + <cstring>_tarCommand</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>This setting controls the command that KDat uses to perform the tape backup. The full path should be given. The default is <b>tar</b>.</string> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="text"> + <string>Tape device:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>_tapeDevice</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>buttonGroup1</cstring> + </property> + <property name="title"> + <string>Tape Drive Options</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>_loadOnMount</cstring> + </property> + <property name="text"> + <string>Load tape on mount</string> + </property> + <property name="toolTip" stdset="0"> + <string><qt>Issue an <tt>mtload</tt> command prior to mounting the tape.</qt></string> + </property> + <property name="whatsThis" stdset="0"> + <string>This command issues an <tt>mtload</tt> command to the tape device before trying to mount it. + +This is required by some tape drives.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>_lockOnMount</cstring> + </property> + <property name="text"> + <string>Lock tape drive on mount</string> + </property> + <property name="toolTip" stdset="0"> + <string>Disable the eject button after mounting the tape.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This option makes KDat try to disable the eject button on the tape drive after the tape has been mounted. + +This doesn't work for all tape drives.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>_ejectOnUnmount</cstring> + </property> + <property name="text"> + <string>Eject tape on unmount</string> + </property> + <property name="toolTip" stdset="0"> + <string>Try to eject the tape after it is unmounted. Don't use this for ftape.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Try to eject the tape after it has been unmounted. + +This option should not be used for floppy-tape drives.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>_variableBlockSize</cstring> + </property> + <property name="text"> + <string>Variable block size</string> + </property> + <property name="toolTip" stdset="0"> + <string>Enable variable-block size support in the tape drive.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Some tape drives support different sizes of the data block. With this option, KDat will attempt to enable that support. + +You must still specify the block size.</string> + </property> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>0</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> +</widget> +<connections> + <connection> + <sender>_defaultTapeSize</sender> + <signal>valueChanged(int)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_tapeBlockSize</sender> + <signal>valueChanged(int)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_defaultTapeSizeUnits</sender> + <signal>activated(int)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_tapeDevice</sender> + <signal>textChanged(const QString&)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_tarCommand</sender> + <signal>textChanged(const QString&)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_loadOnMount</sender> + <signal>toggled(bool)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_lockOnMount</sender> + <signal>toggled(bool)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_ejectOnUnmount</sender> + <signal>toggled(bool)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>_variableBlockSize</sender> + <signal>toggled(bool)</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotValueChanged()</slot> + </connection> + <connection> + <sender>browseTapeDevice</sender> + <signal>clicked()</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotBrowseTapeDevice()</slot> + </connection> + <connection> + <sender>browseTarCommand</sender> + <signal>clicked()</signal> + <receiver>OptionsDlgWidget</receiver> + <slot>slotBrowseTarCommand()</slot> + </connection> +</connections> +<includes> + <include location="global" impldecl="in implementation">kapplication.h</include> + <include location="global" impldecl="in implementation">kfiledialog.h</include> + <include location="global" impldecl="in implementation">kmessagebox.h</include> + <include location="global" impldecl="in implementation">kglobal.h</include> + <include location="global" impldecl="in implementation">kdialog.h</include> + <include location="local" impldecl="in implementation">OptionsDlgWidget.ui.h</include> +</includes> +<signals> + <signal>doBrowseTapeDevice()</signal> + <signal>doBrowseTarCommand()</signal> + <signal>valueChanged()</signal> +</signals> +<slots> + <slot access="protected">slotValueChanged()</slot> + <slot access="protected">slotBrowseTapeDevice()</slot> + <slot access="protected">slotBrowseTarCommand()</slot> +</slots> +<layoutdefaults spacing="11" margin="6"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +</UI> diff --git a/kdat/OptionsDlgWidget.ui.h b/kdat/OptionsDlgWidget.ui.h new file mode 100644 index 0000000..6b2d4d8 --- /dev/null +++ b/kdat/OptionsDlgWidget.ui.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you wish to add, delete or rename functions or slots use +** Qt Designer which will update this file, preserving your code. Create an +** init() function in place of a constructor, and a destroy() function in +** place of a destructor. +*****************************************************************************/ + + +void OptionsDlgWidget::slotValueChanged() +{ + emit valueChanged (); +} + + +void OptionsDlgWidget::slotBrowseTapeDevice() +{ + KURL url; + url = KFileDialog::getOpenURL( _tapeDevice->text() ); + + if ( !url.isEmpty() ) + { + if( !url.isLocalFile() ) + { + KMessageBox::sorry( 0L, i18n( "Only local files are supported" ) ); + return; + } + _tapeDevice->setText( url.path() ); + } +} + + +void OptionsDlgWidget::slotBrowseTarCommand() +{ + KURL url; + url = KFileDialog::getOpenURL( _tarCommand->text() ); + + if ( !url.isEmpty() ) + { + if( !url.isLocalFile() ) + { + KMessageBox::sorry( 0L, i18n( "Only local files are currently supported" ) ); + return; + } + _tarCommand->setText( url.path() ); + } +} diff --git a/kdat/RELEASE_NOTES b/kdat/RELEASE_NOTES new file mode 100644 index 0000000..85314f8 --- /dev/null +++ b/kdat/RELEASE_NOTES @@ -0,0 +1,76 @@ + +2002-01-31 + - Debugging statements will be printed to stdout only if + -DDEBUG is included in CXXFLAGS during compilation + - "--nocrashhandler" will be added to the command-line + argument list only if -DDEBUG is included in CXXFLAGS + during compilation + - fix for bugs in which disk-based files were not being + updated. In particular, archive names can now be changed + permanently. Hopefully this will also fix the problem of + tape dumps not being remembered. + +2002-01-30 + - The command-line argument "--nocrashhandler" is now added + to char **argv in main() to make sure that Dr. Konqi isn't + called. This is necessary to allow the user to generate + core dumps. Calling KCrash::setCrashHandler(0); doesn't work. + The contents of argv[] are written to stdout to remind the + user that this has been done. + - A null-pointer problem has been fixed. For now, when that + pointer is null, kdat will print + Found sel==0x0 in KDat.cpp 1250 + to stdout. This message will be removed once the problem is + known to be solved completely. + - The behavior of the tree when a directory is selected or + unselected is changed so that the subdirectory does not + expand or collapse at the same time. This is currently done + by redrawing the entire subtree, which is probably not the + most efficient way to do it. + +2002-01-28 + - KDat now seems to dump and restore properly. + - Frank Pieczynski <pieczy at knuut.de> provided larger + backup, restore, and verify icons. + - Signal hander now allows user to dump core if so desired. + - When user starts backups, a warning appears that instructs + him/her how to restore data from the tape using tar on the + command line. This is necessary becase sometimes the new + archive isn't saved so kdat can't restore the data. This + problem appears to have disappeared spontaneously today, and + was linked to the variable _stubbed=FALSE. + +2002-01-24 + - Frank Pieczynski <pieczy at knuut.de> provided improved blue icons. + The original yellow icons are retained in the distribution with + ".yellow" appended to their basenames for those who want them. + - Added new dialog box that allows the user to cancel a backup + while the size of the backup is being estimated. This is useful + when the amount of data is clearly larger than the size of + the medium. + +2002-01-21 + - Added signal handler so user can have a chance to figure out what + program was doing when it received one of the signals SIGHUP, + SIGINT, SIGFPE, SIGSEGV, SIGTERM. The program can always be killed + with "kill -9 <pid>". + +2002-01-20 + - Made the selected/deselected icons more similar to the original + easy-to-read versions + - Fixed bug in which a single mouse click to select or + deselect would change the status but not update the + display. The display is now updated. Double-clicking still + works. + - Fixed bug that caused the tree widget to overlap the + information widget. + - Added compile-time option to arrange the tree widget and the + information widget either horizontally or vertically. The default + is now horizontal, and the top-level window size is + increased from 600x400 to 600x600 pixels. + - NOTE: the Backup Profiles have been moved by the KDE project from + $HOME/.kdat + to + $HOME/.kde/share/apps/kdat + If you simply copy your old Backup Profiles to the new directory, + they should work correctly. diff --git a/kdat/Range.cpp b/kdat/Range.cpp new file mode 100644 index 0000000..b2836aa --- /dev/null +++ b/kdat/Range.cpp @@ -0,0 +1,132 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <assert.h> + +#include "Range.h" + +Range::Range( int start, int end ) + : _start( start ), + _end( end ) +{ +} + +int Range::getStart() +{ + return _start; +} + +int Range::getEnd() +{ + return _end; +} + +void Range::setStart( int start ) +{ + _start = start; +} + +void Range::setEnd( int end ) +{ + _end = end; +} + +RangeList::RangeList() +{ +} + +RangeList::~RangeList() +{ + while ( _ranges.first() ) { + delete _ranges.first(); + _ranges.removeFirst(); + } +} + +const QPtrList<Range>& RangeList::getRanges() const +{ + return _ranges; +} + +void RangeList::addRange( int start, int end ) +{ + assert( end >= start ); + + if ( start == end ) { + return; + } + + // Remove all of the ranges that are totally contained by the new range. + for ( _ranges.first(); _ranges.current(); ) { + if ( ( _ranges.current()->getStart() >= start ) && ( _ranges.current()->getEnd() <= end ) ) { + _ranges.remove(); + } else if ( ( start >= _ranges.current()->getStart() ) && ( end <= _ranges.current()->getEnd() ) ) { + // The new range is totally contained in the current range. + return; + } else { + _ranges.next(); + } + } + + // Find the correct insertion point for the new range. + int idx = 0; + for ( _ranges.first(); _ranges.current(); _ranges.next(), idx++ ) { + if ( _ranges.current()->getStart() > start ) { + break; + } + } + + if ( !_ranges.current() ) { + // Append new range to end of list. + if ( ( _ranges.last() ) && ( _ranges.last()->getEnd() >= start ) ) { + // Ranges overlap, merge them. + _ranges.last()->setEnd( end ); + } else { + // Append a new range. + _ranges.append( new Range( start, end ) ); + } + } else { + // Insert new range before current range. + if ( _ranges.current()->getStart() <= end ) { + // Ranges overlap, merge them. + _ranges.current()->setStart( start ); + end = _ranges.current()->getEnd(); + + // Check previous range for overlap. + if ( ( _ranges.prev() ) && ( _ranges.current()->getEnd() >= start ) ) { + // New range overlapped both the before and after ranges. + _ranges.current()->setEnd( end ); + _ranges.next(); + _ranges.remove(); + } + } else { + // Check previous range for overlap. + if ( ( _ranges.prev() ) && ( _ranges.current()->getEnd() >= start ) ) { + // New range overlapped the before range. + _ranges.current()->setEnd( end ); + } else { + _ranges.insert( idx, new Range( start, end ) ); + } + } + } +} + +void RangeList::clear() +{ + _ranges.clear(); +} diff --git a/kdat/Range.h b/kdat/Range.h new file mode 100644 index 0000000..5b042c3 --- /dev/null +++ b/kdat/Range.h @@ -0,0 +1,107 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _Range_h_ +#define _Range_h_ + +#include <qptrlist.h> + +/** + * @short This class represents a range of tar records. + */ +class Range { + int _start; + int _end; +public: + /** + * Create a new range. + * + * @param start The first tar record in the range. + * @param end The last tar record in the range. + */ + Range( int start = 0, int end = 0 ); + + /** + * Get the first tar record in this range. + * + * @return The starting tar record. + */ + int getStart(); + + /** + * Get the last tar record in this range. + * + * @return The ending tar record. + */ + int getEnd(); + + /** + * Set the first tar record in this range. + * + * @param start The starting tar record. + */ + void setStart( int start ); + + /** + * Set the last tar record in this range. + * + * @param end The ending tar record. + */ + void setEnd( int end ); +}; + +/** + * A simple list of Ranges. + */ +class RangeList { + QPtrList<Range> _ranges; +public: + /** + * Create an empty list of ranges. + */ + RangeList(); + + /** + * Destroy each range in the list. + */ + ~RangeList(); + + /** + * Get the simplified list of ranges. + * + * @return The list of ranges. + */ + const QPtrList<Range>& getRanges() const; + + /** + * Intelligently add the given range to the list of ranges. If possible, + * the new range is merged with one (or two) of the existing ranges in + * the list. Otherwise, a new range is added to the list. + * + * @param start The starting tar record. + * @param end The ending tar record. + */ + void addRange( int start, int end ); + + /** + * Erase the list of ranges. + */ + void clear(); +}; + +#endif diff --git a/kdat/TODO b/kdat/TODO new file mode 100644 index 0000000..e1bf865 --- /dev/null +++ b/kdat/TODO @@ -0,0 +1,225 @@ +Outstanding Bugs + +2002-04-22 + From Bas: + 1) DAT tape works with tar from command line but not within kdat when the + device is specified as /dev/st0. It works within kdat when a symbolic + link to /dev/tape is made and given to kdat, but only if the startup + option "mount tape at startup" is NOT checked. + +2002-01-31 + From RG: + 1) Restore data + when asked for "Restore to directory:" in Dialog box "Restore Options" + enter a not existing directory (e.g.: /tmp/xyz ), kdat will restore, but no + data is written to requested place, Logfile shows success. + !!!! The data are restored to the place, where application kdat is started ! + reason: file VerifyDlg.cpp line: 397 + > void VerifyDlg::show() + > { + > chdir( QFile::encodeName(_workingDir) ); + > + there is no error handling !!! + + same problem for Verify ! + How to deal with ?? + - Restore: ask the user, if kdat should create the new directory ? + - Verify: a verify makes no sense, if the directory is not existing + + So I think a correction should be done within the + VerifyOptDlg::okClicked() method !?! + 2) adding new data to a tape, UI is not updated, while forwarding to the end of + tape + 3) forward to the end of tape: text in status line not completely visible + 4) tape with 10 and more archives: + Tape or Tape Index: Archives not correctly ordered: + 1, 10, 12, 12, 2, 3, 4, 5, 6 + Note: The reindexing run lists them in correct oder + 5) status line: only sunken, where text is why not complete status line ? + +2002-01-30 + 1) When a dump fails with a write() error, no index is written + to disk. When the tape is then mounted, kdat does not + prompt to ask whether the index should be recreated. + /* 2002-01-31 LEW. This may now be fixed by adding freopen() */ + 2) From RG: + - change of Archive Name is not written to file (Tape Index) + open Tape Index, select archive, modify Archive Name (right pane), + press Apply, Note: tree is updated, + quit kdat, restart kdat, open Tape Index, old name appears + /* 2002-01-31 LEW. This is now fixed by adding freopen() */ + - kdat, mounted tape, open tree Tape, select an archive, + go to right pane, unmount tape via toolbar icon + --> right pane is still active, but useless ?? + Do now a modification of Archive name, -> Entry under + Tape INdexes is updated ! + - Tree: Click on Selected/unselected Icon opend/closes the subtree + /* 2002-01-30 LEW: workaround is in place */ + + Nice to have: + - System Notification (Sound, when backup/Restore/Verify is + finished, OK or error ) + - Rewind on unmount ?!? + - kdat does not respect the user setting of the decimal seperator in + kcontrol -> Personalization -> Contry & Language -> Numbers + + 3) From FP: + A.) * mount a tape with a backup on it + - open a directory with click on the plus sign + - open again a dir only with click on plus until you see a file + - click on the white quadrat to select this file + => crash + /* 2002-01-30 LEW: hopefully, this is fixed in the KDat.cpp:1250 patch */ + B.) - select a single file (black x): + - the restore doesn't restore only that file, but also + the content of the directories above (they marked with the + grey X for "some_selected") + C.) * mount a tape with a backup on it + - open a directory with click on the plus sign + - click on the name of a directory + => "Restore" and "Verify" in Toolbar becomes active, + but there are no visible (x) selected dirs/files shown + (A restore in that situation restores that file and all + dirs above including there additional content - see B) + D.) - save log in the restore window brings up a Qt file dialog? + E.) - a directory containing a file (size 194byte) is not restored + - not with trying the file directly: startrecord: 10104 end 10109 + or the dir above: startrecord 10103 end 10104 + F.) * mount a tape with a backup on it + - open the tree, do 2 dirs deeper and select a file inside (black X) + - restore that file + the restore window comes up select a dir etc, + after the successful restore close the Restore window with ok + -> the file is still in black selected, the dirs above in grey + - now collapse the tree above the file with clicking on the "+" + of a grey marked dir + => crash + /* 2002-01-30 LEW: hopefully, this is fixed in the KDat.cpp:1250 patch */ + +2002-01-29 + 1) Major memory leak or very inefficient memory allocation: + Saving the log file to disk is very slow (158560 lines of text + over 50 min). The free function shows that at the end of the save() + function, there were 35MB of swap space left on the machine. After + the log file was freed from memory (again a very long process), + there were 361MB free (a difference of 326MB, or 2KB per line, + where each line has an average of 63 characters. + top showed that the size of kdat was 235M with RSS=215M when + the log file memory allocation was freed. free RAM during + this time was 3MB (out of 257MB). When kdat was terminated, free + RAM increased from 3MB to 214MB. + +2002-01-28 + 1) The font size and line spacing vary from machine to machine + (e.g., Red Hat 7.0 and Red Hat 7.2). The dialog window has + been made larger until a way to size automatically is set up. + +2002-01-25 + + 1) FP notes "This part of KDat seems to need a cleanup in a + later version. For example the toolbar icons are in real at + a size at 22x22, but the filenames say 16x16. If you + change the names to hi22 kdat don't find them :-( In the + future there should be a set of 16x16, 22x22 and 32x32 + toolbar icons, the menu and toolbar can then be created + with the KAction class and XMLGUI and the toolbar menu will + then have an effect :-)" + +2002-01-23 + + 1) FP reports: "One more problem: If you open in the tree a + archive and try to select some dir inside, a error handler + comes up and I have to kill KDat." /* 2002-01-28 LEW: this + problem is intermittent. A signal handler is now added + that allows the user to dump core when a problem occurs. */ + + 2) LEW can't see archives on tape. /* 2002-01-28 LEW: this + problem mysteriously disappeared today. Perhaps it had + something to do with configuration. In case it recurs, the + notes below showing a relationship to _stubbed are retained. */ + /* 2002-01-31 LEW. This may now be fixed by adding freopen() */ + + Progress so far: + + 2002-01-26 LEW: in Tape.cpp, readVersion4Index() on a recent tape dump + shows that numArchives is 0 even though the tape has the dumped files + on it (as shown by running tar tfv /dev/nst0 3 times). A problem with + creating the archive after the tape is dumped? In fact, the Archive + dialog during the dump shows that no files are being dumped when in + fact they are. + + Also, TapeManager::findTape() shows that the newly mounted tape's ID + is used to retrieve the Tape* entry in _tapes, but _tapes has no + entries. It is supposed to contain the details from all the index + files in the appdata directory. But, when those files are read when + the TapeManager is instantiated, only their names are stored + (in QStringList _tapeIDs). The files themselves are not examined. + + 2002-01-28 LEW: On further examination, the BackupDlg widget does in fact + call Tape::addChild( Archive* archive ) to add the new archive, and + addChild() does call read() in preparation for updating the on-disk + tape file. However, read() refuses to do anything because the tape + isn't stubbed (_stubbed==FALSE). I don't know what this means yet. + Forcing _stubbed=TRUE doesn't work either. It looks like _stubbed has + to do with whether the tape ID is recognized as belonging to an index + file on disk or not (see the second paragraph of 2002-01-26). + +2002-01-21 + + 1) While dumping 9.5GB to 8.3GB tape: + In file tools/qgarray.cpp, line 227: Out of memory + kdeinit: Fatal IO error: client killed + kdeinit: sending SIGHUP to children. + Mutex destroy failure: Device or resource busy + kdeinit: sending SIGTERM to children. + kdeinit: Exit. + /* 2002-01-21 LEW: added signal handler to KDat so it won't + crash when it receives SIGHUPs, SIGTERMs, etc., and that + allows user to dump core if (s)he wishes. */ + +c/o rolandg at onlinehome.de: + + 2) Tree Widget: Mounted Tape + Icon too big -> the backup sets are not visible on the screen. + If I select the archive, I see the upper pixel of some marked text. + + 3) Tree Widget: Tape Index is empty, but I did just a few minutes ago + a 1st backup onto the the tape. + The tape index file is stored in .kde/share/apps/kdat. + /* 2002-01-24 RG sent patch */ + + 4) Backup Profiles + o Delete Profile does not work + o after creating several new profiles the directory .kde/share/apps/kdat + lists some with their default names, and some with the by me defined + profile names. + 5) When backup up some more files, kdat calculates the complete size + of the data. In the status line the filenames are running through. + I see no way to cancel this action. /* 2002-01-24 LEW: new dialog added */ + +2002-01-20 Outstanding bugs + + 1) The vertical line in the tree widget that shows indenting levels is not + drawn correctly: there is a small hole in the dir lines. (Thanks + to Frank Pieczynski <pieczyk at knuut.de>.) /* fixed 2002-01-24 with new + icons that FP sent in. */ + 2) when files are restored, they are copied correctly from the tape to + disk, but their names in the log window are corrupted (this is an old + bug). /* reported fixed by RG 2002-01-23 */ + 3) Verify quits early (this is an old bug). + +2001-08-01 These are possible sources of bugs: + +1) don't return 0 instead of a null QString + (e.g., BackupProfile::getWorkingDirectory()) +2) don't test for FALSE predicate values with + !(). Use == FALSE instead. +3) don't piggyback .remove with .first: + _relativeFiles.remove(_relativeFiles.first()) + + Instead, separate the two: + QString my_first = _relativeFiles.first(); + _relativeFiles.remove( my_first ); + + This may be important with any call to .remove() + as may be found with: `grep "\.remove" *.cpp` diff --git a/kdat/Tape.cpp b/kdat/Tape.cpp new file mode 100644 index 0000000..b015376 --- /dev/null +++ b/kdat/Tape.cpp @@ -0,0 +1,933 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> + +#include <qfile.h> + +#include <kmessagebox.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kapplication.h> + +#include "IndexDlg.h" +#include "KDatMainWindow.h" +#include "Options.h" +#include "Tape.h" +#include "TapeDrive.h" +#include "TapeManager.h" +#include "kdat.h" + +Tape::Tape() + : _stubbed( FALSE ), + _name( i18n( "New Tape" ) ), + _size( Options::instance()->getDefaultTapeSize() ), + _fptr( 0 ) +{ + char buffer[1024]; + gethostname( buffer, 1024 ); + time_t tm = time( NULL ); + _ctime = tm; + _mtime = tm; + _id.sprintf("%s:%d", buffer, tm); + + write(); +} + +Tape::Tape( const char * id ) + : _stubbed( TRUE ), + _id( id ), + _ctime( -1 ), + _mtime( -1 ), + _name( "<unknown>" ), + _size( -1 ), + _fptr( 0 ) +{ +} + +Tape::~Tape() +{ + clear(); +} + +void Tape::format() +{ + // Rewind tape. + if ( !TapeDrive::instance()->rewind() ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Rewinding tape failed." ), i18n("Format Failed")); + return; + } + + // Set block size for tape. + if ( !TapeDrive::instance()->setBlockSize( Options::instance()->getTapeBlockSize() ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Cannot set tape block size." ), i18n("Format Failed")); + return; + } + + // TAPE HEADER + int iv; + + // KDat magic string. + if ( TapeDrive::instance()->write( KDAT_MAGIC, KDAT_MAGIC_LENGTH ) < KDAT_MAGIC_LENGTH ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Writing magic string failed." ), i18n("Format Failed")); + return; + } + + // Tape header version number. + iv = KDAT_TAPE_HEADER_VERSION; + if ( TapeDrive::instance()->write( (const char*)&iv, 4 ) < 4 ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Writing version number failed." ), i18n("Format Failed") ); + return; + } + + // Write tape ID. Tape ID is machine name + current time. + iv = _id.length() + 1; + if ( TapeDrive::instance()->write( (const char*)&iv, 4 ) < 4 ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Writing tape ID length failed." ), i18n("Format Failed") ); + return; + } + if ( TapeDrive::instance()->write( _id, iv ) < iv ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Writing tape ID failed." ), i18n("Format Failed") ); + return; + } + + // Write end of file marker. + TapeDrive::instance()->close(); + TapeDrive::instance()->open(); + + // Write new tape index. + write(); +} + +void Tape::read() +{ + if ( !_stubbed ) { + /* 2002-01-28 LEW */ + // printf("Can't read tape data because tape isn't stubbed\n" ); + /* 2002-01-28 LEW */ + return; + } + + _stubbed = FALSE; + + /* 2002-01-28 LEW */ + // printf("Preparing to read tape data\n" ); + // QString filename1 = locateLocal( "appdata", _id); + // printf("The tape data are in \"%s\"\n", filename1.ascii() ); + /* 2002-01-28 LEW */ + + if ( !_fptr ) { + QString filename = locateLocal( "appdata", _id); + _fptr = fopen( QFile::encodeName(filename), "r" ); + + /* 2002-01-28 LEW */ +#ifdef DEBUG + // printf("Opened tape archive file \"%s\". %s %d\n", + // filename.ascii(), __FILE__, __LINE__ ); +#endif /* DEBUG */ + /* 2002-01-28 LEW */ + + } + + if ( !_fptr ) { + // No tape index file was found. + int result = KMessageBox::warningContinueCancel( KDatMainWindow::getInstance(), + i18n( "No index file was found for this tape.\n" + "Recreate the index from tape?" ), + i18n("Tape Index"), + i18n("Recreate")); + if (result == KMessageBox::Continue ) { + write(); + + IndexDlg dlg( this, KDatMainWindow::getInstance() ); + dlg.exec(); + TapeManager::instance()->addTape( this ); + return; + } else { + return; + } + } + + char buf[4096]; + + fseek( _fptr, 0, SEEK_SET ); + + // Read index file version number. + if ( !fgets( buf, 4096, _fptr ) ) { + fclose( _fptr ); + KMessageBox::error( KDatMainWindow::getInstance(), + i18n( "Reading version number failed." ), + i18n("Index File Error") ); + return; + } + int version = atoi( buf ); + + switch ( version ) { + case 1: + readVersion1Index( _fptr ); + readAll( version ); + calcRanges(); + fclose( _fptr ); + _fptr = NULL; + write(); + break; + + case 2: + readVersion2Index( _fptr ); + readAll( version ); + calcRanges(); + fclose( _fptr ); + _fptr = NULL; + write(); + break; + + case 3: + readVersion3Index( _fptr ); + readAll( version ); + calcRanges(); + fclose( _fptr ); + _fptr = NULL; + write(); + break; + + case 4: + readVersion4Index( _fptr ); + break; + + default: + { + KMessageBox::sorry( KDatMainWindow::getInstance(), + i18n( "The tape index file format is version %d. The index cannot be read by this version of KDat. Perhaps the tape index file was created by a newer version of KDat?" ).arg(version ), + i18n("Tape Index") ); + } + } +} + +void Tape::readAll( int version ) +{ + read(); + + QPtrListIterator<Archive> i( _children ); + for ( ; i.current(); ++i ) { + i.current()->readAll( version ); + } +} + +void Tape::write() +{ + QString filename = locateLocal( "appdata", _id); + + /* 2002-01-28 LEW */ +#ifdef DEBUG + // printf("Preparing to write the archive data to \"%s\". %s %d\n", + // filename.ascii(), __FILE__, __LINE__ ); +#endif /* DEBUG */ + /* 2002-01-28 LEW */ + + if ( !_fptr ) { + _fptr = fopen( QFile::encodeName(filename), "w" ); + + /* 2002-01-31 LEW */ +#ifdef DEBUG + // printf("Opened new archive file \"%s\". %s %d\n", + // filename.ascii(), __FILE__, __LINE__ ); +#endif /* DEBUG */ + /* 2002-01-31 LEW */ + + if ( !_fptr ) { + // Suck! + printf( "Tape::write() -- cannot open '%s' for writing!\n", filename.ascii() ); + return; + } + } else { + freopen( QFile::encodeName(filename), "w", _fptr ); + /* 2002-01-31 LEW */ +#ifdef DEBUG + // printf("Reopened new archive file \"%s\". %s %d\n", + // filename.ascii(), __FILE__, __LINE__ ); +#endif /* DEBUG */ + /* 2002-01-31 LEW */ + } + + int zero = 0; + + //===== Write the tape data ===== + + fprintf( _fptr, "%d\n", KDAT_INDEX_FILE_VERSION ); + fprintf( _fptr, "%s\n", _id.data() ); + + fwrite( &_ctime, sizeof( _ctime ), 1, _fptr ); + fwrite( &_mtime, sizeof( _mtime ), 1, _fptr ); + fwrite( &_size , sizeof( _size ), 1, _fptr ); + char buf[4096]; + memset( buf, 0, 4096 ); + memcpy( buf, _name.ascii(), _name.length() > 4095 ? 4095 : _name.length() ); + fwrite( buf, sizeof( char ), 4096, _fptr ); + int ival = _children.count(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + + // Fill in the archive offsets later... + int archiveTable = ftell( _fptr ); + for ( uint i = 0; i < MAX_NUM_ARCHIVES; i++ ) { + fwrite( &zero, sizeof( zero ), 1, _fptr ); + } + + //===== Write archives ===== + QPtrListIterator<Archive> i( _children ); + int count = 0; + for ( ; i.current(); ++i, count++ ) { + // Fill in the file offset. + int here = ftell( _fptr ); + fseek( _fptr, archiveTable + 4*count, SEEK_SET ); + fwrite( &here, sizeof( here ), 1, _fptr ); + fseek( _fptr, here, SEEK_SET ); + + i.current()->write( _fptr ); + } + + freopen( QFile::encodeName(filename), "r+", _fptr ); +} + +QString Tape::getID() +{ + read(); + + return _id; +} + +QString Tape::getName() +{ + read(); + + return _name; +} + +int Tape::getCTime() +{ + read(); + + return _ctime; +} + +int Tape::getMTime() +{ + read(); + + return _mtime; +} + +int Tape::getSize() +{ + read(); + + return _size; +} + +const QPtrList<Archive>& Tape::getChildren() +{ + read(); + + return _children; +} + +void Tape::setName( const QString & name ) +{ + /* 2002-01-31 LEW */ + int i; + /* 2002-01-31 LEW */ + + read(); + + if ( !_fptr ) { + // Create a new empty index file. + write(); + } + + // change file to read-write so we can update it 2002-01-31 LEW + QString filename = locateLocal( "appdata", _id); + freopen( QFile::encodeName(filename), "r+", _fptr ); + + _name = name; + + char buf[4096]; + if( fseek( _fptr, 0, SEEK_SET ) < 0 ) + { + clearerr( _fptr ); +#ifdef DEBUG /* Can't create new i18n strings now, so we'll save this for KDE 3.1 */ + /* Note to translator: this error checking was added while tracking + down a bug that turns out to be unrelated. The errors I'm looking + for here haven't happened in real use, so there is no rush to add + these checks in KDE 3.0. Thanks. - LW */ + QString msg = i18n("Error during fseek #1 while accessing archive: \""); + msg.append( getID() ); + msg.append( "\": " ); + msg.append( i18n(strerror( errno )) ); + printf("%s\n", msg.latin1()); + KMessageBox::error(NULL, msg, i18n("File Access Error")); +#endif /* DEBUG */ + return; + } + if( fgets( buf, 4096, _fptr ) == NULL ) + { + clearerr( _fptr ); +#ifdef DEBUG /* Can't create new i18n strings now, so we'll save this for KDE 3.1 */ + QString msg = i18n("Error while accessing string #1 in archive: \""); + msg.append( getID() ); + msg.append( "\": " ); + msg.append( i18n(strerror( errno )) ); + printf("%s\n", msg.latin1()); + KMessageBox::error(NULL, msg, i18n("File Access Error")); +#endif /* DEBUG */ + return; + } + if( fgets( buf, 4096, _fptr ) == NULL ) + { + clearerr( _fptr ); +#ifdef DEBUG /* Can't create new i18n strings now, so we'll save this for KDE 3.1 */ + QString msg = i18n("Error while accessing string #2 in archive: \""); + msg.append( getID() ); + msg.append( "\": " ); + msg.append( i18n(strerror( errno )) ); + printf("%s\n", msg.latin1()); + KMessageBox::error(NULL, msg, i18n("File Access Error")); +#endif /* DEBUG */ + return; + } + if( fseek( _fptr, 12, SEEK_CUR ) < 0 ) + { + clearerr( _fptr ); +#ifdef DEBUG /* Can't create new i18n strings now, so we'll save this for KDE 3.1 */ + QString msg = i18n("Error during fseek #2 while accessing archive: \""); + msg.append( getID() ); + msg.append( "\": " ); + msg.append( i18n(strerror( errno )) ); + printf("%s\n", msg.latin1()); + KMessageBox::error(NULL, msg, i18n("File Access Error")); +#endif /* DEBUG */ + return; + } + memset( buf, 0, 4096 ); + memcpy( buf, _name.ascii(), _name.length() > 4095 ? 4095 : _name.length() ); + i = fwrite( buf, sizeof( char ), 4096, _fptr ); + /* 2002-01-31 LEW */ + /* Note to translator: I know it's past the deadline, so don't translate this. I think + I fixed the bug that caused this error to occur. Thanks - LW */ + if( ( i < 4096 ) || ( ferror( _fptr ) != 0 )){ + clearerr( _fptr ); + QString msg = i18n("Error while updating archive name: "); + msg.append( i18n(strerror( errno )) ); + printf("%s\n", msg.latin1()); + KMessageBox::error(NULL, msg, i18n("File Access Error")); + // return; + /* 2002-01-31 LEW */ + } + fflush( _fptr ); + + /* 2002-01-31 LEW */ +#ifdef DEBUG + // printf("Tape::setName. wrote \"%s\" (%d bytes) into archive ID %s. %s %d\n", + // _name.ascii(), _name.length(), getID().latin1(), __FILE__, __LINE__); + // printf(" The buffer size is 4096. %d bytes were written\n", i); +#endif /* DEBUG */ + /* 2002-01-31 LEW */ + + // change back to read-only 2002-01-31 LEW + freopen( QFile::encodeName(filename), "r", _fptr ); + + TapeManager::instance()->tapeModified( this ); +} + +void Tape::setMTime( int mtime ) +{ + read(); + + if ( !_fptr ) { + // Create a new empty index file. + write(); + } + + // change file to read-write so we can update it 2002-01-31 LEW + QString filename = locateLocal( "appdata", _id); + freopen( QFile::encodeName(filename), "r+", _fptr ); + + _mtime = mtime; + + char buf[4096]; + fseek( _fptr, 0, SEEK_SET ); + fgets( buf, 4096, _fptr ); + fgets( buf, 4096, _fptr ); + fseek( _fptr, 4, SEEK_CUR ); + fwrite( &_mtime, sizeof( _mtime ), 1, _fptr ); + fflush( _fptr ); + + // change back to read-only 2002-01-31 LEW + freopen( QFile::encodeName(filename), "r", _fptr ); + + TapeManager::instance()->tapeModified( this ); +} + +void Tape::setSize( int size ) +{ + read(); + + if ( !_fptr ) { + // Create a new empty index file. + write(); + } + + _size = size; + + // change file to read-write so we can update it 2002-01-31 LEW + QString filename = locateLocal( "appdata", _id); + freopen( QFile::encodeName(filename), "r+", _fptr ); + + char buf[4096]; + fseek( _fptr, 0, SEEK_SET ); + fgets( buf, 4096, _fptr ); + fgets( buf, 4096, _fptr ); + fseek( _fptr, 8, SEEK_CUR ); + fwrite( &_size, sizeof( _size ), 1, _fptr ); + fflush( _fptr ); + + // change back to read-only 2002-01-31 LEW + freopen( QFile::encodeName(filename), "r", _fptr ); + + TapeManager::instance()->tapeModified( this ); +} + +void Tape::addChild( Archive* archive ) +{ + /* 2002-01-28 LEW */ + // printf("Preparing to add archive to tape file\n" ); + /* 2002-01-28 LEW */ + + read(); + + // change file to read-write so we can update it 2002-01-31 LEW + QString filename = locateLocal( "appdata", _id); + freopen( QFile::encodeName(filename), "r+", _fptr ); + + archive->calcRanges(); + + _children.append( archive ); + + char buf[4096]; + fseek( _fptr, 0, SEEK_END ); + int here = ftell( _fptr ); + fseek( _fptr, 0, SEEK_SET ); + fgets( buf, 4096, _fptr ); + fgets( buf, 4096, _fptr ); + fseek( _fptr, 12 + 4096, SEEK_CUR ); + int ival = _children.count(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + fseek( _fptr, ( _children.count() - 1 ) * 4, SEEK_CUR ); + fwrite( &here, sizeof( here ), 1, _fptr ); + fseek( _fptr, here, SEEK_SET ); + archive->write( _fptr ); + fflush( _fptr ); + + setMTime( time( NULL ) ); + + // change back to read-only 2002-01-31 LEW + freopen( QFile::encodeName(filename), "r", _fptr ); + + /* 2002-01-28 LEW */ + // printf("Done adding archive to tape file\n" ); + /* 2002-01-28 LEW */ +} + +void Tape::removeChild( Archive* archive ) +{ + read(); + + while ( _children.last() != archive ) { + _children.removeLast(); + } + _children.removeLast(); + + char buf[4096]; + fseek( _fptr, 0, SEEK_SET ); + fgets( buf, 4096, _fptr ); + fgets( buf, 4096, _fptr ); + fseek( _fptr, 12 + 4096, SEEK_CUR ); + int ival = _children.count(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + int here; + if ( ival > 0 ) { + fseek( _fptr, _children.count() * 4, SEEK_CUR ); + fread( &here, sizeof( here ), 1, _fptr ); + } else { + fseek( _fptr, MAX_NUM_ARCHIVES * 4, SEEK_CUR ); + here = ftell( _fptr ); + } + + QString filename = locateLocal( "appdata", _id); + truncate( QFile::encodeName(filename), here ); + freopen( QFile::encodeName(filename), "r+", _fptr ); + fflush( _fptr ); + + // change back to read-only 2002-01-31 LEW + freopen( QFile::encodeName(filename), "r", _fptr ); + + setMTime( time( NULL ) ); +} + +void Tape::clear() +{ + if ( _children.count() < 1 ) { + return; + } + + while ( _children.first() ) { + delete _children.first(); + _children.removeFirst(); + } + + char buf[4096]; + fseek( _fptr, 0, SEEK_SET ); + fgets( buf, 4096, _fptr ); + fgets( buf, 4096, _fptr ); + fseek( _fptr, 12 + 4096, SEEK_CUR ); + int ival = _children.count(); + fwrite( &ival, sizeof( ival ), 1, _fptr ); + fseek( _fptr, MAX_NUM_ARCHIVES * 4, SEEK_CUR ); + int here = ftell( _fptr ); + + QString filename = locateLocal( "appdata", _id); + truncate( QFile::encodeName(filename), here ); + freopen( QFile::encodeName(filename), "r+", _fptr ); + fflush( _fptr ); + + // change back to read-only 2002-01-31 LEW + freopen( QFile::encodeName(filename), "r", _fptr ); + + setMTime( time( NULL ) ); +} + +void Tape::readVersion1Index( FILE* fptr ) +{ + fseek( fptr, 0, SEEK_CUR ); + + char buf[4096]; + + // Read the tapeID. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID failed." ), i18n("Index File Error") ); + return; + } + QCString tapeID = buf; + tapeID.truncate( tapeID.length() - 1 ); + if ( tapeID !=_id ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Tape ID on tape does not match tape ID in index file." ), i18n("Index File Error") ); + return; + } + + // Read creation time. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading creation time failed." ), i18n("Index File Error") ); + return; + } + _ctime = atoi( buf ); + + // Read modification time. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading modification time failed." ), i18n("Index File Error") ); + return; + } + _mtime = atoi( buf ); + + // Read tape name. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape name failed." ), i18n("Index File Error") ); + return; + } + _name = buf; + _name.truncate( _name.length() - 1 ); + + // Read tape size. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape size failed." ), i18n("Index File Error") ); + return; + } + _size = atoi( buf ); + + // Read number of archives. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading archive count failed." ), i18n("Index File Error") ); + return; + } + int numArchives = atoi( buf ); + + for ( ; numArchives; numArchives-- ) { + // Read archive name. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading archive name failed." ), i18n("Index File Error") ); + return; + } + QString archiveName = buf; + archiveName.truncate( archiveName.length() - 1 ); + + // Read archive time stamp. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading archive time stamp failed." ), i18n("Index File Error") ); + return; + } + int archiveTimeStamp = atoi( buf ); + + // Read archive start block. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading archive start block failed." ), i18n("Index File Error") ); + return; + } + int archiveStartBlock = atoi( buf ); + + // Read archive end block. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading archive end block failed." ), i18n("Index File Error") ); + return; + } + int archiveEndBlock = atoi( buf ); + + // Create the archive. + Archive* archive = new Archive( this, archiveTimeStamp, archiveName ); + archive->setEndBlock( archiveEndBlock - archiveStartBlock ); + + _children.append( archive ); + + // Read the number of files. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading archive file count failed." ), i18n("Index File Error") ); + return; + } + int archiveNumFiles = atoi( buf ); + + QString tmpFileName; + int tmpFileSize = -1; + int tmpFileMTime = -1; + int tmpFileStartRecord = -1; + for ( ; archiveNumFiles; archiveNumFiles-- ) { + // Read file name. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading file name failed." ), i18n("Index File Error") ); + return; + } + QString fileName = buf; + fileName.truncate( fileName.length() - 1 ); + + // Read file size. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading file size failed." ), i18n("Index File Error") ); + return; + } + int fileSize = atoi( buf ); + + // Read file modification time. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading file modification time failed." ), i18n("Index File Error") ); + return; + } + int fileModTime = atoi( buf ); + + // Read file record number. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading file record number failed." ), i18n("Index File Error") ); + return; + } + int fileRecord = atoi( buf ); + + if ( tmpFileName.length() > 0 ) { + archive->addFile( tmpFileSize, tmpFileMTime, tmpFileStartRecord, fileRecord, tmpFileName ); + } + + tmpFileName = fileName.copy(); + tmpFileSize = fileSize; + tmpFileMTime = fileModTime; + tmpFileStartRecord = fileRecord; + } + + if ( tmpFileName.length() > 0 ) { + archive->addFile( tmpFileSize, tmpFileMTime, tmpFileStartRecord, archive->getEndBlock() * ( Options::instance()->getTapeBlockSize() / 512 ), tmpFileName ); + } + } +} + +void Tape::readVersion2Index( FILE* fptr ) +{ + fseek( fptr, 0, SEEK_CUR ); + + char buf[4096]; + + // Read the tapeID. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID failed." ), i18n("Index File Error") ); + return; + } + QCString tapeID = buf; + tapeID.truncate( tapeID.length() - 1 ); + if ( tapeID != _id ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Tape ID on tape does not match tape ID in index file." ), i18n("Index File Error") ); + return; + } + + // Read creation time. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading creation time failed." ), i18n("Index File Error") ); + return; + } + _ctime = atoi( buf ); + + // Read modification time. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading modification time failed." ), i18n("Index File Error") ); + return; + } + _mtime = atoi( buf ); + + // Read tape name. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape name failed." ), i18n("Index File Error") ); + return; + } + _name = buf; + _name.truncate( _name.length() - 1 ); + + // Read tape size. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape size failed." ), i18n("Index File Error") ); + return; + } + _size = atoi( buf ); + + // Read number of archives. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading archive count failed." ), i18n("Index File Error") ); + return; + } + int numArchives = atoi( buf ); + + int ival; + for ( ; numArchives; numArchives-- ) { + fread( &ival, sizeof( ival ), 1, fptr ); + _children.append( new Archive( this, fptr, ival ) ); + } +} + +void Tape::readVersion3Index( FILE* fptr ) +{ + fseek( fptr, 0, SEEK_CUR ); + + char buf[4097]; + buf[4096] = '\0'; + + // Read the tapeID. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID failed." ), i18n("Index File Error") ); + return; + } + QCString tapeID = buf; + tapeID.truncate( tapeID.length() - 1 ); + if ( tapeID != _id ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Tape ID on tape does not match tape ID in index file." ), i18n("Index File Error") ); + return; + } + + // Read creation time. + fread( &_ctime, sizeof( _ctime ), 1, fptr ); + + // Read modification time. + fread( &_mtime, sizeof( _mtime ), 1, fptr ); + + // Read tape size. + fread( &_size, sizeof( _size ), 1, fptr ); + + // Read tape name. + fread( buf, sizeof( char ), 4096, fptr ); + _name = buf; + + // Read number of archives. + int numArchives; + fread( &numArchives, sizeof( numArchives ), 1, fptr ); + + int ival; + for ( ; numArchives; numArchives-- ) { + fread( &ival, sizeof( ival ), 1, fptr ); + _children.append( new Archive( this, fptr, ival ) ); + } +} + +void Tape::readVersion4Index( FILE* fptr ) +{ + fseek( fptr, 0, SEEK_CUR ); + + char buf[4097]; + buf[4096] = '\0'; + + // Read the tapeID. + if ( !fgets( buf, 4096, fptr ) ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID failed." ), i18n("Index File Error") ); + return; + } + QCString tapeID = buf; + tapeID.truncate( tapeID.length() - 1 ); + if ( tapeID != _id ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Tape ID on tape does not match tape ID in index file." ), i18n("Index File Error") ); + return; + } + + // Read creation time. + fread( &_ctime, sizeof( _ctime ), 1, fptr ); + + // Read modification time. + fread( &_mtime, sizeof( _mtime ), 1, fptr ); + + // Read tape size. + fread( &_size, sizeof( _size ), 1, fptr ); + + // Read tape name. + fread( buf, sizeof( char ), 4096, fptr ); + _name = buf; + + // Read number of archives. + int numArchives; + fread( &numArchives, sizeof( numArchives ), 1, fptr ); + + int ival; + for ( ; numArchives; numArchives-- ) { + fread( &ival, sizeof( ival ), 1, fptr ); + _children.append( new Archive( this, fptr, ival ) ); + } + + /* 2002-01-31 LEW */ +#ifdef DEBUG + printf("Read contents of archive file \"%s\". %s %d\n", + _name.latin1(), __FILE__, __LINE__ ); +#endif /* DEBUG */ + /* 2002-01-31 LEW */ +} + +void Tape::calcRanges() +{ + QPtrListIterator<Archive> it( getChildren() ); + for ( ; it.current(); ++it ) { + it.current()->calcRanges(); + } +} diff --git a/kdat/Tape.h b/kdat/Tape.h new file mode 100644 index 0000000..eab7cb3 --- /dev/null +++ b/kdat/Tape.h @@ -0,0 +1,156 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _Tape_h_ +#define _Tape_h_ + +#include <qptrlist.h> +#include <qstring.h> + +#include "Archive.h" + +/** + * @short This class represents a single tape index. + */ +class Tape { + bool _stubbed; + QCString _id; + int _ctime; + int _mtime; + QString _name; + int _size; + QPtrList<Archive> _children; + + FILE* _fptr; + + void readVersion1Index( FILE* fptr ); + void readVersion2Index( FILE* fptr ); + void readVersion3Index( FILE* fptr ); + void readVersion4Index( FILE* fptr ); + + void calcRanges(); + + void read(); + void readAll( int version ); + void write(); +public: + /** + * Create a new tape index, and automatically generate a unique tape ID. + */ + Tape(); + + /** + * Create a new tape index for the given tape index ID. + * + * @param id The unique tape index identifier. + */ + Tape( const char * id ); + + /** + * Destroy the tape index. + */ + ~Tape(); + + /** + * Writes a KDat header containing the tape ID, at the beginning of the + * tape. All data on the tape will be lost. + */ + void format(); + + /** + * Get the unique ID for the tape. + * + * @return The tape id. + */ + QString getID(); + + /** + * Get the user-specified name for the tape. + * + * @return The name of the tape. + */ + QString getName(); + + /** + * Get the date and time that the tape was formatted. + * + * @return The tape format time, in seconds since the Epoch. + */ + int getCTime(); + + /** + * Get the last time that the tape was modified. + * + * @return The tape modification time, in seconds since the Epoch. + */ + int getMTime(); + + /** + * Get the total tape capacity. + * + * @return The tape capacity in kilobytes. + */ + int getSize(); + + /** + * Get the list of archives on this tape. + * + * @return The list of all archives on the tape. + */ + const QPtrList<Archive>& getChildren(); + + /** + * Set the name for the tape. + * + * @param name The new name for the tape. + */ + void setName( const QString & name ); + + /** + * Set the modification time for the tape to be the current time.. + */ + void setMTime( int mtime ); + + /** + * Set the total capacity of the tape. + * + * @param size The total size, in kilobytes, of the tape. + */ + void setSize( int size ); + + /** + * Add an archive to the tape index. + * + * @param archive The archive to add. + */ + void addChild( Archive* archive ); + + /** + * Remove an archive and all the archives that follow it from the index. + * + * @param archive The archive to remove. + */ + void removeChild( Archive* archive ); + + /** + * Recursively destroy all children of the tape index. + */ + void clear(); +}; + +#endif diff --git a/kdat/TapeDrive.cpp b/kdat/TapeDrive.cpp new file mode 100644 index 0000000..64cb46f --- /dev/null +++ b/kdat/TapeDrive.cpp @@ -0,0 +1,642 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/mtio.h> +#include <unistd.h> +#include <time.h> + +#include <qfile.h> + +#include <kmessagebox.h> +#include <kapplication.h> +#include <klocale.h> + +#include "KDatMainWindow.h" +#include "Options.h" +#include "Tape.h" +#include "TapeManager.h" +#include "TapeDrive.h" +#include "kdat.h" + +#include "TapeDrive.moc" + +TapeDrive* TapeDrive::_instance = 0; + +TapeDrive* TapeDrive::instance() +{ + if ( !_instance ) { + _instance = new TapeDrive(); + } + + return _instance; +} + +TapeDrive::TapeDrive() + : _fd ( -1 ), + _readOnly( TRUE ), + _tape( 0 ), + _writeBuf( 0 ), + _readBuf( 0 ), + _writeBufIdx( 0 ), + _readBufIdx( Options::instance()->getTapeBlockSize() ) +{ + setBlockSize( Options::instance()->getTapeBlockSize() ); + _writeBuf = new char[ Options::instance()->getTapeBlockSize() ]; + _readBuf = new char[ Options::instance()->getTapeBlockSize() ]; +} + +TapeDrive::~TapeDrive() +{ + if ( Options::instance()->getLockOnMount() ) { + unlock(); + } + + delete [] _writeBuf; + delete [] _readBuf; +} + +void TapeDrive::flush() +{ + _readBufIdx = Options::instance()->getTapeBlockSize(); + + if ( _writeBufIdx <= 0 ) { + return; + } + + memset( _writeBuf + _writeBufIdx, 0, Options::instance()->getTapeBlockSize() - _writeBufIdx ); + int ret = ::write( _fd, _writeBuf, Options::instance()->getTapeBlockSize() ); + if ( ret < 0 ) { + printf( "TapeDrive::flush() -- write failed!\n" ); + } + + _writeBufIdx = 0; +} + +bool TapeDrive::load() +{ + if ( _fd < 0 ) { + return FALSE; + } + +#ifdef MTLOAD + struct mtop tapeOp; + tapeOp.mt_op = MTLOAD; + tapeOp.mt_count = 1; + + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::lock() -- ioctl( MTLOAD ) failed!\n" ); + } + + return ret >= 0; +#else + return TRUE; +#endif +} + +bool TapeDrive::lock() +{ + if ( _fd < 0 ) { + return FALSE; + } + +#ifdef MTLOCK + struct mtop tapeOp; + tapeOp.mt_op = MTLOCK; + tapeOp.mt_count = 1; + + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::lock() -- ioctl( MTLOCK ) failed!\n" ); + } + + return ret >= 0; +#else + return TRUE; +#endif +} + +bool TapeDrive::unlock() +{ + if ( _fd < 0 ) { + return FALSE; + } + +#ifdef MTUNLOCK + struct mtop tapeOp; + tapeOp.mt_op = MTUNLOCK; + tapeOp.mt_count = 1; + + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::unlock() -- ioctl( MTUNLOCK ) failed!\n" ); + } + + return ret >= 0; +#else + return TRUE; +#endif +} + +bool TapeDrive::isReadOnly() +{ + return _readOnly; +} + +bool TapeDrive::isTapePresent() +{ + open(); + if ( _fd < 0 ) { + return FALSE; + } + + // Get tape status. + struct mtget tapeStatus; + int ret = ioctl( _fd, MTIOCGET, &tapeStatus ); + if ( ret < 0 ) { + return FALSE; + } + + // Check for the presence of a tape. +// if ( !GMT_DR_OPEN( tapeStatus.mt_gstat ) ) { + if ( GMT_ONLINE( tapeStatus.mt_gstat ) ) { + // Lock the tape drive door. + //struct mtop tapeOp; + //tapeOp.mt_op = MTLOCK; + //tapeOp.mt_count = 1; + //int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + //if ( ret < 0 ) { + // printf( "TapeDrive::isTapePresent() -- ioctl( MTLOCK ) failed!\n" ); + //} + + if ( _readOnly ) { + emit sigStatus( i18n( "Tape mounted readonly." ) ); + } else { + emit sigStatus( i18n( "Tape mounted read/write." ) ); + } + return TRUE; + } else { + return FALSE; + } +} + +void TapeDrive::eject() +{ + if ( _fd < 0 ) { + return; + } + + flush(); + + struct mtop tapeOp; + tapeOp.mt_op = MTOFFL; + tapeOp.mt_count = 1; + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::ejectTape() -- ioctl( MTOFFL ) failed!\n" ); + } +} + +Tape* TapeDrive::readHeader() +{ + _tape = NULL; + + // Rewind tape. + emit sigStatus( "Rewinding tape..." ); + if ( !rewind() ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Rewinding tape failed." )); + return NULL; + } + + // KDat magic string. + emit sigStatus( i18n( "Reading magic string..." ) ); + char magic[9]; + if ( read( magic, 9 ) < 9 ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading magic string failed." )); + return NULL; + } + if ( strncmp( "KDatMAGIC", magic, 9 ) ) { + // Bad magic. + return NULL; + } + + // Read version number. + emit sigStatus( i18n( "Reading version number..." ) ); + int version; + if ( read( (char*)&version, 4 ) < 4 ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading version number failed." )); + return NULL; + } + + if ( version > KDAT_TAPE_HEADER_VERSION ) { + KMessageBox::information( KDatMainWindow::getInstance(), i18n( "Tape was formatted by a more recent version of KDat. Consider upgrading." )); + } + + // Read tape ID. + emit sigStatus( i18n( "Reading tape ID..." ) ); + int len; + if ( read( (char*)&len, 4 ) < 4 ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID length failed." )); + return NULL; + } + char* tapeID = new char[len]; + if ( read( tapeID, len ) < len ) { + KMessageBox::error( KDatMainWindow::getInstance(), i18n( "Reading tape ID failed." )); + delete [] tapeID; + return NULL; + } + + _tape = TapeManager::instance()->findTape( tapeID ); + delete [] tapeID; + return _tape; +} + +bool TapeDrive::rewind() +{ + if ( _fd < 0 ) { + return FALSE; + } + + flush(); + + struct mtop tapeOp; + tapeOp.mt_op = MTREW; + tapeOp.mt_count = 1; + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::rewind() -- ioctl( MTREW ) failed!\n" ); + } + + return ret >= 0; +} + +int TapeDrive::getFile() +{ + if ( _fd < 0 ) { + return -1; + } + + struct mtget tapePos; + + if ( ioctl( _fd, MTIOCGET, &tapePos ) < 0 ) { + printf( "TapeDrive::getFile() -- ioctl( MTIOCGET ) failed!\n" ); + return -1; + } + + return tapePos.mt_fileno; +} + +int TapeDrive::getBlock() +{ + if ( _fd < 0 ) { + return -1; + } + + struct mtget tapePos; + + if ( ioctl( _fd, MTIOCGET, &tapePos ) < 0 ) { + printf( "TapeDrive::getBlock() -- ioctl( MTIOCGET ) failed!\n" ); + return -1; + } + + //%%% I don't think this makes sense anymore because the tape buffer size == the tape block size. + // Need to subtract off the blocks that the application has not "read" yet. + //int unread = ( Options::instance()->getTapeBlockSize() - _readBufIdx ) / Options::instance()->getTapeBlockSize(); + + //return tapePos.mt_blkno - unread; + + return tapePos.mt_blkno; +} + +bool TapeDrive::nextFile( int count ) +{ + if ( _fd < 0 ) { + return FALSE; + } + + flush(); + + struct mtop tapeOp; + tapeOp.mt_op = MTFSF; + tapeOp.mt_count = count; + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::nextFile() -- ioctl( MTFSF ) failed!\n" ); + } + return ret >= 0; +} + +bool TapeDrive::prevFile( int count ) +{ + if ( _fd < 0 ) { + return FALSE; + } + + flush(); + + struct mtop tapeOp; + tapeOp.mt_op = MTBSF; + tapeOp.mt_count = count; + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::prevFile() -- ioctl( MTBSF ) failed!\n" ); + } + return ret >= 0; +} + +bool TapeDrive::nextRecord( int count ) +{ + if ( _fd < 0 ) { + return FALSE; + } + + flush(); + + struct mtop tapeOp; + tapeOp.mt_op = MTFSR; + tapeOp.mt_count = count; + + bool status = ( ioctl( _fd, MTIOCTOP, &tapeOp ) >= 0 ); + + if ( !status ) { + // Try reading count * TapeBlockSize bytes. + char *buf = new char[Options::instance()->getTapeBlockSize()]; + int bytes = count * Options::instance()->getTapeBlockSize(); + int ret; + while ( bytes > 0 ) { + ret = read( buf, bytes > Options::instance()->getTapeBlockSize() ? Options::instance()->getTapeBlockSize() : bytes ); + if ( ret <= 0 ) { + status = FALSE; + break; + } + bytes -= ret; + } + delete [] buf; + status = TRUE; + } + return status; +} + +bool TapeDrive::prevRecord( int count ) +{ + if ( _fd < 0 ) { + return FALSE; + } + + flush(); + + struct mtop tapeOp; + tapeOp.mt_op = MTBSR; + tapeOp.mt_count = count; + int ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::prevRecord() -- ioctl( MTBSR ) failed!\n" ); + } + return ret >= 0; +} + +void TapeDrive::close() +{ + if ( _fd < 0 ) { + return; + } + + flush(); + + ::close( _fd ); +} + +void TapeDrive::open() +{ + close(); + + // Open the tape device. + _fd = ::open( QFile::encodeName(Options::instance()->getTapeDevice()), O_RDWR ); + if ( _fd < 0 ) { + _fd = ::open( QFile::encodeName(Options::instance()->getTapeDevice()), O_RDONLY ); + if ( _fd < 0 ) { + return; + } else { + _readOnly = TRUE; + } + } else { + _readOnly = FALSE; + } + + // Set the tape block size after the device is opened. + setBlockSize( Options::instance()->getTapeBlockSize() ); +} + +int TapeDrive::read( char* buf, int len ) +{ + if ( _fd < 0 ) { + return -1; + } + + //printf( "TapeDrive::read() -- _readBufIdx = %d\n", _readBufIdx ); + + int remain = Options::instance()->getTapeBlockSize() - _readBufIdx; + if ( len <= remain ) { + memcpy( buf, _readBuf + _readBufIdx, len ); + _readBufIdx += len; + } else { + memcpy( buf, _readBuf + _readBufIdx, remain ); + _readBufIdx = Options::instance()->getTapeBlockSize(); + + int need = Options::instance()->getTapeBlockSize(); + int count = 0; + while ( need > 0 ) { + int ret = ::read( _fd, _readBuf + count, Options::instance()->getTapeBlockSize() ); + if ( ret <= 0 ) return ret; + need -= ret; + count += ret; + } + + memcpy( buf + remain, _readBuf, len - remain ); + _readBufIdx = len - remain; + } + + //int ret = ::read( _fd, buf, len ); + return len; +} + +int TapeDrive::write( const char* buf, int len ) +{ + if ( _fd < 0 ) { + return -1; + } + + int bufIdx = 0; + while ( len + _writeBufIdx - bufIdx > Options::instance()->getTapeBlockSize() ) { + memcpy( _writeBuf + _writeBufIdx, buf + bufIdx, Options::instance()->getTapeBlockSize() - _writeBufIdx ); + int ret = ::write( _fd, _writeBuf, Options::instance()->getTapeBlockSize() ); + if ( ret < 0 ) { + printf( "TapeDrive::write() -- write failed!\n" ); + return ret; + } + bufIdx += Options::instance()->getTapeBlockSize() - _writeBufIdx; + _writeBufIdx = 0; + } + + if ( bufIdx < len ) { + memcpy( _writeBuf + _writeBufIdx, buf + bufIdx, len - bufIdx ); + _writeBufIdx += len - bufIdx; + } + + return len; +} + +bool TapeDrive::seek( int file, int tarBlock ) +{ +// printf( "TapeDrive::seek() -- file = %d, block = %d\n", file, tarBlock ); + + if ( _fd < 0 ) { + printf( "bailing1\n" ); + return FALSE; + } + + flush(); + + // Go to the desired archive. + emit sigStatus( i18n( "Skipping to archive..." ) ); + + int curFile = getFile(); +// printf( "TapeDrive::seek() -- curFile = %d\n", curFile ); + if ( curFile < 0 ) { + emit sigStatus( i18n( "Rewinding tape..." ) ); + rewind(); + curFile = 0; + } + + int fileDiff = file - curFile; + if ( fileDiff > 0 ) { + nextFile( fileDiff ); + } else if ( fileDiff < 0 ) { + prevFile( -fileDiff + 1 ); + nextFile( 1 ); + } + + int tapeBlock = tarBlock / ( Options::instance()->getTapeBlockSize() / 512 ); +// printf( "TapeDrive::seek() -- desired tapeBlock = %d\n", tapeBlock ); + + // Go to the desired record within the archive. + emit sigStatus( i18n( "Skipping to block..." ) ); + int curBlock = getBlock(); +// printf( "TapeDrive::seek() -- curBlock = %d\n", curBlock ); + if ( curBlock < 0 ) { + emit sigStatus( i18n( "Rewinding tape..." ) ); + rewind(); + nextFile( file ); + if ( ( curBlock = getBlock() ) < 0 ) { + printf( "bailing2\n" ); + return FALSE; + } + } + + if ( tapeBlock > curBlock ) { + if ( !nextRecord( tapeBlock - curBlock ) ) { + printf( "bailing3\n" ); + return FALSE; + } + } else if ( tapeBlock < curBlock ) { + if ( tapeBlock == 0 ) { + if ( !prevFile( 1 ) ) { + printf( "bailing6\n" ); + return FALSE; + } + if ( !nextFile( 1 ) ) { + printf( "bailing7\n" ); + return FALSE; + } + } else { + if ( !prevRecord( curBlock - tapeBlock + 1 ) ) { + printf( "bailing4\n" ); + return FALSE; + } + if ( !nextRecord( 1 ) ) { + printf( "bailing5\n" ); + return FALSE; + } + } + } + +// printf( "TapeDrive::seek() -- now: file = %d, block = %d\n", getFile(), getBlock() ); + + // If tapeBlockSize > 512, we may need to skip some tar blocks. +// printf ( "TapeDrive::seek() -- skipping %d tar blocks.\n", tarBlock - tapeBlock * ( Options::instance()->getTapeBlockSize() / 512 ) ); + char *buf = new char[ Options::instance()->getTapeBlockSize() ]; + read( buf, 512 * ( tarBlock - tapeBlock * ( Options::instance()->getTapeBlockSize() / 512 ) ) ); + + delete [] buf; + + return TRUE; +} + +bool TapeDrive::pastEOF() +{ + if ( _fd < 0 ) { + return FALSE; + } + + struct mtget tapeStatus; + if ( ioctl( _fd, MTIOCGET, &tapeStatus ) < 0 ) { + printf( "TapeDrive::pastEOF() -- ioctl( MTIOCGET ) failed!\n" ); + return FALSE; + } + + return GMT_EOF( tapeStatus.mt_gstat ); +} + +bool TapeDrive::setBlockSize( int blockSize ) +{ + if ( _fd < 0 ) { + return FALSE; + } + +#ifdef MTSETBLK + flush(); + + int ret = 0; + if ( Options::instance()->getVariableBlockSize() ) { + struct mtop tapeOp; + tapeOp.mt_op = MTSETBLK; + tapeOp.mt_count = blockSize; + ret = ioctl( _fd, MTIOCTOP, &tapeOp ); + if ( ret < 0 ) { + printf( "TapeDrive::setBlockSize() -- ioctl( MTSETBLK ) failed!\n" ); + } + } + + _readBufIdx = blockSize; + _writeBufIdx = 0; + delete [] _readBuf; + _readBuf = new char[ blockSize ]; + delete [] _writeBuf; + _writeBuf = new char[ blockSize ]; + + return ret >= 0; +#else + // some systems (e.g. HP-UX) encode block size into device file names + // so setting the block size by software does not make sense + return TRUE; +#endif +} diff --git a/kdat/TapeDrive.h b/kdat/TapeDrive.h new file mode 100644 index 0000000..4709eed --- /dev/null +++ b/kdat/TapeDrive.h @@ -0,0 +1,247 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _TapeDrive_h_ +#define _TapeDrive_h_ + +#include <qobject.h> + +class Tape; + +/** + * @short An OO interface to the tape drive. + */ +class TapeDrive : public QObject { + Q_OBJECT + int _fd; + bool _readOnly; + Tape* _tape; + char* _writeBuf; + char* _readBuf; + int _writeBufIdx; + int _readBufIdx; + + static TapeDrive* _instance; + + TapeDrive(); +public: + /** + * Destroy the tape drive (figuratively speaking, of course). + */ + ~TapeDrive(); + + /** + * Get a reference to the single instance of the tape drive object. + * + * @return A pointer to the tape drive object. + */ + static TapeDrive* instance(); + + /** + * Close the tape device. If data was written to the tape drive, then this + * will also cause a single end-of-file marker to be written out. This is + * the preferred way to write an end-of-file marker to the tape. + */ + void close(); + + /** + * Physically eject the tape from the drive. Some tape drives do not + * support this operation well. + */ + void eject(); + + /** + * If there is unwritten data in the buffer, it will be flushed out. This + * method is called when necessary by the other tape drive methods. + */ + void flush(); + + /** + * Get the current tape file number. The first tape file is number 0. + * + * @return >= 0 on success, -1 on failure. + */ + int getFile(); + + /** + * Get the current tape block number within the current file. The first + * tape block number within a file is 0. + * + * @return >= 0 on success, -1 on failure. + */ + int getBlock(); + + /** + * Determine whether the tape can be written to. + * + * @return TRUE if the tape cannot be written to, otherwise FALSE. + */ + bool isReadOnly(); + + /** + * Determine whether there is a tape in the drive. + * + * @return TRUE if there is a tape, otherwise FALSE. + */ + bool isTapePresent(); + + /** + * Load the tape into the drive. + * + * @return TRUE on success, otherwise FALSE. + */ + bool load(); + + /** + * Lock the tape in the drive, so that it cannot be ejected manually by the + * user. Not all tape drives support this operation. + * + * @return TRUE on success, otherwise FALSE. + */ + bool lock(); + + /** + * Skip over one or more end-of-file markers on the tape. The tape is + * positioned just after the last skipped end-of-file marker. + * + * @param count The number of end-of-file markers to skip over. + * + * @return TRUE on success, otherwise FALSE. + */ + bool nextFile( int count ); + + /** + * Skip over one or more tape blocks within the current tape file. + * + * @param count The number of tape blocks to skip over. + * + * @return TRUE on success, otherwise FALSE. + */ + bool nextRecord( int count ); + + /** + * Open the tape device for reading and writing. If this fails, try + * opening the tape device for reading only. Check isReadOnly() to see + * if the tape device was opened for reading and writing. + * + * @return TRUE if the tape device was opened for reading and/or writing, + * otherwise FALSE. + */ + void open(); + + /** + * Determine wether the tape is positioned just after an end-of-file + * marker. + * + * @return TRUE if the tape is positioned after an end-of-file marker, + * otherwise FALSE. + */ + bool pastEOF(); + + /** + * Backspace over one or more end-of-file markers. The tape is positioned + * just before the last end-of-file marker. + * + * @param count The number of end-of-file markers to backspace over. + * + * @return TRUE on success, otherwise FALSE. + */ + bool prevFile( int count ); + + /** + * Backspace over one or more tape blocks. + * + * @param count The number of tape block to backspace over. + * + * @return TRUE on success, otherwise FALSE. + */ + bool prevRecord( int count ); + + /** + * Read data from the tape. + * + * @param buf The buffer to read into. + * @param len The number of bytes to read. + * + * @return The actual number of bytes read, or -1 on failure. + */ + int read( char* buf, int len ); + + /** + * Read the KDat tape header (if there is one), and get the index + * associated with the tape. + * + * @return A pointer to the tape index, or NULL if the tape does not have + * a KDat header. + */ + Tape* readHeader(); + + /** + * Rewind the tape. + * + * @return TRUE on success, otherwise FALSE. + */ + bool rewind(); + + /** + * Move the tape to the given tar block within the given tape file. This + * method will intelligently move the tape to the desired position. + * + * @param file The tape file number. + * @param tarBlock The desired tar block (NOT tape block). + * + * @return TRUE on success, otherwise FALSE. + */ + bool seek( int file, int tarBlock ); + + /** + * Set the hardware tape block size. + * + * @param blockSize The new tape block size in bytes. + * + * @return TRUE on success, otherwise FALSE. + */ + bool setBlockSize( int blockSize ); + + /** + * Unlock the tape in the drive, so that it can be ejected manually by the + * user. Not all tape drives support this operation. + * + * @return TRUE on success, otherwise FALSE. + */ + bool unlock(); + + /** + * Write data to the tape. + * + * @param buf The buffer to write from. + * @param len The number of bytes to write. + * + * @return The actual number of bytes written, or -1 on failure. + */ + int write( const char* buf, int len ); +signals: + /** + * This signal is emitted for one-line, informational messages. + * + * @param msg The informational message. + */ + void sigStatus( const QString & msg ); +}; + +#endif diff --git a/kdat/TapeFileInfoWidget.cpp b/kdat/TapeFileInfoWidget.cpp new file mode 100644 index 0000000..de5a453 --- /dev/null +++ b/kdat/TapeFileInfoWidget.cpp @@ -0,0 +1,133 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <time.h> + +#include <qlabel.h> +#include <qlayout.h> + +#include <kapplication.h> + +#include "File.h" +#include "TapeFileInfoWidget.h" +#include "Util.h" +#include <klocale.h> + +#include "TapeFileInfoWidget.moc" + +TapeFileInfoWidget::TapeFileInfoWidget( QWidget* parent, const char* name ) + : QWidget( parent, name ) +{ + QLabel* lbl1 = new QLabel( i18n( "File name:" ), this ); + QLabel* lbl2 = new QLabel( i18n( "Last modified:" ), this ); + QLabel* lbl3 = new QLabel( i18n( "Size:" ), this ); + QLabel* lbl4 = new QLabel( i18n( "Start record:" ), this ); + QLabel* lbl5 = new QLabel( i18n( "End record:" ), this ); + + int max = lbl1->sizeHint().width(); + if ( lbl2->sizeHint().width() > max ) max = lbl2->sizeHint().width(); + if ( lbl3->sizeHint().width() > max ) max = lbl3->sizeHint().width(); + if ( lbl4->sizeHint().width() > max ) max = lbl4->sizeHint().width(); + if ( lbl5->sizeHint().width() > max ) max = lbl5->sizeHint().width(); + + lbl1->setFixedSize( max, lbl1->sizeHint().height() ); + lbl2->setFixedSize( max, lbl2->sizeHint().height() ); + lbl3->setFixedSize( max, lbl3->sizeHint().height() ); + lbl4->setFixedSize( max, lbl4->sizeHint().height() ); + lbl5->setFixedSize( max, lbl5->sizeHint().height() ); + + _fileName = new QLabel( "???", this ); + _fileName->setFixedHeight( _fileName->sizeHint().height() ); + + _mtime = new QLabel( "???", this ); + _mtime->setFixedHeight( _mtime->sizeHint().height() ); + + _size = new QLabel( "???", this ); + _size->setFixedHeight( _size->sizeHint().height() ); + + _startRecord = new QLabel( "???", this ); + _startRecord->setFixedHeight( _startRecord->sizeHint().height() ); + + _endRecord = new QLabel( "???", this ); + _endRecord->setFixedHeight( _endRecord->sizeHint().height() ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 4, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addWidget( lbl1 ); + l1_1->addWidget( _fileName, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addWidget( lbl2 ); + l1_2->addWidget( _mtime, 1 ); + + QHBoxLayout* l1_3 = new QHBoxLayout(); + l1->addLayout( l1_3 ); + l1_3->addWidget( lbl3 ); + l1_3->addWidget( _size, 1 ); + + QHBoxLayout* l1_4 = new QHBoxLayout(); + l1->addLayout( l1_4 ); + l1_4->addWidget( lbl4 ); + l1_4->addWidget( _startRecord, 1 ); + + QHBoxLayout* l1_5 = new QHBoxLayout(); + l1->addLayout( l1_5 ); + l1_5->addWidget( lbl5 ); + l1_5->addWidget( _endRecord, 1 ); + + l1->addStretch( 1 ); +} + +TapeFileInfoWidget::~TapeFileInfoWidget() +{ +} + +void TapeFileInfoWidget::setFile( File* file ) +{ + _file = file; + + if ( !_file ) { + return; + } + + _fileName->setText( _file->getName() ); + + QString tmp; + time_t tm = _file->getMTime(); + tmp = ctime( &tm ); + tmp = tmp.stripWhiteSpace(); + _mtime->setText( tmp ); + + _startRecord->setText( Util::bytesToString( _file->getStartRecord() ) ); + + _endRecord->setText( Util::bytesToString( _file->getEndRecord() ) ); + + if ( _file->isDirectory() ) { + QPtrListIterator<Range> it( _file->getRanges() ); + int records = 0; + for ( ; it.current(); ++it ) { + records += it.current()->getEnd() - it.current()->getStart(); + } + _size->setText( Util::kbytesToString( records / 2 ) ); + } else { + _size->setText( Util::bytesToString( _file->getSize() ) ); + } +} diff --git a/kdat/TapeFileInfoWidget.h b/kdat/TapeFileInfoWidget.h new file mode 100644 index 0000000..0eaa2a6 --- /dev/null +++ b/kdat/TapeFileInfoWidget.h @@ -0,0 +1,59 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _TapeFileInfoWidget_h_ +#define _TapeFileInfoWidget_h_ + +#include <qwidget.h> + +class QLabel; + +class File; + +/** + * @short Display information about a tape file. + */ +class TapeFileInfoWidget : public QWidget { + Q_OBJECT + File* _file; + QLabel* _fileName; + QLabel* _mtime; + QLabel* _startRecord; + QLabel* _endRecord; + QLabel* _size; +public: + /** + * Create a new tape file info widget. + * + * @param parent The parent widget. + * @param name The name of this widget. + */ + TapeFileInfoWidget( QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the tape file info widget. + */ + ~TapeFileInfoWidget(); + + /** + * Display the information for the given tape file. + */ + void setFile( File* file ); +}; + +#endif diff --git a/kdat/TapeInfoWidget.cpp b/kdat/TapeInfoWidget.cpp new file mode 100644 index 0000000..8a22656 --- /dev/null +++ b/kdat/TapeInfoWidget.cpp @@ -0,0 +1,275 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdlib.h> +#include <time.h> + +#include <qcombobox.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> + +#include <kapplication.h> +#include <kglobal.h> +#include <klocale.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "Options.h" +#include "Tape.h" +#include "TapeInfoWidget.h" +#include "Util.h" + +#include "TapeInfoWidget.moc" + +TapeInfoWidget::TapeInfoWidget( QWidget* parent, const char* name ) + : QWidget( parent, name ), + _tape( 0 ) +{ + QLabel* lbl1 = new QLabel( i18n( "Tape name:" ), this ); + QLabel* lbl2 = new QLabel( i18n( "Tape size:" ), this ); + QLabel* lbl3 = new QLabel( i18n( "Tape ID:" ), this ); + QLabel* lbl4 = new QLabel( i18n( "Created on:" ), this ); + QLabel* lbl5 = new QLabel( i18n( "Last modified:" ), this ); + QLabel* lbl6 = new QLabel( i18n( "Archive count:" ), this ); + QLabel* lbl7 = new QLabel( i18n( "Space used:" ), this ); + + int max = lbl1->sizeHint().width(); + if ( lbl2->sizeHint().width() > max ) max = lbl2->sizeHint().width(); + if ( lbl3->sizeHint().width() > max ) max = lbl3->sizeHint().width(); + if ( lbl4->sizeHint().width() > max ) max = lbl4->sizeHint().width(); + if ( lbl5->sizeHint().width() > max ) max = lbl5->sizeHint().width(); + if ( lbl6->sizeHint().width() > max ) max = lbl6->sizeHint().width(); + if ( lbl7->sizeHint().width() > max ) max = lbl7->sizeHint().width(); + + lbl1->setFixedSize( max, lbl1->sizeHint().height() ); + lbl2->setFixedSize( max, lbl2->sizeHint().height() ); + lbl3->setFixedSize( max, lbl3->sizeHint().height() ); + lbl4->setFixedSize( max, lbl4->sizeHint().height() ); + lbl5->setFixedSize( max, lbl5->sizeHint().height() ); + lbl6->setFixedSize( max, lbl6->sizeHint().height() ); + lbl7->setFixedSize( max, lbl7->sizeHint().height() ); + + _tapeName = new QLineEdit( this ); + _tapeName->setFixedHeight( _tapeName->sizeHint().height() ); + + _tapeSize = new QLineEdit( this ); + _tapeSize->setFixedSize( 48, _tapeSize->sizeHint().height() ); + + _tapeSizeUnits = new QComboBox( this ); + _tapeSizeUnits->setFixedSize( 48, _tapeSizeUnits->sizeHint().height() ); + _tapeSizeUnits->insertItem( "MB" ); + _tapeSizeUnits->insertItem( "GB" ); + + _tapeID = new QLabel( "???", this ); + _tapeID->setFixedHeight( _tapeID->sizeHint().height() ); + + _ctime = new QLabel( "???", this ); + _ctime->setFixedHeight( _ctime->sizeHint().height() ); + + _mtime = new QLabel( "???", this ); + _mtime->setFixedHeight( _mtime->sizeHint().height() ); + + _archiveCount = new QLabel( "???", this ); + _archiveCount->setFixedHeight( _archiveCount->sizeHint().height() ); + + _spaceUsed = new QLabel( "???", this ); + _spaceUsed->setFixedHeight( _spaceUsed->sizeHint().height() ); + + _apply = new KPushButton( KStdGuiItem::apply(), this ); + _apply->setFixedSize( 80, _apply->sizeHint().height() ); + _apply->setEnabled( FALSE ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 4, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addWidget( lbl1 ); + l1_1->addWidget( _tapeName, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addWidget( lbl2 ); + l1_2->addWidget( _tapeSize ); + l1_2->addWidget( _tapeSizeUnits ); + l1_2->addStretch( 1 ); + + QHBoxLayout* l1_3 = new QHBoxLayout(); + l1->addLayout( l1_3 ); + l1_3->addWidget( lbl3 ); + l1_3->addWidget( _tapeID ); + + QHBoxLayout* l1_4 = new QHBoxLayout(); + l1->addLayout( l1_4 ); + l1_4->addWidget( lbl4 ); + l1_4->addWidget( _ctime ); + + QHBoxLayout* l1_5 = new QHBoxLayout(); + l1->addLayout( l1_5 ); + l1_5->addWidget( lbl5 ); + l1_5->addWidget( _mtime ); + + QHBoxLayout* l1_6 = new QHBoxLayout(); + l1->addLayout( l1_6 ); + l1_6->addWidget( lbl6 ); + l1_6->addWidget( _archiveCount ); + + QHBoxLayout* l1_7 = new QHBoxLayout(); + l1->addLayout( l1_7 ); + l1_7->addWidget( lbl7 ); + l1_7->addWidget( _spaceUsed ); + + l1->addStretch( 1 ); + + QHBoxLayout* l1_8 = new QHBoxLayout(); + l1->addLayout( l1_8 ); + l1_8->addStretch( 1 ); + l1_8->addWidget( _apply ); + + connect( _tapeName , SIGNAL( textChanged( const QString & ) ), this, SLOT( slotTextChanged( const QString & ) ) ); + connect( _tapeSize , SIGNAL( textChanged( const QString & ) ), this, SLOT( slotTextChanged( const QString & ) ) ); + connect( _tapeSizeUnits, SIGNAL( activated( int ) ) , this, SLOT( slotActivated( int ) ) ); + connect( _apply , SIGNAL( clicked() ) , this, SLOT( slotApply() ) ); +} + +TapeInfoWidget::~TapeInfoWidget() +{ +} + +void TapeInfoWidget::setTape( Tape* tape ) +{ + _tape = tape; + + if ( !_tape ) { + return; + } + + _tapeName->setText( _tape->getName() ); + + int size = _tape->getSize(); + if ( ( size >= 1024*1024 ) && ( size % ( 1024*1024 ) == 0 ) ) { + // GB + size /= 1024*1024; + _tapeSizeUnits->setCurrentItem( 1 ); + } else { + // MB + size /= 1024; + _tapeSizeUnits->setCurrentItem( 0 ); + } + QString tmp; + tmp.setNum( size ); + _tapeSize->setText( tmp ); + + _tapeID->setText( _tape->getID() ); + + time_t tm = _tape->getCTime(); + tmp = ctime( &tm ); + tmp = tmp.stripWhiteSpace(); + _ctime->setText( tmp ); + + tm = _tape->getMTime(); + tmp = ctime( &tm ); + tmp = tmp.stripWhiteSpace(); + _mtime->setText( tmp ); + + tmp.setNum( _tape->getChildren().count() ); + _archiveCount->setText( tmp ); + + QPtrListIterator<Archive> i( _tape->getChildren() ); + int used = 1; + for ( ; i.current(); ++i ) { + used += i.current()->getEndBlock(); + } + int blockSize = Options::instance()->getTapeBlockSize(); + if ( blockSize < 1024 ) { + used /= 1024 / blockSize; + } else if ( blockSize > 1024 ) { + used *= blockSize / 1024; + } + if ( _tape->getSize() > 0 ) { + tmp = QString::fromLatin1( "%1 / %2 (%3%)") + .arg(Util::kbytesToString( used )) + .arg(Util::kbytesToString( _tape->getSize() )) + .arg(used * 100 / _tape->getSize() ); + } else { + tmp = Util::kbytesToString( used ); + } + _spaceUsed->setText( tmp ); +} + +bool TapeInfoWidget::isModified() +{ + if ( _tape->getName() != _tapeName->text() ) { + return TRUE; + } + + int size = (int)KGlobal::locale()->readNumber( _tapeSize->text() ); + if ( _tapeSizeUnits->currentItem() == 0 ) { + // MB + size *= 1024; + } else { + // GB + size *= 1024*1024; + } + + return _tape->getSize() != size; +} + +void TapeInfoWidget::slotTextChanged( const QString & ) +{ + if ( !_tape ) { + return; + } + + _apply->setEnabled( isModified() ); +} + +void TapeInfoWidget::slotActivated( int ) +{ + if ( !_tape ) { + return; + } + + _apply->setEnabled( isModified() ); +} + +void TapeInfoWidget::slotApply() +{ + if ( !_tape ) { + return; + } + + int size = (int)KGlobal::locale()->readNumber( _tapeSize->text() ); + if ( _tapeSizeUnits->currentItem() == 0 ) { + // MB + size *= 1024; + } else { + // GB + size *= 1024*1024; + } + + if ( _tape->getName() != _tapeName->text() ) { + _tape->setName( _tapeName->text() ); + } + + if ( size != _tape->getSize() ) { + _tape->setSize( size ); + } + + _apply->setEnabled( FALSE ); +} diff --git a/kdat/TapeInfoWidget.h b/kdat/TapeInfoWidget.h new file mode 100644 index 0000000..4b3b3a2 --- /dev/null +++ b/kdat/TapeInfoWidget.h @@ -0,0 +1,71 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _TapeInfoWidget_h_ +#define _TapeInfoWidget_h_ + +#include <qwidget.h> + +class QComboBox; +class QLabel; +class QLineEdit; +class QPushButton; + +class Tape; + +/** + * @short Display/edit information about a tape index. + */ +class TapeInfoWidget : public QWidget { + Q_OBJECT + Tape* _tape; + QLineEdit* _tapeName; + QLineEdit* _tapeSize; + QComboBox* _tapeSizeUnits; + QLabel* _tapeID; + QLabel* _ctime; + QLabel* _mtime; + QLabel* _archiveCount; + QLabel* _spaceUsed; + QPushButton* _apply; + + bool isModified(); +private slots: + void slotTextChanged( const QString & text ); + void slotActivated( int index ); + void slotApply(); +public: + /** + * Create a new tape info widget. + */ + TapeInfoWidget( QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the tape info widget. + */ + ~TapeInfoWidget(); + + /** + * Change the tape index that the widget displays/edits. + * + * @param tape The new tape index to display/edit. + */ + void setTape( Tape* tape ); +}; + +#endif diff --git a/kdat/TapeManager.cpp b/kdat/TapeManager.cpp new file mode 100644 index 0000000..5de218e --- /dev/null +++ b/kdat/TapeManager.cpp @@ -0,0 +1,151 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <stdlib.h> +#include <unistd.h> + +#include <qdir.h> +#include <qregexp.h> +#include <qfile.h> + +#include <kglobal.h> +#include <kstandarddirs.h> + +#include "TapeManager.h" + +#include "TapeManager.moc" + +TapeManager::TapeManager() + : _mountedTape( 0 ) +{ + _tapes.setAutoDelete( TRUE ); + + // Get a list of all available tape indexes. + QStringList relList; + + // Fix 2002-01-24 c/o RG. The problem was: "Tape Index is empty, but I did + // just a few minutes ago a 1st backup onto the the tape." + // (void) KGlobal::dirs()->findAllResources( "appdata", ".*:[0-9]+", false, true, relList); + (void) KGlobal::dirs()->findAllResources( "appdata", "*:[0-9]*", false, true, relList); + + for(QStringList::Iterator it = relList.begin(); + it != relList.end(); + it++) + { + QString fn = *it; + // Convert to outdated QStringList :-) + _tapeIDs.append( QFile::encodeName(fn) ); + } +} + +TapeManager::~TapeManager() +{ +} + +TapeManager* TapeManager::_instance = 0; + +TapeManager* TapeManager::instance() +{ + if ( _instance == 0 ) { + _instance = new TapeManager(); + } + + return _instance; +} + +const QStringList& TapeManager::getTapeIDs() +{ + return _tapeIDs; +} + +Tape* TapeManager::findTape( const QString & id ) +{ + /* 2002-01-26 LEW */ + // printf("Contents of _tapeIDs: %d entries\n", _tapes.count()); + // for ( QStringList::Iterator it = _tapeIDs.begin(); it != _tapeIDs.end(); ++it ) { + // printf("%s\n", (*it).latin1()); + // } + // printf("\n"); + + // QDictIterator<Tape> it( _tapes ); + // printf("Contents of _tapes: %d entries\n", _tapes.count()); + // for( ; it.current(); ++it ) + // printf("index ?: %s (%d)\n", it.current()->getName().latin1(), + // it.current()->getCTime()); + // printf("\n"); + /* 2002-01-26 LEW */ + + Tape* tape = _tapes[ id ]; + + if ( !tape ) { + tape = new Tape( id.ascii() ); + _tapes.insert( tape->getID(), tape ); + } + + return tape; +} + +void TapeManager::addTape( Tape* tape ) +{ + Tape* old = _tapes[ tape->getID() ]; + if ( old ) { + removeTape( old ); + } + + _tapeIDs.append( tape->getID() ); + _tapes.insert( tape->getID(), tape ); + + emit sigTapeAdded( tape ); +} + +void TapeManager::removeTape( Tape* tape ) +{ + emit sigTapeRemoved( tape ); + + // Remove the index file. + QString filename = locateLocal( "appdata", tape->getID() ); + + unlink( QFile::encodeName(filename) ); + + _tapeIDs.remove( tape->getID() ); + _tapes.remove( tape->getID() ); +} + +void TapeManager::tapeModified( Tape* tape ) +{ + emit sigTapeModified( tape ); +} + +void TapeManager::mountTape( Tape* tape ) +{ + _mountedTape = tape; + emit sigTapeMounted(); +} + +void TapeManager::unmountTape() +{ + emit sigTapeUnmounted(); + _mountedTape = 0; +} + +Tape* TapeManager::getMountedTape() +{ + return _mountedTape; +} + + diff --git a/kdat/TapeManager.h b/kdat/TapeManager.h new file mode 100644 index 0000000..bc91aab --- /dev/null +++ b/kdat/TapeManager.h @@ -0,0 +1,154 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _TapeManager_h_ +#define _TapeManager_h_ + +#include <qdict.h> +#include <qobject.h> +#include <qstrlist.h> + +#include "Tape.h" + +/** + * @short Control access to the set of tape indexes. + * + * Each user has a set of tape indexes that are stored under + * <TT>$HOME/.kdat/</TT>. This class provides a single point of access for + * reading and writing these index files. + * + * Other objects can register to be notified when a tape index is added or + * removed, and when a tape index is modified. + * + * A reference to the index of the currently mounted tape is maintained, and + * other objects can register to be notified when a tape is mounted and + * unmounted. + * + * The TapeManager follows the Singleton pattern. + */ +class TapeManager : public QObject { + Q_OBJECT + + static TapeManager* _instance; + + QDict<Tape> _tapes; + QStringList _tapeIDs; + Tape* _mountedTape; + + TapeManager(); +public: + ~TapeManager(); + + /** + * All access to the TapeManager goes through this method. + * + * @return a pointer to the single instance of the TapeManager. + */ + static TapeManager* instance(); + + /** + * Get the list of all known tape IDs. + * + * @return a QStringList containing the tape IDs. + */ + const QStringList& getTapeIDs(); + + /** + * Retrieve the index for a tape. + * + * @param id the ID of the tape. + * @return the tape's index. + */ + Tape* findTape( const QString & id ); + + /** + * Add a new tape index. + * + * @param tape a pointer to the new tape index. + */ + void addTape( Tape* tape ); + + /** + * Remove a tape index. The tape index is removed from memory and from + * disk. A signal is emitted before the tape is actually removed. + * + * @param tape a pointer to the tape index to remove. + */ + void removeTape( Tape* tape ); + + /** + * Notify anyone who cares that the tape index has been modified. + * + * @param tape a pointer to the tape index that was modified. + */ + void tapeModified( Tape* tape ); + + /** + * Call this method whenever a tape is first mounted. + * + * @param tape a pointer to the newly mounted tape's index. + */ + void mountTape( Tape* tape ); + + /** + * Call this method whenever the current tape is about to be unmounted. + */ + void unmountTape(); + + /** + * Get a handle on the currently mounted tape's index. + * + * @return a pointer to the mounted tape's index, or NULL if no tape is + * mounted. + */ + Tape* getMountedTape(); +signals: + /** + * Emitted after a new tape index is created. + * + * @param tape a pointer to the new tape index. + */ + void sigTapeAdded( Tape* tape ); + + /** + * Emitted before a tape index is destroyed. This signal is emitted + * immediately before the tape index is deleted. + * + * @param tape a pointer to the tape index that is about to be destroyed. + */ + void sigTapeRemoved( Tape* tape ); + + /** + * Emitted after a tape index has been changed in some way. + * + * @param tape a pointer to the tape index that has been modified. + */ + void sigTapeModified( Tape* tape ); + + /** + * Emitted after a tape has been mounted. + */ + void sigTapeMounted(); + + /** + * Emitted just before the current tape is unmounted. + */ + void sigTapeUnmounted(); +}; + +#endif diff --git a/kdat/TarParser.cpp b/kdat/TarParser.cpp new file mode 100644 index 0000000..4529d64 --- /dev/null +++ b/kdat/TarParser.cpp @@ -0,0 +1,117 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "TarParser.h" + +#include "TarParser.moc" + +TarParser::TarParser() + : _bufIdx( 0 ), + _blocksToSkip( 0 ), + _record( 0 ), + _longname( FALSE ), + _extended( FALSE ), + _archnameIdx( 0 ) +{ +} + +void TarParser::slotData( const char * data, int length ) +{ + for ( int i = 0; i < length; i++ ) { + if ( _bufIdx >= 512 ) { + if ( _blocksToSkip > 0 ) { + if ( _extended ) { + record* rec = (record*)_buf; + _extended = rec->ext_hdr.isextended; + } else { + _blocksToSkip--; + if ( _longname ) { + memcpy( _archname + _archnameIdx, _buf, 512 ); + _archnameIdx += 512; + } + } + } else { + parseTarBlock(); + } + _bufIdx = 0; + _record++; + } + _buf[_bufIdx++] = data[i]; + } +} + +int TarParser::parseOctal( const char* buf, int length ) +{ + int val = 0; + + for ( int i = 0; i < length; i++ ) { + val *= 8; + if ( ( buf[i] >= '1' ) && ( buf[i] <= '7' ) ) { + val += buf[i] - '0'; + } + } + + return val; +} + +void TarParser::parseTarBlock() +{ + record* rec = (record*)_buf; + +#if 0 + printf( "----- parsing tar block -----\n" ); + printf( "arch_name = '%s'\n", rec->header.arch_name ); + printf( "mode = '%s'\n", rec->header.mode ); + printf( "uid = %d\n", parseOctal( rec->header.uid, 6 ) ); + printf( "gid = %d\n", parseOctal( rec->header.gid, 6 ) ); + printf( "size = %d\n", parseOctal( rec->header.size, 11 ) ); + printf( "mtime = %d\n", parseOctal( rec->header.mtime, 11 ) ); + printf( "chksum = '%s'\n", rec->header.chksum ); + printf( "linkflag = '%c'\n", rec->header.linkflag ); + printf( "arch_linkname = '%s'\n", rec->header.arch_linkname ); + printf( "magic = '%s'\n", rec->header.magic ); + printf( "uname = '%s'\n", rec->header.uname ); + printf( "gname = '%s'\n", rec->header.gname ); + printf( "devmajor = %d\n", parseOctal( rec->header.devmajor, 8 ) ); + printf( "devminor = %d\n", parseOctal( rec->header.devminor, 8 ) ); + printf( "atime = %d\n", parseOctal( rec->header.atime, 11 ) ); + printf( "ctime = %d\n", parseOctal( rec->header.ctime, 11 ) ); + printf( "offset = %d\n", parseOctal( rec->header.offset, 11 ) ); + printf( "longnames = '%s'\n", rec->header.longnames ); + printf( "isextended = %d\n", rec->header.isextended ); + printf( "realsize = %d\n", parseOctal( rec->header.realsize, 11 ) ); +#endif + + if ( rec->header.magic[0] != 0 ) { + _blocksToSkip = ( parseOctal( rec->header.size, 11 ) + 511 ) / 512; + _extended = rec->header.isextended; + + if ( rec->header.linkflag == LF_LONGNAME ) { + // The actual file name is stored in the next _blocksToSkip blocks of the tar-file. + _longname = TRUE; + _archnameIdx = 0; + } else { + if ( _longname ) { + _longname = FALSE; + emit sigEntry( _archname, parseOctal( rec->header.size, 11 ), parseOctal( rec->header.mtime, 11 ), _record ); + } else { + emit sigEntry( rec->header.arch_name, parseOctal( rec->header.size, 11 ), parseOctal( rec->header.mtime, 11 ), _record ); + } + } + } +} diff --git a/kdat/TarParser.h b/kdat/TarParser.h new file mode 100644 index 0000000..b43fd35 --- /dev/null +++ b/kdat/TarParser.h @@ -0,0 +1,207 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _TarParser_h_ +#define _TarParser_h_ + +#include <qobject.h> + +/* Standard Archive Format - Standard TAR - USTAR. */ + +/* Header block on tape. + + We use traditional DP naming conventions here. A "block" is a big chunk + of stuff that we do I/O on. A "record" is a piece of info that we care + about. Typically many "record"s fit into a "block". */ + +#define RECORDSIZE 512 +#define NAMSIZ 100 +#define TUNMLEN 32 +#define TGNMLEN 32 +#define SPARSE_EXT_HDR 21 +#define SPARSE_IN_HDR 4 + +struct sparse +{ + char offset[12]; + char numbytes[12]; +}; + +union record +{ + char charptr[RECORDSIZE]; + + struct header + { + char arch_name[NAMSIZ]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char linkflag; + char arch_linkname[NAMSIZ]; + char magic[8]; + char uname[TUNMLEN]; + char gname[TGNMLEN]; + char devmajor[8]; + char devminor[8]; + + /* The following fields were added for GNU and are not standard. */ + + char atime[12]; + char ctime[12]; + char offset[12]; + char longnames[4]; + /* Some compilers would insert the pad themselves, so pad was + once autoconfigured. It is simpler to always insert it! */ + char pad; + struct sparse sp[SPARSE_IN_HDR]; + char isextended; + char realsize[12]; /* true size of the sparse file */ +#if 0 + char ending_blanks[12]; /* number of nulls at the end of the file, + if any */ +#endif + } + header; + + struct extended_header + { + struct sparse sp[21]; + char isextended; + } + ext_hdr; +}; + +/* The checksum field is filled with this while the checksum is computed. */ +#define CHKBLANKS " " /* 8 blanks, no null */ + +/* The magic field is filled with this value if uname and gname are valid, + marking the archive as being in standard POSIX format (though GNU tar + itself is not POSIX conforming). */ +#define TMAGIC "ustar " /* 7 chars and a null */ + +/* The magic field is filled with this if this is a GNU format dump entry. + But I suspect this is not true anymore. */ +#define GNUMAGIC "GNUtar " /* 7 chars and a null */ + +/* The linkflag defines the type of file. */ +#define LF_OLDNORMAL '\0' /* normal disk file, Unix compat */ +#define LF_NORMAL '0' /* normal disk file */ +#define LF_LINK '1' /* link to previously dumped file */ +#define LF_SYMLINK '2' /* symbolic link */ +#define LF_CHR '3' /* character special file */ +#define LF_BLK '4' /* block special file */ +#define LF_DIR '5' /* directory */ +#define LF_FIFO '6' /* FIFO special file */ +#define LF_CONTIG '7' /* contiguous file */ +/* Further link types may be defined later. */ + +/* Note that the standards committee allows only capital A through + capital Z for user-defined expansion. This means that defining + something as, say '8' is a *bad* idea. */ + +/* This is a dir entry that contains the names of files that were in the + dir at the time the dump was made. */ +#define LF_DUMPDIR 'D' + +/* Identifies the NEXT file on the tape as having a long linkname. */ +#define LF_LONGLINK 'K' + +/* Identifies the NEXT file on the tape as having a long name. */ +#define LF_LONGNAME 'L' + +/* This is the continuation of a file that began on another volume. */ +#define LF_MULTIVOL 'M' + +/* For storing filenames that didn't fit in 100 characters. */ +#define LF_NAMES 'N' + +/* This is for sparse files. */ +#define LF_SPARSE 'S' + +/* This file is a tape/volume header. Ignore it on extraction. */ +#define LF_VOLHDR 'V' + +#if 0 +/* The following two blocks of #define's are unused in GNU tar. */ + +/* Bits used in the mode field - values in octal */ +#define TSUID 04000 /* set UID on execution */ +#define TSGID 02000 /* set GID on execution */ +#define TSVTX 01000 /* save text (sticky bit) */ + +/* File permissions */ +#define TUREAD 00400 /* read by owner */ +#define TUWRITE 00200 /* write by owner */ +#define TUEXEC 00100 /* execute/search by owner */ +#define TGREAD 00040 /* read by group */ +#define TGWRITE 00020 /* write by group */ +#define TGEXEC 00010 /* execute/search by group */ +#define TOREAD 00004 /* read by other */ +#define TOWRITE 00002 /* write by other */ +#define TOEXEC 00001 /* execute/search by other */ + +#endif + +/* End of Standard Archive Format description. */ + +/** + * @short A parser for GNU tar archives. + */ +class TarParser : public QObject { + Q_OBJECT + char _buf[512]; + int _bufIdx; + int _blocksToSkip; + int _record; + bool _longname; + bool _extended; + char _archname[8192]; + int _archnameIdx; + + int parseOctal( const char* data, int length ); + void parseTarBlock(); +public: + /** + * Create a new GNU tar archive parser. + */ + TarParser(); +public slots: + /** + * This slot receives raw data that represents a GNU tar archive. + * + * @param data An array of data bytes. + * @param length The length of the data array. + */ + void slotData( const char * data, int length ); +signals: + /** + * This signal is emitted whenever a file entry is parsed from the archive. + * + * @param name The name of the the file. + * @param size The size of the file, in bytes. + * @param mtime The last modification time for the file. + * @param record The tar record that this file begins on. + */ + void sigEntry( const QString & name, int size, int mtime, int record ); +}; + +#endif diff --git a/kdat/Util.cpp b/kdat/Util.cpp new file mode 100644 index 0000000..7fb06a3 --- /dev/null +++ b/kdat/Util.cpp @@ -0,0 +1,71 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <qstringlist.h> +#include <kglobal.h> +#include <klocale.h> + +#include "Util.h" + +QString Util::bytesToString( uint bytes ) +{ + return KGlobal::locale()->formatNumber(bytes, 0); +} + +QString Util::kbytesToString( uint kbytes ) +{ + return KGlobal::locale()->formatNumber(kbytes, 0) + 'k'; +} + +QString Util::longestCommonPath( const QStringList& files ) +{ + QStringList filesTmp = files; + QStringList::Iterator i = filesTmp.begin(); + + uint minLen = (*i).length(); + for ( ; i != filesTmp.end(); ++i ) { + if ( minLen > (*i).length() ) { + minLen = (*i).length(); + } + } + uint j; + for ( j = 0; j < minLen; j++ ) { + i = filesTmp.begin(); + QString first = *i; + for ( ; i != filesTmp.end(); ++i ) { + if ( first.left(j) != (*i).left(j) ) { + // Prefixes are NOT the same. + break; + } + } + if ( i != filesTmp.end() ) { + // The common prefix is 0 to j-1, inclusive. + break; + } + } + i = filesTmp.begin(); + QString prefix = *i; + int idx = prefix.findRev( '/', j ); + if ( idx > -1 ) { + prefix = prefix.left( prefix.findRev( '/', j ) ); + } else { + prefix = ""; + } + + return prefix; +} diff --git a/kdat/Util.h b/kdat/Util.h new file mode 100644 index 0000000..bf761e5 --- /dev/null +++ b/kdat/Util.h @@ -0,0 +1,47 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _Util_h_ +#define _Util_h_ + +#include <qstring.h> + +/** + * @short A collection of common useful methods. + */ +class Util { +public: + /** + * Format the given number of bytes into a comma-separated string of + * digits. + */ + static QString bytesToString( uint bytes ); + + /** + * Format the given number of kilobytes into a comma-separated string of + * digits, followed by a 'k'. + */ + static QString kbytesToString( uint kbytes ); + + /** + * Find the longest common path prefix for a list of files. + */ + static QString longestCommonPath( const QStringList& files ); +}; + +#endif diff --git a/kdat/VerifyDlg.cpp b/kdat/VerifyDlg.cpp new file mode 100644 index 0000000..6714322 --- /dev/null +++ b/kdat/VerifyDlg.cpp @@ -0,0 +1,415 @@ +// $Id$ +// +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> +#include <stdio.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qfile.h> + +#include <kapplication.h> +#include <kprocess.h> +#include <klocale.h> +#include <kdebug.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "LoggerWidget.h" +#include "Options.h" +#include "VerifyDlg.h" +#include "TapeDrive.h" +#include "Util.h" + +#include "VerifyDlg.moc" + + +VerifyDlg::VerifyDlg( const QString & workingDir, int fileno, const RangeList& ranges, + bool restore, QWidget* parent, const char* name ) + : QDialog( parent, name, TRUE ), + _restore( restore ), + _proc( NULL ), + _workingDir( workingDir ), + _fileno( fileno ), + _ranges( ranges ), + _totalKBytes( 0.0 ), + _fileCount( 0 ), + _wroteStdin( TRUE ), + _aborted( FALSE ), + _done( FALSE ) +{ + // Calculate size of verify. + QPtrListIterator<Range> i( _ranges.getRanges() ); + _archiveSize = 0; + for ( ; i.current(); ++i ) { + _archiveSize += i.current()->getEnd() - i.current()->getStart(); + } + _archiveSize = ( _archiveSize + 1 ) / 2; + + if ( _restore ) { + setCaption( i18n( "KDat: Restore" ) ); + setIconText( i18n( "KDat: Restore" ) ); + } else { + setCaption( i18n( "KDat: Verify" ) ); + setIconText( i18n( "KDat: Verify" ) ); + } + + resize( 500, 300 ); + + /* 2002-01-26 LEW: "Time remaining" was cut off in mid-"g" + in BackupDlg.cpp, + so we'll provide that plus some space beyond it. */ + // const int labelWidth = 96; + const int labelWidth = 110; + + QFrame* f1 = new QFrame( this ); + f1->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + QFrame* f2 = new QFrame( this ); + f2->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + QLabel* lbl1 = new QLabel( i18n( "Elapsed time:" ), f1 ); + lbl1->setFixedSize( labelWidth, lbl1->sizeHint().height() ); + + _elapsedTime = new QLabel( i18n( "00:00:00" ), f1 ); + _elapsedTime->setFixedHeight( _elapsedTime->sizeHint().height() ); + + QLabel* lbl2 = new QLabel( i18n( "Time remaining:" ), f2 ); + lbl2->setFixedSize( labelWidth, lbl2->sizeHint().height() ); + + _timeRemaining = new QLabel( i18n( "00:00:00" ), f2 ); + _timeRemaining->setFixedHeight( _timeRemaining->sizeHint().height() ); + + QLabel* lbl3 = new QLabel( i18n( "Total KB:" ), f1 ); + lbl3->setFixedSize( labelWidth, lbl3->sizeHint().height() ); + + QLabel* totalKbytes = new QLabel( Util::kbytesToString( _archiveSize ), f1 ); + totalKbytes->setFixedHeight( totalKbytes->sizeHint().height() ); + + QLabel* lbl4 = new QLabel( i18n( "KB read:" ), f2 ); + lbl4->setFixedSize( labelWidth, lbl4->sizeHint().height() ); + + _kbytesRead = new QLabel( i18n( "0KB" ), f2 ); + _kbytesRead->setFixedHeight( _kbytesRead->sizeHint().height() ); + + QLabel* lbl5 = new QLabel( i18n( "Transfer rate:" ), f1 ); + lbl5->setFixedSize( labelWidth, lbl5->sizeHint().height() ); + + _transferRate = new QLabel( i18n( "0KB/min" ), f1 ); + _transferRate->setFixedHeight( _transferRate->sizeHint().height() ); + + QLabel* lbl6; + if ( _restore ) { + lbl6 = new QLabel( i18n( "Files:" ), f2 ); + lbl6->setFixedSize( labelWidth, lbl6->sizeHint().height() ); + } else { + lbl6 = new QLabel( i18n( "Differences:" ), f2 ); + lbl6->setFixedSize( labelWidth, lbl6->sizeHint().height() ); + } + + _files = new QLabel( "0", f2 ); + _files->setFixedHeight( _files->sizeHint().height() ); + + if ( _restore ) { + _log = new LoggerWidget( i18n( "Restore log:" ), this ); + } else { + _log = new LoggerWidget( i18n( "Verify log:" ), this ); + } + + _ok = new KPushButton( KStdGuiItem::ok(), this ); + _ok->setFixedSize( 80, _ok->sizeHint().height() ); + connect( _ok, SIGNAL( clicked() ), this, SLOT( slotOK() ) ); + _ok->setEnabled( FALSE ); + + _save = new QPushButton( i18n( "&Save Log..." ), this ); + _save->setFixedSize( 80, _save->sizeHint().height() ); + connect( _save, SIGNAL( clicked() ), _log, SLOT( save() ) ); + _save->setEnabled( FALSE ); + + _abort = new QPushButton( i18n( "&Abort" ), this ); + _abort->setFixedSize( 80, _abort->sizeHint().height() ); + connect( _abort, SIGNAL( clicked() ), this, SLOT( slotAbort() ) ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 8, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addStrut( 3 * lbl1->height() + 16 ); + l1_1->addWidget( f1 ); + l1_1->addWidget( f2 ); + + QVBoxLayout* l1_1_1 = new QVBoxLayout( f1, 4, 4 ); + + QHBoxLayout* l1_1_1_1 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_1 ); + l1_1_1_1->addWidget( lbl1 ); + l1_1_1_1->addWidget( _elapsedTime, 1 ); + + QHBoxLayout* l1_1_1_2 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_2 ); + l1_1_1_2->addWidget( lbl3 ); + l1_1_1_2->addWidget( totalKbytes, 1 ); + + QHBoxLayout* l1_1_1_3 = new QHBoxLayout(); + l1_1_1->addLayout( l1_1_1_3 ); + l1_1_1_3->addWidget( lbl5 ); + l1_1_1_3->addWidget( _transferRate, 1 ); + + QVBoxLayout* l1_1_2 = new QVBoxLayout( f2, 4, 4 ); + + QHBoxLayout* l1_1_2_1 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_1 ); + l1_1_2_1->addWidget( lbl2 ); + l1_1_2_1->addWidget( _timeRemaining, 1 ); + + QHBoxLayout* l1_1_2_2 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_2 ); + l1_1_2_2->addWidget( lbl4 ); + l1_1_2_2->addWidget( _kbytesRead, 1 ); + + QHBoxLayout* l1_1_2_3 = new QHBoxLayout(); + l1_1_2->addLayout( l1_1_2_3 ); + l1_1_2_3->addWidget( lbl6 ); + l1_1_2_3->addWidget( _files, 1 ); + + l1->addWidget( _log, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2 ); + l1_2->addStretch( 1 ); + l1_2->addWidget( _ok ); + l1_2->addWidget( _save ); + l1_2->addWidget( _abort ); +} + +VerifyDlg::~VerifyDlg() +{ +} + +void VerifyDlg::show() +{ + chdir( QFile::encodeName(_workingDir) ); + + _proc = new KProcess(); + //_proc->setExecutable( Options::instance()->getTarCommand() ); + *_proc << Options::instance()->getTarCommand(); + if ( _restore ) { + *_proc << "-xvf" << "-"; + } else { + *_proc << "-dvf" << "-"; + } + + connect( _proc, SIGNAL( processExited( KProcess* ) ), this, SLOT( slotProcessExited( KProcess* ) ) ); + connect( _proc, SIGNAL( receivedStdout( KProcess*, char*, int ) ), this, SLOT( slotStdout( KProcess*, char*, int ) ) ); + connect( _proc, SIGNAL( wroteStdin( KProcess* ) ), this, SLOT( slotWroteStdin( KProcess* ) ) ); + + _startTime = time( NULL ); + startTimer( 100 ); + + _proc->start( KProcess::NotifyOnExit, KProcess::All ); + + QDialog::show(); +} + +void VerifyDlg::slotProcessExited( KProcess* ) +{ + killTimers(); + delete _proc; + + // Set this, or we get caught in a loop. + _done = TRUE; + + _ok->setEnabled( TRUE ); + _ok->setDefault( TRUE ); + _save->setEnabled( TRUE ); + _abort->setEnabled( FALSE ); +} + +void VerifyDlg::slotStdout( KProcess*, char* buf, int len ) +{ + QString data; + data.replace( 0, len, buf ); + /* 2002-02-23 RG */ + // data[len] = '\0'; + data.truncate( len ); + /* 2002-02-23 RG */ + _leftover += data; + + int newlineIndex; + while ( ( newlineIndex = _leftover.find( '\n' ) ) > -1 ) { + _log->append( _leftover.left( newlineIndex ) ); + + // Count differences. + if ( _restore ) { + _fileCount++; + } else { + int len = _lastFileName.length(); + if ( len > 0 ) { + if ( _lastFileName == _leftover.left( len ) ) { + if ( ( _leftover[len] == ':' ) && ( _leftover[len+1] == ' ' ) ) { + _fileCount++; + } else { + _lastFileName = _leftover.left( newlineIndex ); + } + } else { + _lastFileName = _leftover.left( newlineIndex ); + } + } else { + _lastFileName = _leftover.left( newlineIndex ); + } + } + + _leftover.remove( 0, newlineIndex + 1 ); + QString tmp; + tmp.setNum( _fileCount ); + _files->setText( tmp ); + } +} + +void VerifyDlg::slotWroteStdin( KProcess* ) +{ + _wroteStdin = TRUE; +} + +void VerifyDlg::slotOK() +{ + if ( _aborted ) { + reject(); + } else { + accept(); + } +} + +void VerifyDlg::slotAbort() +{ + _aborted = TRUE; +} + +void VerifyDlg::timerEvent( QTimerEvent* ) +{ + killTimers(); + + int oldElapsed = 0; + + int bytesToRead; + int count; + char *buf = new char[Options::instance()->getTapeBlockSize()]; + QPtrListIterator<Range> i( _ranges.getRanges() ); + for ( ; ( !_done ) && ( !_aborted ) && ( i.current() ); ++i ) { + // Move to the beginning of the next range. + TapeDrive::instance()->seek( _fileno, i.current()->getStart() ); + + /* 2002-01-30 LEW */ +#ifdef DEBUG + printf("Seeking to next range: %d-%d\n", i.current()->getStart(), i.current()->getEnd()); +#endif /* DEBUG */ + /* 2002-01-30 LEW */ + + // Read in the records and forward them to tar. + bytesToRead = ( i.current()->getEnd() - i.current()->getStart() ) * 512; + while ( bytesToRead > 0 ) { + count = TapeDrive::instance()->read( buf, bytesToRead > Options::instance()->getTapeBlockSize() ? Options::instance()->getTapeBlockSize() : bytesToRead ); + + if ( count == 0 ) { + // I hope this means end-of-file. Break out of the while loop, and process the next range. + /* 2002-01-30 LEW */ +#ifdef DEBUG + printf("VerifyDlg::timerEvent count==0, so I'm skipping to the next range\n"); +#endif /* DEBUG */ + /* 2002-01-30 LEW */ + break; + } + + if ( count < 0 ) { + kdError() << i18n( "failed while reading tape data.\n" ); + _proc->closeStdin(); + delete [] buf; + return; + } + + while ( ( !_done ) && ( !_aborted ) && ( !_wroteStdin ) ) + KApplication::kApplication()->processEvents(); + if ( _done || _aborted ) { + /* 2002-01-30 LEW */ +#ifdef DEBUG + printf("VerifyDlg::timerEvent done/aborted, so I'm skipping to the next range\n"); +#endif /* DEBUG */ + /* 2002-01-30 LEW */ + break; + } + _wroteStdin = FALSE; + _proc->writeStdin( buf, count ); + bytesToRead -= count; + _totalKBytes += (float)count / 1024.0; + + // Update stats. + int elapsed = time( NULL ) - _startTime; + if ( elapsed > oldElapsed ) + { + updateStats(); + KApplication::kApplication()->processEvents(); + if ( _done || _aborted ) { + break; + } + oldElapsed = elapsed; + } + } + } + + // Update stats. + updateStats(); + + // Send an EOF block to tar. + memset( buf, 0, Options::instance()->getTapeBlockSize() ); + _proc->writeStdin( buf, Options::instance()->getTapeBlockSize() ); + + _proc->closeStdin(); + delete [] buf; +} + +void VerifyDlg::updateStats() +{ + QString str; + int elapsed = time( NULL ) - _startTime; + + str= QString::fromUtf8( QCString().sprintf( i18n( "%02d:%02d:%02d" ).utf8(), elapsed / 3600, elapsed / 60 % 60, elapsed % 60 ) ); + _elapsedTime->setText( str ); + + int remain = 0; + if ( (int)_totalKBytes > 0 ) { + remain = (int)(( (float)_archiveSize - _totalKBytes ) * (float)elapsed / _totalKBytes); + } + if ( remain < 0 ) { + remain = 0; + } + str = QString::fromUtf8( QCString().sprintf( i18n( "%02d:%02d:%02d" ).utf8(), remain / 3600, remain / 60 % 60, remain % 60 ) ); + _timeRemaining->setText( str ); + + str = Util::kbytesToString( (int)_totalKBytes ); + _kbytesRead->setText( str ); + + if ( elapsed > 0 ) { + str = i18n( "%1/min" ).arg(Util::kbytesToString( (int)_totalKBytes * 60 / elapsed ) ); + _transferRate->setText( str ); + } +} diff --git a/kdat/VerifyDlg.h b/kdat/VerifyDlg.h new file mode 100644 index 0000000..0d7648e --- /dev/null +++ b/kdat/VerifyDlg.h @@ -0,0 +1,94 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _VerifyDlg_h_ +#define _VerifyDlg_h_ + +#include <qdialog.h> +#include <qptrlist.h> + +#include "Range.h" + +class QLabel; +class QPushButton; + +class KProcess; + +class LoggerWidget; +class TapeDrive; + +/** + * @short Status dialog for verifying/restoring parts of an archive. + */ +class VerifyDlg : public QDialog { + Q_OBJECT + bool _restore; + KProcess* _proc; + QString _workingDir; + int _fileno; + const RangeList& _ranges; + QString _leftover; + QLabel* _elapsedTime; + QLabel* _timeRemaining; + QLabel* _kbytesRead; + QLabel* _transferRate; + QLabel* _files; + LoggerWidget* _log; + QPushButton* _ok; + QPushButton* _save; + QPushButton* _abort; + int _startTime; + float _totalKBytes; + int _fileCount; + int _archiveSize; + bool _wroteStdin; + bool _aborted; + bool _done; + QString _lastFileName; + + void updateStats(); +private slots: + void slotProcessExited( KProcess* proc ); + void slotStdout( KProcess* proc, char* buf, int len ); + void slotWroteStdin( KProcess* proc ); + void slotOK(); + void slotAbort(); +protected: + void show(); + void timerEvent( QTimerEvent* e ); +public: + /** + * Create a new verify/restore dialog. + * + * @param workingDir The directory to restore files to or verify files against. + * @param fileno The tape file number of the archive to verify/restore. + * @param ranges A list of tar block ranges to read. + * @param restore TRUE means restore, FALSE means verify. + * @param parent The parent widget for the dialog. + * @param name The name of this widget. + */ + VerifyDlg( const QString & workingDir, int fileno, const RangeList& ranges, + bool restore = FALSE, QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the verify/restore dialog. + */ + ~VerifyDlg(); +}; + +#endif diff --git a/kdat/VerifyOptDlg.cpp b/kdat/VerifyOptDlg.cpp new file mode 100644 index 0000000..3eb272d --- /dev/null +++ b/kdat/VerifyOptDlg.cpp @@ -0,0 +1,129 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <unistd.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistbox.h> + +#include <kapplication.h> +#include <kfiledialog.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include "VerifyOptDlg.h" +#include <klocale.h> + +#include "VerifyOptDlg.moc" + +VerifyOptDlg::VerifyOptDlg( const QString & def, const QStringList& files, bool restore, QWidget* parent, const char* name ) + : QDialog( parent, name, TRUE ), + _restore( restore ) +{ + if ( _restore ) { + setIconText( i18n( "KDat: Restore Options" ) ); + setCaption( i18n( "KDat: Restore Options" ) ); + } else { + setIconText( i18n( "KDat: Verify Options" ) ); + setCaption( i18n( "KDat: Verify Options" ) ); + } + + QLabel* lbl1; + if ( _restore ) { + lbl1 = new QLabel( i18n( "Restore to folder:" ), this ); + } else { + lbl1 = new QLabel( i18n( "Verify in folder:" ), this ); + } + lbl1->setFixedSize( lbl1->sizeHint() ); + + _entry = new QLineEdit( this ); + _entry->setText( def ); + _entry->setFixedHeight( _entry->sizeHint().height() ); + + QPushButton* browse = new QPushButton( i18n( "..." ), this ); + browse->setFixedSize( browse->sizeHint() ); + + QLabel* lbl2; + if ( _restore ) { + lbl2 = new QLabel( i18n( "Restore files:" ), this ); + } else { + lbl2 = new QLabel( i18n( "Verify files:" ), this ); + } + lbl2->setFixedHeight( lbl2->sizeHint().height() ); + + QListBox* fileList = new QListBox( this ); + fileList->insertStringList(files); + + KPushButton* ok = new KPushButton( KStdGuiItem::ok(), this ); + ok->setFixedSize( 80, ok->sizeHint().height() ); + KPushButton* cancel = new KPushButton( KStdGuiItem::cancel(), this ); + cancel->setFixedSize( 80, cancel->sizeHint().height() ); + + QVBoxLayout* l1 = new QVBoxLayout( this, 8, 4 ); + + QHBoxLayout* l1_1 = new QHBoxLayout(); + l1->addLayout( l1_1 ); + l1_1->addWidget( lbl1 ); + l1_1->addWidget( _entry, 1 ); + l1_1->addWidget( browse ); + + l1->addWidget( lbl2 ); + l1->addWidget( fileList, 1 ); + + QHBoxLayout* l1_2 = new QHBoxLayout(); + l1->addLayout( l1_2, 0 ); + l1_2->addStretch( 1 ); + l1_2->addWidget( ok, 0 ); + l1_2->addWidget( cancel, 0 ); + + resize( 400, 300 ); + + _entry->setFocus(); + _entry->selectAll(); + + connect( _entry, SIGNAL( returnPressed() ), this, SLOT( okClicked() ) ); + connect( browse, SIGNAL( clicked() ) , this, SLOT( slotBrowse() ) ); + connect( ok , SIGNAL( clicked() ) , this, SLOT( okClicked() ) ); + connect( cancel, SIGNAL( clicked() ) , this, SLOT( reject() ) ); +} + +VerifyOptDlg::~VerifyOptDlg() +{ +} + +void VerifyOptDlg::okClicked() +{ + _workingDir = _entry->text(); + accept(); +} + +QString VerifyOptDlg::getWorkingDirectory() +{ + return _workingDir; +} + +void VerifyOptDlg::slotBrowse() +{ + QString tmp; + tmp = KFileDialog::getExistingDirectory( _entry->text() ); + if ( tmp.length() ) { + _entry->setText( tmp ); + } +} diff --git a/kdat/VerifyOptDlg.h b/kdat/VerifyOptDlg.h new file mode 100644 index 0000000..f5023e4 --- /dev/null +++ b/kdat/VerifyOptDlg.h @@ -0,0 +1,66 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _VerifyOptDlg_h_ +#define _VerifyOptDlg_h_ + +#include <qdialog.h> +#include <qstring.h> + +class QLineEdit; + +/** + * @short Display/edit the information for performing a verify/restore operation. + * + * The verify and restore operations are similiar enough that their options + * have been combined into a single dialog. + */ +class VerifyOptDlg : public QDialog { + Q_OBJECT + bool _restore; + QString _workingDir; + QLineEdit* _entry; +private slots: + void okClicked(); + void slotBrowse(); +public: + /** + * Create a new verify/restore options dialog. + * + * @param def The default working directory. + * @param files The list of files that will be verified/restored. + * @param restore TRUE means we are doing a restore, FALSE means we are doing a verify. + * @param parent The parent widget for the dialog. + * @param name The name for the dialog. + */ + VerifyOptDlg( const QString & def, const QStringList& files, bool restore = FALSE, QWidget* parent = 0, const char* name = 0 ); + + /** + * Destroy the verify/restore options dialog. + */ + ~VerifyOptDlg(); + + /** + * Get thre working directory entered by the user. + * + * @return The working directory for the verify/restore operation. + */ + QString getWorkingDirectory(); +}; + +#endif diff --git a/kdat/configure.in.in b/kdat/configure.in.in new file mode 100644 index 0000000..9f09880 --- /dev/null +++ b/kdat/configure.in.in @@ -0,0 +1,10 @@ +AC_MSG_CHECKING(whether sys/mtio.h defines GMT_EOF and mtget has a member mt_gstat) +AC_LANG_C +AC_TRY_COMPILE( +[#include <sys/mtio.h>], +[struct mtget tapeStatus; GMT_EOF ( tapeStatus.mt_gstat );], +[AC_MSG_RESULT(yes)], +[ DO_NOT_COMPILE="$DO_NOT_COMPILE kdat" + AC_MSG_RESULT([no...Skipping kdat]) ] +) + diff --git a/kdat/kdat.desktop b/kdat/kdat.desktop new file mode 100644 index 0000000..3aeb7ac --- /dev/null +++ b/kdat/kdat.desktop @@ -0,0 +1,92 @@ +[Desktop Entry] +Name=KDat +Name[af]=Kdat +Name[ar]=برنامج KDat +Name[bn]=কে-ড্যাট +Name[eo]=Surbendigilo +Name[hi]=के-डेट +Name[mn]=КДЕ Дат +Name[ne]=केडीई ड्याट +Name[sv]=Kdat +Name[ta]=கேடாட் +Name[tg]=KДат +Name[th]=เทปสำรองข้อมูล - K +GenericName=Tape Backup Tool +GenericName[af]=Kaset Rugsteun Program +GenericName[ar]=أداة نسخ المحفوظات على الشريط +GenericName[az]=Kasetə Ehtiyat Alma Vasitəsi +GenericName[bg]=Лентов архиватор +GenericName[bn]=টেপ ব্যাকআপ সরঞ্জাম +GenericName[br]=Ostilh saveteiñ war seizenn +GenericName[bs]=Alat za backup na traku +GenericName[ca]=Utilitat per a còpies de seguretat en cintes +GenericName[cs]=Zálohování na pásku +GenericName[cy]=Erfyn Cefn-gopïo ar Dâp +GenericName[da]=Værktøj til sikkerhedskopiering på bånd +GenericName[de]=Dienstprogramm für Bandsicherungen +GenericName[el]=Εργαλείο λήψη αντιγράφων ασφαλείας σε κασέτα +GenericName[eo]=Bendsekurigilo +GenericName[es]=Copia de cintas +GenericName[et]=Varukoopiate haldamine (lindil) +GenericName[eu]=Zinta backup tresna +GenericName[fa]=ابزار پشتیبانی نوار +GenericName[fi]=Nauhavarmistustyökalu +GenericName[fo]=Trygdarritanaramboð +GenericName[fr]=Outil de sauvegarde sur bandes +GenericName[ga]=Uirlis Chúltaca Téipe +GenericName[gl]=Cópia de Seguridade en Cintas +GenericName[he]=כלי גיבוי באמצעות סרט +GenericName[hi]=टेप बैकअप औज़ार +GenericName[hr]=Alat za izradu sigurnosne kopije na traci +GenericName[hu]=Szalagos mentés +GenericName[is]=Spólu afritunartól +GenericName[it]=Strumento per la copia di sicurezza su nastro +GenericName[ja]=テープバックアップツール +GenericName[ka]=სარეზერვო ასლის ხელსაწყო +GenericName[kk]=Таспаға сақтық көшірмелеу құралы +GenericName[km]=ឧបករណ៍បម្រុងទុកកាសែត +GenericName[ko]=테이프 백업 도구 +GenericName[lt]=Juostinio archyvavimo priemonė +GenericName[lv]=Lentas Rezervēšanas Rīks +GenericName[mk]=Алатка за заштитни копии на ленти +GenericName[mn]=Соронзон хальсанд Хадгалагч +GenericName[ms]=Alat Ganti Sedia +GenericName[mt]=Għodda għal backup fuq tapes +GenericName[nb]=Verktøy for sikkerhetskopiering til magnetbånd +GenericName[nds]=Bandsekerheitkopiewarktüüch +GenericName[ne]=टेप जगेडा उपकरण +GenericName[nl]=Tape backup-programma +GenericName[nn]=Reservekopiering på band +GenericName[pa]=ਟੇਪ ਬੈਕਅੱਪ ਸੰਦ +GenericName[pl]=Narzędzie do kopii na taśmie +GenericName[pt]=Ferramenta de Salvaguarda em Fita +GenericName[pt_BR]=Ferramenta de Backup em Fita +GenericName[ro]=Utilitar de salvare pe benzi +GenericName[ru]=Утилита архивирования на ленту +GenericName[se]=Liigemáŋgen báddái +GenericName[sk]=Zálohovanie na pásku +GenericName[sl]=Orodje za tračne rezervne kopije +GenericName[sr]=Алат за архивирање на траку +GenericName[sr@Latn]=Alat za arhiviranje na traku +GenericName[sv]=Verktyg för bandsäkerhetskopiering +GenericName[ta]=நாடா காப்பக கருவி +GenericName[tg]=Утилита барои архив кардан ба тасма +GenericName[th]=เครื่องมือเทปสำรองข้อมูล +GenericName[tr]=Teyp Yedekleme Aracı +GenericName[uk]=Засіб резервування на стрічку +GenericName[ven]=Tshishumiswa tsha Theipi tshau Thusedza +GenericName[vi]=Công cụ sao lưu băng +GenericName[wa]=Usteye di copeyes di såvrité so bindes +GenericName[xh]=Isixhobo Sokuncedisa Iteyipu +GenericName[zh_CN]=磁带备份工具 +GenericName[zh_HK]=磁帶備份工具 +GenericName[zh_TW]=磁帶備份工具 +GenericName[zu]=Ithuluzi Yethephu Yokugcina umsebenzi uphephile +Exec=kdat %i %m -caption "%c" +Icon=kdat +Type=Application +X-KDE-StartupNotify=true +DocPath=kdat/index.html +Terminal=false +X-DCOP-ServiceType=Multi +Categories=Qt;KDE;System;X-KDE-More; diff --git a/kdat/kdat.h b/kdat/kdat.h new file mode 100644 index 0000000..ed897ee --- /dev/null +++ b/kdat/kdat.h @@ -0,0 +1,42 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef _kdat_h_ +#define _kdat_h_ + +// KDat program version. +#define KDAT_VERSION "2.0.2" + +// Magic string. +#define KDAT_MAGIC "KDatMAGIC" +#define KDAT_MAGIC_LENGTH 9 + +// Tape header version numbers. +#define KDAT_TAPE_HEADER_VERSION_1_0 1 +#define KDAT_TAPE_HEADER_VERSION KDAT_TAPE_HEADER_VERSION_1_0 + +// Index file version numbers. +#define KDAT_INDEX_FILE_VERSION_1_0 4 +#define KDAT_INDEX_FILE_VERSION KDAT_INDEX_FILE_VERSION_1_0 + +// Constants for tape file format. +#define MAX_TAPE_NAME_LEN 4096 +#define MAX_ARCHIVE_NAME_LEN 4096 +#define MAX_NUM_ARCHIVES 4096 + +#endif diff --git a/kdat/ktreeview.cpp b/kdat/ktreeview.cpp new file mode 100644 index 0000000..334a834 --- /dev/null +++ b/kdat/ktreeview.cpp @@ -0,0 +1,2074 @@ +/* + * $Id$ + * + * KTreeView implementation + * + * Copyright (C) 1997 Johannes Sixt + * + * based on KTreeList, which is + * Copyright (C) 1996 Keith Brown and KtSoft + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABLILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. You should have received a copy + * of the GNU General Public License along with this program; if not, write + * to the Free Software Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include <ktreeview.h> +#include "ktreeview.moc" +#include <qapplication.h> /* used for QApplication::closingDown() */ +#include <qkeycode.h> /* used for keyboard interface */ +#include <qpainter.h> /* used to paint items */ +#include <qscrollbar.h> +#include <qstyle.h> +#include <assert.h> + +/* + * ------------------------------------------------------------------- + * + * KTreeViewItem + * + * ------------------------------------------------------------------- + */ + +// constructor +KTreeViewItem::KTreeViewItem(const QString& theText) : + owner(0), + numChildren(0), + doExpandButton(true), + expanded(false), + delayedExpanding(false), + doTree(true), + doText(true), + child(0), + parent(0), + sibling(0), + deleteChildren(false) +{ + text = theText; +} + +// constructor that takes a pixmap +KTreeViewItem::KTreeViewItem(const QString& theText, + const QPixmap& thePixmap) : + owner(0), + numChildren(0), + doExpandButton(true), + expanded(false), + delayedExpanding(false), + doTree(true), + doText(true), + child(0), + parent(0), + sibling(0), + deleteChildren(false) +{ + text = theText; + pixmap = thePixmap; +} + +// destructor +KTreeViewItem::~KTreeViewItem() +{ + if (deleteChildren) { + // remove the children + KTreeViewItem* i = child; + while (i) { + KTreeViewItem* d = i; + i = i->getSibling(); + delete d; + } + } +} + +// appends a direct child +void KTreeViewItem::appendChild(KTreeViewItem* newChild) +{ + newChild->parent = this; + newChild->owner = owner; + if (!getChild()) { + child = newChild; + } + else { + KTreeViewItem* lastChild = getChild(); + while (lastChild->hasSibling()) { + lastChild = lastChild->getSibling(); + } + lastChild->sibling = newChild; + } + newChild->sibling = 0; + numChildren++; +} + +// returns the bounding rectangle of the item content (not including indent +// and branches) for hit testing +QRect KTreeViewItem::boundingRect(int indent) const +{ + const QFontMetrics& fm = owner->fontMetrics(); + int rectX = indent; + int rectY = 1; + int rectW = width(indent, fm) - rectX; + int rectH = height(fm) - 2; + return QRect(rectX, rectY, rectW, rectH); +} + +// returns the child item at the specified index +KTreeViewItem* KTreeViewItem::childAt(int index) const +{ + if (!hasChild()) + return 0; + KTreeViewItem* item = getChild(); + while (index > 0 && item != 0) { + item = item->getSibling(); + index--; + } + return item; +} + +// returns the number of children this item has +uint KTreeViewItem::childCount() const +{ + return numChildren; +} + +/* returns the index of the given child item in this item's branch */ +int KTreeViewItem::childIndex(KTreeViewItem* searched) const +{ + assert(searched != 0); + int index = 0; + KTreeViewItem* item = getChild(); + while (item != 0 && item != searched) { + item = item->getSibling(); + index++; + } + return item == 0 ? -1 : index; +} + +// indicates whether mouse press is in expand button +inline bool KTreeViewItem::expandButtonClicked(const QPoint& coord) const +{ + return expandButton.contains(coord); +} + +bool KTreeViewItem::mousePressEvent( const QPoint& ) +{ + return FALSE; +} + +// returns a pointer to first child item +KTreeViewItem* KTreeViewItem::getChild() const +{ + return child; +} + +// returns the parent of this item +KTreeViewItem* KTreeViewItem::getParent() const +{ + return parent; +} + +// returns reference to the item pixmap +const QPixmap& KTreeViewItem::getPixmap() const +{ + return pixmap; +} + +// returns the sibling below this item +KTreeViewItem* KTreeViewItem::getSibling() const +{ + return sibling; +} + +// returns a pointer to the item text +const QString& KTreeViewItem::getText() const +{ + return text; +} + +// indicates whether this item has any children +bool KTreeViewItem::hasChild() const +{ + return child != 0; +} + +// indicates whether this item has a parent +bool KTreeViewItem::hasParent() const +{ + return parent != 0; +} + +// indicates whether this item has a sibling below it +bool KTreeViewItem::hasSibling() const +{ + return sibling != 0; +} + +int KTreeViewItem::height() const +{ + assert(owner != 0); + return height(owner->fontMetrics()); +} + +// returns the maximum height of text and pixmap including margins +// or -1 if item is empty -- SHOULD NEVER BE -1! +int KTreeViewItem::height(const QFontMetrics& fm) const +{ + int maxHeight = pixmap.height(); + int textHeight = fm.ascent() + fm.leading(); + maxHeight = textHeight > maxHeight ? textHeight : maxHeight; + return maxHeight == 0 ? -1 : maxHeight + 8; +} + +// inserts child item at specified index in branch +void KTreeViewItem::insertChild(int index, KTreeViewItem* newChild) +{ + if (index < 0) { + appendChild(newChild); + return; + } + newChild->parent = this; + newChild->owner = owner; + if (index == 0) { + newChild->sibling = getChild(); + child = newChild; + } + else { + KTreeViewItem* prevItem = getChild(); + KTreeViewItem* nextItem = prevItem->getSibling(); + while (--index > 0 && nextItem) { + prevItem = nextItem; + nextItem = prevItem->getSibling(); + } + prevItem->sibling = newChild; + newChild->sibling = nextItem; + } + numChildren++; + + if ( owner ) { + owner->updateVisibleItems(); + owner->update(); + } +} + +// indicates whether this item is displayed expanded +// NOTE: a TRUE response does not necessarily indicate the item +// has any children +bool KTreeViewItem::isExpanded() const +{ + return expanded; +} + +// returns true if all ancestors are expanded +bool KTreeViewItem::isVisible() const +{ + for (KTreeViewItem* p = getParent(); p != 0; p = p->getParent()) { + if (!p->isExpanded()) + return false; + } + return true; +} + +// paint this item, including tree branches and expand button +void KTreeViewItem::paint(QPainter* p, int indent, const QColorGroup& cg, + bool highlighted) const +{ + assert(getParent() != 0); /* can't paint root item */ + + p->save(); + p->setPen(cg.text()); + p->setBackgroundColor(cg.base()); + + int cellHeight = height(p->fontMetrics()); + + if (doTree) { + paintTree(p, indent, cellHeight); + } + + /* + * If this item has at least one child and expand button drawing is + * enabled, set the rect for the expand button for later mouse press + * bounds checking, and draw the button. + */ + if (doExpandButton && (child || delayedExpanding)) { + paintExpandButton(p, indent, cellHeight); + } + + // now draw the item pixmap and text, if applicable + int pixmapX = indent; + int pixmapY = (cellHeight - pixmap.height()) / 2; + p->drawPixmap(pixmapX, pixmapY, pixmap); + + if (doText) { + paintText(p, indent, cellHeight, cg, highlighted); + } + p->restore(); +} + +void KTreeViewItem::paintExpandButton(QPainter* p, int indent, int cellHeight) const +{ + int parentLeaderX = indent - (22 / 2); + int cellCenterY = cellHeight / 2; + + expandButton.setRect(parentLeaderX - 4, cellCenterY - 4, 9, 9); + p->setBrush(Qt::white); + p->drawRect(expandButton); + if (expanded) { + p->drawLine(parentLeaderX - 2, cellCenterY, + parentLeaderX + 2, cellCenterY); + } else { + p->drawLine(parentLeaderX - 2, cellCenterY, + parentLeaderX + 2, cellCenterY); + p->drawLine(parentLeaderX, cellCenterY - 2, + parentLeaderX, cellCenterY + 2); + } + p->setBrush(Qt::NoBrush); +} + +// paint the highlight +void KTreeViewItem::paintHighlight(QPainter* p, int indent, const QColorGroup& colorGroup, + bool hasFocus, Qt::GUIStyle style) const +{ + QColor fc; + if (style == Qt::WindowsStyle) + fc = Qt::darkBlue; /* hardcoded in Qt */ + else + fc = colorGroup.text(); + QRect textRect = textBoundingRect(indent); + int t,l,b,r; + textRect.coords(&l, &t, &r, &b); + p->fillRect(textRect, fc); /* draw highlight background */ + if (hasFocus) { + if(style == Qt::WindowsStyle) { /* draw Windows style highlight */ + textRect.setCoords(l - 1, t - 1, r + 1, b + 1); + p->setPen(QPen(Qt::yellow, 0, Qt::DotLine)); + p->setBackgroundColor(fc); + p->setBackgroundMode(Qt::OpaqueMode); + p->drawRect(textRect); + textRect.setCoords(l - 2, t - 2, r + 2, b + 2); + p->setPen(fc); + p->drawRect(textRect); + } + else { /* draw Motif style highlight */ + textRect.setCoords(l - 2, t - 2, r + 2, b + 2); + p->drawRect(textRect); + } + } +} + +// draw the text, highlighted if requested +void KTreeViewItem::paintText(QPainter* p, int indent, int cellHeight, + const QColorGroup& cg, bool highlighted) const +{ + int textX = indent + pixmap.width() + 4; + int textY = cellHeight - ((cellHeight - p->fontMetrics().ascent() - + p->fontMetrics().leading()) / 2); + if (highlighted) { + paintHighlight(p, indent, cg, owner->hasFocus(), + (Qt::GUIStyle)owner->style().styleHint(QStyle::SH_GUIStyle)); // Qt3 doesn't make this easy ;) + p->setPen(cg.base()); + p->setBackgroundColor(cg.text()); + } + else { + p->setPen(cg.text()); + p->setBackgroundColor(cg.base()); + } + p->drawText(textX, textY, text); +} + +// paint the tree structure +void KTreeViewItem::paintTree(QPainter* p, int indent, int cellHeight) const +{ + int parentLeaderX = indent - (22 / 2); + int cellCenterY = cellHeight / 2; + int cellBottomY = cellHeight - 1; + int itemLeaderX = indent - 3; + + /* + * If this is not the first item in the tree draw the line up + * towards parent or sibling. + */ + if (parent->parent != 0 || parent->getChild() != this) + p->drawLine(parentLeaderX, 0, parentLeaderX, cellCenterY); + + // draw the line down towards sibling + if (sibling) + p->drawLine(parentLeaderX, cellCenterY, parentLeaderX, cellBottomY); + + /* + * If this item has children or siblings in the tree or is a child of + * an item other than the root item then draw the little line from the + * item out to the left. + */ + if (sibling || (doExpandButton && (child || delayedExpanding)) || + parent->parent != 0 || + /* + * The following handles the case of an item at the end of the + * topmost level which doesn't have children. + */ + parent->getChild() != this) + { + p->drawLine(parentLeaderX, cellCenterY, itemLeaderX, cellCenterY); + } + + /* + * If there are siblings of ancestors below, draw our portion of the + * branches that extend through this cell. + */ + KTreeViewItem* prevRoot = parent; + while (prevRoot->getParent() != 0) { /* while not root item */ + if (prevRoot->hasSibling()) { + int sibLeaderX = owner->indentation(prevRoot) - (22 / 2); + p->drawLine(sibLeaderX, 0, sibLeaderX, cellBottomY); + } + prevRoot = prevRoot->getParent(); + } +} + +// removes the given (direct) child from the branch +bool KTreeViewItem::removeChild(KTreeViewItem* theChild) +{ + // search item in list of children + KTreeViewItem* prevItem = 0; + KTreeViewItem* toRemove = getChild(); + while (toRemove && toRemove != theChild) { + prevItem = toRemove; + toRemove = toRemove->getSibling(); + } + + if (toRemove) { + // found it! + if (prevItem == 0) { + child = toRemove->getSibling(); + } else { + prevItem->sibling = toRemove->getSibling(); + } + numChildren--; + toRemove->owner = 0; + } + assert((numChildren == 0) == (child == 0)); + + if ( owner ) { + owner->updateVisibleItems(); + owner->update(); + } + + return toRemove != 0; +} + +// sets the delayed-expanding flag +void KTreeViewItem::setDelayedExpanding(bool flag) +{ + delayedExpanding = flag; +} + +// tells the item whether it shall delete child items +void KTreeViewItem::setDeleteChildren(bool flag) +{ + deleteChildren = flag; +} + +// sets the draw expand button flag of this item +void KTreeViewItem::setDrawExpandButton(bool doit) +{ + doExpandButton = doit; +} + +// sets the draw text flag of this item +void KTreeViewItem::setDrawText(bool doit) +{ + doText = doit; +} + +// sets the draw tree branch flag of this item +void KTreeViewItem::setDrawTree(bool doit) +{ + doTree = doit; +} + +// sets the expanded flag of this item +void KTreeViewItem::setExpanded(bool is) +{ + expanded = is; + if ( owner ) { + owner->updateVisibleItems(); + owner->update(); + } +} + +// sets the item pixmap to the given pixmap +void KTreeViewItem::setPixmap(const QPixmap& pm) +{ + pixmap = pm; + if ( owner ) { + owner->updateVisibleItems(); + owner->update(); + } +} + +// sets the item text to the given string +void KTreeViewItem::setText(const QString& t) +{ + text = t; + if ( owner ) { + owner->updateVisibleItems(); + owner->update(); + } +} + +// counts the child items and stores the result in numChildren +void KTreeViewItem::synchNumChildren() +{ + numChildren = 0; + KTreeViewItem* item = getChild(); + while (item != 0) { + numChildren++; + item = item->getSibling(); + } +} + +/* + * returns the bounding rect of the item text in cell coordinates couldn't + * get QFontMetrics::boundingRect() to work right so I made my own + */ +QRect KTreeViewItem::textBoundingRect(int indent) const +{ + const QFontMetrics& fm = owner->fontMetrics(); + int cellHeight = height(fm); + int rectX = indent + pixmap.width() + 3; + int rectY = (cellHeight - fm.ascent() - fm.leading()) / 2 + 2; + int rectW = fm.width(text) + 1; + int rectH = fm.ascent() + fm.leading(); + return QRect(rectX, rectY, rectW, rectH); +} + +// returns the total width of text and pixmap, including margins, spacing +// and indent, or -1 if empty -- SHOULD NEVER BE -1! +int KTreeViewItem::width(int indent) const +{ + return width(indent, owner->fontMetrics()); +} + +int KTreeViewItem::width(int indent, const QFontMetrics& fm) const +{ + int maxWidth = pixmap.width(); + maxWidth += (4 + fm.width(text)); + return maxWidth == 0 ? -1 : indent + maxWidth + 3; +} + + +/* + * ------------------------------------------------------------------- + * + * KTreeView + * + * ------------------------------------------------------------------- + */ + +// constructor +KTreeView::KTreeView(QWidget *parent, + const char *name, + WFlags f) : + QGridView(parent, name, f), + clearing(false), + current(-1), + drawExpandButton(true), + drawTree(true), + expansion(0), + goingDown(false), + itemIndent(22), + showText(true), + itemCapacity(500), + visibleItems(0), + rubberband_mode(false) +{ + setCellHeight(0); +// setCellWidth(0); + setNumRows(0); + setNumCols(1); + setVScrollBarMode(Auto); + setHScrollBarMode(Auto); + //switch(style().guiStyle()) { + //case WindowsStyle: + //case MotifStyle: + setFrameStyle(QFrame::WinPanel | QFrame::Sunken); + setBackgroundColor(colorGroup().base()); + //break; + /*default: + setFrameStyle(QFrame::Panel | QFrame::Plain); + setLineWidth(1); + }*/ + // setAcceptFocus(true); + treeRoot = new KTreeViewItem; + treeRoot->setExpanded(true); + treeRoot->owner = this; + + visibleItems = new KTreeViewItem*[itemCapacity]; + // clear those pointers + for (int j = itemCapacity-1; j >= 0; j--) { + visibleItems[j] = 0; + } +} + +// destructor +KTreeView::~KTreeView() +{ + goingDown = true; + clear(); + delete[] visibleItems; + delete treeRoot; +} + +// appends a child to the item at the given index with the given text +// and pixmap +void KTreeView::appendChildItem(const QString & theText, const QPixmap& thePixmap, + int index) +{ + KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); + item->setDeleteChildren(true); + appendChildItem(item, index); +} + +// appends a child to the item at the end of the given path with +// the given text and pixmap +void KTreeView::appendChildItem(const QString & theText, const QPixmap& thePixmap, + const KPath& thePath) +{ + KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); + item->setDeleteChildren(true); + appendChildItem(item, thePath); +} + +// appends the given item to the children of the item at the given index +void KTreeView::appendChildItem(KTreeViewItem* newItem, int index) +{ + /* find parent item and append new item to parent's sub tree */ + KTreeViewItem* parentItem = itemAt(index); + if (!parentItem) + return; + appendChildItem(parentItem, newItem); +} + +// appends the given item to the children of the item at the end of the +// given path +void KTreeView::appendChildItem(KTreeViewItem* newItem, const KPath& thePath) +{ + /* find parent item and append new item to parent's sub tree */ + KTreeViewItem* parentItem = itemAt(thePath); + if (!parentItem) + return; + appendChildItem(parentItem, newItem); +} + +// indicates whether horizontal scrollbar appears only when needed +bool KTreeView::autoBottomScrollBar() const +{ + return (hScrollBarMode() == Auto); +} + +// indicates whether vertical scrollbar appears only when needed +bool KTreeView::autoScrollBar() const +{ + return (vScrollBarMode() == Auto); +} + +// indicates whether display updates automatically on changes +bool KTreeView::autoUpdate() const +{ + return isUpdatesEnabled(); +} + +// indicates whether horizontal scrollbar is present +bool KTreeView::bottomScrollBar() const +{ + return !(horizontalScrollBar()->isHidden()); +} + +// find item at specified index and change pixmap and/or text +void KTreeView::changeItem(const QString & newText, + const QPixmap *newPixmap, + int index) +{ + KTreeViewItem *item = itemAt(index); + if(item) + changeItem(item, index, newText, newPixmap); +} + +// find item at end of specified path, and change pixmap and/or text +void KTreeView::changeItem(const QString & newText, + const QPixmap* newPixmap, + const KPath& thePath) +{ + KTreeViewItem* item = itemAt(thePath); + if (item) { + int index = itemRow(item); + changeItem(item, index, newText, newPixmap); + } +} + +// clear all items from list and erase display +void KTreeView::clear() +{ + setCurrentItem(-1); + + /* somewhat of a hack for takeItem so it doesn't update the current item... */ + clearing = TRUE; + + bool autoU = autoUpdate(); + setAutoUpdate(FALSE); + QPtrStack<KTreeViewItem> stack; + stack.push(treeRoot); + while(!stack.isEmpty()) { + KTreeViewItem *item = stack.pop(); + if(item->hasChild()) { + stack.push(item); + stack.push(item->getChild()); + } + else if(item->hasSibling()) { + stack.push(item); + stack.push(item->getSibling()); + } + else if(item->getParent() != 0) { + takeItem(item); + delete item; + } + } + clearing = FALSE; + if(goingDown || QApplication::closingDown()) + return; + if(autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); +} + +// return a count of all the items in the tree, whether visible or not +uint KTreeView::count() +{ + int total = 0; + forEveryItem(&KTreeView::countItem, (void *)&total); + return total; +} + +// returns the index of the current (highlighted) item +int KTreeView::currentItem() const +{ + return current; +} + +// only collapses the item if it is expanded. If not expanded, or +// index invalid, does nothing +void KTreeView::collapseItem(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item && item->isExpanded()) + expandOrCollapse(item); +} + +// only expands the item if it is collapsed. If not collapsed, or +// index invalid, does nothing +void KTreeView::expandItem(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item && !item->isExpanded()) + expandOrCollapse(item); +} + +// returns the depth the tree is automatically expanded to when +// items are added +int KTreeView::expandLevel() const +{ + return expansion; +} + +// expands or collapses the subtree rooted at the given item, +// as approptiate +// if item has no children, does nothing +void KTreeView::expandOrCollapseItem(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item) + expandOrCollapse(item); +} + +// visits every item in the tree, visible or not and applies +// the user supplied function with the item and user data passed as parameters +// if user supplied function returns true, traversal ends and function returns +bool KTreeView::forEveryItem(KForEvery func, void* user, KTreeViewItem* item) +{ + if (item == 0) { + item = treeRoot; + } + assert(item->owner == this); + item = item->getChild(); + + while (item != 0) { + // visit the siblings + if ((*func)(item, user)) { + return true; + } + // visit the children (recursively) + if (item->hasChild()) { + if (forEveryItem(func, user, item)) + return true; + } + item = item->getSibling(); + } + return false; +} + +// visits every visible item in the tree in order and applies the +// user supplied function with the item and user data passed as parameters +// if user supplied function returns TRUE, traversal ends and function +// returns +bool KTreeView::forEveryVisibleItem(KForEvery func, void *user, + KTreeViewItem* item) +{ + if (item == 0) { + item = treeRoot; + } else { + // children are invisible (hence, nothing to do) + // if item is invisible or collapsed + if (!item->isVisible() || !item->isExpanded()) { + return false; + } + } + assert(item->owner == this); + item = item->getChild(); + + while (item != 0) { + // visit the siblings + if ((*func)(item, user)) { + return true; + } + // visit the children (recursively) + if (item->hasChild() && item->isExpanded()) { + if (forEveryVisibleItem(func, user, item)) + return true; + } + item = item->getSibling(); + } + return false; +} + +// returns a pointer to the KTreeViewItem at the current index +// or 0 if no current item +KTreeViewItem *KTreeView::getCurrentItem() +{ + if(current == -1) return 0; + return itemAt(current); +} + +// returns the current indent spacing +int KTreeView::indentSpacing() +{ + return itemIndent; +} + +// inserts a new item with the specified text and pixmap before +// or after the item at the given index, depending on the value +// of prefix +// if index is negative, appends item to tree at root level +bool KTreeView::insertItem(const QString & theText, const QPixmap& thePixmap, + int row, bool prefix) +{ + KTreeViewItem* refItem = itemAt(row); + + KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); + item->setDeleteChildren(true); + + bool success = insertItem(refItem, item, prefix); + if (!success) + delete item; + return success; +} + +// inserts a new item with the specified text and pixmap before +// or after the item at the end of the given path, depending on the value +// of prefix +bool KTreeView::insertItem(const QString & theText, const QPixmap& thePixmap, + const KPath& path, bool prefix) +{ + KTreeViewItem* refItem = itemAt(path); + + KTreeViewItem* item = new KTreeViewItem(theText, thePixmap); + item->setDeleteChildren(true); + + bool success = insertItem(refItem, item, prefix); + if (!success) + delete item; + return success; +} + +// inserts the given item or derived object into the tree before +// or after the item at the given index, depending on the value +// of prefix +// if index is negative, appends item to tree at root level +bool KTreeView::insertItem(KTreeViewItem* newItem, + int index, bool prefix) +{ + // find the item currently at the index, if there is one + KTreeViewItem* refItem = itemAt(index); + + // insert new item at the appropriate place + return insertItem(refItem, newItem, prefix); +} + +// inserts the given item or derived object into the tree before +// or after the item at the end of the given path, depending on the value +// of prefix +bool KTreeView::insertItem(KTreeViewItem* newItem, + const KPath& thePath, bool prefix) +{ + // find the item currently at the end of the path, if there is one + KTreeViewItem* refItem = itemAt(thePath); + + // insert new item at appropriate place + return insertItem(refItem, newItem, prefix); +} + +/* + * returns pointer to KTreeViewItem at the specifed row or 0 if row is out + * of limits. + */ +KTreeViewItem* KTreeView::itemAt(int row) +{ + if (row < 0 || row >= numRows()) { + return 0; + } + else { + // lookup the item in the list of visible items + assert(row < itemCapacity); + KTreeViewItem* i = visibleItems[row]; + assert(i != 0); + return i; + } +} + +// returns pointer to KTreeViewItem at the end of the +// path or 0 if not found +KTreeViewItem* KTreeView::itemAt(const KPath& path) +{ + if (path.isEmpty()) + return 0; + + // need a copy of the path because recursiveFind will destroy it + KPath pathCopy; + pathCopy.setAutoDelete(false); + pathCopy = path; + + return recursiveFind(pathCopy); +} + +// computes the path of the item at the specified index +// if index is invalid, nothing is done. +void KTreeView::itemPath(int row, KPath& path) +{ + KTreeViewItem* item = itemAt(row); + if (item != 0) { + itemPath(item, path); + } +} + +// returns the row in the visible tree of the given item or +// -1 if not found +int KTreeView::itemRow(KTreeViewItem* item) +{ + if (item->owner == this) { + // search in list of visible items + for (int i = numRows()-1; i >= 0; i--) { + if (visibleItems[i] == item) { + return i; + } + } + } + // not found + return -1; +} + +/* + * move the subtree at the specified index up one branch level (make root + * item a sibling of its current parent) + */ +void KTreeView::join(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item) + join(item); +} + +/* + * move the subtree at the specified index up one branch level (make root + * item a sibling of it's current parent) + */ +void KTreeView::join(const KPath& path) +{ + KTreeViewItem *item = itemAt(path); + if (item) + join(item); +} + +/* move item at specified index one slot down in its parent's sub tree */ +void KTreeView::lowerItem(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item) + lowerItem(item); +} + +/* move item at specified path one slot down in its parent's sub tree */ +void KTreeView::lowerItem(const KPath& path) +{ + KTreeViewItem* item = itemAt(path); + if (item) + lowerItem(item); +} + +/* move item at specified index one slot up in its parent's sub tree */ +void KTreeView::raiseItem(int index) +{ + KTreeViewItem* item = itemAt(index); + if (item) + raiseItem(item); +} + +/* move item at specified path one slot up in its parent's sub tree */ +void KTreeView::raiseItem(const KPath& path) +{ + KTreeViewItem* item = itemAt(path); + if (item) + raiseItem(item); +} + +// remove the item at the specified index and delete it +void KTreeView::removeItem(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item) { + takeItem(item); + delete item; + } +} + +// remove the item at the end of the specified path and delete it +void KTreeView::removeItem(const KPath& thePath) +{ + KTreeViewItem* item = itemAt(thePath); + if (item) { + takeItem(item); + delete item; + } +} + +// indicates whether vertical scrollbar is present +bool KTreeView::scrollBar() const +{ + return !(verticalScrollBar()->isHidden()); +} + +// enables/disables auto update of display +void KTreeView::setAutoUpdate(bool enable) +{ + setUpdatesEnabled(enable); +} + +// enables/disables horizontal scrollbar +void KTreeView::setBottomScrollBar(bool enable) +{ + setHScrollBarMode(enable ? AlwaysOn : AlwaysOff); +} + +// sets the current item and hightlights it, emitting signals +void KTreeView::setCurrentItem(int row) +{ + if (row == current) + return; + int numVisible = numRows(); + if (row > numVisible) { + emit highlighted( current ); + return; + } + int oldCurrent = current; + current = row; + if (oldCurrent < numVisible) + updateCell(oldCurrent, 0); + if (current > -1) { + updateCell(current, 0); + } + + emit highlighted( current ); +} + +// enables/disables drawing of expand button +void KTreeView::setExpandButtonDrawing(bool enable) +{ + if(drawExpandButton == enable) + return; + drawExpandButton = enable; + forEveryItem(&KTreeView::setItemExpandButtonDrawing, 0); + if(autoUpdate() && isVisible()) + repaint(); +} + +// sets depth to which subtrees are automatically expanded, and +// redraws tree if auto update enabled +void KTreeView::setExpandLevel(int level) +{ + if(expansion == level) + return; + expansion = level; + KTreeViewItem *item = getCurrentItem(); + forEveryItem(&KTreeView::setItemExpanded, 0); + while(item) { + if(item->getParent()->isExpanded()) + break; + item = item->getParent(); + } + setCurrentItem(itemRow(item)); + if(autoUpdate() && isVisible()) + repaint(); +} + +// sets the indent margin for all branches and repaints if auto update enabled +void KTreeView::setIndentSpacing(int spacing) +{ + if (itemIndent == spacing) + return; + itemIndent = spacing; + updateCellWidth(); + if (autoUpdate() && isVisible()) + repaint(); +} + +// enables/disables vertical scrollbar +void KTreeView::setScrollBar(bool enable) +{ + setVScrollBarMode(enable? AlwaysOn : AlwaysOff ); +} + +// enables/disables display of item text (keys) +void KTreeView::setShowItemText(bool enable) +{ + if(showText == enable) + return; + showText = enable; + forEveryItem(&KTreeView::setItemShowText, 0); + if(autoUpdate() && isVisible()) + repaint(); +} + +// indicates whether vertical scrolling is by pixel or row +void KTreeView::setSmoothScrolling(bool enable) +{ + verticalScrollBar()->setLineStep(enable ? 1 : cellHeight()); +} + +// enables/disables tree branch drawing +void KTreeView::setTreeDrawing(bool enable) +{ + if(drawTree == enable) + return; + drawTree = enable; + forEveryItem(&KTreeView::setItemTreeDrawing, 0); + if(autoUpdate() && isVisible()) + repaint(); +} + +// indicates whether item text keys are displayed +bool KTreeView::showItemText() const +{ + return showText; +} + +// indicates whether scrolling is by pixel or row +bool KTreeView::smoothScrolling() const +{ + return (verticalScrollBar()->lineStep() == 1); +} + +// indents the item at the given index, splitting the tree into +// a new branch +void KTreeView::split(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item) + split(item); +} + +// indents the item at the given path, splitting the tree into +// a new branch +void KTreeView::split(const KPath& path) +{ + KTreeViewItem* item = itemAt(path); + if (item) + split(item); +} + +// removes item at specified index from tree but does not delete it +// returns pointer to the item or 0 if not succesful +KTreeViewItem *KTreeView::takeItem(int index) +{ + KTreeViewItem *item = itemAt(index); + if(item) + takeItem(item); + return item; +} + +// removes item at specified path from tree but does not delete it +// returns pointer to the item or 0 if not successful +KTreeViewItem* KTreeView::takeItem(const KPath& path) +{ + KTreeViewItem* item = itemAt(path); + if (item) + takeItem(item); + return item; +} + +// indicates whether tree branches are drawn +bool KTreeView::treeDrawing() const +{ + return drawTree; +} + + +// appends a child to the specified parent item (note: a child, not a sibling, is added!) +void KTreeView::appendChildItem(KTreeViewItem* theParent, + KTreeViewItem* newItem) +{ + theParent->appendChild(newItem); + + // set item state + newItem->setDrawExpandButton(drawExpandButton); + newItem->setDrawTree(drawTree); + newItem->setDrawText(showText); + if (level(newItem) < expansion) { + newItem->setExpanded(true); + } + + // fix up branch levels of any children that the new item may already have + if(newItem->hasChild()) { + fixChildren(newItem); + } + + // if necessary, adjust cell width, number of rows and repaint + if (newItem->isVisible() || theParent->childCount() == 1) { + bool autoU = autoUpdate(); + setAutoUpdate(false); + updateVisibleItems(); + if(autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); + } +} + +// changes the given item with the new text and/or pixmap +void KTreeView::changeItem(KTreeViewItem* toChange, int itemRow, + const QString & newText, const QPixmap* newPixmap) +{ + int indent = indentation(toChange); + int oldWidth = toChange->width(indent); + if(!newText.isNull()) + toChange->setText(newText); + if (newPixmap) + toChange->setPixmap(*newPixmap); + if(oldWidth != toChange->width(indent)) + updateCellWidth(); + if(itemRow == -1) + return; + if(autoUpdate()) + updateCell(itemRow, 0); +} + +// collapses the subtree at the specified item +void KTreeView::collapseSubTree(KTreeViewItem* subRoot) +{ + assert(subRoot->owner == this); + + // must move the current item if it is visible + KTreeViewItem* cur = current >= 0 ? itemAt(current) : 0; + + subRoot->setExpanded(false); + if (subRoot->isVisible()) { + updateVisibleItems(); + // re-seat current item + if (cur != 0) { + setCurrentItem(itemRow(cur)); + } + } + // roland + repaint(); + setAutoUpdate(TRUE); + // roland +} + +// used by count() with forEach() function to count total number +// of items in the tree +bool KTreeView::countItem(KTreeViewItem*, void* total) +{ + int* t = static_cast<int*>(total); + (*t)++; + return false; +} + +// if item is expanded, collapses it or vice-versa +void KTreeView::expandOrCollapse(KTreeViewItem* parent) +{ + bool autoU = autoUpdate(); + setAutoUpdate(false); + int parentIndex = itemRow(parent); + if (parent->isExpanded()) { + collapseSubTree(parent); + emit collapsed(parentIndex); + } + else { + expandSubTree(parent); + emit expanded(parentIndex); + } + if (autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); +} + +// expands the subtree at the given item +void KTreeView::expandSubTree(KTreeViewItem* subRoot) +{ + assert(subRoot->owner == this); + + // must move the current item if it is visible + KTreeViewItem* cur = current >= 0 ? itemAt(current) : 0; + + bool allow = true; + + if (subRoot->delayedExpanding) { + emit expanding(subRoot, allow); + } + if (!allow) + return; + + subRoot->setExpanded(true); + + if (subRoot->isVisible()) { + updateVisibleItems(); + // re-seat current item + if (cur != 0) { + setCurrentItem(itemRow(cur)); + } + } + // roland + repaint(); + setAutoUpdate(TRUE); + // roland + +} + +// fix up branch levels out of whack from split/join operations on the tree +void KTreeView::fixChildren(KTreeViewItem *parentItem) +{ + KTreeViewItem* childItem = 0; + KTreeViewItem* siblingItem = 0; +// int childBranch = parentItem->getBranch() + 1; + if(parentItem->hasChild()) { + childItem = parentItem->getChild(); +// childItem->setBranch(childBranch); + childItem->owner = parentItem->owner; + fixChildren(childItem); + } + while(childItem && childItem->hasSibling()) { + siblingItem = childItem->getSibling(); +// siblingItem->setBranch(childBranch); + siblingItem->owner = parentItem->owner; + fixChildren(siblingItem); + childItem = siblingItem; + } +} + +// handles QFocusEvent processing by setting current item to top +// row if there is no current item, and updates cell to add or +// delete the focus rectangle on the highlight bar +void KTreeView::focusInEvent(QFocusEvent *) +{ + if(current < 0 && numRows() > 0) + setCurrentItem(rowAt(contentsY())); + updateCell(current, 0); +} + +// visits every item in the tree, visible or not and applies the user +// supplied member function with the item and user data passed as parameters +// if the user supplied member function returns TRUE, traversal +// ends and the function returns +void KTreeView::forEveryItem(KForEveryM func, + void *user) +{ + KTreeViewItem *item = treeRoot->getChild(); + QPtrStack<KTreeViewItem> stack; + while(item) { + stack.push(item); + while(!stack.isEmpty()) { + KTreeViewItem *poppedItem = stack.pop(); + if((this->*func)(poppedItem, user)) + return; + if(poppedItem->hasChild()) { + KTreeViewItem *childItem = poppedItem->getChild(); + while(childItem) { + stack.push(childItem); + childItem = childItem->getSibling(); + } + } + } + item = item->getSibling(); + } +} + +// visits every visible item in the tree in order and applies the user +// supplied member function with the item and user data passed as parameters +// if user supplied function returns TRUE, traversal ends and function +// returns +void KTreeView::forEveryVisibleItem(KForEveryM func, + void *user) +{ + QPtrStack<KTreeViewItem> stack; + KTreeViewItem *item = treeRoot->getChild(); + do { + while(item) { + if((this->*func)(item, user)) return; + if(item->hasChild() && item->isExpanded()) { + stack.push(item); + item = item->getChild(); + } + else + item = item->getSibling(); + } + if(stack.isEmpty()) + break; + item = stack.pop()->getSibling(); + } while(TRUE); +} + +// called by updateCellWidth() for each item in the visible list +bool KTreeView::getMaxItemWidth(KTreeViewItem *item, void *user) +{ + int indent = indentation(item); + int* maxW = static_cast<int*>(user); + int w = item->width(indent); + if (w > *maxW) + *maxW = w; + return false; +} + +int KTreeView::indentation(KTreeViewItem* item) const +{ + return level(item) * itemIndent + itemIndent + 3; +} + +// inserts the new item before or after the reference item, depending +// on the value of prefix +bool KTreeView::insertItem(KTreeViewItem* referenceItem, + KTreeViewItem* newItem, + bool prefix) +{ + assert(newItem != 0); + assert(referenceItem == 0 || referenceItem->owner == this); + + /* set the new item's state */ + newItem->setDrawExpandButton(drawExpandButton); + newItem->setDrawTree(drawTree); + newItem->setDrawText(showText); + if (cellHeight() == 0) + { + setCellHeight(newItem->height(fontMetrics())); + } + KTreeViewItem* parentItem; + if (referenceItem) { + parentItem = referenceItem->getParent(); + int insertIndex = parentItem->childIndex(referenceItem); + if (!prefix) + insertIndex++; + parentItem->insertChild(insertIndex, newItem); + } + else { + // no reference item, append at end of visible tree + // need to repaint + parentItem = treeRoot; + parentItem->appendChild(newItem); + } + + // set item expansion + if (level(newItem) < expansion) + newItem->setExpanded(true); + + // fix up branch levels of any children + if (newItem->hasChild()) + fixChildren(newItem); + + // if repaint necessary, do it if visible and auto update + // enabled + if (newItem->isVisible() || parentItem->childCount() == 1) { + bool autoU = autoUpdate(); + setAutoUpdate(FALSE); + updateVisibleItems(); + if(autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); + } + return true; +} + +/* + * returns pointer to item's path + */ +void KTreeView::itemPath(KTreeViewItem* item, KPath& path) const +{ + assert(item != 0); + assert(item->owner == this); + if (item != treeRoot) { + itemPath(item->getParent(), path); + path.push(new QString(item->getText())); + } +} + +/* + * joins the item's branch into the tree, making the item a sibling of its + * parent + */ +void KTreeView::join(KTreeViewItem *item) +{ + KTreeViewItem *itemParent = item->getParent(); + if(itemParent->hasParent()) { + bool autoU = autoUpdate(); + setAutoUpdate(FALSE); + takeItem(item); + insertItem(itemParent, item, FALSE); + if(autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); + } +} + +// handles keyboard interface to tree list +void KTreeView::keyPressEvent(QKeyEvent *e) +{ + if(numRows() == 0) + + // nothing to be done + + return; + if(currentItem() < 0) + + // if no current item, make the top item current + + setCurrentItem(rowAt(contentsY())); + int pageSize, delta; + switch(e->key()) { + case Key_Up: + + // make previous item current, scroll up if necessary + + if(currentItem() > 0) { + setCurrentItem(currentItem() - 1); + ensureCellVisible(currentItem(), 0); + } + break; + case Key_Down: + + // make next item current, scroll down if necessary + + if (currentItem() < numRows() - 1) { + setCurrentItem(currentItem() + 1); + ensureCellVisible(currentItem(), 0); + } + break; + case Key_Next: + + // move highlight one page down and scroll down + + delta = QMAX(1, visibleHeight() / cellHeight()); + setCurrentItem(QMIN(currentItem() + delta, numRows() - 1)); + ensureCellVisible(currentItem(), 0); + break; + case Key_Prior: + + // move highlight one page up and scroll up + + delta = QMAX(1, visibleHeight() / cellHeight()); + setCurrentItem(QMAX(currentItem() - delta, 0)); + ensureCellVisible(currentItem(), 0); + break; + case Key_Plus: + + // if current item has subtree and is collapsed, expand it + + if(currentItem() >= 0) + expandItem(currentItem()); + break; + case Key_Minus: + + // if current item has subtree and is expanded, collapse it + + if(currentItem() >= 0) + collapseItem(currentItem()); + break; + case Key_Return: + case Key_Enter: + + // select the current item + + if(currentItem() >= 0) + emit selected(currentItem()); + break; + default: + break; + } +} + +int KTreeView::level(KTreeViewItem* item) const +{ + assert(item != 0); + assert(item->owner == this); + assert(item != treeRoot); + int l = 0; + item = item->parent->parent; /* since item != treeRoot, there is a parent */ + while (item != 0) { + item = item->parent; + l++; + } + return l; +} + +/* move specified item down one slot in parent's subtree */ +void KTreeView::lowerItem(KTreeViewItem *item) +{ + KTreeViewItem *itemParent = item->getParent(); + uint itemChildIndex = itemParent->childIndex(item); + if(itemChildIndex < itemParent->childCount() - 1) { + bool autoU = autoUpdate(); + setAutoUpdate(FALSE); + takeItem(item); + insertItem(itemParent->childAt(itemChildIndex), item, FALSE); + if(autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); + } +} + +// handle mouse double click events by selecting the clicked item +// and emitting the signal +void KTreeView::mouseDoubleClickEvent(QMouseEvent *e) +{ + // find out which row has been clicked + + QPoint mouseCoord = viewportToContents(e->pos()); + int itemClicked = rowAt(mouseCoord.y()); + + // if a valid row was not clicked, do nothing + + if(itemClicked == -1) + return; + + KTreeViewItem *item = itemAt(itemClicked); + if(!item) return; + + // translate mouse coord to cell coord + + int cellY = cellHeight()*itemClicked; + int cellX = 0; + QPoint cellCoord(mouseCoord.x() - cellX, mouseCoord.y() - cellY); + + // hit test item + int indent = indentation(item); + if(item->boundingRect(indent).contains(cellCoord)) + emit selected(itemClicked); +} + +// handle mouse movement events +void KTreeView::mouseMoveEvent(QMouseEvent *e) +{ + // in rubberband_mode we actually scroll the window now + if (rubberband_mode) + { + move_rubberband(e->pos()); + } +} + + +// handle single mouse presses +void KTreeView::mousePressEvent(QMouseEvent *e) +{ + // first: check which button was pressed + + if (e->button() == MidButton) + { + // RB: the MMB is hardcoded to the "rubberband" scroll mode + if (!rubberband_mode) { + start_rubberband(e->pos()); + } + return; + } + else if ( ( rubberband_mode ) && ( e->button() != RightButton ) ) + { + // another button was pressed while rubberbanding, stop the move. + // RB: if we allow other buttons while rubberbanding the tree can expand + // while rubberbanding - we then need to reclaculate and resize the + // rubberband rect and show the new size + end_rubberband(); + return; // should we process the button press? + } + + // find out which row has been clicked + QPoint mouseCoord = viewportToContents(e->pos()); + int itemClicked = rowAt(mouseCoord.y()); + + // nothing to do if not on valid row + if (itemClicked == -1) + return; + + KTreeViewItem* item = itemAt(itemClicked); + if (!item) + return; + + // translate mouse coord to cell coord + int cellX = 0; + int cellY = cellHeight()*itemClicked; + QPoint cellCoord(mouseCoord.x() - cellX, mouseCoord.y() - cellY); + + /* hit expand button (doesn't set currentItem) */ + if(item->expandButtonClicked(cellCoord)) { + expandOrCollapse(item); + } + // hit select button (to select/deselect the item for backup) + // 2002-01-20 LEW: I'm adding the emit() here so that the screen gets updated, + // as in KTreeView::mouseDoubleClickEvent(). KTreeViewItem::mousePressEvent() + // returns false, so I guess some other function is being called here. + else if ( item->mousePressEvent( cellCoord ) ) { + // Item processed the button press in localSelected(itemClicked) + emit selected(itemClicked); + } + // hit item (to show info on the file/dir label clicked) + else if (item->boundingRect(indentation(item)).contains(cellCoord)) { + setCurrentItem(itemClicked); // highlight item + if ( e->button() == RightButton ) { + emit popupMenu( itemClicked, mapToGlobal( QPoint( e->pos().x(), e->pos().y() ) ) ); + } + } +} + +// handle mouse release events +void KTreeView::mouseReleaseEvent(QMouseEvent *e) +{ + /* if it's the MMB end rubberbanding */ + if (rubberband_mode && e->button()==MidButton) + { + end_rubberband(); + } +} + +// rubberband move: draw the rubberband +void KTreeView::draw_rubberband() +{ +#if 0 + /* + * RB: I'm using a white pen because of the XorROP mode. I would prefer + * to draw the rectangle in red but I don't now how to get a pen which + * draws red in XorROP mode (this depends on the background). In fact + * the color should be configurable. + */ + + if (!rubberband_mode) return; + QPainter paint(this); + paint.setPen(white); + paint.setRasterOp(XorROP); + paint.drawRect(xOffset()*viewWidth()/totalWidth(), + yOffset()*viewHeight()/totalHeight(), + rubber_width+1, rubber_height+1); + paint.end(); +#endif +} + +// rubberband move: start move +void KTreeView::start_rubberband(const QPoint& where) +{ +#if 0 + if (rubberband_mode) { // Oops! + end_rubberband(); + } + /* RB: Don't now, if this check is necessary */ + if (!viewWidth() || !viewHeight()) return; + if (!totalWidth() || !totalHeight()) return; + + // calculate the size of the rubberband rectangle + rubber_width = viewWidth()*viewWidth()/totalWidth(); + if (rubber_width > viewWidth()) rubber_width = viewWidth(); + rubber_height = viewHeight()*viewHeight()/totalHeight(); + if (rubber_height > viewHeight()) rubber_height = viewHeight(); + + // remember the cursor position and the actual offset + rubber_startMouse = where; + rubber_startX = xOffset(); + rubber_startY = yOffset(); + rubberband_mode=TRUE; + draw_rubberband(); +#endif +} + +// rubberband move: end move +void KTreeView::end_rubberband() +{ +#if 0 + if (!rubberband_mode) return; + draw_rubberband(); + rubberband_mode = FALSE; +#endif +} + +// rubberband move: handle mouse moves +void KTreeView::move_rubberband(const QPoint& where) +{ +#if 0 + if (!rubberband_mode) return; + + // look how much the mouse moved and calc the new scroll position + QPoint delta = where - rubber_startMouse; + int nx = rubber_startX + delta.x() * totalWidth() / viewWidth(); + int ny = rubber_startY + delta.y() * totalHeight() / viewHeight(); + + // check the new position (and make it valid) + if (nx < 0) nx = 0; + else if (nx > maxXOffset()) nx = maxXOffset(); + if (ny < 0) ny = 0; + else if (ny > maxYOffset()) ny = maxYOffset(); + + // redraw the rubberband at the new position + draw_rubberband(); + setOffset(nx,ny); + draw_rubberband(); +#endif +} + + +// paints the cell at the specified row and col +// col is ignored for now since there is only one +void KTreeView::paintCell(QPainter* p, int row, int) +{ + KTreeViewItem* item = itemAt(row); + if (item == 0) + return; + + p->setClipRect(cellRect(), QPainter::CoordPainter ); + QColorGroup cg = colorGroup(); + int indent = indentation(item); + item->paint(p, indent, cg, + current == row); /* highlighted */ + p->setClipping(false); +} + +/* raise the specified item up one slot in parent's subtree */ +void KTreeView::raiseItem(KTreeViewItem *item) +{ + KTreeViewItem *itemParent = item->getParent(); + int itemChildIndex = itemParent->childIndex(item); + if(itemChildIndex > 0) { + bool autoU = autoUpdate(); + setAutoUpdate(FALSE); + takeItem(item); + insertItem(itemParent->childAt(--itemChildIndex), item, TRUE); + if(autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); + } +} + +// find the item at the path +KTreeViewItem* KTreeView::recursiveFind(KPath& path) +{ + if (path.isEmpty()) + return treeRoot; + + // get the next key + QString* searchString = path.pop(); + + // find the parent item + KTreeViewItem* parent = recursiveFind(path); + if (parent == 0) + return 0; + + /* + * Iterate through the parent's children searching for searchString. + */ + KTreeViewItem* sibling = parent->getChild(); + while (sibling != 0) { + if (*searchString == sibling->getText()) { + break; /* found it! */ + } + sibling = sibling->getSibling(); + } + return sibling; +} + +// called by setExpandLevel for each item in tree +bool KTreeView::setItemExpanded(KTreeViewItem *item, void *) +{ + if (level(item) < expansion) { + if(item->hasChild() && !item->isExpanded()) + expandSubTree(item); + else + item->setExpanded(TRUE); + } + else { + if (item->hasChild() && item->isExpanded()) + collapseSubTree(item); + else + item->setExpanded(FALSE); + } + return FALSE; +} + +// called by setExpandButtonDrawing for every item in tree +bool KTreeView::setItemExpandButtonDrawing(KTreeViewItem *item, + void *) +{ + item->setDrawExpandButton(drawExpandButton); + return FALSE; +} + +// called by setShowItemText for every item in tree +bool KTreeView::setItemShowText(KTreeViewItem *item, + void *) +{ + item->setDrawText(showText); + return FALSE; +} + +// called by setTreeDrawing for every item in tree +bool KTreeView::setItemTreeDrawing(KTreeViewItem *item, void *) +{ + item->setDrawTree(drawTree); + return FALSE; +} + +// makes the item a child of the item above it, splitting +// the tree into a new branch +void KTreeView::split(KTreeViewItem *item) +{ + KTreeViewItem *itemParent = item->getParent(); + int itemChildIndex = itemParent->childIndex(item); + if(itemChildIndex == 0) + return; + bool autoU = autoUpdate(); + setAutoUpdate(FALSE); + takeItem(item); + appendChildItem(itemParent->childAt(--itemChildIndex), item); + if(autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); +} + +// removes the item from the tree without deleting it +void KTreeView::takeItem(KTreeViewItem* item) +{ + assert(item->owner == this); + + // TODO: go over this function again + + bool wasVisible = item->isVisible(); + /* + * If we have a current item, make sure that it is not in the subtree + * that we are about to remove. If the current item is in the part + * below the taken-out subtree, we must move it up a number of rows if + * the taken-out subtree is at least partially visible. + */ + KTreeViewItem* cur = current >= 0 ? itemAt(current) : 0; + if (wasVisible && cur != 0) { + KTreeViewItem* c = cur; + while (c != 0 && c != item) { + c = c->getParent(); + } + if (c != 0) { + // move current item to parent + cur = item->getParent(); + if (cur == treeRoot) + cur = 0; + } + } + KTreeViewItem* parentItem = item->getParent(); + parentItem->removeChild(item); + item->sibling = 0; + if (wasVisible || parentItem->childCount() == 0) { + bool autoU = autoUpdate(); + setAutoUpdate(FALSE); + updateVisibleItems(); + + if (autoU && isVisible()) + repaint(); + setAutoUpdate(autoU); + } + + // re-seat the current item + setCurrentItem(cur != 0 ? itemRow(cur) : -1); +} + +// visits each item, calculates the maximum width +// and updates QGridView +void KTreeView::updateCellWidth() +{ + // make cells at least 1 pixel wide to avoid singularities (division by zero) + int maxW = 1; + forEveryVisibleItem(&KTreeView::getMaxItemWidth, &maxW); + maxItemWidth = maxW; + setCellWidth(maxW); + update(); +} + +void KTreeView::updateVisibleItems() +{ + int index = 0; + int count = 0; + updateVisibleItemRec(treeRoot, index, count); + assert(index == count); + setNumRows(count); + updateCellWidth(); +} + +void KTreeView::updateVisibleItemRec(KTreeViewItem* item, int& index, int& count) +{ + if (!item->isExpanded()) { + // no visible items if not expanded + return; + } + + /* + * Record the children of item in the list of visible items. + * + * Don't register item itself, it's already in the list. Also only + * allocate new space for children. + */ + count += item->childCount(); + if (count > itemCapacity) { + // must reallocate + int newCapacity = itemCapacity; + do { + newCapacity += newCapacity; + } while (newCapacity < count); + KTreeViewItem** newItems = new KTreeViewItem*[newCapacity]; + // clear the unneeded space + for (int i = index; i < newCapacity; i++) { + newItems[i] = 0; + } + // move already accumulated items over + for (int i = index-1; i >= 0; i--) { + newItems[i] = visibleItems[i]; + } + delete[] visibleItems; + visibleItems = newItems; + itemCapacity = newCapacity; + } + // insert children + for (KTreeViewItem* i = item->getChild(); i != 0; i = i->getSibling()) { + visibleItems[index++] = i; + updateVisibleItemRec(i, index, count); + } +} diff --git a/kdat/ktreeview.h b/kdat/ktreeview.h new file mode 100644 index 0000000..ad1fe6b --- /dev/null +++ b/kdat/ktreeview.h @@ -0,0 +1,899 @@ +/* + * KTreeView class interface + * + * Copyright (C) 1997 Johannes Sixt + * + * based on KTreeList, which is + * Copyright (C) 1996 Keith Brown and KtSoft + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABLILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. You should have received a copy + * of the GNU General Public License along with this program; if not, write + * to the Free Software Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef KDE_KTREE_VIEW_H +#define KDE_KTREE_VIEW_H + +#include <qpixmap.h> /* used in items */ +#include <qptrstack.h> /* used to specify tree paths */ +#include <qstring.h> /* used in items */ +#include <qgridview.h> /* base class for widget */ + +// use stack of strings to represent path information +typedef QPtrStack<QString> KPath; + +class KTreeView; /* forward declaration */ + +/** Items for the KTreeView widget */ +class KTreeViewItem +{ + friend class KTreeView; +public: + /** + * Item constructor. While text defaults to a null string, and the + * item can be constructed this way, the text has to be non-null when + * the item is added to the tree, or it will not be inserted. + * + * The constructor sets the delete-children flag to false. This flag + * tells the item whether it shall delete the child items when it is + * itself deleted. By default the creator of the item is responsible to + * also delete the child items. (However, the versions of + * KTreeView::appendChildItem and KTreeView::insertChildItem that do + * not take a KTreeViewItem set the delete-children flag to true.) + */ + KTreeViewItem(const QString& theText = QString()); // text can not be null when added to the list! + KTreeViewItem(const QString& theText, const QPixmap& thePixmap); + + /** + * Destructor. It destroys its children if this item has been marked + * with setDeleteChildren(true). + */ + virtual ~KTreeViewItem(); + + /** + * Appends a new (direct) child item at the end. It does not update + * administrative data in newChild except for its parent (which is this + * item) and owner. + */ + void appendChild(KTreeViewItem* newChild); + + /** + * Returns a pointer to the child item at the given index in this + * item's sub tree, or 0 if not found. + */ + KTreeViewItem* childAt(int index) const; + + /** + * Returns the number of child items in this item's sub tree. + */ + uint childCount() const; + + /** + * Returns the index in this items sub tree of the given item or -1 if + * not found. The specified child must not be 0. + */ + int childIndex(KTreeViewItem* child) const; + + /** + * Determines whether the specified point is inside the expand button. + */ + bool expandButtonClicked(const QPoint& coord) const; + + /** + * Give the item a chance to process the mouse event. + */ + virtual bool mousePressEvent( const QPoint& coord ); + + /** + * Returns a pointer to the first child item in this item's sub tree, or + * 0 if none. + */ + KTreeViewItem* getChild() const; + + /** + * Returns a pointer to the parent of this item, or 0 if none. + */ + KTreeViewItem* getParent() const; + + /** + * Returns a reference to this item's pixmap. If there is no pixmap + * associated with this item, it will return a reference to a valid, + * null QPixmap. + */ + const QPixmap& getPixmap() const; + + /** + * Returns a pointer to the next item in the same branch below this + * one, or 0 if none. + */ + KTreeViewItem* getSibling() const; + + /** + * Returns this item's text. + */ + const QString& getText() const; + + /** + * Indicates whether this item has any children. + */ + bool hasChild() const; + + /** + * Indicates whether this item has a parent. + */ + bool hasParent() const; + + /** + * Indicates whether this item has a sibling item, that is, an item + * that would be displayed below it at the same level as this item. + */ + bool hasSibling() const; + + /** + * Inserts the a new (direct) child in this item before the child at + * the specified index (first child is index 0). If there is no child + * at the specified index, the item is appended. It does not update + * administrative data in newChild except for its parent (which is this + * item) and owner. + */ + void insertChild(int index, KTreeViewItem* newChild); + + /** + * Indicateds whether the item is expanded, that is, whether the child + * items (if any) would be visible if this item were visible. + * + * Note: If this function returns true, it does not necessarily indicate that + * this item is visible or that this item has any children. + */ + bool isExpanded() const; + + /** + * Returns true if the item is visible. An item is visible if all its + * ancestors are expanded. + */ + bool isVisible() const; + + /** + * Removes the specified (direct) child from this item and returns + * true. If it is not a direct child of this item, nothing happens, and + * false is returned. This function does not update the owning + * KTreeView. + */ + bool removeChild(KTreeViewItem* child); + + /** + * Sets the delayed-expanding flag. If this flag is true, the expanding + * signal is emitted when the item is about to be expanded. The expand + * button is always painted for this item, even if it doesn't have + * children. + */ + void setDelayedExpanding(bool flag); + + /** + * Tells the item whether it should delete its children when it is + * deleted. The default is false, which means that the child items must + * be deleted explicitly. + */ + void setDeleteChildren(bool flag); + + void setDrawExpandButton(bool doit); + + void setDrawText(bool doit); + + void setDrawTree(bool doit); + + void setExpanded(bool is); + + /** + * Sets the item pixmap to the given pixmap. It does not redraw the + * item or update the owning KTreeView. + */ + void setPixmap(const QPixmap& pm); + + /** + * Sets the item text. This function does not redraw the item or update + * the owning KTreeView. + */ + void setText(const QString& t); + +protected: + /** + * Returns the bounding rectangle of the item. + */ + virtual QRect boundingRect(int indent) const; + + /** + * Returns the hieght of the item. The default implementation uses font + * metrics of the owning KTreeView widget. + */ + virtual int height() const; + + /* + * Returns the height of the item depending on the passed-in font + * metrics. + */ + virtual int height(const QFontMetrics& fm) const; + + /** + * Paints the item: pixmap, text, expand button, parent branches + */ + virtual void paint(QPainter* p, int indent, + const QColorGroup& cg, bool highlighted) const; + + /** + * paints the expand button + */ + virtual void paintExpandButton(QPainter* p, int indent, int cellHeight) const; + + /** + * paints the highlighted text + */ + virtual void paintHighlight(QPainter* p, int indent, + const QColorGroup& cg, bool hasFocus, + Qt::GUIStyle style) const; + + /** + * paints the item's text + */ + virtual void paintText(QPainter* p, int indent, int cellHeight, + const QColorGroup& cg, bool highlighted) const; + + /** + * paints the item's tree part. + */ + virtual void paintTree(QPainter* p, int indent, int cellHeight) const; + + /** + * Internal function that counts the number of child items. + */ + void synchNumChildren(); + + /** + * Returns the bounding rectangle of the text. + */ + virtual QRect textBoundingRect(int indent) const; + + /** + * Returns the width of the item taking into account the specified + * indentation. The default implementation uses font metrics of the + * owning KTreeView widget. + */ + virtual int width(int indent) const; + + /** + * Returns the width of the item depending on the passed-in font + * metrics and taking into account the specified indentation. + */ + virtual int width(int indent, const QFontMetrics& fm) const; + +protected: + /** The KTreeView that this item belongs to */ + KTreeView* owner; + int numChildren; + bool doExpandButton; + bool expanded; + bool delayedExpanding; + bool doTree; + bool doText; + mutable QRect expandButton; /* is set in paint() */ + KTreeViewItem* child; + KTreeViewItem* parent; + KTreeViewItem* sibling; + QPixmap pixmap; + QString text; + bool deleteChildren; +}; + +// easier declarations of function prototypes for forEvery type functions +typedef bool (KTreeView::*KForEveryM) + (KTreeViewItem *, void *); +typedef bool (*KForEvery) + (KTreeViewItem *, void *); + +/** + A collapsible treelist widget. + + 1. Introduction + 2. Features + 3. Installation + 4. Public interface + + 1. Introduction + ================================================================================ + + KTreeView is a class inherited from QTableView in the Qt user interface + library. It provides a way to display hierarchical data in a single-inheritance + tree, similar to tree controls in Microsoft Windows and other GUI's. It is most + suitable for directory trees or outlines, but I'm sure other uses will come to + mind. Frankly, it was designed mostly with the above two functions in mind, but + I have tried to make it as flexible as I know how to make it easy to adapt to + other uses. + + In case of problems, I encourage you to read all of the other documentation + files in this package before contacting me as you may find the answer to your + question in one of them. Also read the source code if you have time. I have + tried to comment it adequately and make the source understandable. + + 2. Features + ================================================================================ + + * Displays both text and optional pixmap supplied by the programmer. A support + class, KTreeViewItem, can be inherited and modified to draw items as needed + by the programmer. + + * The list items can be returned by index or logical path and the tree + navigated by parent, child or sibling references contained in them. Also, + item information such as text, pixmap, branch level can be obtained. + + * Items can be inserted, changed and removed either by index in the visible + structure, or by logical paths through the tree hierarchy. + + * The logical path through the tree for any item can be obtained with the index + of the item. + + * Tree structure display and expanding/collapsing of sub-trees is handled with + no intervention from the programmer. + + * entire tree can be expanded or collapsed to a specified sub-level (handy for + outline views) + + * Configuration as follows: + + enable/disable item text display (if you only want to display pixmaps) + + enable/disable drawing of expand/collapse button + + enable/disable drawing of tree structure + + * Keyboard support as follows: + + up/down arrows move the highlight appropriately and scroll the list an item at + a time, if necessary + + pgup/pgdn move the highlight a 'page' up or down as applicable and scroll the + view + + +/- keys expand/collapse the highlighted item if it appropriate + + enter key selects the highlighted item + + * Mouse support as follows: + + left click on item highlights it + + left click on an item "hot button" expands or collapses its sub-tree, as + applicable + + double click on item selects it + + normal scrolling functions in conjunction with scrollbars if present + + 2nd scrolling with the middle mouse button: pressing MMB inserts a + rubberband, showing which part of the whole tree is currently visible. + moving the mouse will scroll the visible part + + * Signals/Slots + + signal void highlighted(int) - emitted when an item in the tree is + highlighted; sends the index of the item + + signal void selected(int) - emitted when an item in the tree is + selected; sends the index of the item + + signal void expanded(int) - emitted when an item in the tree is expanded; + sends the index of the item + + signal void collpased(int) - emitted when an item in the tree is collapsed; + sends the index of the item + */ +class KTreeView : public QGridView +{ + friend class KTreeViewItem; + Q_OBJECT +public: + /** + * Widget contructor. Passes all parameters on to base QTableView, and + * does not use them directly. Does internal initialization, sets the + * current item to -1, and sets default values for scroll bars (both + * auto). + */ + KTreeView(QWidget* parent = 0, const char* name = 0, WFlags f = 0); + + /* + * Desctructor. Deletes all items from the topmost level that have been + * marked with setDeleteChildren(true). + */ + virtual ~KTreeView(); + + /** + * Appends a new child item to the item at the specified row. If that + * item already has children, the new item is appended below these + * children. A KTreeViewItem is created for which the delete-children + * flag is set to true. + */ + void appendChildItem(const QString & theText, const QPixmap& thePixmap, + int index); + + /** + * Same as above except that the parent item is specified by a path. + */ + void appendChildItem(const QString & theText, const QPixmap& thePixmap, + const KPath& thePath); + + /** + * Appendss the specified item as a child of the item that is at the + * specified row. If that item already has children, the new item is + * appended below these children. + */ + void appendChildItem(KTreeViewItem* newItem, int index); + + /** + * Same as above except that the parent item is specified by a path. + */ + void appendChildItem(KTreeViewItem* newItem, const KPath& thePath); + + /** + Returns a bool value indicating whether the list will display a + horizontal scrollbar if one of the displayed items is wider than can + be displayed at the current width of the view. + */ + bool autoBottomScrollBar() const; + + /** + Returns a bool value indicating whether the list will display a + vertical scrollbar if the number of displayed items is more than can + be displayed at the current height of the view. + */ + bool autoScrollBar() const; + + /** + Returns a bool value indicating whether the list will update + immediately on changing the state of the widget in some way. + */ + bool autoUpdate() const; + + /** + Returns a bool value indicating whether the list has currently has a + horizontal scroll bar. + */ + bool bottomScrollBar() const; + + /** + Changes the text and/or pixmap of the given item at the specified + index to the given values and updates the display if auto update + enabled. If changing only the text or pixmap, set the other parameter + to 0. + */ + void changeItem(const QString & newText, + const QPixmap *newPixmap, + int index); + + /** + Same as above function, except item to change is specified by a path + through the tree. + */ + void changeItem(const QString & newText, + const QPixmap *newPixmap, + const KPath& thePath); + + /** + Removes all items from the tree. + + */ + void clear(); + + /** + Returns the total number of items in the tree, whether visible + (expanded sub-trees) or not (collapsed). + */ + uint count(); + + /** + Returns the index of the current (highlighted) item. If no current + item, returns -1. + */ + int currentItem() const; + + /** + Collapses the sub-tree at the specified index. + */ + void collapseItem(int index); + + /** + Expands the sub-tree at the specified index. + */ + void expandItem(int index); + + /** + Returns the depth to which all parent items are automatically + expanded. + */ + int expandLevel() const; + + /** + Same as above functions combined into one. If sub-tree is expanded, + collapses it, if it is collapsed, it expands it. + */ + void expandOrCollapseItem(int index); + + /** + * Iterates every item in the tree, visible or not, and applies the + * function func with a pointer to each item and user data supplied as + * parameters. The children of the specified root item are visited + * (root itself is not visited!). If root is 0 all items in the tree + * are visited. KForEveryFunc is defined as: + * + * typedef bool (*KForEvery)(KTreeViewItem*, void*); + * + * That is, a function that returns bool and takes a pointer to a + * KTreeViewItem and pointer to void as parameters. The traversal ends + * earlier if the supplied function returns bool. In this case the + * return value is also true. + */ + bool forEveryItem(KForEvery func, void* user, + KTreeViewItem* root = 0); + + /** + * Same as above, but only iterates visible items, in order. If the + * specified root item is invisible no items are visited. + */ + bool forEveryVisibleItem(KForEvery func, void *user, + KTreeViewItem* root = 0); + + /** + Returns a pointer to the current item if there is one, or 0. + */ + KTreeViewItem *getCurrentItem(); + + /** + * Returns the number of pixels an item is indented for each level. If, + * in a derived class, the levels are indented differently this value + * may be ignored. + */ + int indentSpacing(); + + /** + * Inserts an item into the tree with the given text and pixmap either + * before or after the item currently at the given row, depending on + * the value of prefix. The new item is added to the same branch as the + * referenced item. If row is -1, the item is simply appended to the + * tree at the topmost level. A KTreeViewItem is created for which the + * delete-children flag is set to true. Returns true if the item has + * been successfully inserted in the tree, otherwise false. + */ + bool insertItem(const QString & theText, const QPixmap& thePixmap, + int row = -1, bool prefix = true); + + /** + * Same as above, but uses a path through the tree to reference the + * insert position. If there is no item at the specified path, the item + * is simply appended to the tree at the topmost level. + */ + bool insertItem(const QString & theText, const QPixmap& thePixmap, + const KPath& thePath, bool prefix = true); + + /** + * Same as above, but an item is specified instead of a text and a pixmap. + */ + bool insertItem(KTreeViewItem *newItem, + int row = -1, bool prefix = true); + + /** + * Same as above, but uses a path through the tree to reference the + * insert position. + */ + bool insertItem(KTreeViewItem *newItem, + const KPath& thePath, bool prefix = true); + + /** + * Returns a pointer to the item in the specified row, or 0 if the + * specified row is outside the limits. This is a cheap operation. + */ + KTreeViewItem* itemAt(int row); + + /** + * Returns a pointer to the item at the end of the path. + */ + KTreeViewItem* itemAt(const KPath& path); + + /** + * Returns the row at which the specified item is found in the visible + * tree or -1 if the item is not visible or not in the tree. + */ + int itemRow(KTreeViewItem* item); + + /** + * Fills path with the logical path to the item at the specified row. + * The specified path variable should be empty. Any strings popped from + * the path must be deleted by the caller. If the row is invalid, path + * remains unchanged (i.e. empty). + */ + void itemPath(int row, KPath& path); + + /** + * Outdents the item at the given row one level so that it becomes a + * sibling of its parent. + */ + void join(int index); + + /** + * Same as above but uses a path to specify the item. + */ + void join(const KPath& path); + + /** + * Moves the item at the specified row down one row in its current + * branch. + */ + void lowerItem(int index); + + /** + * Same as above but uses a path to specify the item. + */ + void lowerItem(const KPath& path); + + /** + * Moves the item at the specified row up one row in its current + * branch. + */ + void raiseItem(int row); + + /** + * Same as above but uses a path to specify the item. + */ + void raiseItem(const KPath& path); + + /** + * Removes the item at the specified row. + */ + void removeItem(int row); + + /** + * Same as above except uses path through the tree to find the item. + */ + void removeItem(const KPath& thePath); + + /** + Returns bool value indicating whether the list currently displays a + vertical scroll bar. + */ + bool scrollBar() const; + + /** + If enable is TRUE (default), enables auto update, else disables it. + */ + void setAutoUpdate(bool enable); + + /** + If enable is TRUE, displays a horizontal scroll bar, else hides it. + */ + void setBottomScrollBar(bool enable); + + /** + * Makes the item at row current and highlights it. The signal + * highlighted is emitted if the current item changes. + */ + void setCurrentItem(int row); + + void setExpandButtonDrawing(bool enable); + + void setExpandLevel(int level); + + /** + * Sets the indentation stepping, in pixels. If, in a derived class, + * the levels are indented differently this value may be ignored. + */ + void setIndentSpacing(int spacing); + + /** + If enable is TRUE, displays a vertical scroll bar, else hides it. + */ + void setScrollBar(bool enable); + + /** + If enable is TRUE (default), item text will be displayed, otherwise + it will not, and no highlight will be shown in the default widget. + */ + void setShowItemText(bool enable); + + /** + If enable is TRUE, enables smooth scrolling, else disables + it (default). + */ + void setSmoothScrolling(bool enable); + + /** + If enable is TRUE (default), lines depicting the structure of the + tree will be drawn, otherwise they will not. + */ + void setTreeDrawing(bool enable); + + /** + Indicates whether item text is displayed. + */ + bool showItemText() const; + + /** + Returns a bool value indicating whether smooth scrolling is enabled. + */ + bool smoothScrolling() const; + + /** + * Indents the item at the specified index, creating a new branch. + */ + void split(int index); + + /** + * Same as above but uses a path to specify the item. + */ + void split(const KPath& path); + + /** + * Removes the item at the given index from the tree, but does not + * delete it, returning a pointer to the removed item. + */ + KTreeViewItem* takeItem(int index); + + /** + * Same as above but uses a path to specify the item to take. + */ + KTreeViewItem* takeItem(const KPath& path); + + /** + Indicates whether the tree structure is drawn. + */ + bool treeDrawing() const; + + /** + * This function is deprecated. Use numRows() instead. + * Returns the number of items that are visible (their parents are + * expanded). + */ + int visibleCount() const { return numRows(); } + +signals: + void collapsed(int index); + void expanded(int index); + /** + * The expanding signal is emitted when an item that has the + * delayedExpanding flag set is about to be expanded. The + * delayedExpanding flag is not reset; the slot that the signal is + * connected to should do so. The item being expanded is passed to the + * slot. The slot gets the opportunity to insert child items into that + * item. It should not change the item any other way. It can allow or + * disallow the expansion by setting the second parameter allow. If it + * is set to false, the item is not expanded. + * + * The signal is always emitted, regardless whether the expansion was + * triggered by the user or by the program. + */ + void expanding(KTreeViewItem* item, bool& allow); + void highlighted(int index); + void selected(int index); + + void popupMenu( int index, const QPoint& ); +protected: + /** + * Appends theChild to theParent as a new direct child. All internal + * state is updated and the widget is repainted as necessary. theChild + * remains invisible if any ancestor of theParent is collapsed. + */ + void appendChildItem(KTreeViewItem* theParent, + KTreeViewItem* theChild); + void changeItem(KTreeViewItem* toChange, + int itemRow, const QString & newText, + const QPixmap* newPixmap); + /** + * Collapses the specified subtree and updates the display. subRoot + * need not be visible. + */ + void collapseSubTree(KTreeViewItem* subRoot); + /** Internal function used for counting items */ + bool countItem(KTreeViewItem* item, void* total); + + void expandOrCollapse(KTreeViewItem *parentItem); + /** + * Expands the specified subtree and updates the display. subRoot need + * not be visible. + */ + void expandSubTree(KTreeViewItem* subRoot); + void fixChildren(KTreeViewItem *parentItem); + virtual void focusInEvent(QFocusEvent *e); + void forEveryItem(KForEveryM func, + void *user); + void forEveryVisibleItem(KForEveryM func, + void *user); + + /** internal function used to determine maximum item width */ + bool getMaxItemWidth(KTreeViewItem* item, void *user); + + /** + * Returns the indentation of the specified item in pixels. + */ + virtual int indentation(KTreeViewItem* item) const; + + /** + * Inserts the specified newItem before or after the specified + * referenceItem. If referenceItem is 0, the newItem is appended at the + * topmost level. If referenceItem is not 0, it must be an item that is + * already in the KTreeView. Internal data is updated and the display + * is refreshed as necessary. The inserted item may still be invisible + * if any of the parents is collapsed. newItem must not be 0. + */ + bool insertItem(KTreeViewItem* referenceItem, KTreeViewItem* newItem, + bool prefix); + + /** + * Finds the logical path of the specified item. The specified path + * variable should be empty. + */ + void itemPath(KTreeViewItem* item, KPath& path) const; + + void join(KTreeViewItem *item); + virtual void keyPressEvent(QKeyEvent *e); + int level(KTreeViewItem* item) const; + void lowerItem(KTreeViewItem *item); + virtual void mouseDoubleClickEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void paintCell(QPainter *p, int row, int col); + /* + * virtual void paintItem(QPainter *p, KTreeViewItem *item, + * bool highlighted); + */ + void raiseItem(KTreeViewItem* item); + + /** + * Internal function that finds the item at the given path. Returns 0 + * if the item cannot be found. The path is destroyed by this function. + */ + KTreeViewItem* recursiveFind(KPath& path); + + bool setItemExpanded(KTreeViewItem *item, void *); + bool setItemExpandButtonDrawing(KTreeViewItem *item, void *); + bool setItemShowText(KTreeViewItem *item, void *); + bool setItemTreeDrawing(KTreeViewItem *item, void *); + void split(KTreeViewItem *item); +public: + void takeItem(KTreeViewItem *item); +protected: + virtual void updateCellWidth(); + virtual void updateVisibleItems(); + void updateVisibleItemRec(KTreeViewItem* parent, int& count, int& width); + + KTreeViewItem* treeRoot; + bool clearing; + int current; + bool drawExpandButton; + bool drawTree; + int expansion; + bool goingDown; + int itemIndent; + int maxItemWidth; + bool showText; + // list of visible items + int itemCapacity; /* for how many items we've space allocated */ + KTreeViewItem** visibleItems; + + // Rainer Bawidamann: move window in "rubberband" mode + bool rubberband_mode; // true if in "rubberband_mode" + QPoint rubber_startMouse; // where the user pressed the MMB + int rubber_height, rubber_width, // the size if the rubberband rect + rubber_startX, rubber_startY; // the x/yOffset() when the MMB was pressed + void draw_rubberband(); + void start_rubberband(const QPoint& where); + void end_rubberband(); + void move_rubberband(const QPoint& where); +}; + +#endif // KDE_KTREE_VIEW_H diff --git a/kdat/large-kdat.png b/kdat/large-kdat.png Binary files differnew file mode 100644 index 0000000..737972d --- /dev/null +++ b/kdat/large-kdat.png diff --git a/kdat/main.cpp b/kdat/main.cpp new file mode 100644 index 0000000..0d17ce8 --- /dev/null +++ b/kdat/main.cpp @@ -0,0 +1,97 @@ +// KDat - a tar-based DAT archiver +// Copyright (C) 1998-2000 Sean Vyain, svyain@mail.tds.net +// Copyright (C) 2001-2002 Lawrence Widman, kdat@cardiothink.com +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +#include <qfile.h> +#include <qdir.h> + +#include <kapplication.h> +#include <klocale.h> +#include <kcmdlineargs.h> +#include <kaboutdata.h> +#include <kcrash.h> +#include <kmessagebox.h> + +#include <signal.h> + +#include "kdat.h" +#include "KDatMainWindow.h" + +static const char description[] = + I18N_NOOP("tar-based DAT archiver for KDE"); + +/* in ErrorHandler.cpp */ +void error_handler(int err_sig); + +int main( int argc, char** argv ) +{ + KAboutData aboutData( "kdat", I18N_NOOP("KDat"), + KDAT_VERSION, description, KAboutData::License_GPL, + "(c) 1999-2000, Sean Vyain; 2001-2002 Lawrence Widman"); + + /* 2002-01-28 LEW: so we can dump core if we want to */ + // KCrash::setCrashHandler(0); // this is supposed to work, but it doesn't +#ifdef DEBUG + { + char *newarg; + if( ( newarg = (char *)malloc( strlen("--nocrashhandler") + 1 ) ) == NULL ) + { + KMessageBox::sorry(NULL, i18n("Can't allocate memory in kdat")); + exit(1); + } + strcpy( newarg, "--nocrashhandler" ); + argv[ argc ] = newarg; + argc++; + } + { + int i; + for(i=0; i<argc; i++){ + printf("Arg %d: %s\n", i, argv[i]); + } + } +#endif /* DEBUG */ + /* 2002-01-28 LEW */ + + KCmdLineArgs::init( argc, argv, &aboutData ); + aboutData.addAuthor( "Lawrence Widman", 0, "kdat@cardiothink.com"); +// KCmdLineArgs::addCmdLineOptions( options ); // Add our own options. + KApplication app; + + app.setMainWidget( KDatMainWindow::getInstance() ); + + /* set up error handler so we don't crash without notice */ + signal(SIGHUP, error_handler); + signal(SIGINT, error_handler); + signal(SIGFPE, error_handler); + signal(SIGSEGV, error_handler); + signal(SIGTERM, error_handler); + + if ( app.isRestored() && KDatMainWindow::canBeRestored( 1 ) ) { + KDatMainWindow::getInstance()->restore( 1 ); + } else { + KDatMainWindow::getInstance()->show(); + } + + return app.exec(); +} + diff --git a/kdat/pics/Makefile.am b/kdat/pics/Makefile.am new file mode 100644 index 0000000..4695907 --- /dev/null +++ b/kdat/pics/Makefile.am @@ -0,0 +1,3 @@ +kdaticondir = $(kde_datadir)/kdat/icons +kdaticon_ICON = AUTO +KDE_ICON = kdat diff --git a/kdat/pics/cr16-app-kdat_archive.png b/kdat/pics/cr16-app-kdat_archive.png Binary files differnew file mode 100644 index 0000000..6e0afc7 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_archive.png diff --git a/kdat/pics/cr16-app-kdat_backup.png b/kdat/pics/cr16-app-kdat_backup.png Binary files differnew file mode 100644 index 0000000..58178eb --- /dev/null +++ b/kdat/pics/cr16-app-kdat_backup.png diff --git a/kdat/pics/cr16-app-kdat_eject.png b/kdat/pics/cr16-app-kdat_eject.png Binary files differnew file mode 100644 index 0000000..fb14e79 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_eject.png diff --git a/kdat/pics/cr16-app-kdat_mounted.png b/kdat/pics/cr16-app-kdat_mounted.png Binary files differnew file mode 100644 index 0000000..8fb9e64 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_mounted.png diff --git a/kdat/pics/cr16-app-kdat_restore.png b/kdat/pics/cr16-app-kdat_restore.png Binary files differnew file mode 100644 index 0000000..0555436 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_restore.png diff --git a/kdat/pics/cr16-app-kdat_select_all.png b/kdat/pics/cr16-app-kdat_select_all.png Binary files differnew file mode 100644 index 0000000..6016b3e --- /dev/null +++ b/kdat/pics/cr16-app-kdat_select_all.png diff --git a/kdat/pics/cr16-app-kdat_select_none.png b/kdat/pics/cr16-app-kdat_select_none.png Binary files differnew file mode 100644 index 0000000..065ca56 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_select_none.png diff --git a/kdat/pics/cr16-app-kdat_select_some.png b/kdat/pics/cr16-app-kdat_select_some.png Binary files differnew file mode 100644 index 0000000..a0ffed5 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_select_some.png diff --git a/kdat/pics/cr16-app-kdat_unmounted.png b/kdat/pics/cr16-app-kdat_unmounted.png Binary files differnew file mode 100644 index 0000000..512d458 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_unmounted.png diff --git a/kdat/pics/cr16-app-kdat_verify.png b/kdat/pics/cr16-app-kdat_verify.png Binary files differnew file mode 100644 index 0000000..e823ac2 --- /dev/null +++ b/kdat/pics/cr16-app-kdat_verify.png diff --git a/kdat/pics/cr32-app-kdat_backup.png b/kdat/pics/cr32-app-kdat_backup.png Binary files differnew file mode 100644 index 0000000..2ba1fea --- /dev/null +++ b/kdat/pics/cr32-app-kdat_backup.png diff --git a/kdat/pics/cr32-app-kdat_restore.png b/kdat/pics/cr32-app-kdat_restore.png Binary files differnew file mode 100644 index 0000000..8638fe7 --- /dev/null +++ b/kdat/pics/cr32-app-kdat_restore.png diff --git a/kdat/pics/cr32-app-kdat_verify.png b/kdat/pics/cr32-app-kdat_verify.png Binary files differnew file mode 100644 index 0000000..8e76d2a --- /dev/null +++ b/kdat/pics/cr32-app-kdat_verify.png diff --git a/kdat/pics/hi16-app-kdat.png b/kdat/pics/hi16-app-kdat.png Binary files differnew file mode 100644 index 0000000..214150e --- /dev/null +++ b/kdat/pics/hi16-app-kdat.png diff --git a/kdat/pics/hi22-app-kdat.png b/kdat/pics/hi22-app-kdat.png Binary files differnew file mode 100644 index 0000000..0480297 --- /dev/null +++ b/kdat/pics/hi22-app-kdat.png diff --git a/kdat/pics/hi32-app-kdat.png b/kdat/pics/hi32-app-kdat.png Binary files differnew file mode 100644 index 0000000..8f37f56 --- /dev/null +++ b/kdat/pics/hi32-app-kdat.png diff --git a/kdat/pics/hi48-app-kdat.png b/kdat/pics/hi48-app-kdat.png Binary files differnew file mode 100644 index 0000000..737972d --- /dev/null +++ b/kdat/pics/hi48-app-kdat.png diff --git a/kdat/pics/lo16-app-kdat.png b/kdat/pics/lo16-app-kdat.png Binary files differnew file mode 100644 index 0000000..214150e --- /dev/null +++ b/kdat/pics/lo16-app-kdat.png diff --git a/kdat/pics/lo16-app-kdat_archive.png b/kdat/pics/lo16-app-kdat_archive.png Binary files differnew file mode 100644 index 0000000..6e0afc7 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_archive.png diff --git a/kdat/pics/lo16-app-kdat_backup.png b/kdat/pics/lo16-app-kdat_backup.png Binary files differnew file mode 100644 index 0000000..58178eb --- /dev/null +++ b/kdat/pics/lo16-app-kdat_backup.png diff --git a/kdat/pics/lo16-app-kdat_eject.png b/kdat/pics/lo16-app-kdat_eject.png Binary files differnew file mode 100644 index 0000000..fb14e79 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_eject.png diff --git a/kdat/pics/lo16-app-kdat_mounted.png b/kdat/pics/lo16-app-kdat_mounted.png Binary files differnew file mode 100644 index 0000000..8fb9e64 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_mounted.png diff --git a/kdat/pics/lo16-app-kdat_restore.png b/kdat/pics/lo16-app-kdat_restore.png Binary files differnew file mode 100644 index 0000000..0555436 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_restore.png diff --git a/kdat/pics/lo16-app-kdat_select_all.png b/kdat/pics/lo16-app-kdat_select_all.png Binary files differnew file mode 100644 index 0000000..6016b3e --- /dev/null +++ b/kdat/pics/lo16-app-kdat_select_all.png diff --git a/kdat/pics/lo16-app-kdat_select_none.png b/kdat/pics/lo16-app-kdat_select_none.png Binary files differnew file mode 100644 index 0000000..065ca56 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_select_none.png diff --git a/kdat/pics/lo16-app-kdat_select_some.png b/kdat/pics/lo16-app-kdat_select_some.png Binary files differnew file mode 100644 index 0000000..a0ffed5 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_select_some.png diff --git a/kdat/pics/lo16-app-kdat_unmounted.png b/kdat/pics/lo16-app-kdat_unmounted.png Binary files differnew file mode 100644 index 0000000..512d458 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_unmounted.png diff --git a/kdat/pics/lo16-app-kdat_verify.png b/kdat/pics/lo16-app-kdat_verify.png Binary files differnew file mode 100644 index 0000000..e823ac2 --- /dev/null +++ b/kdat/pics/lo16-app-kdat_verify.png diff --git a/kdat/pics/lo32-app-kdat.png b/kdat/pics/lo32-app-kdat.png Binary files differnew file mode 100644 index 0000000..74dcb14 --- /dev/null +++ b/kdat/pics/lo32-app-kdat.png |