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 | |
43 | static const char description[] = |
44 | I18N_NOOP("KIO Exec - Opens remote files, watches modifications, asks for upload" ); |
45 | |
46 | |
47 | KIOExec::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 | |
124 | void 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 | |
160 | void 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 | |
251 | int 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 | |