1/*
2 Copyright (c) 2005 Tom Albers <tomalbers@kde.nl>
3 Copyright (c) 1997-1999 Stefan Taferner <taferner@kde.org>
4
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
9
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
19*/
20
21#include "kfileio.h"
22#include "kpimutils_export.h"
23
24#include <KDebug>
25#include <KLocalizedString>
26#include <KMessageBox>
27#include <KStandardGuiItem>
28#include <kde_file.h> //krazy:exclude=camelcase
29
30#include <QDir>
31#include <QByteArray>
32#include <QWidget>
33#include <QFile>
34#include <QFileInfo>
35
36#include <sys/stat.h>
37#include <sys/types.h>
38#include <assert.h>
39
40namespace KPIMUtils {
41
42//-----------------------------------------------------------------------------
43static void msgDialog( const QString &msg )
44{
45 KMessageBox::sorry( 0, msg, i18n( "File I/O Error" ) );
46}
47
48//-----------------------------------------------------------------------------
49QByteArray kFileToByteArray( const QString &aFileName, bool aEnsureNL,
50 bool aVerbose )
51{
52 QByteArray result;
53 QFileInfo info( aFileName );
54 unsigned int readLen;
55 unsigned int len = info.size();
56 QFile file( aFileName );
57
58 //assert(aFileName!=0);
59 if ( aFileName.isEmpty() ) {
60 return "";
61 }
62
63 if ( !info.exists() ) {
64 if ( aVerbose ) {
65 msgDialog( i18n( "The specified file does not exist:\n%1", aFileName ) );
66 }
67 return QByteArray();
68 }
69 if ( info.isDir() ) {
70 if ( aVerbose ) {
71 msgDialog( i18n( "This is a folder and not a file:\n%1", aFileName ) );
72 }
73 return QByteArray();
74 }
75 if ( !info.isReadable() ) {
76 if ( aVerbose ) {
77 msgDialog( i18n( "You do not have read permissions to the file:\n%1", aFileName ) );
78 }
79 return QByteArray();
80 }
81 if ( len == 0 ) {
82 return QByteArray();
83 }
84
85 if ( !file.open( QIODevice::Unbuffered|QIODevice::ReadOnly ) ) {
86 if ( aVerbose ) {
87 switch ( file.error() ) {
88 case QFile::ReadError:
89 msgDialog( i18n( "Could not read file:\n%1", aFileName ) );
90 break;
91 case QFile::OpenError:
92 msgDialog( i18n( "Could not open file:\n%1", aFileName ) );
93 break;
94 default:
95 msgDialog( i18n( "Error while reading file:\n%1", aFileName ) );
96 }
97 }
98 return QByteArray();
99 }
100
101 result.resize( len + int( aEnsureNL ) );
102 readLen = file.read( result.data(), len );
103 if ( aEnsureNL ) {
104 if ( result[readLen-1] != '\n' ) {
105 result[readLen++] = '\n';
106 len++;
107 } else {
108 result.truncate( len );
109 }
110 }
111
112 if ( readLen < len ) {
113 QString msg = i18np( "Could only read 1 byte of %2.",
114 "Could only read %1 bytes of %2.",
115 readLen, len );
116 msgDialog( msg );
117 result.truncate( readLen );
118 }
119
120 return result;
121}
122
123//-----------------------------------------------------------------------------
124bool kByteArrayToFile( const QByteArray &aBuffer, const QString &aFileName,
125 bool aAskIfExists, bool aBackup, bool aVerbose )
126{
127 // TODO: use KSaveFile
128 QFile file( aFileName );
129
130 //assert(aFileName!=0);
131 if ( aFileName.isEmpty() ) {
132 return false;
133 }
134
135 if ( file.exists() ) {
136 if ( aAskIfExists ) {
137 QString str;
138 str = i18n( "File %1 exists.\nDo you want to replace it?", aFileName );
139 const int rc =
140 KMessageBox::warningContinueCancel( 0, str, i18n( "Save to File" ),
141 KGuiItem( i18n( "&Replace" ) ) );
142 if ( rc != KMessageBox::Continue ) {
143 return false;
144 }
145 }
146 if ( aBackup ) {
147 // make a backup copy
148 // TODO: use KSaveFile::backupFile()
149 QString bakName = aFileName;
150 bakName += QLatin1Char('~');
151 QFile::remove( bakName );
152 if ( !QDir::current().rename( aFileName, bakName ) ) {
153 // failed to rename file
154 if ( !aVerbose ) {
155 return false;
156 }
157 const int rc =
158 KMessageBox::warningContinueCancel(
159 0,
160 i18n( "Failed to make a backup copy of %1.\nContinue anyway?", aFileName ),
161 i18n( "Save to File" ), KStandardGuiItem::save() );
162
163 if ( rc != KMessageBox::Continue ) {
164 return false;
165 }
166 }
167 }
168 }
169
170 if ( !file.open( QIODevice::Unbuffered|QIODevice::WriteOnly|QIODevice::Truncate ) ) {
171 if ( aVerbose ) {
172 switch ( file.error() ) {
173 case QFile::WriteError:
174 msgDialog( i18n( "Could not write to file:\n%1", aFileName ) );
175 break;
176 case QFile::OpenError:
177 msgDialog( i18n( "Could not open file for writing:\n%1", aFileName ) );
178 break;
179 default:
180 msgDialog( i18n( "Error while writing file:\n%1", aFileName ) );
181 }
182 }
183 return false;
184 }
185
186 const int writeLen = file.write( aBuffer.data(), aBuffer.size() );
187
188 if ( writeLen < 0 ) {
189 if ( aVerbose ) {
190 msgDialog( i18n( "Could not write to file:\n%1", aFileName ) );
191 }
192 return false;
193 } else if ( writeLen < aBuffer.size() ) {
194 QString msg = i18np( "Could only write 1 byte of %2.",
195 "Could only write %1 bytes of %2.",
196 writeLen, aBuffer.size() );
197 if ( aVerbose ) {
198 msgDialog( msg );
199 }
200 return false;
201 }
202
203 return true;
204}
205
206QString checkAndCorrectPermissionsIfPossible( const QString &toCheck,
207 const bool recursive,
208 const bool wantItReadable,
209 const bool wantItWritable )
210{
211 // First we have to find out which type the toCheck is. This can be
212 // a directory (follow if recursive) or a file (check permissions).
213 // Symlinks are followed as expected.
214 QFileInfo fiToCheck( toCheck );
215 fiToCheck.setCaching( false );
216 QByteArray toCheckEnc = QFile::encodeName( toCheck );
217 QString error;
218 KDE_struct_stat statbuffer;
219
220 if ( !fiToCheck.exists() ) {
221 error.append( i18n( "%1 does not exist", toCheck ) + QLatin1Char('\n') );
222 }
223
224 // check the access bit of a folder.
225 if ( fiToCheck.isDir() ) {
226 if ( KDE_stat( toCheckEnc, &statbuffer ) != 0 ) {
227 kDebug() << "wantItA: Can't read perms of" << toCheck;
228 }
229 QDir g( toCheck );
230 if ( !g.isReadable() ) {
231 if ( chmod( toCheckEnc, statbuffer.st_mode + S_IXUSR ) != 0 ) {
232 error.append( i18n( "%1 is not accessible and that is "
233 "unchangeable.", toCheck ) + QLatin1Char('\n') );
234 } else {
235 kDebug() << "Changed access bit for" << toCheck;
236 }
237 }
238 }
239
240 // For each file or folder we can check if the file is readable
241 // and writable, as requested.
242 if ( fiToCheck.isFile() || fiToCheck.isDir() ) {
243
244 if ( !fiToCheck.isReadable() && wantItReadable ) {
245 // Get the current permissions. No need to do anything with an
246 // error, it will het added to errors anyhow, later on.
247 if ( KDE_stat( toCheckEnc, &statbuffer ) != 0 ) {
248 kDebug() << "wantItR: Can't read perms of" << toCheck;
249 }
250
251 // Lets try changing it.
252 if ( chmod( toCheckEnc, statbuffer.st_mode + S_IRUSR ) != 0 ) {
253 error.append( i18n( "%1 is not readable and that is unchangeable.",
254 toCheck ) + QLatin1Char('\n') );
255 } else {
256 kDebug() << "Changed the read bit for" << toCheck;
257 }
258 }
259
260 if ( !fiToCheck.isWritable() && wantItWritable ) {
261 // Gets the current persmissions. Needed because it can be changed
262 // curing previous operation.
263 if ( KDE_stat( toCheckEnc, &statbuffer ) != 0 ) {
264 kDebug() << "wantItW: Can't read perms of" << toCheck;
265 }
266
267 // Lets try changing it.
268 if ( chmod ( toCheckEnc, statbuffer.st_mode + S_IWUSR ) != 0 ) {
269 error.append( i18n( "%1 is not writable and that is unchangeable.", toCheck ) + QLatin1Char('\n') );
270 } else {
271 kDebug() << "Changed the write bit for" << toCheck;
272 }
273 }
274 }
275
276 // If it is a folder and recursive is true, then we check the contents of
277 // the folder.
278 if ( fiToCheck.isDir() && recursive ) {
279 QDir g( toCheck );
280 // First check if the folder is readable for us. If not, we get
281 // some ugly crashes.
282 if ( !g.isReadable() ) {
283 error.append( i18n( "Folder %1 is inaccessible.", toCheck ) + QLatin1Char('\n') );
284 } else {
285 foreach ( const QFileInfo &fi, g.entryInfoList() ) {
286 QString newToCheck = toCheck + QLatin1Char('/') + fi.fileName();
287 if ( fi.fileName() != QLatin1String(".") && fi.fileName() != QLatin1String("..") ) {
288 error.append (
289 checkAndCorrectPermissionsIfPossible( newToCheck, recursive,
290 wantItReadable, wantItWritable ) );
291 }
292 }
293 }
294 }
295 return error;
296}
297
298bool checkAndCorrectPermissionsIfPossibleWithErrorHandling( QWidget *parent,
299 const QString &toCheck,
300 const bool recursive,
301 const bool wantItReadable,
302 const bool wantItWritable )
303{
304 QString error =
305 checkAndCorrectPermissionsIfPossible( toCheck, recursive, wantItReadable, wantItWritable );
306
307 // There is no KMessageBox with Retry, Cancel and Details.
308 // so, I can't provide a functionality to recheck. So it now
309 // it is just a warning.
310 if ( !error.isEmpty() ) {
311 kDebug() << "checkPermissions found:" << error;
312 KMessageBox::detailedSorry( parent,
313 i18n( "Some files or folders do not have the "
314 "necessary permissions, please correct "
315 "them manually." ),
316 error, i18n( "Permissions Check" ), 0 );
317 return false;
318 } else {
319 return true;
320 }
321}
322
323bool removeDirAndContentsRecursively( const QString & path )
324{
325 bool success = true;
326
327 QDir d;
328 d.setPath( path );
329 d.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks );
330
331 QFileInfoList list = d.entryInfoList();
332
333 Q_FOREACH ( const QFileInfo &fi, list ) {
334 if ( fi.isDir() ) {
335 if ( fi.fileName() != QLatin1String(".") && fi.fileName() != QLatin1String("..") ) {
336 success = success && removeDirAndContentsRecursively( fi.absoluteFilePath() );
337 }
338 } else {
339 success = success && d.remove( fi.absoluteFilePath() );
340 }
341 }
342
343 if ( success ) {
344 success = success && d.rmdir( path ); // nuke ourselves, we should be empty now
345 }
346 return success;
347}
348
349}
350