Huggle  build:^490^dce1e5c
 All Classes Namespaces Functions Variables Enumerations Enumerator Pages
revertquery.cpp
1 //This program is free software: you can redistribute it and/or modify
2 //it under the terms of the GNU General Public License as published by
3 //the Free Software Foundation, either version 3 of the License, or
4 //(at your option) any later version.
5 
6 //This program is distributed in the hope that it will be useful,
7 //but WITHOUT ANY WARRANTY; without even the implied warranty of
8 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 //GNU General Public License for more details.
10 
11 #include "revertquery.hpp"
12 
13 using namespace Huggle;
14 
15 RevertQuery::RevertQuery()
16 {
17  this->Type = QueryRevert;
18  this->qRevert = NULL;
19  this->edit = NULL;
20  this->PreflightFinished = false;
21  this->RollingBack = false;
22  this->timer = NULL;
23  this->UsingSR = false;
24  this->Token = "";
25  this->qRetrieve = NULL;
26  this->Summary = "";
27  this->MinorEdit = false;
28  this->EditQuerySoftwareRollback = NULL;
29  this->qPreflight = NULL;
30  this->Timeout = Configuration::WriteTimeout;
31 }
32 
33 RevertQuery::RevertQuery(WikiEdit *Edit)
34 {
35  Edit->RegisterConsumer(HUGGLECONSUMER_REVERTQUERY);
36  this->Type = QueryRevert;
37  this->qRevert = NULL;
38  this->edit = Edit;
39  this->PreflightFinished = false;
40  this->RollingBack = false;
41  this->timer = NULL;
42  this->qRetrieve = NULL;
43  this->IgnorePreflightCheck = false;
44  this->UsingSR = false;
45  this->EditQuerySoftwareRollback = NULL;
46  this->Token = "";
47  this->MinorEdit = false;
48  this->Summary = Configuration::GetDefaultRevertSummary(this->edit->User->Username);
49  this->qPreflight = NULL;
50  this->Timeout = 280;
51 }
52 
54 {
55  if (this->Status == StatusProcessing)
56  {
57  Core::DebugLog("Cowardly refusing to double process the query");
58  return;
59  }
60  this->Status = StatusProcessing;
61  if (timer != NULL)
62  {
63  delete timer;
64  }
65  this->StartTime = QDateTime::currentDateTime();
66  this->timer = new QTimer(this);
67  connect(this->timer, SIGNAL(timeout()), this, SLOT(OnTick()));
68  this->timer->start(800);
69  this->RegisterConsumer("RevertQuery::Timer");
70  this->CustomStatus = "Preflight check";
71  this->Preflight();
72 }
73 
75 {
76  if (PreflightFinished && this->qRevert != NULL)
77  {
78  this->qRevert->Kill();
79  } else if (this->qPreflight != NULL)
80  {
81  this->qPreflight->Kill();
82  }
83  this->Status = StatusInError;
84  if (this->Result == NULL)
85  {
86  this->Result = new QueryResult();
87  this->Result->ErrorMessage = "Killed";
88  this->Result->Failed = true;
89  }
90  if (this->qRetrieve != NULL)
91  {
92  this->qRetrieve->UnregisterConsumer(HUGGLECONSUMER_REVERTQUERY);
93  }
94  this->qRetrieve = NULL;
95  this->Exit();
96 }
97 
98 RevertQuery::~RevertQuery()
99 {
100  if (this->edit != NULL)
101  {
102  this->edit->UnregisterConsumer(HUGGLECONSUMER_REVERTQUERY);
103  this->edit->UnregisterConsumer("Core::RevertEdit");
104  }
105  delete timer;
106 }
107 
109 {
110  return this->edit->Page->PageName;
111 }
112 
114 {
115  if (!this->PreflightFinished)
116  {
117  return false;
118  }
119 
120  if (this->Status != StatusDone)
121  {
122  if (CheckRevert())
123  {
124  this->Status = StatusDone;
125  return true;
126  }
127  return false;
128  }
129 
130  return true;
131 }
132 
133 void RevertQuery::OnTick()
134 {
135  if (this->Status != StatusDone)
136  {
137  if (!PreflightFinished)
138  {
139  CheckPreflight();
140  return;
141  }
142 
143  if (!RollingBack)
144  {
145  this->Rollback();
146  return;
147  }
148  }
149 
150  if (Processed())
151  {
152  this->timer->stop();
153  this->UnregisterConsumer("RevertQuery::Timer");
154  }
155 }
156 
157 QString RevertQuery::GetCustomRevertStatus(QString RevertData)
158 {
159  QDomDocument d;
160  d.setContent(RevertData);
161  QDomNodeList l = d.elementsByTagName("error");
162  if (l.count() > 0)
163  {
164  if (l.at(0).toElement().attributes().contains("code"))
165  {
166  QString Error = "";
167  Error = l.at(0).toElement().attribute("code");
168 
169  if (Error == "alreadyrolled")
170  {
171  return "Edit was reverted by someone else - skipping";
172  }
173 
174  if (Error == "onlyauthor")
175  {
176  return "ERROR: Cannot rollback - page only has one author";
177  }
178  return "In error (" + Error +")";
179  }
180  }
181  return "Reverted";
182 }
183 
184 void RevertQuery::Preflight()
185 {
186  // check if there is more edits in queue
187  int x=0;
188  bool failed = false;
189  bool MadeBySameUser = true;
190  while (x < WikiEdit::EditList.count())
191  {
192  WikiEdit *w = WikiEdit::EditList.at(x);
193  if (w != this->edit)
194  {
195  if (w->Page->PageName != this->edit->Page->PageName)
196  {
197  x++;
198  continue;
199  }
200  if (w->Time > this->edit->Time)
201  {
202  if (w->User->Username != this->edit->User->Username)
203  {
204  MadeBySameUser = false;
205  }
206  failed = true;
207  }
208  }
209  x++;
210  }
211  if (failed)
212  {
214  {
215  this->Cancel();
216  return;
217  }
218  QString text;
219  if (MadeBySameUser)
220  {
221  text = ("There are newer edits to " + this->edit->Page->PageName + ", are you sure you want to revert them?");
222  } else
223  {
224  text = ("There are new edits made to " + this->edit->Page->PageName + " by a different user, are you sure you want to revert them all? (it will likely fail anyway because of old token)");
225  }
226  QMessageBox::StandardButton re;
227  re = QMessageBox::question(Core::Main, "Preflight check", text, QMessageBox::Yes|QMessageBox::No);
228  if (re == QMessageBox::No)
229  {
230  this->Cancel();
231  return;
232  } else
233  {
234  this->IgnorePreflightCheck = true;
235  }
236  }
237  this->qPreflight = new ApiQuery();
238  this->qPreflight->SetAction(ActionQuery);
239  this->qPreflight->Parameters = "prop=revisions&rvprop=ids%7Cflags%7Ctimestamp%7Cuser%7Cuserid%7Csize%7Csha1%7Ccomment&rvlimit=20&titles="
240  + QUrl::toPercentEncoding(this->edit->Page->PageName);
241  this->qPreflight->Process();
242 }
243 
244 void RevertQuery::CheckPreflight()
245 {
246  if (this->IgnorePreflightCheck)
247  {
248  this->PreflightFinished = true;
249  return;
250  }
251  if (this->qPreflight == NULL)
252  {
253  return;
254  }
255  if (!this->qPreflight->Processed())
256  {
257  return;
258  }
259  if (this->qPreflight->Result->Failed)
260  {
261  Core::Log("Failed to preflight check the edit: " + this->qPreflight->Result->ErrorMessage);
262  this->Kill();
263  this->Status = StatusDone;
264  this->Result = new QueryResult();
265  this->Result->Failed = true;
266  return;
267  }
268  QDomDocument d;
269  d.setContent(this->qPreflight->Result->Data);
270  QDomNodeList l = d.elementsByTagName("rev");
271  int x=0;
272  bool MadeBySameUser = true;
273  bool passed = true;
274  while (x < l.count())
275  {
276  QDomElement e = l.at(x).toElement();
277  if (e.attributes().contains("revid"))
278  {
279  if (edit->RevID == e.attribute("revid").toInt())
280  {
281  x++;
282  continue;
283  }
284  } else
285  {
286  x++;
287  continue;
288  }
289  if (this->edit->RevID != WIKI_UNKNOWN_REVID && e.attribute("revid").toInt() > edit->RevID)
290  {
291  passed = false;
292  }
293  x++;
294  }
295 
296  if (!passed)
297  {
298  QString text = ":)";
299  if (MadeBySameUser)
300  {
301  text = ("There are newer edits to " + this->edit->Page->PageName + ", are you sure you want to revert them");
302  } else
303  {
304  text = ("There are new edits made to " + this->edit->Page->PageName + " by a different user, are you sure you want to revert them all? (it will likely fail anyway because of old token)");
305  }
307  {
308  this->Cancel();
309  return;
310  }
311  QMessageBox::StandardButton re;
312  re = QMessageBox::question(Core::Main, "Preflight check", text, QMessageBox::Yes|QMessageBox::No);
313  if (re == QMessageBox::No)
314  {
315  // abort
316  this->Exit();
317  this->CustomStatus = "Stopped";
318  this->Result = new QueryResult();
319  this->Result->Failed = true;
320  this->Result->ErrorMessage = "User requested to abort this";
321  this->Status = StatusDone;
322  this->PreflightFinished = true;
323  return;
324  }
325  }
326 
327  this->PreflightFinished = true;
328 }
329 
330 bool RevertQuery::CheckRevert()
331 {
332  if (this->UsingSR)
333  {
334  return ProcessRevert();
335  }
336  if (this->qRevert == NULL)
337  {
338  return false;
339  }
340  if (!this->qRevert->Processed())
341  {
342  return false;
343  }
344  this->CustomStatus = RevertQuery::GetCustomRevertStatus(this->qRevert->Result->Data);
345  if (this->CustomStatus != "Reverted")
346  {
347  Core::Log("Unable to revert " + this->qRevert->Target + ": " + this->CustomStatus);
348  qRevert->Result->Failed = true;
349  qRevert->Result->ErrorMessage = CustomStatus;
350  this->Result = new QueryResult();
352  this->Result->Failed = true;
353  } else
354  {
355  HistoryItem item;
356  this->Result = new QueryResult();
357  this->Result->Data = this->qRevert->Result->Data;
358  item.Target = this->qRevert->Target;
359  item.Type = HistoryRollback;
360  item.Result = "Success";
361  if (Core::Main != NULL)
362  {
363  Core::Main->_History->Prepend(item);
364  }
365  }
366  this->qRevert->UnregisterConsumer(HUGGLECONSUMER_REVERTQUERY);
367  this->qRevert = NULL;
368  return true;
369 }
370 
371 void RevertQuery::Cancel()
372 {
373  this->Exit();
374  this->CustomStatus = "Stopped";
375  this->Result = new QueryResult();
376  this->Result->Failed = true;
377  this->Result->ErrorMessage = "User requested to abort this";
378  this->Status = StatusDone;
379  this->PreflightFinished = true;
380 }
381 
383 {
384  if (this->EditQuerySoftwareRollback != NULL)
385  {
386  if (EditQuerySoftwareRollback->Processed() == false)
387  {
388  return false;
389  }
390  this->EditQuerySoftwareRollback->UnregisterConsumer(HUGGLECONSUMER_REVERTQUERY);
391  return true;
392  }
393 
394  if (this->qPreflight == NULL)
395  {
396  return false;
397  }
398 
399  if (this->qPreflight->Processed() != true)
400  {
401  return false;
402  }
403 
404  if (this->qPreflight->Result->Failed)
405  {
406  Core::Log("Failed to retrieve a list of edits made to this page: " + this->qPreflight->Result->ErrorMessage);
407  this->Kill();
408  this->Status = StatusDone;
409  this->Result = new QueryResult();
410  this->Result->ErrorMessage = "Failed to retrieve a list of edits made to this page: " + this->qPreflight->Result->ErrorMessage;
411  this->Result->Failed = true;
412  return true;
413  }
414  QDomDocument d;
415  d.setContent(this->qPreflight->Result->Data);
416  QDomNodeList l = d.elementsByTagName("rev");
417  // we need to find a first revision that is made by a different user
418  // but first we need to check if last revision was actually made by this user, because if not
419  // it's possible that someone else already reverted them
420  if (l.count() == 0)
421  {
422  // if we have absolutely no revisions in the result, it's pretty fucked
423  Core::Log("Failed to retrieve a list of edits made to this page, query returned no data");
424  this->Kill();
425  this->Status = StatusDone;
426  this->Result = new QueryResult();
427  this->Result->ErrorMessage = "Failed to retrieve a list of edits made to this page, query returned no data";
428  this->Result->Failed = true;
429  return true;
430  }
431  QDomElement latest = l.at(0).toElement();
432  // if the latest revid doesn't match our revid it means that someone made an edit
433  bool passed = true;
434  int depth = 0;
435  int x = 0;
436  while (x < l.count())
437  {
438  QDomElement e = l.at(x).toElement();
439  if (e.attributes().contains("revid"))
440  {
441  if (edit->RevID == e.attribute("revid").toInt())
442  {
443  x++;
444  continue;
445  }
446  } else
447  {
448  x++;
449  continue;
450  }
451  if (this->edit->RevID != WIKI_UNKNOWN_REVID && e.attribute("revid").toInt() > edit->RevID)
452  {
453  passed = false;
454  }
455  x++;
456  }
457  if (!passed)
458  {
459  Core::Log("Unable to revert the page " + this->edit->Page->PageName + " because it was edited meanwhile");
460  this->Kill();
461  this->Status = StatusDone;
462  this->Result = new QueryResult();
463  this->Result->ErrorMessage = "Unable to revert the page " + this->edit->Page->PageName + " because it was edited meanwhile";
464  this->Result->Failed = true;
465  return true;
466  }
467 
468  // now we need to find the first revision that was done by some different user
469  x = 0;
470  int RevID = WIKI_UNKNOWN_REVID;
471  QString content = "";
472  QString target = "";
473  // FIXME: this list needs to be sorted by RevID
474  while (x < l.count())
475  {
476  QDomElement e = l.at(x).toElement();
477  if (!e.attributes().contains("revid") || !e.attributes().contains("user"))
478  {
479  // this is fucked up piece of shit
480  Core::Log("Unable to revert the page " + this->edit->Page->PageName + " because mediawiki returned some non-sense");
481  this->Kill();
482  this->Status = StatusDone;
483  this->Result = new QueryResult();
484  this->Result->ErrorMessage = "Unable to revert the page " + this->edit->Page->PageName + " because mediawiki returned some non-sense";
485  this->Result->Failed = true;
486  Core::DebugLog("Nonsense: " + this->qPreflight->Result->Data);
487  return true;
488  }
489  if (e.attribute("user") != this->edit->User->Username)
490  {
491  // we got it
492  RevID = e.attribute("revid").toInt();
493  target = e.attribute("user");
494  content = e.text();
495  break;
496  }
497  depth++;
498  x++;
499  }
500  // let's check if depth isn't too low
501  if (depth == 0)
502  {
503  // something is wrong
504  /// \todo LOCALIZE ME
505  Core::Log("Unable to revert the page " + this->edit->Page->PageName + " because it was edited meanwhile");
506  this->Kill();
507  this->Status = StatusDone;
508  this->Result = new QueryResult();
509  this->Result->ErrorMessage = "Unable to revert the page " + this->edit->Page->PageName + " because it was edited meanwhile";
510  this->Result->Failed = true;
511  return true;
512  }
513  // now we need to change the content of page
514  this->qRetrieve = new ApiQuery();
515  // localize me
516  QString summary = Configuration::LocalConfig_SoftwareRevertDefaultSummary;
517  summary = summary.replace("$1", this->edit->User->Username)
518  .replace("$2", target)
519  .replace("$3", QString::number(depth))
520  .replace("$4", QString::number(RevID));
521  EditQuerySoftwareRollback = Core::EditPage(this->edit->Page, content, summary, MinorEdit);
522  this->EditQuerySoftwareRollback->RegisterConsumer(HUGGLECONSUMER_REVERTQUERY);
523  /// \todo LOCALIZE ME
524  this->CustomStatus = "Editing page";
525  return false;
526 }
527 
529 {
530  if (this->RollingBack)
531  {
532  // wtf happened
533  Core::DebugLog("Multiple request to rollback same query");
534  return;
535  }
536  this->RollingBack = true;
537 
538  if (this->Summary == "")
539  {
540  this->Summary = Configuration::GetDefaultRevertSummary(this->edit->User->Username);
541  }
542 
543  if (this->Summary.contains("$1"))
544  {
545  this->Summary = this->Summary.replace("$1", edit->User->Username);
546  }
547 
548  this->edit->User->setBadnessScore(this->edit->User->getBadnessScore() + 200);
549  WikiUser::UpdateUser(edit->User);
550 
551  if (this->UsingSR)
552  {
553  Revert();
554  return;
555  }
556  if (!Configuration::Rights.contains("rollback"))
557  {
558  /// \todo LOCALIZE ME
559  Core::Log("You don't have rollback rights, fallback to software rollback");
560  this->UsingSR = true;
561  this->Revert();
562  return;
563  }
564 
565  if (this->Token == "")
566  {
567  this->Token = this->edit->RollbackToken;
568  }
569 
570  if (this->Token == "")
571  {
572  /// \todo LOCALIZE ME
573  Core::Log("ERROR, unable to rollback, because the rollback token was empty: " + this->edit->Page->PageName);
574  this->Result = new QueryResult();
575  this->Result->Failed = true;
576  /// \todo LOCALIZE ME
577  this->Result->ErrorMessage = "ERROR, unable to rollback, because the rollback token was empty: " + this->edit->Page->PageName;
578  this->Status = StatusDone;
579  this->Exit();
580  return;
581  }
582  this->qRevert = new ApiQuery();
583  this->qRevert->SetAction(ActionRollback);
584  QString token = this->Token;
585  if (token.endsWith("+\\"))
586  {
587  token = QUrl::toPercentEncoding(token);
588  }
589  this->qRevert->Parameters = "title=" + QUrl::toPercentEncoding(edit->Page->PageName)
590  + "&token=" + token
591  + "&user=" + QUrl::toPercentEncoding(edit->User->Username)
592  + "&summary=" + QUrl::toPercentEncoding(this->Summary);
593  this->qRevert->Target = edit->Page->PageName;
594  this->qRevert->UsingPOST = true;
595  this->qRevert->RegisterConsumer("RevertQuery");
596  if (Configuration::Verbosity > 0)
597  {
598  Core::AppendQuery(this->qRevert);
599  }
600  /// \todo LOCALIZE ME
601  this->CustomStatus = "Rolling back " + edit->Page->PageName;
602  Core::DebugLog("Rolling back " + edit->Page->PageName);
603  this->qRevert->Process();
604 }
605 
606 void RevertQuery::Revert()
607 {
608  // Get a list of edits made to this page
609  this->qPreflight = new ApiQuery();
610  this->qPreflight->SetAction(ActionQuery);
611  this->qPreflight->Parameters = "prop=revisions&rvprop=" + QUrl::toPercentEncoding("ids|flags|timestamp|user|userid|content|size|sha1|comment")
612  + "&rvlimit=20&titles=" + QUrl::toPercentEncoding(this->edit->Page->PageName);
613  this->qPreflight->Process();
614 }
615 
616 void RevertQuery::Exit()
617 {
618  if (this->timer != NULL)
619  {
620  this->timer->stop();
621  }
622  if (EditQuerySoftwareRollback != NULL)
623  {
624  EditQuerySoftwareRollback->UnregisterConsumer(HUGGLECONSUMER_REVERTQUERY);
625  EditQuerySoftwareRollback = NULL;
626  }
627  this->UnregisterConsumer("RevertQuery::Timer");
628  if (this->qRevert != NULL)
629  {
630  this->qRevert->UnregisterConsumer(HUGGLECONSUMER_REVERTQUERY);
631  }
632 }
static void Log(QString Message)
Write text to terminal as well as ring log.
Definition: core.cpp:563
History * _History
Pointer to history.
Definition: mainwindow.hpp:142
QString Target
This is optional property which contains a label of target this query is for.
Definition: apiquery.hpp:98
int RevID
Revision ID.
Definition: wikiedit.hpp:105
void Kill()
Terminate the query.
Definition: apiquery.cpp:229
bool UsingSR
Whether software rollback should be used instead of regular rollback.
Definition: revertquery.hpp:45
virtual bool Processed()
Returns true in case that query is processed.
Definition: query.cpp:45
void UnregisterConsumer(const int consumer)
This function will remove a string which prevent the object from being removed.
Definition: collectable.cpp:68
Result of query.
Definition: queryresult.hpp:19
void RegisterConsumer(const int consumer)
Registers a consumer.
Definition: collectable.cpp:57
static void AppendQuery(Query *item)
Insert a query to internal list of running queries, so that they can be watched This will insert it t...
Definition: core.cpp:557
QString Username
Username.
Definition: wikiuser.hpp:52
long getBadnessScore(bool _resync=true)
Retrieve a badness score for current user, see WikiUser::BadnessScore for more.
Definition: wikiuser.cpp:271
void Process()
Run.
Definition: apiquery.cpp:138
void Kill()
Terminates a query.
Definition: revertquery.cpp:74
static QList< WikiEdit * > EditList
This list contains reference to all existing edits in memory.
Definition: wikiedit.hpp:71
QueryType Type
Type of a query.
Definition: query.hpp:80
QString QueryTargetToString()
Return a target of a query.
bool Processed()
Returns true in case that query is processed.
void Prepend(HistoryItem item)
Insert a new item to top of list.
Definition: history.cpp:37
void SetAction(const Action action)
Change the action type.
Definition: apiquery.cpp:185
static unsigned int Verbosity
Verbosity for debugging to terminal etc, can be switched with parameter –verbosity.
bool UsingPOST
Whether the query will submit parameters using POST data.
Definition: apiquery.hpp:77
static int WriteTimeout
Timeout for write / update queries.
bool Processed()
Returns true in case that query is processed.
Definition: editquery.cpp:49
HistoryType Type
Type of item.
Definition: history.hpp:46
void Process()
Execute query.
Definition: revertquery.cpp:53
WikiUser * User
User who changed the page.
Definition: wikiedit.hpp:89
static void DebugLog(QString Message, unsigned int Verbosity=1)
This log is only shown if verbosity is same or larger than requested verbosity.
Definition: core.cpp:641
static bool AutomaticallyResolveConflicts
Resolve edit conflict without asking user.
bool IgnorePreflightCheck
Rollback with no check if it&#39;s a good idea or not (revert even whitelisted users, sysops etc) ...
Definition: revertquery.hpp:48
QString ErrorMessage
If query is in error the reason for error is stored here.
Definition: queryresult.hpp:27
QString Parameters
Parameters for action, for example page title.
Definition: apiquery.hpp:84
History consist of these items.
Definition: history.hpp:35
Wiki edit.
Definition: wikiedit.hpp:67
QString CustomStatus
Custom status.
Definition: query.hpp:76
static MainWindow * Main
Pointer to main.
Definition: core.hpp:111
QString PageName
Name of page.
Definition: wikipage.hpp:48
This class can be used to execute any kind of api query on any wiki.
Definition: apiquery.hpp:55
static QString GetDefaultRevertSummary(QString source)
GetDefaultRevertSummary Retrieve default summary.
WikiPage * Page
Page that was changed by edit.
Definition: wikiedit.hpp:87
static void UpdateUser(WikiUser *us)
Update a list of problematic users.
Definition: wikiuser.cpp:59
QString Data
Data retrieved by query.
Definition: queryresult.hpp:25
QueryResult * Result
Result of query, see documentation of QueryResult for more.
Definition: query.hpp:68