redmine.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. /*
  2. mephi-tasks — a client to NRNU MEPhI Redmine server
  3. Copyright (C) 2015 Dmitry Yu Okunev <dyokunev@ut.mephi.ru> 0x8E30679C
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include "redmine.h"
  16. #include "common.h"
  17. #include <QDir>
  18. #include <QFile>
  19. #include <QStandardPaths>
  20. #include <QMessageBox>
  21. Redmine::Redmine()
  22. {
  23. if ( ! QSslSocket::supportsSsl() ) {
  24. qDebug ( "! QSslSocket::supportsSsl()" );
  25. QMessageBox messageBox;
  26. messageBox.critical(0, "Error", "Отсутствует поддержка SSL. Проверьте наличие библиотек libeay32.dll и ssleay32.dll, либо установите пакет OpenSSL.");
  27. }
  28. this->setBaseUrl ( SERVER_URL );
  29. #ifdef __MOBILE__
  30. this->cacheBasePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
  31. #else
  32. this->cacheBasePath = "cache/";
  33. #endif
  34. }
  35. Redmine::~Redmine()
  36. {
  37. this->cacheSave();
  38. }
  39. QString Redmine::apiKey ( QString apiKey )
  40. {
  41. return this->_apiKey = apiKey;
  42. }
  43. QString Redmine::apiKey()
  44. {
  45. return this->_apiKey;
  46. }
  47. /*
  48. void Redmine::init_quitStatuses(QNetworkReply *reply, QJsonDocument *statuses, void *_null) {
  49. (void)_null; (void)reply; (void)statuses;
  50. this->initBarrier_jobsDone++;
  51. if (this->initBarrier_jobsDone >= 3)
  52. this->initBarrier.exit();
  53. }
  54. void Redmine::init_quitMe(QNetworkReply *reply, QJsonDocument *me, void *_null) {
  55. (void)_null; (void)reply; (void)statuses;
  56. this->initBarrier_jobsDone++;
  57. if (this->initBarrier_jobsDone >= 3)
  58. this->initBarrier.exit();
  59. }
  60. void Redmine::init_quitMyProject(QNetworkReply *reply, QJsonDocument *myProject, void *_null) {
  61. (void)_null; (void)reply; (void)statuses;
  62. this->initBarrier_jobsDone++;
  63. if (this->initBarrier_jobsDone >= 3)
  64. this->initBarrier.exit();
  65. }
  66. */
  67. int Redmine::init()
  68. {
  69. if ( this->apiKey().length() > 0 ) {
  70. this->setAuth ( this->_apiKey );
  71. }
  72. connect ( this, SIGNAL ( requestFinished ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ),
  73. this, SLOT ( callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
  74. this->initBarrier_jobsDone = 0;
  75. QNetworkReply *updateIssueStatusesReply = this->updateIssueStatuses();
  76. // Wait until issue statuses will be received:
  77. connect ( updateIssueStatusesReply, SIGNAL ( finished() ), &this->initBarrier, SLOT ( quit() ) );
  78. this->initBarrier.exec();
  79. QNetworkReply *updateMeReply = this->updateMe();
  80. // Wait until infomation about current user will be received:
  81. connect ( updateMeReply, SIGNAL ( finished() ), &this->initBarrier, SLOT ( quit() ) );
  82. this->initBarrier.exec();
  83. QNetworkReply *updateMyProjectReply = this->updateMyProject();
  84. // Wait until infomation about current user will be received:
  85. connect ( updateMyProjectReply, SIGNAL ( finished() ), &this->initBarrier, SLOT ( quit() ) );
  86. this->initBarrier.exec();
  87. this->cacheLoad();
  88. return 0;
  89. }
  90. /********* cache{Load,Save} *********/
  91. void Redmine::cacheLoad()
  92. {
  93. QDir dir = QDir ( this->cacheBasePath + this->apiKey() );
  94. QFileInfoList fileInfoList = dir.entryInfoList ( QDir::Files );
  95. for ( int i = 0; i < fileInfoList.size(); ++i ) {
  96. QFileInfo fileInfo = fileInfoList.at ( i );
  97. QString filePath = fileInfo.absoluteFilePath();
  98. QString fileName = fileInfo.fileName();
  99. QFile *file = new QFile ( filePath );
  100. if ( !file->open ( QIODevice::ReadOnly ) ) {
  101. qDebug ( "cannot open file \"%s\" for reading", filePath.toUtf8().data() );
  102. continue;
  103. }
  104. QByteArray jsonText = qUncompress ( file->readAll() );
  105. QString uri = fileName.replace ( "!", "/" );
  106. this->cache[uri] = QJsonDocument::fromJson ( jsonText );
  107. delete file;
  108. }
  109. return;
  110. }
  111. void Redmine::cacheSave()
  112. {
  113. QHash<QString, QJsonDocument>::iterator i;
  114. QDir dir = QDir();
  115. if ( !dir.mkpath ( "cache/" + this->apiKey() ) ) {
  116. qDebug ( "cannot create cache directory" );
  117. return;
  118. }
  119. for ( i = this->cache.begin(); i != this->cache.end(); ++i ) {
  120. QByteArray jsonText = qCompress ( i.value().toJson() );
  121. QString fileName = i.key();
  122. fileName = fileName.replace ( "/", "!" );
  123. QString filePath = "cache/" + this->apiKey() + "/" + fileName;
  124. QFile *file = new QFile ( dir.filePath ( filePath ) );
  125. if ( !file->open ( QIODevice::WriteOnly ) ) {
  126. qDebug ( "cannot open file \"%s\" for writting", fileName.toUtf8().data() );
  127. continue;
  128. }
  129. file->write ( jsonText );
  130. file->close();
  131. delete file;
  132. }
  133. return;
  134. }
  135. /********* /cache{Load,Save} *********/
  136. /********* request *********/
  137. struct redmine_request_callback_arg {
  138. void *obj_ptr;
  139. Redmine::callback_t callback;
  140. void *arg;
  141. bool free_arg;
  142. QString signature;
  143. };
  144. void Redmine::callback_cache ( QNetworkReply *reply, QJsonDocument *obj, void *_real_callback_info )
  145. {
  146. struct redmine_request_callback_arg *real_callback_info =
  147. ( struct redmine_request_callback_arg * ) _real_callback_info;
  148. qDebug ( "Got a reply for \"%s\"", real_callback_info->signature.toStdString().c_str() );
  149. if ( real_callback_info->callback != NULL )
  150. this->callback_call ( real_callback_info->obj_ptr, real_callback_info->callback, reply, obj, real_callback_info->arg );
  151. //real_callback_info->callback(reply, obj, real_callback_info->arg);
  152. if ( real_callback_info->free_arg )
  153. free ( real_callback_info->arg );
  154. this->cache.insert ( real_callback_info->signature, *obj );
  155. delete real_callback_info;
  156. return;
  157. }
  158. QNetworkReply *Redmine::request ( RedmineClient::EMode mode,
  159. QString uri,
  160. void *obj_ptr,
  161. callback_t callback,
  162. void *callback_arg,
  163. bool free_arg,
  164. const QString &getParams,
  165. const QByteArray &requestData,
  166. bool useCache
  167. )
  168. {
  169. if ( mode != RedmineClient::EMode::GET || useCache == false ) {
  170. return this->sendRequest ( uri, RedmineClient::JSON, mode, obj_ptr,
  171. ( RedmineClient::callback_t ) callback, callback_arg, free_arg, getParams, requestData );
  172. }
  173. qDebug ( "obj_ptr == %p", obj_ptr );
  174. QString signature = uri + "?" + getParams;
  175. if ( !this->cache[signature].isEmpty() ) {
  176. qDebug ( "Found cache for \"%s\"", signature.toStdString().c_str() );
  177. if (obj_ptr != NULL && callback != NULL)
  178. this->callback_call ( obj_ptr, callback, NULL, &this->cache[signature], callback_arg );
  179. }
  180. /*
  181. QPair <QByteArray, QByteArray> hashes;
  182. hashes.first = QCryptographicHash::hash(signature, QCryptographicHash::Md5);
  183. hashes.second = QCryptographicHash::hash(signature, QCryptographicHash::Sha1);
  184. */
  185. {
  186. struct redmine_request_callback_arg *real_callback_info = new struct redmine_request_callback_arg;
  187. //(struct redmine_request_callback_arg *)calloc(1, sizeof(*real_callback_info));
  188. real_callback_info->obj_ptr = obj_ptr;
  189. real_callback_info->callback = callback;
  190. real_callback_info->arg = callback_arg;
  191. real_callback_info->free_arg = free_arg;
  192. real_callback_info->signature = signature;
  193. qDebug ( "Makeing a request for \"%s\"", signature.toStdString().c_str() );
  194. return this->sendRequest ( uri, RedmineClient::JSON, mode, this,
  195. ( RedmineClient::callback_t ) &Redmine::callback_cache, real_callback_info, false, getParams, requestData );
  196. }
  197. }
  198. QNetworkReply *Redmine::request ( RedmineClient::EMode mode,
  199. QString uri,
  200. void *obj_ptr,
  201. callback_t callback,
  202. void *callback_arg,
  203. bool free_arg,
  204. const QString &getParams,
  205. const QJsonObject &requestJSON
  206. )
  207. {
  208. return this->request ( mode, uri, obj_ptr, callback, callback_arg, free_arg, getParams, QJsonDocument ( requestJSON ).toJson() );
  209. }
  210. QNetworkReply *Redmine::request ( RedmineClient::EMode mode,
  211. QString uri,
  212. void *obj_ptr,
  213. callback_t callback,
  214. void *callback_arg,
  215. bool free_arg,
  216. const QString &getParams,
  217. const QVariantMap &requestVMap
  218. )
  219. {
  220. return this->request ( mode, uri, obj_ptr, callback, callback_arg, free_arg, getParams, QJsonObject::fromVariantMap ( requestVMap ) );
  221. }
  222. /********* /request *********/
  223. /********* updateMe *********/
  224. struct redmine_updateMe_callback_arg {
  225. Redmine::callback_t real_callback;
  226. void *arg;
  227. };
  228. void Redmine::updateMe_callback ( QNetworkReply *reply, QJsonDocument *me_doc, void *_arg )
  229. {
  230. ( void ) reply;
  231. struct redmine_updateMe_callback_arg *arg =
  232. ( struct redmine_updateMe_callback_arg * ) _arg;
  233. callback_t callback;
  234. void *callback_arg;
  235. this->_me = me_doc->object() ["user"].toObject();
  236. if ( _arg != NULL ) {
  237. callback = arg->real_callback;
  238. callback_arg = arg->arg;
  239. this->callback_call ( NULL, callback, reply, me_doc, callback_arg );
  240. }
  241. return;
  242. }
  243. QNetworkReply *Redmine::updateMe ( callback_t callback, void *arg )
  244. {
  245. struct redmine_updateMe_callback_arg *wrapper_arg = NULL;
  246. if ( callback != NULL ) {
  247. wrapper_arg =
  248. ( struct redmine_updateMe_callback_arg * )
  249. calloc ( 1, sizeof ( struct redmine_updateMe_callback_arg ) );
  250. wrapper_arg->real_callback = callback;
  251. wrapper_arg->arg = arg;
  252. }
  253. return this->request (
  254. GET,
  255. "users/current",
  256. this,
  257. &Redmine::updateMe_callback,
  258. wrapper_arg,
  259. true,
  260. NULL,
  261. NULL,
  262. false );
  263. }
  264. /********* /updateMe *********/
  265. /********* updateMyProject *********/
  266. struct redmine_updateMyProject_callback_arg {
  267. Redmine::callback_t real_callback;
  268. void *arg;
  269. };
  270. void Redmine::updateMyProject_callback ( QNetworkReply *reply, QJsonDocument *myProject_doc, void *_arg )
  271. {
  272. ( void ) reply;
  273. struct redmine_updateMyProject_callback_arg *arg =
  274. ( struct redmine_updateMyProject_callback_arg * ) _arg;
  275. callback_t callback;
  276. void *callback_arg;
  277. this->_myProject = myProject_doc->object() ["project"].toObject();
  278. if ( _arg != NULL ) {
  279. callback = arg->real_callback;
  280. callback_arg = arg->arg;
  281. this->callback_call ( NULL, callback, reply, myProject_doc, callback_arg );
  282. }
  283. return;
  284. }
  285. QNetworkReply *Redmine::updateMyProject ( callback_t callback, void *arg )
  286. {
  287. struct redmine_updateMyProject_callback_arg *wrapper_arg = NULL;
  288. if ( callback != NULL ) {
  289. wrapper_arg =
  290. ( struct redmine_updateMyProject_callback_arg * )
  291. calloc ( 1, sizeof ( struct redmine_updateMyProject_callback_arg ) );
  292. wrapper_arg->real_callback = callback;
  293. wrapper_arg->arg = arg;
  294. }
  295. return this->request (
  296. GET,
  297. "projects/" + this->me() ["login"].toString(),
  298. this,
  299. &Redmine::updateMyProject_callback,
  300. wrapper_arg,
  301. true,
  302. NULL,
  303. NULL,
  304. false );
  305. }
  306. /********* /updateMyProject *********/
  307. /********* updateIssueStatuses *********/
  308. struct redmine_updateIssueStatuses_callback_arg {
  309. Redmine::callback_t real_callback;
  310. void *arg;
  311. };
  312. void Redmine::set_issue_status ( int status_id, QJsonObject status )
  313. {
  314. this->issue_statuses.insert ( status_id, status );
  315. return;
  316. }
  317. void Redmine::clear_issue_status()
  318. {
  319. this->issue_statuses.clear();
  320. }
  321. QJsonObject Redmine::get_issue_status ( int issue_status_id )
  322. {
  323. return this->issue_statuses[issue_status_id];
  324. }
  325. QJsonObject Redmine::get_issue_status ( QJsonValueRef issues_status_json )
  326. {
  327. return this->get_issue_status ( issues_status_json.toObject() ["id"].toInt() );
  328. }
  329. QHash<int, QJsonObject> Redmine::get_available_statuses_for ( int issue_id )
  330. {
  331. ( void ) issue_id;
  332. return this->issue_statuses;
  333. }
  334. void Redmine::updateIssueStatuses_callback ( QNetworkReply *reply, QJsonDocument *statuses_doc, void *_arg )
  335. {
  336. ( void ) reply;
  337. struct redmine_updateIssueStatuses_callback_arg *arg =
  338. ( struct redmine_updateIssueStatuses_callback_arg * ) _arg;
  339. callback_t callback;
  340. void *callback_arg;
  341. int statuses_count = 0;
  342. QJsonArray statuses = statuses_doc->object() ["issue_statuses"].toArray();
  343. this->clear_issue_status();
  344. foreach ( const QJsonValue & status_val, statuses ) {
  345. QJsonObject status = status_val.toObject();
  346. status["position"] = statuses_count++;
  347. this->set_issue_status ( status["id"].toInt(), status );
  348. }
  349. if ( arg != NULL ) {
  350. callback = arg->real_callback;
  351. callback_arg = arg->arg;
  352. this->callback_call ( NULL, callback, reply, statuses_doc, callback_arg );
  353. }
  354. return;
  355. }
  356. QNetworkReply *Redmine::updateIssueStatuses ( callback_t callback, void *arg )
  357. {
  358. struct redmine_updateIssueStatuses_callback_arg *wrapper_arg = NULL;
  359. if ( callback != NULL ) {
  360. wrapper_arg =
  361. ( struct redmine_updateIssueStatuses_callback_arg * )
  362. calloc ( 1, sizeof ( struct redmine_updateIssueStatuses_callback_arg ) );
  363. wrapper_arg->real_callback = callback;
  364. wrapper_arg->arg = arg;
  365. }
  366. return this->request ( GET,
  367. "issue_statuses",
  368. this,
  369. &Redmine::updateIssueStatuses_callback,
  370. wrapper_arg,
  371. true,
  372. NULL,
  373. NULL,
  374. false );
  375. }
  376. /********* /updateIssueStatuses *********/
  377. /********* get_memberships *********/
  378. QNetworkReply *Redmine::get_memberships ( callback_t callback,
  379. void *arg, bool free_arg )
  380. {
  381. return this->request ( GET, "memberships", NULL, callback, arg, free_arg, "limit=5000" );
  382. }
  383. /********* /get_memberships *********/
  384. /********* get_enumerations *********/
  385. QNetworkReply *Redmine::get_enumerations ( callback_t callback,
  386. void *arg, bool free_arg )
  387. {
  388. return this->request ( GET, "enumerations", NULL, callback, arg, free_arg, "limit=5000" );
  389. }
  390. /********* /get_enumerations *********/
  391. /********* get_roles *********/
  392. QNetworkReply *Redmine::get_roles ( callback_t callback,
  393. void *arg, bool free_arg )
  394. {
  395. return this->request ( GET, "roles", NULL, callback, arg, free_arg, "limit=5000" );
  396. }
  397. /********* /get_roles *********/
  398. /********* get_issues *********/
  399. QNetworkReply *Redmine::get_issues ( void *obj_ptr, callback_t callback,
  400. void *arg, bool free_arg, QString customFilters )
  401. {
  402. return this->request ( GET, "issues", obj_ptr, callback, arg, free_arg, customFilters + "&" + settings.issuesFilter );
  403. }
  404. QNetworkReply *Redmine::get_issues ( callback_t callback,
  405. void *arg, bool free_arg, QString customFilters )
  406. {
  407. return this->get_issues ( NULL, callback, arg, free_arg, customFilters );
  408. }
  409. /********* /get_issues *********/
  410. /********* get_time_entries *********/
  411. QNetworkReply *Redmine::get_time_entries ( void *obj_ptr, callback_t callback,
  412. void *arg, bool free_arg, QString filterOptions )
  413. {
  414. return this->request ( GET, "time_entries", obj_ptr, callback, arg, free_arg, filterOptions );
  415. }
  416. QNetworkReply *Redmine::get_time_entries ( callback_t callback,
  417. void *arg, bool free_arg, QString filterOptions )
  418. {
  419. qDebug("deprecated variant of Redmine::get_time_entries had been called");
  420. return this->get_time_entries ( NULL, callback, arg, free_arg, filterOptions );
  421. }
  422. QNetworkReply *Redmine::get_time_entries ( int userId, void *obj_ptr, callback_t callback,
  423. void *arg, bool free_arg, QString filterOptions )
  424. {
  425. QString userId_str = ( userId == 0 ? "" : QString ( "user_id=" + QString::number(userId) ) );
  426. return this->get_time_entries ( obj_ptr, callback, arg, free_arg, userId_str+"&"+filterOptions );
  427. }
  428. /********* /get_time_entries *********/
  429. /********* get_projects *********/
  430. QNetworkReply *Redmine::get_projects ( void *obj_ptr, callback_t callback,
  431. void *arg, bool free_arg, QString filterOptions )
  432. {
  433. return this->request ( GET, "projects", obj_ptr, callback, arg, free_arg, "limit=500&" + filterOptions );
  434. }
  435. QNetworkReply *Redmine::get_projects ( callback_t callback,
  436. void *arg, bool free_arg, QString filterOptions )
  437. {
  438. return this->get_projects ( NULL, callback, arg, free_arg, filterOptions );
  439. }
  440. /********* /get_projects *********/
  441. /********* get_user *********/
  442. struct get_user_callback_arg {
  443. int user_id;
  444. Redmine::callback_t real_callback;
  445. void *arg;
  446. };
  447. // Caching function
  448. void Redmine::get_user_callback (
  449. QNetworkReply *reply,
  450. QJsonDocument *user_doc,
  451. void *_arg )
  452. {
  453. struct get_user_callback_arg *arg = ( struct get_user_callback_arg * ) _arg;
  454. int user_id = arg->user_id;
  455. callback_t callback = arg->real_callback;
  456. if ( user_doc != NULL )
  457. this->users[user_id] = *user_doc;
  458. this->callback_call ( NULL, callback, reply, &this->users[user_id], arg->arg );
  459. return;
  460. }
  461. QNetworkReply *Redmine::get_user ( int user_id,
  462. callback_t callback,
  463. void *arg )
  464. {
  465. struct get_user_callback_arg *get_user_callback_arg_p = NULL;
  466. if ( this->users.contains ( user_id ) ) {
  467. this->callback_call ( NULL, callback, NULL, &this->users[user_id], arg );
  468. return 0;
  469. }
  470. get_user_callback_arg_p =
  471. ( struct get_user_callback_arg * ) calloc ( 1, sizeof ( struct get_user_callback_arg ) );
  472. get_user_callback_arg_p->user_id = user_id;
  473. get_user_callback_arg_p->real_callback = callback;
  474. get_user_callback_arg_p->arg = arg;
  475. return this->request (
  476. GET,
  477. "users/" + QString::number ( user_id ),
  478. this,
  479. &Redmine::get_user_callback,
  480. get_user_callback_arg_p,
  481. true,
  482. NULL,
  483. NULL,
  484. false );
  485. }
  486. /********* /get_user *********/
  487. /********* parseDateTime *********/
  488. QDateTime Redmine::parseDateTime ( QString date_str )
  489. {
  490. qDebug ( "Used deprecated function Redmine::parseDateTime(). Use \"QDateTime::fromString (arg, Qt::ISODate)\" instead." );
  491. // TODO: FIXME: make this function working on any timezone.
  492. QDateTime date;
  493. date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+03:00'" );
  494. if ( !date.isValid() )
  495. date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+0300'" );
  496. if ( !date.isValid() )
  497. // TODO: FIXME: add a hour
  498. date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+04:00'" );
  499. if ( !date.isValid() )
  500. // TODO: FIXME: add a hour
  501. date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+0400'" );
  502. return date;
  503. }
  504. QDateTime Redmine::parseDateTime ( QJsonValueRef dataTime_json )
  505. {
  506. return this->parseDateTime ( dataTime_json.toString() );
  507. }
  508. /********* /parseDateTime *********/
  509. /********* getUrl *********/
  510. QUrl Redmine::getUrl ( QString objectType, int objectId )
  511. {
  512. return QUrl ( this->getBaseUrl() + "/" + objectType + "s/" + QString::number ( objectId ) );
  513. }
  514. /********* /getUrl *********/
  515. /********* get_stuff_to_do *********/
  516. QNetworkReply *Redmine::get_stuff_to_do ( void *obj_ptr, callback_t callback,
  517. int user_id,
  518. void *arg, bool free_arg,
  519. QString filterOptions )
  520. {
  521. QString user_id_str = ( user_id == 0 ? "" : "user_id=" + QString::number ( user_id ) );
  522. return this->request(
  523. GET,
  524. "stuff_to_do",
  525. obj_ptr,
  526. callback,
  527. arg,
  528. free_arg,
  529. user_id_str+"&"+filterOptions );
  530. }
  531. /********* /get_stuff_to_do *********/