1/* This file is part of the KDE project
2 Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
3 Copyright (C) 2000-2005 David Faure <faure@kde.org>
4 Copyright (C) 2001 Waldo Bastian <bastian@kde.org>
5
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; see the file COPYING. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21
22#include "main.h"
23
24#include <QtCore/QFile>
25#include <QtCore/Q_PID>
26
27#include <kapplication.h>
28#include <kdeversion.h>
29#include <kstandarddirs.h>
30#include <kdebug.h>
31#include <kmessagebox.h>
32#include <kio/job.h>
33#include <krun.h>
34#include <kio/netaccess.h>
35#include <kservice.h>
36#include <klocale.h>
37#include <kcmdlineargs.h>
38#include <kaboutdata.h>
39#include <kstartupinfo.h>
40#include <kshell.h>
41#include <kde_file.h>
42
43static const char description[] =
44 I18N_NOOP("KIO Exec - Opens remote files, watches modifications, asks for upload");
45
46
47KIOExec::KIOExec()
48 : mExited(false)
49{
50 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
51 if (args->count() < 1)
52 KCmdLineArgs::usageError(i18n("'command' expected.\n"));
53
54 tempfiles = args->isSet("tempfiles");
55 if ( args->isSet( "suggestedfilename" ) )
56 suggestedFileName = args->getOption( "suggestedfilename" );
57 expectedCounter = 0;
58 jobCounter = 0;
59 command = args->arg(0);
60 kDebug() << "command=" << command;
61
62 for ( int i = 1; i < args->count(); i++ )
63 {
64 KUrl url = args->url(i);
65 url = KIO::NetAccess::mostLocalUrl( url, 0 );
66
67 //kDebug() << "url=" << url.url() << " filename=" << url.fileName();
68 // A local file, not an URL ?
69 // => It is not encoded and not shell escaped, too.
70 if ( url.isLocalFile() )
71 {
72 FileInfo file;
73 file.path = url.toLocalFile();
74 file.url = url;
75 fileList.append(file);
76 }
77 // It is an URL
78 else
79 {
80 if ( !url.isValid() )
81 KMessageBox::error( 0L, i18n( "The URL %1\nis malformed" , url.url() ) );
82 else if ( tempfiles )
83 KMessageBox::error( 0L, i18n( "Remote URL %1\nnot allowed with --tempfiles switch" , url.url() ) );
84 else
85 // We must fetch the file
86 {
87 QString fileName = KIO::encodeFileName( url.fileName() );
88 if ( !suggestedFileName.isEmpty() )
89 fileName = suggestedFileName;
90 // Build the destination filename, in ~/.kde/cache-*/krun/
91 // Unlike KDE-1.1, we put the filename at the end so that the extension is kept
92 // (Some programs rely on it)
93 QString tmp = KGlobal::dirs()->saveLocation( "cache", "krun/" ) +
94 QString("%1_%2_%3").arg(getpid()).arg(jobCounter++).arg(fileName);
95 FileInfo file;
96 file.path = tmp;
97 file.url = url;
98 fileList.append(file);
99
100 expectedCounter++;
101 KUrl dest;
102 dest.setPath( tmp );
103 kDebug() << "Copying " << url.prettyUrl() << " to " << dest;
104 KIO::Job *job = KIO::file_copy( url, dest );
105 jobList.append( job );
106
107 connect( job, SIGNAL( result( KJob * ) ), SLOT( slotResult( KJob * ) ) );
108 }
109 }
110 }
111 args->clear();
112
113 if ( tempfiles )
114 {
115 slotRunApp();
116 return;
117 }
118
119 counter = 0;
120 if ( counter == expectedCounter )
121 slotResult( 0L );
122}
123
124void KIOExec::slotResult( KJob * job )
125{
126 if (job && job->error())
127 {
128 // That error dialog would be queued, i.e. not immediate...
129 //job->showErrorDialog();
130 if ( (job->error() != KIO::ERR_USER_CANCELED) )
131 KMessageBox::error( 0L, job->errorString() );
132
133 QString path = static_cast<KIO::FileCopyJob*>(job)->destUrl().path();
134
135 QList<FileInfo>::Iterator it = fileList.begin();
136 for(;it != fileList.end(); ++it)
137 {
138 if ((*it).path == path)
139 break;
140 }
141
142 if ( it != fileList.end() )
143 fileList.erase( it );
144 else
145 kDebug() << path << " not found in list";
146 }
147
148 counter++;
149
150 if ( counter < expectedCounter )
151 return;
152
153 kDebug() << "All files downloaded, will call slotRunApp shortly";
154 // We know we can run the app now - but let's finish the job properly first.
155 QTimer::singleShot( 0, this, SLOT( slotRunApp() ) );
156
157 jobList.clear();
158}
159
160void KIOExec::slotRunApp()
161{
162 if ( fileList.isEmpty() ) {
163 kDebug() << "No files downloaded -> exiting";
164 mExited = true;
165 QApplication::exit(1);
166 return;
167 }
168
169 KService service("dummy", command, QString());
170
171 KUrl::List list;
172 // Store modification times
173 QList<FileInfo>::Iterator it = fileList.begin();
174 for ( ; it != fileList.end() ; ++it )
175 {
176 KDE_struct_stat buff;
177 (*it).time = KDE_stat( QFile::encodeName((*it).path), &buff ) ? 0 : buff.st_mtime;
178 KUrl url;
179 url.setPath((*it).path);
180 list << url;
181 }
182
183 QStringList params = KRun::processDesktopExec(service, list);
184
185 kDebug() << "EXEC " << KShell::joinArgs( params );
186
187#ifdef Q_WS_X11
188 // propagate the startup identification to the started process
189 KStartupInfoId id;
190 id.initId( kapp->startupId());
191 id.setupStartupEnv();
192#endif
193
194 QString exe( params.takeFirst() );
195 const int exit_code = QProcess::execute( exe, params );
196
197#ifdef Q_WS_X11
198 KStartupInfo::resetStartupEnv();
199#endif
200
201 kDebug() << "EXEC done";
202
203 // Test whether one of the files changed
204 it = fileList.begin();
205 for( ;it != fileList.end(); ++it )
206 {
207 KDE_struct_stat buff;
208 QString src = (*it).path;
209 KUrl dest = (*it).url;
210 if ( (KDE::stat( src, &buff ) == 0) &&
211 ((*it).time != buff.st_mtime) )
212 {
213 if ( tempfiles )
214 {
215 if ( KMessageBox::questionYesNo( 0L,
216 i18n( "The supposedly temporary file\n%1\nhas been modified.\nDo you still want to delete it?" , dest.pathOrUrl()),
217 i18n( "File Changed" ), KStandardGuiItem::del(), KGuiItem(i18n("Do Not Delete")) ) != KMessageBox::Yes )
218 continue; // don't delete the temp file
219 }
220 else if ( ! dest.isLocalFile() ) // no upload when it's already a local file
221 {
222 if ( KMessageBox::questionYesNo( 0L,
223 i18n( "The file\n%1\nhas been modified.\nDo you want to upload the changes?" , dest.prettyUrl()),
224 i18n( "File Changed" ), KGuiItem(i18n("Upload")), KGuiItem(i18n("Do Not Upload")) ) == KMessageBox::Yes )
225 {
226 kDebug() << "src='" << src << "' dest='" << dest << "'";
227 // Do it the synchronous way.
228 if ( !KIO::NetAccess::upload( src, dest, 0 ) )
229 {
230 KMessageBox::error( 0L, KIO::NetAccess::lastErrorString() );
231 continue; // don't delete the temp file
232 }
233 }
234 }
235 }
236
237 if ((!dest.isLocalFile() || tempfiles) && exit_code == 0) {
238 // Wait for a reasonable time so that even if the application forks on startup (like OOo or amarok)
239 // it will have time to start up and read the file before it gets deleted. #130709.
240 kDebug() << "sleeping...";
241 sleep(180); // 3 mn
242 kDebug() << "about to delete " << src;
243 unlink( QFile::encodeName(src) );
244 }
245 }
246
247 mExited = true;
248 QApplication::exit(exit_code);
249}
250
251int main( int argc, char **argv )
252{
253 KAboutData aboutData( "kioexec", "kioexec", ki18n("KIOExec"),
254 KDE_VERSION_STRING, ki18n(description), KAboutData::License_GPL,
255 ki18n("(c) 1998-2000,2003 The KFM/Konqueror Developers"));
256 aboutData.addAuthor(ki18n("David Faure"),KLocalizedString(), "faure@kde.org");
257 aboutData.addAuthor(ki18n("Stephan Kulow"),KLocalizedString(), "coolo@kde.org");
258 aboutData.addAuthor(ki18n("Bernhard Rosenkraenzer"),KLocalizedString(), "bero@arklinux.org");
259 aboutData.addAuthor(ki18n("Waldo Bastian"),KLocalizedString(), "bastian@kde.org");
260 aboutData.addAuthor(ki18n("Oswald Buddenhagen"),KLocalizedString(), "ossi@kde.org");
261 aboutData.setProgramIconName("kde");
262 KCmdLineArgs::init( argc, argv, &aboutData );
263
264 KCmdLineOptions options;
265 options.add("tempfiles", ki18n("Treat URLs as local files and delete them afterwards"));
266 options.add("suggestedfilename <file name>", ki18n("Suggested file name for the downloaded file"));
267 options.add("+command", ki18n("Command to execute"));
268 options.add("+[URLs]", ki18n("URL(s) or local file(s) used for 'command'"));
269 KCmdLineArgs::addCmdLineOptions( options );
270
271 KApplication app;
272 app.setQuitOnLastWindowClosed(false);
273
274 KIOExec exec;
275
276 // Don't go into the event loop if we already want to exit (#172197)
277 if (exec.exited())
278 return 0;
279
280 return app.exec();
281}
282
283#include "main.moc"
284