1 //===========================================
2 //  Lumina-DE source code
3 //  Copyright (c) 2014, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #include "FODialog.h"
8 #include "ui_FODialog.h"
9 
10 #include <QApplication>
11 #include <QFontMetrics>
12 
13 #include <ScrollDialog.h>
14 
15 #define DEBUG 0
16 
FODialog(QWidget * parent)17 FODialog::FODialog(QWidget *parent) : QDialog(parent), ui(new Ui::FODialog){
18   ui->setupUi(this); //load the designer file
19   ui->label->setText(tr("Calculating"));
20   ui->progressBar->setVisible(false);
21   ui->push_stop->setIcon( LXDG::findIcon("edit-delete","") );
22   WorkThread = new QThread();
23   Worker = new FOWorker();
24     connect(Worker, SIGNAL(startingItem(int,int,QString,QString)), this, SLOT(UpdateItem(int,int,QString,QString)) );
25     connect(Worker, SIGNAL(finished(QStringList)), this, SLOT(WorkDone(QStringList)) );
26   Worker->moveToThread(WorkThread);
27     WorkThread->start();
28 
29   //Make sure this dialog is centered on the parent
30   if(parent!=0){
31     QPoint ctr = parent->mapToGlobal(parent->geometry().center());
32     this->move( ctr.x()-(this->width()/2), ctr.y()-(this->height()/2) );
33   }
34   this->show();
35 }
36 
~FODialog()37 FODialog::~FODialog(){
38   Worker->stopped = true; //just in case it might still be running when closed
39   WorkThread->quit();
40   WorkThread->wait();
41   delete Worker;
42   //delete WorkThread;
43 }
44 
setOverwrite(bool ovw)45 void FODialog::setOverwrite(bool ovw){
46   if(ovw){ Worker->overwrite = 1; }
47   else{ Worker->overwrite = 0; }
48 }
49 
50 //Public "start" functions
RemoveFiles(QStringList paths)51 bool FODialog::RemoveFiles(QStringList paths){
52   Worker->ofiles = paths;
53   Worker->isRM = true;
54   if(CheckOverwrite()){
55     QTimer::singleShot(10,Worker, SLOT(slotStartOperations()));
56     return true;
57   }else{
58     this->close();
59     return false;
60   }
61 }
62 
CopyFiles(QStringList oldPaths,QStringList newPaths)63 bool FODialog::CopyFiles(QStringList oldPaths, QStringList newPaths){
64   //same permissions as old files
65   if(oldPaths.length() == newPaths.length()){
66     Worker->ofiles = oldPaths;
67     Worker->nfiles = newPaths;
68   }
69   Worker->isCP=true;
70   if(CheckOverwrite()){
71     QTimer::singleShot(10,Worker, SLOT(slotStartOperations()));
72     return true;
73   }else{
74     this->close();
75     return false;
76   }
77 }
78 
RestoreFiles(QStringList oldPaths,QStringList newPaths)79 bool FODialog::RestoreFiles(QStringList oldPaths, QStringList newPaths){
80   //user/group rw permissions
81   if(oldPaths.length() == newPaths.length()){
82     Worker->ofiles = oldPaths;
83     Worker->nfiles = newPaths;
84   }
85   Worker->isRESTORE = true;
86   if(CheckOverwrite()){
87     QTimer::singleShot(10,Worker, SLOT(slotStartOperations()));
88     return true;
89   }else{
90     this->close();
91     return false;
92   }
93 }
94 
MoveFiles(QStringList oldPaths,QStringList newPaths)95 bool FODialog::MoveFiles(QStringList oldPaths, QStringList newPaths){
96   //no change in permissions
97   if(oldPaths.length() == newPaths.length()){
98     Worker->ofiles = oldPaths;
99     Worker->nfiles = newPaths;
100   }
101   Worker->isMV=true;
102   if(CheckOverwrite()){
103     QTimer::singleShot(10,Worker, SLOT(slotStartOperations()));
104     return true;
105   }else{
106     this->close();
107     return false;
108   }
109 }
110 
CheckOverwrite()111 bool FODialog::CheckOverwrite(){
112   bool ok = true;
113   //Quick check that a file is not supposed to be moved/copied/restored onto itself
114   if(!Worker->isRM){
115     for(int i=0; i<Worker->nfiles.length(); i++){
116       if(Worker->nfiles[i] == Worker->ofiles[i]){
117         //duplicate - remove it from the queue
118 	Worker->nfiles.removeAt(i); Worker->ofiles.removeAt(i);
119 	i--;
120       }
121     }
122   }
123   if(!Worker->isRM && Worker->overwrite==-1){
124     //Check if the new files already exist, and prompt for action
125     QStringList existing;
126     for(int i=0; i<Worker->nfiles.length(); i++){
127       if(QFile::exists(Worker->nfiles[i])){ existing << Worker->nfiles[i].section("/",-1); }
128     }
129     if(!existing.isEmpty()){
130       //Prompt for whether to overwrite, not overwrite, or cancel
131       QMessageBox dialog(QMessageBox::Question, tr("Overwrite Files?"), tr("Do you want to overwrite the existing files?")+"\n"+tr("Note: It will just add a number to the filename otherwise.")+"\n\n"+existing.join(", "), QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel, this);
132 
133       dialog.setButtonText(QMessageBox::YesToAll, tr("YesToAll"));
134       dialog.setButtonText(QMessageBox::NoToAll, tr("NoToAll"));
135       dialog.setButtonText(QMessageBox::Cancel, tr("Cancel"));
136       dialog.setDefaultButton(QMessageBox::NoToAll);
137       const int ans = dialog.exec();
138       if(ans==QMessageBox::NoToAll){ Worker->overwrite = 0; } //don't overwrite
139       else if(ans==QMessageBox::YesToAll){ Worker->overwrite = 1; } //overwrite
140       else{ qDebug() << " - Cancelled"; Worker->overwrite = -1; ok = false; } //cancel operations
141       if(DEBUG){ qDebug() << " - Overwrite:" << Worker->overwrite; }
142     }
143   }
144   QApplication::processEvents();
145   QApplication::processEvents();
146   return ok;
147 }
148 
UpdateItem(int cur,int tot,QString oitem,QString nitem)149 void FODialog::UpdateItem(int cur, int tot, QString oitem, QString nitem){
150   ui->progressBar->setRange(0,tot);
151   ui->progressBar->setValue(cur);
152   ui->progressBar->setVisible(true);
153   QString msg;
154   if(Worker->isRM){ msg = tr("Removing: %1"); }
155   else if(Worker->isCP){ msg = tr("Copying: %1 to %2"); }
156   else if(Worker->isRESTORE){ msg = tr("Restoring: %1 as %2"); }
157   else if(Worker->isMV){ msg = tr("Moving: %1 to %2"); }
158   if(msg.contains("%2")){
159     msg = msg.arg(oitem.section("/",-1), nitem.section("/",-1));
160   }else{
161     msg = msg.arg(oitem.section("/",-1));
162   }
163   msg = ui->label->fontMetrics().elidedText(msg, Qt::ElideRight, ui->label->width());
164   ui->label->setText( msg );
165 }
166 
WorkDone(QStringList errlist)167 void FODialog::WorkDone(QStringList errlist){
168   if(!errlist.isEmpty()){
169     QString msg;
170     if(Worker->isRM){ msg = tr("Could not remove these files:"); }
171     else if(Worker->isCP){ msg = tr("Could not copy these files:"); }
172     else if(Worker->isRESTORE){ msg = tr("Could not restore these files:"); }
173     else if(Worker->isMV){ msg = tr("Could not move these files:"); }
174     ScrollDialog dlg(this);
175       dlg.setWindowTitle(tr("File Errors"));
176       dlg.setText( msg+"\n\n"+errlist.join("\n") );
177       dlg.exec();
178   }
179   noerrors = errlist.isEmpty();
180   this->close();
181 }
182 
on_push_stop_clicked()183 void FODialog::on_push_stop_clicked(){
184   Worker->stopped = true;
185 }
186 
187 // ===================
188 // ==== FOWorker Class ====
189 // ===================
subfiles(QString dirpath,bool dirsfirst)190 QStringList FOWorker::subfiles(QString dirpath, bool dirsfirst){
191   //NOTE: dirpath (input) is always the first/last item in the output as well!
192   QStringList out;
193   if(dirsfirst){ out << dirpath; }
194   if( QFileInfo(dirpath).isDir() ){
195     QDir dir(dirpath);
196     if(dirsfirst){
197       //Now recursively add any subdirectories and their contents
198       QStringList subdirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden, QDir::NoSort);
199       for(int i=0; i<subdirs.length(); i++){ out << subfiles(dir.absoluteFilePath(subdirs[i]), dirsfirst); }
200     }
201     //List the files
202     QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System, QDir::NoSort);
203     for(int i=0; i<files.length(); i++){ out << dir.absoluteFilePath(files[i]); }
204 
205     if(!dirsfirst){
206       //Now recursively add any subdirectories and their contents
207       QStringList subdirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden, QDir::NoSort);
208       for(int i=0; i<subdirs.length(); i++){ out << subfiles(dir.absoluteFilePath(subdirs[i]), dirsfirst); }
209     }
210   }
211   if(!dirsfirst){ out << dirpath; }
212   return out;
213 }
214 
newFileName(QString path)215 QString FOWorker::newFileName(QString path){
216   int num=1;
217   QString extension = path.section("/",-1).section(".",-1);
218   if( path.section("/",-1) == "."+extension || extension ==path.section("/",-1) ){ extension.clear(); } //just a hidden file without extension or directory
219   if(!extension.isEmpty() ){
220     extension.prepend(".");
221     path.chop(extension.length());
222   }
223   while( QFile::exists(path+"-"+QString::number(num)+extension) ){ num++; }
224   return QString(path+"-"+QString::number(num)+extension);
225 }
226 
removeItem(QString path,bool recursive)227 QStringList FOWorker::removeItem(QString path, bool recursive){
228   //qDebug() << "Remove Path:" << path;
229   QStringList items;
230   if(recursive){ items = subfiles(path,false); }
231   else{ items << path; } //only the given path
232   //qDebug() << " - Subfiles:" << items;
233   QStringList err;
234   for(int i=0; i<items.length(); i++){
235     if(QFileInfo(items[i]).isDir()){
236       if(items[i]==path){
237         //Current Directory Removal
238         QDir dir;
239         if( !dir.rmdir(items[i]) ){ err << items[i]; }
240       }else{
241         //Recursive Directory Removal
242         err << removeItem(items[i], recursive);
243       }
244     }else{
245       //Simple File Removal
246       if( !QFile::remove(items[i]) ){ err << items[i]; }
247     }
248   }
249   return err;
250 }
251 
copyItem(QString oldpath,QString newpath)252 QStringList FOWorker::copyItem(QString oldpath, QString newpath){
253   QStringList err;
254   if(oldpath == newpath){ return err; } //copy something onto itself - just skip it
255   if(QFileInfo(oldpath).isDir()){
256     //Create a new directory with the same name (no way to copy dir+contents)
257     QDir dir;
258     if( !dir.mkpath(newpath) ){ err << oldpath; }
259   }else{
260     //Copy the file and reset permissions as necessary
261     if( !QFile::copy(oldpath, newpath) ){ err << oldpath; }
262     else{
263       if(isCP){
264 	QFile::setPermissions(newpath, QFile::permissions(oldpath));
265 	//Nothing special for copies at the moment (might be something we run into later)
266       }else if(isRESTORE){
267 	QFile::setPermissions(newpath, QFile::permissions(oldpath));
268 	//Nothing special for restores at the moment (might be something we run into later)
269       }
270     }
271   }
272   return err;
273 }
274 
275 // ==== PRIVATE SLOTS ====
slotStartOperations()276 void FOWorker::slotStartOperations(){
277   if(DEBUG){ qDebug() << "Start File operations" << isRM << isCP << isMV << ofiles << nfiles << overwrite; }
278   //Now setup the UI
279   /*ui->progressBar->setRange(0,ofiles.length());
280   ui->progressBar->setValue(0);
281   ui->progressBar->setVisible(true);
282   QApplication::processEvents();*/
283   /*if(!isRM && overwrite==-1){
284     //Check if the new files already exist, and prompt for action
285     QStringList existing;
286     for(int i=0; i<nfiles.length(); i++){
287       if(QFile::exists(nfiles[i])){ existing << nfiles[i].section("/",-1); }
288     }
289     if(!existing.isEmpty()){
290       //Prompt for whether to overwrite, not overwrite, or cancel
291       QMessageBox::StandardButton ans = QMessageBox::question(this, tr("Overwrite Files?"), tr("Do you want to overwrite the existing files?")+"\n"+tr("Note: It will just add a number to the filename otherwise.")+"\n\n"+existing.join(", "), QMessageBox::YesToAll | QMessageBox::NoToAll | QMessageBox::Cancel, QMessageBox::NoToAll);
292       if(ans==QMessageBox::NoToAll){ overwrite = 0; } //don't overwrite
293       else if(ans==QMessageBox::YesToAll){ overwrite = 1; } //overwrite
294       else{ emit finished(QStringList()); return; } //cancel operations
295     }
296   }*/
297 
298   //Get the complete number of items to be operated on (better tracking)
299   QStringList olist, nlist; //old/new list to actually be used (not inputs - modified/added as necessary)
300   for(int i=0; i<ofiles.length() && !stopped; i++){
301     if(isRM){ //only old files
302       olist << subfiles(ofiles[i], false); //dirs need to be last for removals
303     }else if(isCP || isRESTORE){
304       if(QFile::exists(nfiles[i])){
305 	if(overwrite!=1){
306 	  qDebug() << " - Get New Filename:" << nfiles[i];
307 	  nfiles[i] = newFileName(nfiles[i]); //prompt for new file name up front before anything starts
308 	  qDebug() << " -- " << nfiles[i];
309 	}
310       }
311       if(nfiles[i] == ofiles[i] && overwrite==1){
312 	//Trying to copy a file/dir to itself - skip it
313 	continue;
314       }
315       QStringList subs = subfiles(ofiles[i], true); //dirs need to be first for additions
316       for(int s=0; s<subs.length(); s++){
317         olist << subs[s];
318 	QString newsub = subs[s].section(ofiles[i],0,100, QString::SectionSkipEmpty);
319 	    newsub.prepend(nfiles[i]);
320 	nlist << newsub;
321       }
322     }else{ //Move/rename
323       if( nfiles[i].startsWith(ofiles[i]+"/") ){
324 	//This is trying to move a directory into itself  (not possible)
325 	// Example: move "~/mydir" -> "~/mydir/mydir2"
326 	QStringList err; err << tr("Invalid Move") << QString(tr("It is not possible to move a directory into itself. Please make a copy of the directory instead.\n\nOld Location: %1\nNew Location: %2")).arg(ofiles[i], nfiles[i]);
327 	emit finished(err); return;
328       }else{
329 	//Check for existance of the new name
330 	if(QFile::exists(nfiles[i])){
331 	  if(overwrite!=1){
332 	    nfiles[i] = newFileName(nfiles[i]); //prompt for new file name up front before anything starts
333 	  }
334         }
335 	//no changes necessary
336         olist << ofiles[i];
337         nlist << nfiles[i];
338       }
339     }
340   }
341   //Now start iterating over the operations
342   QStringList errlist;
343   for(int i=0; i<olist.length() && !stopped; i++){
344     if(isRM){
345       /*ui->label->setText( QString(tr("Removing: %1")).arg(olist[i].section("/",-1)) );
346       QApplication::processEvents();*/
347       emit startingItem(i+1,olist.length(), olist[i], "");
348       errlist << removeItem(olist[i]);
349     }else if(isCP || isRESTORE){
350       /*ui->label->setText( QString(tr("Copying: %1 to %2")).arg(olist[i].section("/",-1), nlist[i].section("/",-1)) );
351       QApplication::processEvents();*/
352       emit startingItem(i+1,olist.length(), olist[i],nlist[i]);
353       if(QFile::exists(nlist[i])){
354 	if(overwrite==1){
355 	  errlist << removeItem(nlist[i], true); //recursively remove the file/dir since we are supposed to overwrite it
356 	}
357       }
358       //If a parent directory fails to copy, skip all the children as well (they will also fail)
359       //QApplication::processEvents();
360       if( !errlist.contains(olist[i].section("/",0,-1)) ){
361         errlist << copyItem(olist[i], nlist[i]);
362       }
363     }else if(isMV){
364       /*ui->label->setText( QString(tr("Moving: %1 to %2")).arg(ofiles[i].section("/",-1), nfiles[i].section("/",-1)) );
365       QApplication::processEvents();*/
366       emit startingItem(i+1,olist.length(), olist[i], nlist[i]);
367       //Clean up any overwritten files/dirs
368       if(QFile::exists(nlist[i])){
369 	if(overwrite==1){
370 	  errlist << removeItem(nlist[i], true); //recursively remove the file/dir since we are supposed to overwrite it
371 	}
372       }
373       //Perform the move if no error yet (including skipping all children)
374       if( !errlist.contains(olist[i].section("/",0,-1)) ){
375         if( !QFile::rename(ofiles[i], nfiles[i]) ){
376           errlist << ofiles[i];
377         }
378       }
379     }
380     //ui->progressBar->setValue(i+1);
381     //QApplication::processEvents();
382   }
383   //All finished, emit the signal
384   errlist.removeAll(""); //make sure to clear any empty items
385   emit finished(errlist);
386   qDebug() << "Done with File Operations";
387 }
388