1/*
2 * ark -- archiver for the KDE project
3 *
4 * Copyright (C) 2008 Harald Hvaal <haraldhv@stud.ntnu.no>
5 * Copyright (C) 2009-2010 Raphael Kubo da Costa <rakuco@FreeBSD.org>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ( INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "batchextract.h"
30
31#include "kerfuffle/archive.h"
32#include "kerfuffle/extractiondialog.h"
33#include "kerfuffle/jobs.h"
34#include "kerfuffle/queries.h"
35
36#include <KDebug>
37#include <KGlobal>
38#include <KLocale>
39#include <KMessageBox>
40#include <KRun>
41#include <KIO/RenameDialog>
42#include <kwidgetjobtracker.h>
43
44#include <QDir>
45#include <QFileInfo>
46#include <QTimer>
47#include <QWeakPointer>
48
49BatchExtract::BatchExtract()
50 : KCompositeJob(0),
51 m_autoSubfolder(false),
52 m_preservePaths(true),
53 m_openDestinationAfterExtraction(false)
54{
55 setCapabilities(KJob::Killable);
56
57 connect(this, SIGNAL(result(KJob*)), SLOT(showFailedFiles()));
58}
59
60BatchExtract::~BatchExtract()
61{
62 if (!m_inputs.isEmpty()) {
63 KIO::getJobTracker()->unregisterJob(this);
64 }
65}
66
67void BatchExtract::addExtraction(Kerfuffle::Archive* archive)
68{
69 QString destination = destinationFolder();
70
71 if ((autoSubfolder()) && (!archive->isSingleFolderArchive())) {
72 const QDir d(destination);
73 QString subfolderName = archive->subfolderName();
74
75 if (d.exists(subfolderName)) {
76 subfolderName = KIO::RenameDialog::suggestName(destination, subfolderName);
77 }
78
79 d.mkdir(subfolderName);
80
81 destination += QLatin1Char( '/' ) + subfolderName;
82 }
83
84 Kerfuffle::ExtractionOptions options;
85 options[QLatin1String( "PreservePaths" )] = preservePaths();
86
87 Kerfuffle::ExtractJob *job = archive->copyFiles(QVariantList(), destination, options);
88
89 kDebug() << QString(QLatin1String( "Registering job from archive %1, to %2, preservePaths %3" )).arg(archive->fileName()).arg(destination).arg(preservePaths());
90
91 addSubjob(job);
92
93 m_fileNames[job] = qMakePair(archive->fileName(), destination);
94
95 connect(job, SIGNAL(percent(KJob*,ulong)),
96 this, SLOT(forwardProgress(KJob*,ulong)));
97 connect(job, SIGNAL(userQuery(Kerfuffle::Query*)),
98 this, SLOT(slotUserQuery(Kerfuffle::Query*)));
99}
100
101void BatchExtract::slotUserQuery(Kerfuffle::Query *query)
102{
103 query->execute();
104}
105
106bool BatchExtract::autoSubfolder() const
107{
108 return m_autoSubfolder;
109}
110
111void BatchExtract::setAutoSubfolder(bool value)
112{
113 m_autoSubfolder = value;
114}
115
116void BatchExtract::start()
117{
118 QTimer::singleShot(0, this, SLOT(slotStartJob()));
119}
120
121void BatchExtract::slotStartJob()
122{
123 // If none of the archives could be loaded, there is no subjob to run
124 if (m_inputs.isEmpty()) {
125 emitResult();
126 return;
127 }
128
129 foreach(Kerfuffle::Archive *archive, m_inputs) {
130 addExtraction(archive);
131 }
132
133 KIO::getJobTracker()->registerJob(this);
134
135 emit description(this,
136 i18n("Extracting file..."),
137 qMakePair(i18n("Source archive"), m_fileNames.value(subjobs().at(0)).first),
138 qMakePair(i18n("Destination"), m_fileNames.value(subjobs().at(0)).second)
139 );
140
141 m_initialJobCount = subjobs().size();
142
143 kDebug() << "Starting first job";
144
145 subjobs().at(0)->start();
146}
147
148void BatchExtract::showFailedFiles()
149{
150 if (!m_failedFiles.isEmpty()) {
151 KMessageBox::informationList(0, i18n("The following files could not be extracted:"), m_failedFiles);
152 }
153}
154
155void BatchExtract::slotResult(KJob *job)
156{
157 kDebug();
158
159 // TODO: The user must be informed about which file caused the error, and that the other files
160 // in the queue will not be extracted.
161 if (job->error()) {
162 kDebug() << "There was en error, " << job->errorText();
163
164 setErrorText(job->errorText());
165 setError(job->error());
166
167 removeSubjob(job);
168
169 KMessageBox::error(NULL, job->errorText().isEmpty() ?
170 i18n("There was an error during extraction.") : job->errorText()
171 );
172
173 emitResult();
174
175 return;
176 } else {
177 removeSubjob(job);
178 }
179
180 if (!hasSubjobs()) {
181 if (openDestinationAfterExtraction()) {
182 KUrl destination(destinationFolder());
183 destination.cleanPath();
184 KRun::runUrl(destination, QLatin1String( "inode/directory" ), 0);
185 }
186
187 kDebug() << "Finished, emitting the result";
188 emitResult();
189 } else {
190 kDebug() << "Starting the next job";
191 emit description(this,
192 i18n("Extracting file..."),
193 qMakePair(i18n("Source archive"), m_fileNames.value(subjobs().at(0)).first),
194 qMakePair(i18n("Destination"), m_fileNames.value(subjobs().at(0)).second)
195 );
196 subjobs().at(0)->start();
197 }
198}
199
200void BatchExtract::forwardProgress(KJob *job, unsigned long percent)
201{
202 Q_UNUSED(job)
203 int jobPart = 100 / m_initialJobCount;
204 setPercent(jobPart *(m_initialJobCount - subjobs().size()) + percent / m_initialJobCount);
205}
206
207bool BatchExtract::addInput(const KUrl& url)
208{
209 Kerfuffle::Archive *archive = Kerfuffle::Archive::create(url.pathOrUrl(), this);
210
211 if ((archive == NULL) || (!QFileInfo(url.pathOrUrl()).exists())) {
212 m_failedFiles.append(url.fileName());
213 return false;
214 }
215
216 m_inputs.append(archive);
217
218 return true;
219}
220
221bool BatchExtract::openDestinationAfterExtraction() const
222{
223 return m_openDestinationAfterExtraction;
224}
225
226bool BatchExtract::preservePaths() const
227{
228 return m_preservePaths;
229}
230
231QString BatchExtract::destinationFolder() const
232{
233 if (m_destinationFolder.isEmpty()) {
234 return QDir::currentPath();
235 } else {
236 return m_destinationFolder;
237 }
238}
239
240void BatchExtract::setDestinationFolder(const QString& folder)
241{
242 if (QFileInfo(folder).isDir()) {
243 m_destinationFolder = folder;
244 }
245}
246
247void BatchExtract::setOpenDestinationAfterExtraction(bool value)
248{
249 m_openDestinationAfterExtraction = value;
250}
251
252void BatchExtract::setPreservePaths(bool value)
253{
254 m_preservePaths = value;
255}
256
257bool BatchExtract::showExtractDialog()
258{
259 QWeakPointer<Kerfuffle::ExtractionDialog> dialog =
260 new Kerfuffle::ExtractionDialog;
261
262 if (m_inputs.size() > 1) {
263 dialog.data()->batchModeOption();
264 }
265
266 dialog.data()->setAutoSubfolder(autoSubfolder());
267 dialog.data()->setCurrentUrl(destinationFolder());
268 dialog.data()->setPreservePaths(preservePaths());
269
270 if (m_inputs.size() == 1) {
271 if (m_inputs.at(0)->isSingleFolderArchive()) {
272 dialog.data()->setSingleFolderArchive(true);
273 }
274 dialog.data()->setSubfolder(m_inputs.at(0)->subfolderName());
275 }
276
277 if (!dialog.data()->exec()) {
278 delete dialog.data();
279 return false;
280 }
281
282 setAutoSubfolder(dialog.data()->autoSubfolders());
283 setDestinationFolder(dialog.data()->destinationDirectory().pathOrUrl());
284 setOpenDestinationAfterExtraction(dialog.data()->openDestinationAfterExtraction());
285 setPreservePaths(dialog.data()->preservePaths());
286
287 delete dialog.data();
288
289 return true;
290}
291
292#include <batchextract.moc>
293