💾 Archived View for gmi.noulin.net › gitRepositories › easydoneitCTui › file › edCore.c.gmi captured on 2023-01-29 at 13:15:46. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
edCore.c (135248B)
1 #include "libsheepyObject.h" 2 #include "shpPackages/ini/src/ini.h" 3 #include "edCore.h" 4 5 /** @package edi_core 6 * Core module 7 */ 8 9 /** user_interface is initialized in start() and changes the behavior of some functions to allow a functional user interface design. The default is not used, start() is always called before using edi_core. 10 * Values: 'cli', 'web' 11 */ 12 static char *user_interface = NULL; 13 14 /** path to easydoneit.ini 15 * used in edi desktop 16 */ 17 static char *inipath = NULL; 18 smallDictt *ini = NULL; 19 20 char *data_location = NULL; 21 static char *data_location_tasks = NULL; 22 static char *data_location_groups = NULL; 23 static char *data_location_tree = NULL; 24 25 char *saved_data_location = NULL; 26 27 /** Available databases */ 28 static smallArrayt *databases = NULL; 29 30 /** list selected databases */ 31 static smallDictt *selected_d = NULL; 32 smallArrayt *selected = NULL; 33 static smallArrayt *selected_path = NULL; 34 /** default database where tasks are created */ 35 static char *default_add_in = NULL; 36 37 /** add new tasks in 'bottom' of group or 'top' of group */ 38 char *add_top_or_bottom = NULL; 39 40 /** Autlink groups (array of tids) */ 41 static smallArrayt *autolink = NULL; 42 43 /** list of groups for edi ls -L, -La and -Lx (array of tids) */ 44 static smallArrayt *list_of_groups = NULL; 45 46 /** characters for task ids */ 47 #define ID_BASE_DF ",0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" 48 static char *ID_BASE = ID_BASE_DF; 49 static char *ID_BASE_STRING = ID_BASE_DF; 50 /** Length of order id in groups */ 51 #define ORDER_ID_LENGTH 8 52 size_t BASE = sizeof(ID_BASE_DF)-1; 53 54 char *TASK_STATUS[] = {" Active", 55 " Done", 56 " Ongoing", 57 " Pending", 58 "Inactive", 59 " Void", 60 NULL}; 61 char **TASK_STATUS_TRIM = NULL; 62 63 /** Sort order for sort_task_attributes function, ongoing, active, pending, done, inactive, void */ 64 static u8 SORT_TASK_ORDER[] = {2,0,3,1,4,5}; 65 66 /* TODO remove unused - static char *LIST_OPTIONS[] = {"tids", */ 67 /* "positions", */ 68 /* "html"}; */ 69 static char *list_option = "tids"; 70 71 char *STATUS_FILTER_STATES[] = {"enable","disable"}; 72 73 /** enables all status filters */ 74 smallArrayt *status_filters = NULL; 75 76 /** status filter dictionary, initialized after loading ini file */ 77 smallDictt *status_filters_d= NULL; 78 79 /** colors (default) */ 80 //static i16 no_color[4] = {-1,-1,-1,255}; 81 //static i16 status_fgColors[6][4] = {{0,0,0,255},{0,255,0,255},{255,128,0,255},{255,0,0,255},{192,192,192,255},{0,0,0,255}}; 82 static smallArrayt *no_color = NULL; 83 static smallArrayt *status_fgColors = NULL; 84 static smallDictt *status_fgColors_d = NULL; 85 /** no background color by default */ 86 //static i16 status_bgColors[6][4] = {{-1,-1,-1,255}}; 87 static smallArrayt *status_bgColors = NULL; 88 static smallDictt *status_bgColors_d = NULL; 89 90 /** text editor in terminal - easydoneit.ini [settings] EDITOR */ 91 static char *editor = "vi"; 92 93 /** Agenda generated by edi.in_cli */ 94 static smallArrayt *agenda = NULL; 95 96 /** user name */ 97 static char *user = NULL; 98 99 /** user email */ 100 static char *email = NULL; 101 102 /** bookmarks */ 103 smallArrayt *bookmarks = NULL; 104 105 /** stats for statistics function */ 106 static smallDictt *stats = NULL; 107 108 /** total number of tasks for statistics function */ 109 static u64 stats_total = 0; 110 111 /** creation dates of tasks in statistics, used to find out oldest task */ 112 static smallArrayt *stats_creation_dates = NULL; 113 114 /** timely state changes and creation dates 115 * Type: dict of dict of integers 116 * keys are dates 117 * each dates is a dictionary with STATS_OVERTIME_KEYS keys 118 * each key is the amount for states and creation 119 */ 120 static smallDictt *stats_overtime = NULL; 121 122 /** timely state changes and creation dates */ 123 //STATS_OVERTIME_KEYS = [TASK_STATUS[i] for i in range(len(TASK_STATUS))] + ['Creation'] 124 125 /* Group directory, cache result to speed up color functions */ 126 static smallArrayt *group_directory_file_list = NULL; 127 128 129 130 static char *html_header = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n\ 131 <html>\n\ 132 <head>\n\ 133 <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\">\n\ 134 <title>Tasks</title>\n\ 135 <style type=\"text/css\">\n\ 136 .subject {text-align: left}\n\ 137 .plannedStartDateTime {text-align: right}\n\ 138 .priority {text-align: right}\n\ 139 .inactive {color: #5E5E5E}\n\ 140 .late {color: #A020F0}\n\ 141 .active {color: #000000}\n\ 142 .duesoon {color: #FF8000}\n\ 143 .overdue {color: #FF0000}\n\ 144 .completed {color: #037700}\n\ 145 \n\ 146 body {\n\ 147 color: #333;\n\ 148 background-color: white;\n\ 149 font: 11px verdana, arial, helvetica, sans-serif;\n\ 150 }\n\ 151 \n\ 152 /* Styles for the title and table caption */\n\ 153 h1, caption {\n\ 154 text-align: center;\n\ 155 font-size: 18px;\n\ 156 font-weight: 900;\n\ 157 color: #778;\n\ 158 }\n\ 159 \n\ 160 /* Styles for the whole table */\n\ 161 #table {\n\ 162 border-collapse: collapse;\n\ 163 border: 2px solid #ebedff;\n\ 164 margin: 10px;\n\ 165 padding: 0;\n\ 166 }\n\ 167 \n\ 168 /* Styles for the header row */\n\ 169 .header {\n\ 170 font: bold 12px/14px verdana, arial, helvetica, sans-serif;\n\ 171 color: #07a;\n\ 172 background-color: #ebedff;\n\ 173 }\n\ 174 \n\ 175 /* Mark the column that is sorted on */\n\ 176 #sorted {\n\ 177 text-decoration: underline;\n\ 178 }\n\ 179 \n\ 180 /* Styles for a specific column */\n\ 181 .subject {\n\ 182 font-weight: bold;\n\ 183 }\n\ 184 \n\ 185 /* Styles for regular table cells */\n\ 186 td {\n\ 187 padding: 5px;\n\ 188 border: 2px solid #ebedff;\n\ 189 }\n\ 190 \n\ 191 /* Styles for table header cells */\n\ 192 th {\n\ 193 padding: 5px;\n\ 194 border: 2px solid #ebedff;\n\ 195 }\n\ 196 \n\ 197 </style>\n\ 198 </head>\n\ 199 <body>\n\ 200 <h1>Tasks</h1>\n\ 201 <table border=\"1\" id=\"table\">\n\ 202 <thead>\n\ 203 <tr class=\"header\">\n\ 204 <th class=\"subject\" scope=\"col\">Subject</th>\n\ 205 <th class=\"description\" scope=\"col\">Description</th>\n\ 206 </tr>\n\ 207 </thead>\n\ 208 <tbody>"; 209 210 static char *html_footer = " </tbody>\n\ 211 </table>\n\ 212 </body>\n\ 213 </html>"; 214 215 216 217 218 219 //////////// 220 // MACROS 221 //////////// 222 223 /** 224 * generate task path in var 225 * tid is the task identity, should be declared in the context 226 */ 227 #define genTaskPath(var, path)\ 228 sprintf(var, "%s/" path, generate_task_path(tid)); 229 230 /** 231 * declare var and generate path in easydoneit database 232 * var is a local string 233 * path is literal string representing a path in the task: "fgColor" 234 */ 235 #define createTaskPath(var, path)\ 236 char var[8192];\ 237 genTaskPath(var, path) 238 239 //////////// 240 // MACROS END 241 //////////// 242 243 244 245 246 // git support 247 void keepDir(char *path) { 248 FILE *f; 249 f = fopen(path, "w"); 250 free(path); 251 fclose(f); 252 } 253 254 int mkdirFree(char *path) { 255 int r; 256 r = mkdirParents(path); 257 free(path); 258 return r; 259 } 260 261 int copyFree(char *s, char *d) { 262 int r; 263 r = copy(s, d); 264 freeManyS(s,d); 265 return r; 266 } 267 268 /** initializes path to data. 269 * creates directories<br> 270 * creates root group and task<br> 271 * @ingroup EDI_CORE 272 */ 273 void init(void) { 274 // Set global variables 275 smallArrayt *ts = allocG(rtSmallArrayt); 276 fromArrayG(ts, TASK_STATUS, 0); 277 278 status_filters_d = allocG(rtSmallDictt); 279 zipG(status_filters_d, ts, status_filters); 280 //logVarG(status_filters_d); 281 status_fgColors_d = allocG(rtSmallDictt); 282 zipG(status_fgColors_d, ts, status_fgColors); 283 //logVarG(status_fgColors_d); 284 status_bgColors_d = allocG(rtSmallDictt); 285 zipG(status_bgColors_d, ts, status_bgColors); 286 //logVarG(status_bgColors_d); 287 terminateG(ts); 288 289 data_location_tasks = appendG(data_location, "/tasks"); 290 data_location_groups = appendG(data_location, "/groups"); 291 data_location_tree = appendG(data_location, "/tree"); 292 293 if (not fileExistsG(data_location_tasks)) { 294 mkdirParentsG(data_location_tasks); 295 mkdirParentsG(data_location_groups); 296 mkdirParentsG(data_location_tree); 297 // git support 298 keepDir(appendG(data_location_tree, "/.keepDir")); 299 mkdirFree(appendG(data_location_groups, "/root")); 300 301 // Create root description in tasks 302 char *task_path = appendG(data_location_tasks, "/root"); 303 mkdirParentsG(task_path); 304 // copy root description from script directory 305 char *d = shDirname(getProgPath()); 306 copyFree(appendG(d, "/root.txt"), appendG(task_path, "/description.txt")); 307 free(d); 308 309 // Create status 310 char **l = NULL; 311 pushG(&l, TASK_STATUS[TASK_STATUS_VOID]); 312 char *tmp = appendG(task_path, "/status"); 313 writeFileG(l, tmp); 314 freeG(l); 315 free(task_path); 316 } 317 318 // git support - when a new database is created, the group folder is empty and not saved in git 319 // create the group folder when the database is a cloned git with missing group folder 320 if (not fileExistsG(data_location_groups)) { 321 mkdirParentsG(data_location_groups); 322 } 323 if (not fileExistsG(data_location_tree)) { 324 mkdirParentsG(data_location_tree); 325 } 326 } 327 328 /** log actions in database - data_location/log.txt 329 * @param[in] s string without user name and timestamp 330 * @ingroup EDI_CORE 331 */ 332 void edi_log(const char *s) { 333 char *fn = appendG(data_location, "/log.txt"); 334 FILE *f = fopen(fn, "a"); 335 if (!f) { 336 goto retn; 337 } 338 339 char *sT = trimG(s); 340 // get current time 341 char *t = timeToS(time(0)); 342 fprintf(f, "%s - %s <%s> - %s\n", t, user, email, sT); 343 freeManyS(sT, t); 344 fclose(f); 345 retn: 346 free(fn); 347 } 348 349 /** string returned from select_database */ 350 static char result_select_database[8192]; 351 352 /** select location database 353 * @param[in] location database name 354 * @ingroup EDI_CORE 355 */ 356 const char *select_database(char *location) { 357 358 if (hasG(selected_d, location)) { 359 freeManyS(data_location, data_location_tasks, data_location_groups, data_location_tree); 360 data_location = getNDupG(selected_d, rtChar, location); 361 362 data_location_tasks = appendG(data_location, "/tasks"); 363 data_location_groups = appendG(data_location, "/groups"); 364 data_location_tree = appendG(data_location, "/tree"); 365 366 if (not fileExists(data_location)) { 367 strcpy(result_select_database, data_location); 368 strcat(result_select_database, " is unreachable"); 369 } 370 else { 371 result_select_database[0] = 0; 372 } 373 } 374 else { 375 strcpy(result_select_database, location); 376 strcat(result_select_database, " is not found in the configuration (.easydoneit.ini)"); 377 } 378 return result_select_database; 379 } 380 381 /** Mix foreground colors from task, groups and default colors. 382 * @return color array 383 * @param[in] tid task id 384 * @ingroup EDI_CORE 385 * Collect all defined colors in link groups. 386 * For each group, the group gets the first color defined in the tree<br> 387 * compute average color 388 */ 389 smallArrayt *mix_fgcolors(const char *tid) { 390 // mix colors 391 // collect all defined colors in groups to root 392 createAllocateSmallArray(colors); 393 createAllocateSmallArray(color); 394 // root has no parent group, dont search parent group color 395 if (not eqG(tid, "root")) { 396 // find all groups - link and parent group 397 char taskPath[8192]; 398 createAllocateSmallArray(groups); 399 if (is_linked(tid)) { 400 genTaskPath(taskPath, "groups/"); 401 groups = readDirG(rtSmallArrayt, taskPath); 402 } 403 else { 404 pushG(groups, (char*)find_group_containing_task(tid)); 405 } 406 407 // walk parent groups until a color or root is found 408 while (lenG(groups)) { 409 enum {search, found_color}; 410 int status_color = search; 411 sprintf(taskPath, "%s/fgColor", generate_task_path(getG(groups, rtChar, 0))); 412 if (fileExists(taskPath)) { 413 createAllocateSmallString(f); 414 readFileG(f, taskPath); 415 smallArrayt *c = splitG(f, ","); 416 // convert c strings to int 417 enumerateSmallArray(c, CP, ci) { 418 castS(cp, CP); 419 setG(c, ci, parseIntG(cp)); 420 finishG(cp); 421 } 422 terminateG(f); 423 // -1 means no_color, mix colors 424 if (getG(c, rtI64 ,0) != -1) { 425 pushNFreeG(colors, c); 426 status_color = found_color; 427 } 428 else { 429 // c is not a color 430 terminateG(c); 431 } 432 } 433 if (status_color == search) { 434 char *parent_group = (char *) find_group_containing_task(getG(groups, rtChar, 0)); 435 if (eqG(parent_group, "error")) { 436 // break infinite loop when there is an error in the database 437 break; 438 } 439 if (not eqG(parent_group, "root")) { 440 pushG(groups, parent_group); 441 } 442 } 443 delG(groups, 0, 1); 444 } 445 terminateG(groups); 446 447 // compute average color 448 if (lenG(colors)) { 449 pushG(color, 0);pushG(color, 0);pushG(color, 0);pushG(color, 0); 450 forEachSmallArray(colors, C) { 451 cast(smallArrayt*, c, C); 452 range(i, lenG(c)) { 453 *getG(color, rtI32P, i) += getG(c, rtI32, i); 454 } 455 finishG(C); 456 } 457 size_t colorCount = lenG(colors); 458 range(i, lenG(color)) { 459 *getG(color, rtI32P, i) /= colorCount; 460 } 461 } 462 } 463 if (not lenG(colors)) { 464 // no defined colors, use default color for status 465 char *task_status = (char *)get_status(tid); 466 terminateG(color); 467 color = getNDupG(status_fgColors_d, rtSmallArrayt, task_status); 468 } 469 terminateG(colors); 470 return color; 471 } 472 473 /** get color for task tid 474 * @return color array 475 *# @param[in] tid task id 476 * @ingroup EDI_CORE 477 * when no color is set, mix colors from groups or use defaults 478 */ 479 smallArrayt *get_forground_color(const char *tid) { 480 //color = no_color 481 smallArrayt *color = NULL; 482 createTaskPath(fgPath, "fgColor"); 483 484 if (fileExists(fgPath)) { 485 createAllocateSmallArray(color); 486 createAllocateSmallString(f); 487 readFileG(f, fgPath); 488 smallArrayt *c = splitG(f, ","); 489 // convert c strings to int 490 forEachSmallArray(c, CP) { 491 castS(cp, CP); 492 pushG(color, parseIntG(cp)); 493 finishG(cp); 494 } 495 terminateManyG(f, c); 496 // -1 means no_color, mix colors 497 if (getG(color, rtI64 ,0) != -1) { 498 terminateG(color); 499 color = mix_fgcolors(tid); 500 } 501 } 502 else { 503 color = mix_fgcolors(tid); 504 } 505 if (not color) { 506 color = dupG(no_color); 507 } 508 return color; 509 } 510 511 /** set color for task tid 512 * @param[in] tid task id 513 * @param[in] color_s color array 514 * @ingroup EDI_CORE 515 */ 516 void set_forground_color(const char *tid , const char *color_s) { 517 createTaskPath(fgPath, "fgColor"); 518 writeFileG(color_s, fgPath); 519 sprintf(fgPath, "set foreground color in %s to %s", tid,color_s); 520 edi_log(fgPath); 521 } 522 523 /** remove foreground color for task tid 524 * @param[in] tid task id 525 * @ingroup EDI_CORE 526 */ 527 void remove_foreground_color(const char *tid) { 528 createTaskPath(fgPath, "fgColor"); 529 if (fileExists(fgPath)) { 530 rmAllG(fgPath); 531 } 532 } 533 534 /** Mix background colors from task, groups and default colors. 535 * @return color array 536 * @param[in] tid task id 537 * @ingroup EDI_CORE 538 * Collect all defined colors in link groups. 539 * For each group, the group gets the first color defined in the tree<br> 540 * compute average color 541 */ 542 smallArrayt *mix_bgcolors(const char *tid) { 543 // mix colors 544 // collect all defined colors in groups to root 545 createAllocateSmallArray(colors); 546 createAllocateSmallArray(color); 547 // root has no parent group, dont search parent group color 548 if (not eqG(tid, "root")) { 549 // find all groups - link and parent group 550 char taskPath[8192]; 551 createAllocateSmallArray(groups); 552 if (is_linked(tid)) { 553 genTaskPath(taskPath, "groups/"); 554 groups = readDirG(rtSmallArrayt, taskPath); 555 } 556 else { 557 pushG(groups, (char*)find_group_containing_task(tid)); 558 } 559 560 // walk parent groups until a color or root is found 561 while (lenG(groups)) { 562 enum {search, found_color}; 563 int status_color = search; 564 sprintf(taskPath, "%s/bgColor", generate_task_path(getG(groups, rtChar, 0))); 565 if (fileExists(taskPath)) { 566 createAllocateSmallString(f); 567 readFileG(f, taskPath); 568 smallArrayt *c = splitG(f, ","); 569 // convert c strings to int 570 enumerateSmallArray(c, CP, ci) { 571 castS(cp, CP); 572 setG(c, ci, parseIntG(cp)); 573 finishG(cp); 574 } 575 terminateG(f); 576 // -1 means no_color, mix colors 577 if (getG(c, rtI64 ,0) != -1) { 578 pushNFreeG(colors, c); 579 status_color = found_color; 580 } 581 else { 582 // c is not a color 583 terminateG(c); 584 } 585 } 586 if (status_color == search) { 587 char *parent_group = (char *) find_group_containing_task(getG(groups, rtChar, 0)); 588 if (eqG(parent_group, "error")) { 589 // break infinite loop when there is an error in the database 590 break; 591 } 592 if (not eqG(parent_group, "root")) { 593 pushG(groups, parent_group); 594 } 595 } 596 delG(groups, 0, 1); 597 } 598 terminateG(groups); 599 600 // compute average color 601 if (lenG(colors)) { 602 pushG(color, 0);pushG(color, 0);pushG(color, 0);pushG(color, 0); 603 forEachSmallArray(colors, C) { 604 cast(smallArrayt*, c, C); 605 range(i, lenG(c)) { 606 *getG(color, rtI32P, i) += getG(c, rtI32, i); 607 } 608 finishG(C); 609 } 610 size_t colorCount = lenG(colors); 611 range(i, lenG(color)) { 612 *getG(color, rtI32P, i) /= colorCount; 613 } 614 } 615 } 616 if (not lenG(colors)) { 617 // no defined colors, use default color for status 618 char *task_status = (char *)get_status(tid); 619 terminateG(color); 620 color = getNDupG(status_bgColors_d, rtSmallArrayt, task_status); 621 } 622 return color; 623 } 624 625 /** get color for task tid 626 * @return color array 627 * @param[in] tid task id 628 * @ingroup EDI_CORE 629 * when no color is set, mix colors from groups or use defaults 630 */ 631 smallArrayt *get_background_color(const char *tid) { 632 //color = no_color 633 smallArrayt *color = NULL; 634 createTaskPath(bgPath, "bgColor"); 635 636 if (fileExists(bgPath)) { 637 createAllocateSmallArray(color); 638 createAllocateSmallString(f); 639 readFileG(f, bgPath); 640 smallArrayt *c = splitG(f, ","); 641 // convert c strings to int 642 forEachSmallArray(c, CP) { 643 castS(cp, CP); 644 pushG(color, parseIntG(cp)); 645 finishG(cp); 646 } 647 terminateManyG(f, c); 648 // -1 means no_color, mix colors 649 if (getG(color, rtI64 ,0) != -1) { 650 terminateG(color); 651 color = mix_bgcolors(tid); 652 } 653 } 654 else { 655 color = mix_bgcolors(tid); 656 } 657 if (not color) { 658 color = dupG(no_color); 659 } 660 return color; 661 } 662 663 /** set color for task tid 664 * @return color array 665 * @param[in] tid task id 666 * @param[in] color_s color array 667 * @ingroup EDI_CORE 668 */ 669 void set_background_color(const char *tid, const char *color_s) { 670 createTaskPath(bgPath, "bgColor"); 671 writeFileG(color_s, bgPath); 672 sprintf(bgPath, "set background color in %s to %s", tid,color_s); 673 edi_log(bgPath); 674 } 675 676 /** remove background color for task tid 677 * @param[in] tid task id 678 * @ingroup EDI_CORE 679 */ 680 void remove_background_color(const char *tid) { 681 createTaskPath(bgPath, "bgColor"); 682 if (fileExists(bgPath)) { 683 rmAllG(bgPath); 684 } 685 } 686 687 /** string returned from color_to_hex FF22AA */ 688 static char return_color_to_hex[7]; 689 690 /** converts color to hex format 691 * @return color hex string 692 * @param[in] color color array 693 * @ingroup EDI_CORE 694 */ 695 const char *color_to_hex(smallArrayt *color) { 696 createAllocateSmallArray(c); 697 forEachSmallArray(color, N) { 698 cast(smallIntt *, n, N); 699 if (getG(n, rtI64, unusedV) == -1) { 700 // -1 is no color, white 701 pushG(c, 255); 702 } 703 else { 704 pushNFreeG(c, dupG(n)); 705 } 706 finishG(n); 707 } 708 sprintf(return_color_to_hex, "%02x%02x%02x", getG(c, rtI64, 0), getG(c, rtI64, 1), getG(c, rtI64, 2)); 709 terminateG(c); 710 return return_color_to_hex; 711 } 712 713 714 /** string returned from generate_task_path */ 715 static char result_generate_task_path[8192]; 716 717 /** filesystem path for task in database tasks 718 * @return path 719 * @param[in] tid task id 720 * @ingroup EDI_CORE 721 */ 722 const char *generate_task_path(const char *tid) { 723 sprintf(result_generate_task_path, "%s/%s", data_location_tasks, tid); 724 return result_generate_task_path; 725 } 726 727 /** string returned from generate_group_path */ 728 static char result_generate_group_path[8192]; 729 730 /** filesystem path for group in database groups 731 * @return path 732 * @param[in] tid task id 733 * @ingroup EDI_CORE 734 */ 735 const char *generate_group_path(const char *tid) { 736 sprintf(result_generate_group_path, "%s/%s/", data_location_groups, tid); 737 return result_generate_group_path; 738 } 739 740 741 static char return_get_status[64]; 742 743 /** get status for tid 744 * @return status string 745 * @param[in] tid task id 746 * @ingroup EDI_CORE 747 */ 748 const char *get_status(const char *tid) { 749 // open status file 750 createTaskPath(statusPath, "status"); 751 createAllocateSmallString(status); 752 if (!readFileG(status, statusPath)) { 753 goto except; 754 } 755 if (lenG(status) > 63) { 756 // the status is too long, this is wrong 757 goto except; 758 } 759 strcpy(return_get_status, ssGet(status)); 760 terminateG(status); 761 return return_get_status; 762 except: 763 printf("%s is an invalid task.", generate_task_path(tid)); 764 return TASK_STATUS[TASK_STATUS_VOID]; 765 } 766 767 /** returns s 768 * @return s 769 * @param[in] tid task id 770 * @param[in] s task title string 771 * @ingroup EDI_CORE 772 * Replaces generate_task_string_with_tid in GUI 773 */ 774 smallStringt *passThroughTitle(const char *tid, smallStringt *s) { 775 return dupG(s); 776 } 777 778 /** creates a string with task id, status and string parameter 779 * @return string 780 * @param[in] tid task id 781 * @param[in] s task title string 782 * @ingroup EDI_CORE 783 * Displays a task depending on edi_core.list_option 784 */ 785 smallStringt *generate_task_string_with_tid(const char *tid, smallStringt *s) { 786 const char *status = get_status(tid); 787 smallStringt *r = NULL; 788 if (eqG(list_option, "tids")) { 789 // %*s to dynamically adjust id length 790 r = formatO("%" stringifyExpr(ID_LENGTH) "s - %s - %s", tid, status, ssGet(s)); 791 } 792 if (eqG(list_option, "positions")) { 793 if (is_this_task_a_group(tid)) { 794 // print tid for groups in the list 795 r = formatO("%" stringifyExpr(ID_LENGTH) "s - %s - %s", tid, status, ssGet(s)); 796 } 797 else { 798 // hide tids for normal tasks 799 r = formatO("%" stringifyExpr(ID_LENGTH) "s - %s - %s", "", status, ssGet(s)); 800 } 801 } 802 if (eqG(list_option, "md")) { 803 // print title and description 804 createTaskPath(taskPath, "description.txt"); 805 createAllocateSmallArray(description); 806 readFileG(description, taskPath); 807 // remove title line 808 delG(description, 0 ,1); 809 810 // add <br> for long titles 811 smallStringt *j = joinG(description, "\n"); 812 r = formatO("\n---\n#%s<br>\n%s", ssGet(s), ssGet(j)); 813 terminateManyG(description, j); 814 } 815 /* if list_option = 'rst' */ 816 /* # print title and description */ 817 /* f = open generate_task_path(tid)+os.sep+'description.txt' */ 818 /* # remove title line */ 819 /* description_l = f readlines[1:] */ 820 /* f close */ 821 /* */ 822 /* # convert urls to link */ 823 /* NOBRACKET = r'[^\]\[]*' */ 824 /* BRK = ( r'\[(' */ 825 /* + (NOBRACKET + r'(\[')*6 */ 826 /* + (NOBRACKET+ r'\])*')*6 */ 827 /* + NOBRACKET + r')\]' ) */ 828 /* NOIMG = r'(?<!\!)' */ 829 /* LINK_RE = NOIMG + BRK + \ */ 830 /* r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' */ 831 /* # [text](url) or [text](<url>) or [text](url "title") */ 832 /* */ 833 /* compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE) */ 834 /* */ 835 /* for line_index,l in enumerate(description_l) */ 836 /* if 'http' in l */ 837 /* try */ 838 /* match = compiled_re.match(l) */ 839 /* url = match.group(9) */ 840 /* text = match.group(2) */ 841 /* before_url = match.group(1) */ 842 /* #title = match.group(13) */ 843 /* after_url = match.group(14) */ 844 /* */ 845 /* # added a space after text because rst complains when there is no space before < */ 846 /* # delimiting the url */ 847 /* l = '%s`%s <%s>`_%s'% (before_url, text, url, after_url) */ 848 /* except */ 849 /* # not used, information */ 850 /* link_status = 'WARNING: the link is not in format [title](url)' */ 851 /* description_l[line_index] = l */ 852 /* */ 853 /* description = ''.join(description_l) */ 854 /* */ 855 /* tid = ' ' */ 856 /* # remove position and type (GROUP, LINK) from title */ 857 /* title = s[11:] lstrip */ 858 /* r = '%s\n'%title + '='*len(title) + '\n%s\n\n'%description */ 859 /* if list_option = 'html' */ 860 /* #<tr bgcolor="#FEFFCC"> */ 861 /* # <td class="subject"> <font color="#000000">t1</font></td> */ 862 /* # <td class="description"> <font color="#000000"> </font></td> */ 863 /* #</tr> */ 864 /* #:define read_description */ 865 /* # convert < to < and > > to ignore html tags in title line */ 866 /* s = s.replace('<','<').replace('>','>') */ 867 /* f = open generate_task_path(tid)+os.sep+'description.txt' */ 868 /* # remove title line, convert < to < and > > to ignore html tags */ 869 /* description_l = ['%s<br>'%i rstrip.replace('<','<').replace('>','>') for i in f readlines[1:]] */ 870 /* f close */ 871 /* */ 872 /* # convert urls to link */ 873 /* NOBRACKET = r'[^\]\[]*' */ 874 /* BRK = ( r'\[(' */ 875 /* + (NOBRACKET + r'(\[')*6 */ 876 /* + (NOBRACKET+ r'\])*')*6 */ 877 /* + NOBRACKET + r')\]' ) */ 878 /* NOIMG = r'(?<!\!)' */ 879 /* LINK_RE = NOIMG + BRK + \ */ 880 /* r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)''' */ 881 /* # [text](url) or [text](<url>) or [text](url "title") */ 882 /* */ 883 /* compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE) */ 884 /* */ 885 /* for line_index,l in enumerate(description_l) */ 886 /* if 'http' in l */ 887 /* try */ 888 /* match = compiled_re.match(l) */ 889 /* url = match.group(9) */ 890 /* text = match.group(2) */ 891 /* before_url = match.group(1) */ 892 /* #title = match.group(13) */ 893 /* after_url = match.group(14) */ 894 /* */ 895 /* l = '%s<a href="%s">%s</a>%s'% (before_url, url, text, after_url) */ 896 /* except */ 897 /* # not used, information */ 898 /* link_status = 'WARNING: the link is not in format [title](url)' */ 899 /* description_l[line_index] = l */ 900 /* */ 901 /* description = '\n'.join(description_l) */ 902 /* #:end */ 903 /* */ 904 /* if is_this_task_a_group(tid) and user_interface = 'web' */ 905 /* subject = '<a href="edi_web.py?tid=%s">%s - %s</a>'%(tid,status,s) */ 906 /* else */ 907 /* subject = '%s - %s'%(status,s) */ 908 /* fg = color_to_hex(get_forground_color(tid)) */ 909 /* bg = color_to_hex(get_background_color(tid)) */ 910 /* r=' <tr bgcolor="#%s">\n <td class="subject"> <font color="#%s">%s</font></td>\n <td class="description"> <font color="#%s">%s</font></td>\n </tr>' % (bg, fg, subject, fg, description) */ 911 return r; 912 } 913 914 /** creates a string with task id, status and string parameter 915 * @return list of strings 916 * @param[in] tid task id 917 * @param[in] s task title string 918 * @ingroup EDI_CORE 919 * Displays a group depending on edi_core.list_option 920 */ 921 smallStringt *generate_group_string_with_tid (const char *tid, smallStringt *s) { 922 const char *status = get_status(tid); 923 smallStringt *r = NULL; 924 if ((eqG(list_option, "tids")) or (eqG(list_option, "positions"))) { 925 // %*s to dynamically adjust id length 926 r = formatO("%" stringifyExpr(ID_LENGTH) "s - %s - %s", tid, status, ssGet(s)); 927 } 928 /* if list_option = 'md' */ 929 /* tid = ' ' */ 930 /* # print group title only */ 931 /* r = '#%s'%s.replace(' 0 - GROUP','') */ 932 /* if agenda */ 933 /* r += '\n---\n# Agenda\n%s' % '\n\n'.join(agenda) */ 934 /* if list_option = 'rst' */ 935 /* tid = ' ' */ 936 /* # print group title only */ 937 /* title = s.replace(' 0 - GROUP','') */ 938 /* r = '='*len(title) + '\n%s\n'%title + '='*len(title) + '\n\n' */ 939 /* if agenda */ 940 /* r += 'Agenda\n======\n%s\n\n' % '\n'.join(agenda) */ 941 /* else */ 942 /* r += '\n.. contents::\n\n' */ 943 /* if list_option = 'html' */ 944 /* #<tr bgcolor="#FEFFCC"> */ 945 /* # <td class="subject"> <font color="#000000">t1</font></td> */ 946 /* # <td class="description"> <font color="#000000"> </font></td> */ 947 /* #</tr> */ 948 /* # display description in html */ 949 /* #:read_description */ 950 /* */ 951 /* subject = '%s - %s'%(status, s) */ 952 /* fg = color_to_hex(get_forground_color(tid)) */ 953 /* bg = color_to_hex(get_background_color(tid)) */ 954 /* r=' <tr bgcolor="#%s">\n <td class="subject"> <font color="#%s">%s</font></td>\n <td class="description"> <font color="#%s">%s</font></td>\n </tr>' % (bg, fg, subject, fg, description) */ 955 return r; 956 } 957 958 /** determines if task is a group 959 * @return empty string or 'this task is a group' 960 * @param[in] tid task id 961 * @ingroup EDI_CORE 962 */ 963 bool is_this_task_a_group(const char *tid) { 964 // Identify if task is a group 965 bool is_a_group = false; 966 // list groups 967 smallArrayt *groups = readDirDirG(rtSmallArrayt, data_location_groups); 968 if (binarySearchG(groups, tid) != -1) { 969 is_a_group = true; 970 } 971 terminateG(groups); 972 return is_a_group; 973 } 974 975 /** get task title 976 * @return first line of task description 977 * @param[in] tid task id 978 * @ingroup EDI_CORE 979 */ 980 smallStringt *get_task_title(const char *tid) { 981 createTaskPath(task_path, "description.txt"); 982 createAllocateSmallArray(f); 983 readFileG(f, task_path); 984 smallStringt *r = getNDupG(f, rtSmallStringt, 0); 985 terminateG(f); 986 if (not r) { 987 // empty description, return empty string 988 r = allocG(""); 989 } 990 return r; 991 } 992 993 /** get creation date 994 * @return array of date string and unix time 995 * @param[in] tid task id 996 * @ingroup EDI_CORE 997 */ 998 smallArrayt *get_creation_date(const char *tid) { 999 createAllocateSmallArray(r); 1000 // Figure out creation time and modification times for description and status 1001 createTaskPath(task_path, "ctime.txt"); 1002 if (not fileExists(task_path)) { 1003 pushG(r, "Not available"); 1004 pushG(r, 0); 1005 } 1006 else { 1007 createAllocateSmallString(task_ctime); 1008 readFileG(task_ctime, task_path); 1009 pushG(r, task_ctime); 1010 struct tm tm; 1011 if (not strptime(ssGet(task_ctime), "%Y-%m-%d %H:%M", &tm)) { 1012 // string time failed to parse 1013 pushG(r, 0); 1014 } 1015 else { 1016 time_t t = mktime(&tm); 1017 pushG(r, (i64) t); 1018 } 1019 finishG(task_ctime); 1020 } 1021 return r; 1022 } 1023 1024 /** get media 1025 * @return media file name 1026 * @param[in] tid task id 1027 * @ingroup EDI_CORE 1028 */ 1029 smallArrayt *get_media(const char *tid) { 1030 createAllocateSmallArray(r); 1031 createTaskPath(task_path, "media"); 1032 if (fileExists(task_path)) { 1033 smallArrayt *files = readDirG(rtSmallArrayt, task_path); 1034 smallStringt *fn = getG(files, rtSmallStringt, 0); 1035 smallArrayt *spl = splitG(fn, "."); 1036 pushG(r, getG(spl, rtChar, 0)); 1037 pushNFreeG(r, catS(task_path, "/", getG(files, rtChar, 0))); 1038 terminateManyG(files, fn, spl); 1039 } 1040 else { 1041 pushG(r, "None"); 1042 } 1043 return r; 1044 } 1045 1046 /** remove media 1047 * @param[in] tid task id 1048 * @ingroup EDI_CORE 1049 */ 1050 void remove_media(const char *tid) { 1051 createTaskPath(task_path, "media"); 1052 if (fileExists(task_path)) { 1053 rmAll(task_path); 1054 } 1055 } 1056 1057 1058 /** set media, one media file per task 1059 * @return media array [media type (sound, image), file name] 1060 * @param[in] tid task id 1061 * @param[in] filename media file to copy to task 1062 * @ingroup EDI_CORE 1063 */ 1064 smallArrayt *set_media(const char *tid, const char *filename) { 1065 createAllocateSmallArray(r); 1066 1067 createTaskPath(task_path, "media"); 1068 char *fn = trimG(filename); 1069 if (endsWithG(fn, "wav")) { 1070 //#:define create_media_folder 1071 if (not fileExists(task_path)) { 1072 // create media folder 1073 mkdirParentsG(task_path); 1074 } 1075 //#:end 1076 else { 1077 // media exists, check type 1078 smallArrayt *files = readDirG(rtSmallArrayt, task_path); 1079 if (not endsWithG(getG(files, rtChar, 0), "wav")) { 1080 smallStringt *s = formatO("%s is not a sound.", tid); 1081 pushNFreeG(r, s); 1082 terminateG(files); 1083 goto end; 1084 } 1085 terminateG(files); 1086 } 1087 // sound file 1088 char fp[8192]; 1089 sprintf(fp, "%s/sound.wav", task_path); 1090 copy(filename, fp); 1091 pushG(r, "sound.wav"); 1092 } 1093 if (endsWithG(fn, "jpg")) { 1094 //#:create_media_folder 1095 if (not fileExists(task_path)) { 1096 // create media folder 1097 mkdirParentsG(task_path); 1098 } 1099 else { 1100 // media exists, check type 1101 smallArrayt *files = readDirG(rtSmallArrayt, task_path); 1102 if (not endsWithG(getG(files, rtChar, 0), "jpg")) { 1103 smallStringt *s = formatO("%s is not an image.", tid); 1104 pushNFreeG(r, s); 1105 terminateG(files); 1106 goto end; 1107 } 1108 terminateG(files); 1109 } 1110 // image file 1111 char fp[8192]; 1112 sprintf(fp, "%s/image.jpg", task_path); 1113 copy(filename, fp); 1114 pushG(r, "image.jpg"); 1115 } 1116 end: 1117 free(fn); 1118 if (not lenG(r)) { 1119 smallStringt *s = formatO("%s is not a supported media file.", filename); 1120 pushNFreeG(r, s); 1121 } 1122 else { 1123 char log[8192]; 1124 sprintf(log, "added media %s in task %s", filename, tid); 1125 edi_log(log); 1126 } 1127 return r; 1128 } 1129 1130 /** get attachments 1131 * @return attachment file names 1132 * @param[in] tid task id 1133 * @ingroup EDI_CORE 1134 */ 1135 smallArrayt *get_attachments(const char *tid) { 1136 smallArrayt *r; 1137 createTaskPath(task_path, "attachments/"); 1138 if (fileExists(task_path)) { 1139 // list files in attachments folder and prepend task_path 1140 // r holds the paths to the attachments 1141 r = readDirAllG(rtSmallArrayt, task_path); 1142 enumerateSmallArray(r, F, i) { 1143 castS(f, F); 1144 prependG(f, task_path); 1145 setNFreePG(r, i, f); 1146 } 1147 } 1148 else { 1149 initiateG(&r); 1150 pushG(r, "None"); 1151 } 1152 return r; 1153 } 1154 1155 /** remove attachments 1156 * @param[in] tid task id 1157 * @ingroup EDI_CORE 1158 */ 1159 void remove_attachments(const char *tid) { 1160 createTaskPath(task_path, "attachments"); 1161 if (fileExists(task_path)) { 1162 rmAll(task_path); 1163 } 1164 } 1165 1166 1167 /** set attachments 1168 * @return attachment file names 1169 * @param[in] tid task id 1170 * @param[in] array of filenames 1171 * @ingroup EDI_CORE 1172 * 1173 * With *, set_attachments copies multiple files at once 1174 */ 1175 smallArrayt *set_attachments(const char *tid, smallArrayt *filenames) { 1176 createTaskPath(task_path, "attachments/"); 1177 if (not fileExists(task_path)) { 1178 // create attachment folder 1179 mkdirParentsG(task_path); 1180 } 1181 1182 createAllocateSmallArray(r); 1183 forEachSmallArray(filenames, FN) { 1184 castS(fn, FN); 1185 if (not copyG(fn,task_path)) goto except; 1186 char log[8192]; 1187 sprintf(log, "added attachment %s in task %s", ssGet(fn), tid); 1188 edi_log(log); 1189 // Remove path from fn, keep only filename 1190 char *s = basename(ssGet(fn)); 1191 pushNFreeG(r, appendG(task_path, s)); 1192 goto end; 1193 except: 1194 pushNFreeG(r, appendG("Failed to copy ", ssGet(fn))); 1195 end: 1196 finishG(fn); 1197 } 1198 return r; 1199 } 1200 1201 /** sort task attributes (ls in edi cli) 1202 * @return sorted task list by state 1203 * @param[in] result from list_group 1204 * @ingroup EDI_CORE 1205 */ 1206 smallArrayt *sort_task_attributes(smallArrayt *task_attributes) { 1207 createAllocateSmallArray(r); 1208 // Keep head group on top 1209 smallDictt *d = getG(task_attributes, rtSmallDictt, 0); 1210 if (eqG(getG(d, rtChar, "head"), "head group")) { 1211 pushNFreeG(r, dupG(d)); 1212 delG(task_attributes, 0, 1); 1213 } 1214 finishG(d); 1215 range(s, COUNT_ELEMENTS(SORT_TASK_ORDER)) { 1216 forEachSmallArray(task_attributes, T) { 1217 cast(smallDictt *, t, T); 1218 if (eqG(getG(t, rtChar, "status"), TASK_STATUS[s])) { 1219 pushG(r, T); 1220 } 1221 finishG(T); 1222 } 1223 } 1224 return r; 1225 } 1226 1227 /** sort function for sorting an array of tasks by date from newest to oldest 1228 * @return compare result 1229 * @param[in] result from list_group 1230 * @ingroup EDI_CORE 1231 */ 1232 int sortdatefunc(smallDictt *x, smallDictt *y) { 1233 i64 xv = getG(x, rtI64, "ctime"); 1234 i64 yv = getG(y, rtI64, "ctime"); 1235 1236 if (xv == yv) return 0; 1237 if (yv > xv) return 1; 1238 else return -1; 1239 } 1240 1241 /** sort task attributes (ls in edi cli) 1242 * @return sorted task list by date 1243 * @param[in] result from list_group 1244 * @ingroup EDI_CORE 1245 */ 1246 smallArrayt *sort_task_attributes_by_date(smallArrayt *task_attributes) { 1247 createAllocateSmallArray(r); 1248 // Keep head group on top 1249 smallDictt *d = getG(task_attributes, rtSmallDictt, 0); 1250 if (eqG(getG(d, rtChar, "head"), "head group")) { 1251 pushNFreeG(r, dupG(d)); 1252 delG(task_attributes, 0, 1); 1253 } 1254 finishG(d); 1255 // -1 to exclude the empty line 1256 smallArrayt *a = copyRngG(task_attributes, 0, -1); 1257 //TODO a.sort(sortdatefunc) 1258 //print task_attributes 1259 appendNSmashG(r, a); 1260 pushNFreeG(r, getG(task_attributes, rtBaset, -1)); 1261 return r; 1262 } 1263 1264 /** Get task item in list_group format 1265 * @return r dictionary representing a task like the items returned by the list_group function 1266 * @param[in] tid task id 1267 * @ingroup EDI_CORE 1268 * for edi desktop 1269 */ 1270 smallDictt *get_task_in_list_group_format(const char *tid) { 1271 createAllocateSmallDict(r); 1272 setG(r, "head", "element"); 1273 setG(r, "tid", tid); 1274 char *group = " "; 1275 if (is_this_task_a_group(tid)) { 1276 group = "GROUP"; 1277 } 1278 if (is_linked(tid)) { 1279 group = " LINK"; 1280 } 1281 setG(r, "group", group); 1282 // figure out position in group 1283 i64 current_position = -1; 1284 const char *group_path = generate_group_path(find_group_containing_task(tid)); 1285 smallArrayt *lGroups = readDirDirG(rtSmallArrayt, group_path); 1286 enumerateSmallArray(lGroups, FN, n) { 1287 castS(fn, FN); 1288 if (hasG(fn, tid)) { 1289 // found tid in list 1290 // position in list 1291 smallStringt *posS = copyRngG(fn, 0 , ORDER_ID_LENGTH); 1292 current_position = baseconvert_to_dec(ssGet(posS)); 1293 terminateG(posS); 1294 break; 1295 } 1296 finishG(fn); 1297 } 1298 setG(r, "position", current_position); 1299 // store title line 1300 createTaskPath(task_path, "description.txt"); 1301 FILE *f = fopen(task_path, "r"); 1302 setNFreeG(r, "title", trimG(readLineG(rtSmallStringt, f))); 1303 fclose(f); 1304 setG(r, "status", get_status(tid)); 1305 smallArrayt *cd = get_creation_date(tid); 1306 setG(r, "ctime", getG(cd, rtI64, 1)); 1307 terminateManyG(lGroups, cd); 1308 return r; 1309 } 1310 1311 /** list id - status - group - first line from description 1312 * @return list of tasks and groups title 1313 * @param[in] tid task id 1314 * @ingroup EDI_CORE 1315 * items in groups have the format 'ORDER_ID''TASK_ID'<br> 1316 * task 0 is group tittle<br> 1317 *<br> 1318 * result array elements:<br> 1319 * {head :string, tid :string, position :value, group :string, title :string, status :string}<br> 1320 * head tells if the element is a group title.<br> 1321 *<br> 1322 * example:<br> 1323 *fTTB1KRWfDpoSR1_ - Active - GROUP Motivational Interviewing<br> 1324 *62ZvFA_q0pCZFr0Y - Active - news<br> 1325 */ 1326 smallArrayt *list_group(const char *tid) { 1327 createAllocateSmallArray(result); 1328 // list visible groups only 1329 const char *task_status = get_status(tid); 1330 if (getG(status_filters_d, rtI32, task_status) == ENABLE) { 1331 // List task titles in group 1332 const char *group_path = generate_group_path(tid); 1333 1334 // List groups to indicate when a task is a group 1335 // Identify if task is a group 1336 smallArrayt *groups = readDirDirG(rtSmallArrayt, data_location_groups); 1337 // End Identify if task is a group 1338 1339 // print items in tid in order 1340 smallArrayt *tasksInGroup = readDirG(rtSmallArrayt, group_path); 1341 enumerateSmallArray(tasksInGroup, FN, n) { 1342 castS(fnp, FN); 1343 smallStringt *fn = allocG(basename(ssGet(fnp))); 1344 if (lenG(fn) != ORDER_ID_LENGTH+ID_LENGTH) { 1345 // verify filename format in groups, cifs creates temporary files, ignore them 1346 printf("EDI_ERROR: Database inconsistent, delete file - rm %s%s\n", group_path, ssGet(fn)); 1347 } 1348 else { 1349 // print position in list 1350 smallStringt *posS = copyRngG(fn, 0 , ORDER_ID_LENGTH); 1351 i64 current_position = baseconvert_to_dec(ssGet(posS)); 1352 terminateG(posS); 1353 1354 // Identify if task is a group 1355 // Remove order_id, keep task id only 1356 char *group = " "; 1357 if (hasG(groups, ssGet(fn)+ORDER_ID_LENGTH)) { 1358 group = "GROUP"; 1359 } 1360 if (is_linked(ssGet(fn)+ORDER_ID_LENGTH)) { 1361 group = " LINK"; 1362 } 1363 // End Identify if task is a group 1364 // Get task title 1365 // Remove order_id, keep task id only 1366 char task_path[8192]; 1367 sprintf(task_path, "%s/description.txt", generate_task_path(ssGet(fn)+ORDER_ID_LENGTH)); 1368 FILE *f = fopen(task_path, "r"); 1369 // Remove order_id, keep task id only 1370 const char *task_status = get_status(ssGet(fn)+ORDER_ID_LENGTH); 1371 if ((not n) and (not eqG(tid, "root"))) { 1372 // First task is group title 1373 // filter status, keep task if status filter is enabled 1374 if (getG(status_filters_d, rtI32, task_status) == ENABLE) { 1375 createAllocateSmallDict(task_d); 1376 setG(task_d, "head", "head group"); 1377 setG(task_d, "tid", ssGet(fn)+ORDER_ID_LENGTH); 1378 setG(task_d, "position", current_position); 1379 setG(task_d, "group", group); 1380 setNFreeG(task_d, "title", trimG(readLineG(rtSmallStringt, f))); 1381 setG(task_d, "status", get_status(ssGet(fn)+ORDER_ID_LENGTH)); 1382 smallArrayt *cd = get_creation_date(ssGet(fn)+ORDER_ID_LENGTH); 1383 setG(task_d, "ctime", getG(cd, rtI64, 1)); 1384 terminateG(cd); 1385 pushNFreeG(result, task_d); 1386 } 1387 } 1388 else { 1389 // filter status, keep task if status filter is enabled 1390 if (getG(status_filters_d, rtI32, task_status) == ENABLE) { 1391 createAllocateSmallDict(task_d); 1392 setG(task_d, "head", "element"); 1393 setG(task_d, "tid", ssGet(fn)+ORDER_ID_LENGTH); 1394 setG(task_d, "position", current_position); 1395 setG(task_d, "group", group); 1396 setNFreeG(task_d, "title", trimG(readLineG(rtSmallStringt, f))); 1397 setG(task_d, "status", get_status(ssGet(fn)+ORDER_ID_LENGTH)); 1398 smallArrayt *cd = get_creation_date(ssGet(fn)+ORDER_ID_LENGTH); 1399 setG(task_d, "ctime", getG(cd, rtI64, 1)); 1400 terminateG(cd); 1401 pushNFreeG(result, task_d); 1402 } 1403 } 1404 fclose(f); 1405 } 1406 terminateG(fn); 1407 finishG(fnp); 1408 } 1409 terminateManyG(groups, tasksInGroup); 1410 createAllocateSmallDict(task_d); 1411 setG(task_d, "head", "empty line"); 1412 setG(task_d, "tid", ""); 1413 setG(task_d, "position", 0); 1414 setG(task_d, "group", ""); 1415 setG(task_d, "title", ""); 1416 setG(task_d, "status", ""); 1417 setG(task_d, "ctime", 0); 1418 pushNFreeG(result, task_d); 1419 } 1420 return result; 1421 } 1422 1423 smallArrayt *listBookmarks(void) { 1424 createAllocateSmallArray(result); 1425 1426 // check if there are bookmarks 1427 if (not eqG(getG(bookmarks, rtChar, 0), "")) { 1428 1429 enumerateSmallArray(bookmarks, FN, n) { 1430 castS(fn, FN); 1431 1432 // print position in list 1433 i64 current_position = n; 1434 1435 // setup database 1436 save_edi_core_data_location(); 1437 setup_data_location_for_tid(ssGet(fn)); 1438 1439 // Identify if task is a group 1440 // Remove order_id, keep task id only 1441 char *group = " "; 1442 if (is_this_task_a_group(ssGet(fn))) { 1443 group = "GROUP"; 1444 } 1445 if (is_linked(ssGet(fn))) { 1446 group = " LINK"; 1447 } 1448 // End Identify if task is a group 1449 // Get task title 1450 char task_path[8192]; 1451 sprintf(task_path, "%s/description.txt", generate_task_path(ssGet(fn))); 1452 FILE *f = fopen(task_path, "r"); 1453 const char *task_status = get_status(ssGet(fn)); 1454 // filter status, keep task if status filter is enabled 1455 if (getG(status_filters_d, rtI32, task_status) == ENABLE) { 1456 createAllocateSmallDict(task_d); 1457 setG(task_d, "head", "element"); 1458 setG(task_d, "tid", ssGet(fn)); 1459 setG(task_d, "position", current_position); 1460 setG(task_d, "group", group); 1461 setNFreeG(task_d, "title", trimG(readLineG(rtSmallStringt, f))); 1462 setG(task_d, "status", get_status(ssGet(fn))); 1463 smallArrayt *cd = get_creation_date(ssGet(fn)); 1464 setG(task_d, "ctime", getG(cd, rtI64, 1)); 1465 terminateG(cd); 1466 pushNFreeG(result, task_d); 1467 } 1468 fclose(f); 1469 restore_edi_core_data_location(); 1470 finishG(fn); 1471 } 1472 } 1473 createAllocateSmallDict(task_d); 1474 setG(task_d, "head", "empty line"); 1475 setG(task_d, "tid", ""); 1476 setG(task_d, "position", 0); 1477 setG(task_d, "group", ""); 1478 setG(task_d, "title", ""); 1479 setG(task_d, "status", ""); 1480 setG(task_d, "ctime", 0); 1481 pushNFreeG(result, task_d); 1482 return result; 1483 } 1484 1485 /** lists all items in the tid group 1486 * @return list of tasks and groups title 1487 * @param[in] tid task id 1488 * @ingroup EDI_CORE 1489 */ 1490 smallArrayt *list_tree(const char *tid) { 1491 createAllocateSmallArray(result); 1492 // walk_group is the list of groups to visit. FIFO 1493 createAllocateSmallArray(walk_group); 1494 pushG(walk_group, tid); 1495 // the while loop goes through all the group that are found 1496 while (lenG(walk_group)) { 1497 // list visible groups only 1498 const char *task_status = get_status(getG(walk_group, rtChar, 0)); 1499 if (getG(status_filters_d, rtI32, task_status) == ENABLE) { 1500 // list items in first group 1501 smallArrayt *tasks = readDirG(rtSmallArrayt, generate_group_path(getG(walk_group, rtChar, 0))); 1502 appendNSmashG(result, list_group(getG(walk_group, rtChar, 0))); 1503 1504 // add group found in first group 1505 forEachSmallArray(tasks, T) { 1506 castS(t, T); 1507 // Check tasks that are not title task in a group 1508 if (not eqG(ssGet(t)+ORDER_ID_LENGTH, getG(walk_group, rtChar, 0)) and is_this_task_a_group(ssGet(t)+ORDER_ID_LENGTH)) { 1509 pushG(walk_group, ssGet(t)+ORDER_ID_LENGTH); 1510 } 1511 finishG(t); 1512 } 1513 terminateG(tasks); 1514 } 1515 1516 // remove first group to list items in next group 1517 delG(walk_group, 0, 1); 1518 } 1519 terminateG(walk_group); 1520 return result; 1521 } 1522 1523 /** string returned from generate_id */ 1524 static char return_generate_id[ID_LENGTH+1]; 1525 1526 /** generates a random task id 1527 * @return new task id 1528 * @ingroup EDI_CORE 1529 */ 1530 const char *generate_id(void) { 1531 return_generate_id[ID_LENGTH] = 0; 1532 1533 randomUrandomOpen(); 1534 range(i, ID_LENGTH) { 1535 return_generate_id[i] = ID_BASE[randomChoice(BASE)]; 1536 } 1537 return return_generate_id; 1538 } 1539 1540 /** string returned from baseconvert */ 1541 static char return_baseconvert[ORDER_ID_LENGTH+1]; 1542 1543 /** converts decimal number to BASE (base 65) 1544 * @return string representing a number in base BASE 1545 * @param[in] n integer 1546 * @ingroup EDI_CORE 1547 */ 1548 const char *baseconvert(i64 n) { 1549 if (n < 0) { 1550 return_baseconvert[0] = 0; 1551 } 1552 else { 1553 return_baseconvert[ORDER_ID_LENGTH] = 0; 1554 u8 i = ORDER_ID_LENGTH-1; 1555 while (1) { 1556 i64 r = n % BASE; 1557 return_baseconvert[i] = ID_BASE[r]; 1558 i--; 1559 n = n / BASE; 1560 if (n == 0) 1561 break; 1562 } 1563 rangeDown(j, i+1) { 1564 return_baseconvert[j] = ID_BASE[0]; 1565 } 1566 } 1567 return return_baseconvert; 1568 } 1569 1570 /** converts BASE number to decimal 1571 * @return n integer 1572 * @param[in] n string representing a number in base BASE 1573 * @ingroup EDI_CORE 1574 */ 1575 i64 baseconvert_to_dec(const char *n) { 1576 i64 r = 0; 1577 i64 power = 1; 1578 rangeDown(digit, lenG(n)) { 1579 int i = 0; 1580 range(a, BASE) { 1581 if (ID_BASE_STRING[a] == getG(n, unusedV, digit)) { 1582 break; 1583 } 1584 i++; 1585 } 1586 r += i * power; 1587 power *= BASE; 1588 } 1589 return r; 1590 } 1591 1592 /** string returned from add_task_to_group_folder */ 1593 static char return_add_task_to_group_folder[8192]; 1594 1595 /** add task to group folder in database groups 1596 * @param[in] tid task id 1597 * @param[in] group task id 1598 * @ingroup EDI_CORE 1599 * Tasks are added at the top or bottom of the list. 1600 */ 1601 const char *add_task_to_group_folder(const char *tid, const char *group) { 1602 // Create an entry in group 1603 smallArrayt *tasks = NULL; 1604 const char *order_id; 1605 tasks = readDirG(rtSmallArrayt, generate_group_path(group)); 1606 // Add +1 to last order_id to have the task last in the list 1607 if (tasks and (lenG(tasks) > 0)) { 1608 if ((lenG(tasks) == 1) or (eqG(add_top_or_bottom, "bottom"))) { 1609 // add tasks in bottom 1610 char *s = copyRngG(getG(tasks, rtChar, -1), 0, ORDER_ID_LENGTH); 1611 order_id = baseconvert(baseconvert_to_dec(s)+1); 1612 free(s); 1613 } 1614 else { 1615 // add tasks on top 1616 // temporary orderid # 1617 char orderid_and_tid[ORDER_ID_LENGTH + ID_LENGTH + 1]; 1618 range(i, ORDER_ID_LENGTH) { 1619 orderid_and_tid[i] = '#'; 1620 } 1621 strcat(orderid_and_tid, tid); 1622 i64 to_pos; 1623 if (eqG(group, "root")) { 1624 to_pos = 0; 1625 } 1626 else { 1627 // add new tasks after group title 1628 to_pos = 1; 1629 } 1630 1631 injectSG(tasks, to_pos, orderid_and_tid); 1632 1633 // Move tasks 1634 const char *path = generate_group_path(group); 1635 enumerateSmallArray(tasks, T, n) { 1636 castS(t, T); 1637 if (n == to_pos) { 1638 // set orderid on top 1639 order_id = baseconvert(n); 1640 } 1641 else { 1642 char src[8192]; 1643 sprintf(src, "%s/%s", path, ssGet(t)); 1644 char dst[8192]; 1645 sprintf(dst, "%s/%s%s", path, baseconvert(n), ssGet(t)+ORDER_ID_LENGTH); 1646 shRename(src, dst); 1647 } 1648 finishG(t); 1649 } 1650 } 1651 } 1652 else { 1653 // start at 0 when group is empty 1654 order_id = baseconvert(0); 1655 } 1656 1657 sprintf(return_add_task_to_group_folder, "%s/%s%s", generate_group_path(group), order_id, tid); 1658 // remove double slash that can come up 1659 char *tmp = return_add_task_to_group_folder; 1660 iUniqSlash(tmp); 1661 FILE *f = fopen(return_add_task_to_group_folder, "w"); 1662 fclose(f); 1663 1664 // return return_add_task_to_group_folder to easily add new tasks to group_directory_file_list and save time 1665 return return_add_task_to_group_folder; 1666 } 1667 1668 /** creates a task and opens vi 1669 * @param[in] group task id 1670 * @ingroup EDI_CORE 1671 */ 1672 const char *create_task(const char *group) { 1673 // Open text editor 1674 // create task in tasks folder 1675 //#:define create_task_part1 1676 const char *tid = generate_id(); 1677 1678 // Save text in tasks 1679 const char *task_path = generate_task_path(tid); 1680 mkdirParentsG(task_path); 1681 //#:end 1682 // create an empty file (for windows and mac osx) 1683 createTaskPath(dPath, "description.txt"); 1684 FILE *f = fopen(dPath, "w"); 1685 fclose(f); 1686 if (hasG(editor, "echo TEST")) { 1687 // use os.system to be able to run edi cli unittests 1688 //char *c = catG(editor, dPath); 1689 char *c = catS("echo TEST > ", dPath); 1690 systemNFreeG(c); 1691 } 1692 else { 1693 // use subprocess.call to wait until the editor is closed (Windows and Mac OSX) because the task is deleted when the description is empty 1694 // create a list [command,param1,param2,task file] to allow options on command line to start the editor 1695 smallStringt *ss = allocG(editor); 1696 smallArrayt *editor_l = splitG(ss, "/"); 1697 terminateG(ss); 1698 // after last os.sep there is the command name and the option. Do this because there can be spaces in the path to the command 1699 ss = getG(editor_l, rtSmallStringt, -1); 1700 smallArrayt *options = splitG(ss, " "); 1701 finishG(ss); 1702 setNFreeG(editor_l, -1, getNDupG(options, rtBaset, 0)); 1703 delG(options, 0, 1); 1704 createAllocateSmallArray(command_line); 1705 ss = joinG(editor_l, "/"); 1706 terminateG(editor_l); 1707 pushG(command_line, ss); 1708 appendNSmashG(command_line, options); 1709 rallocG(ss, dPath); 1710 pushNFreeG(command_line, ss); 1711 ss = joinG(command_line, " "); 1712 terminateG(command_line); 1713 systemNFreeG(ss); 1714 } 1715 if (not fileExists(dPath)) { 1716 // file doesnt exist, abort task creation 1717 rmAllG(task_path); 1718 return "no new task, the description is empty"; 1719 } 1720 1721 // Save creation time 1722 //#:define save_task_creation_time 1723 time_t t = getModificationTimeG(dPath); 1724 char *s = timeToS(t); 1725 genTaskPath(dPath, "ctime.txt"); 1726 writeFileG(s, dPath); 1727 free(s); 1728 //#:end 1729 1730 // create status, active by default 1731 //#:define create_task_part2 1732 // Create status 1733 genTaskPath(dPath, "status"); 1734 writeFileG(TASK_STATUS[TASK_STATUS_ACTIVE], dPath); 1735 1736 const char *tid_in_groups_path = add_task_to_group_folder(tid, group); 1737 1738 if (lenG(group_directory_file_list)) { 1739 pushG(group_directory_file_list, tid_in_groups_path); 1740 } 1741 1742 // autolink 1743 if (autolink) { 1744 forEachSmallArray(autolink, G) { 1745 castS(g, G); 1746 if (is_this_task_a_group(ssGet(g))) { 1747 // link to groups existing in current database 1748 add_task_reference_to_a_group(tid, g); 1749 } 1750 finishG(g); 1751 } 1752 } 1753 //#:end 1754 sprintf(dPath, "created %s in group %s", tid, group); 1755 edi_log(dPath); 1756 return tid; 1757 } 1758 1759 /** create task and copy text file 1760 * @param[in] group task id 1761 * @param[in] text_file filename of text file to copy 1762 * @ingroup EDI_CORE 1763 */ 1764 const char *add_task(const char *group, const char *text_file) { 1765 //#:create_task_part1 1766 const char *tid = generate_id(); 1767 1768 // Save text in tasks 1769 const char *task_path = generate_task_path(tid); 1770 mkdirParentsG(task_path); 1771 createTaskPath(dPath, "description.txt"); 1772 copy(text_file, dPath); 1773 //#:save_task_creation_time 1774 time_t t = getModificationTimeG(dPath); 1775 char *s = timeToS(t); 1776 genTaskPath(dPath, "ctime.txt"); 1777 writeFileG(s, dPath); 1778 free(s); 1779 //#:create_task_part2 1780 // Create status 1781 genTaskPath(dPath, "status"); 1782 writeFileG(TASK_STATUS[TASK_STATUS_ACTIVE], dPath); 1783 1784 const char *tid_in_groups_path = add_task_to_group_folder(tid, group); 1785 1786 if (lenG(group_directory_file_list)) { 1787 pushG(group_directory_file_list, tid_in_groups_path); 1788 } 1789 1790 // autolink 1791 if (autolink) { 1792 forEachSmallArray(autolink, G) { 1793 castS(g, G); 1794 if (is_this_task_a_group(ssGet(g))) { 1795 // link to groups existing in current database 1796 add_task_reference_to_a_group(tid, g); 1797 } 1798 finishG(g); 1799 } 1800 } 1801 sprintf(dPath, "created %s in group %s", tid, group); 1802 edi_log(dPath); 1803 return tid; 1804 } 1805 1806 /** create task with description text 1807 * @param[in] group task id 1808 * @param[in] text string 1809 * @ingroup EDI_CORE 1810 */ 1811 const char *add_text(const char *group, smallStringt *text) { 1812 //#:create_task_part1 1813 const char *tid = generate_id(); 1814 1815 // Save text in tasks 1816 const char *task_path = generate_task_path(tid); 1817 mkdirParentsG(task_path); 1818 createTaskPath(dPath, "description.txt"); 1819 writeFileG(text, dPath); 1820 //#:save_task_creation_time 1821 time_t t = getModificationTimeG(dPath); 1822 char *s = timeToS(t); 1823 genTaskPath(dPath, "ctime.txt"); 1824 writeFileG(s, dPath); 1825 free(s); 1826 //#:create_task_part2 1827 // Create status 1828 genTaskPath(dPath, "status"); 1829 writeFileG(TASK_STATUS[TASK_STATUS_ACTIVE], dPath); 1830 1831 const char *tid_in_groups_path = add_task_to_group_folder(tid, group); 1832 1833 if (lenG(group_directory_file_list)) { 1834 pushG(group_directory_file_list, tid_in_groups_path); 1835 } 1836 1837 // autolink 1838 if (autolink) { 1839 forEachSmallArray(autolink, G) { 1840 castS(g, G); 1841 if (is_this_task_a_group(ssGet(g))) { 1842 // link to groups existing in current database 1843 add_task_reference_to_a_group(tid, g); 1844 } 1845 finishG(g); 1846 } 1847 } 1848 sprintf(dPath, "created %s in group %s", tid, group); 1849 edi_log(dPath); 1850 return tid; 1851 } 1852 1853 /** create task and copy text file, filename is task title 1854 * @param[in] group task id 1855 * @param[in] text_file filename of text file to copy 1856 * @ingroup EDI_CORE 1857 */ 1858 const char *add_task_and_filename(const char *group, const char *text_file) { 1859 const char *tid = add_task(group,text_file); 1860 1861 // Add file name on first line 1862 createTaskPath(dPath, "description.txt"); 1863 createAllocateSmallArray(f); 1864 readFileG(f, dPath); 1865 char *s = basename(text_file); 1866 prependG(f, s); 1867 writeFileG(f, dPath); 1868 terminateG(f); 1869 return tid; 1870 } 1871 1872 /** string returned from add_many_tasks_array */ 1873 static char return_add_many_tasks_array[100]; 1874 1875 /** create many tasks from a text array 1876 * @param[in] group task id 1877 * @param[in] text_array text array to copy 1878 * @ingroup EDI_CORE 1879 * Used directly in Easydoneit Desktop 1880 */ 1881 const char *add_many_tasks_array(const char *group, smallArrayt *text_array) { 1882 return_add_many_tasks_array[0] = 0; 1883 1884 // Copy a task to text string 1885 // Remove '#' from first line, first character position 1886 // Add task to database 1887 createAllocateSmallString(text); 1888 i64 number_of_tasks = 0; 1889 enum {START, WRITING_TASK}; 1890 i8 status = START; 1891 forEachSmallArray(text_array, L) { 1892 castS(l, L); 1893 if (eqG(l, "---")) { 1894 if (status != START) { 1895 // add task to database 1896 add_text(group,text); 1897 number_of_tasks++; 1898 } 1899 emptyG(text); 1900 status = START; 1901 } 1902 else { 1903 if ((status == START) and (ssGet(l)[0] == '#')) { 1904 delG(l, 0, 1); 1905 } 1906 appendNSmashG(text, dupG(l)); 1907 status = WRITING_TASK; 1908 } 1909 finishG(l); 1910 } 1911 1912 if (status == WRITING_TASK) { 1913 // add task to database 1914 add_text(group,text); 1915 number_of_tasks++; 1916 } 1917 terminateG(text); 1918 1919 sprintf(return_add_many_tasks_array, "created %ld tasks in group %s", number_of_tasks, group); 1920 edi_log(return_add_many_tasks_array); 1921 return return_add_many_tasks_array; 1922 } 1923 1924 /** create many tasks from a text file 1925 * @param[in] group task id 1926 * @param[in] text_file filename of text file to copy 1927 * @ingroup EDI_CORE 1928 */ 1929 const char *add_many_tasks(const char *group, const char *text_file) { 1930 1931 // load file and call add_many_tasks_array 1932 createAllocateSmallArray(text_array); 1933 readFileG(text_array, text_file); 1934 const char *r = add_many_tasks_array(group, text_array); 1935 terminateG(text_array); 1936 return r; 1937 } 1938 1939 /** string returned from add_many_one_line_tasks_array */ 1940 static char return_add_many_one_line_tasks_array[100]; 1941 1942 /** create many one line tasks from a text array 1943 * @param[in] group task id 1944 * @param[in] text_array text array to copy 1945 * @ingroup EDI_CORE 1946 * Used directly in Easydoneit Desktop 1947 */ 1948 const char *add_many_one_line_tasks_array(const char *group, smallArrayt *text_array) { 1949 return_add_many_one_line_tasks_array[0] = 0; 1950 1951 i64 number_of_tasks = 0; 1952 forEachSmallArray(text_array, L) { 1953 castS(l, L); 1954 trimG(l); 1955 add_text(group,l); 1956 number_of_tasks++; 1957 finishG(l); 1958 } 1959 1960 sprintf(return_add_many_one_line_tasks_array, "created %ld tasks in group %s", number_of_tasks,group); 1961 edi_log(return_add_many_one_line_tasks_array); 1962 return return_add_many_one_line_tasks_array; 1963 } 1964 1965 /** create many one line tasks from a text file 1966 * @param[in] group task id 1967 * @param[in] text_file filename of text file to copy 1968 * @ingroup EDI_CORE 1969 */ 1970 const char *add_many_one_line_tasks(const char *group, const char *text_file) { 1971 1972 createAllocateSmallArray(text_array); 1973 readFileG(text_array, text_file); 1974 const char *r = add_many_one_line_tasks_array(group, text_array); 1975 terminateG(text_array); 1976 return r; 1977 } 1978 1979 /** string returned from add_many_groups_from_text_array */ 1980 static char return_add_many_groups_from_text_array[100]; 1981 1982 /** create group from text array 1983 * @param[in] group task id 1984 * @param[in] text_array with group structure 1985 * @ingroup EDI_CORE 1986 * Used directly in Easydoneit Desktop 1987 */ 1988 const char *add_many_groups_from_text_array(const char *group, smallArrayt *text_array) { 1989 return_add_many_groups_from_text_array[0] = 0; 1990 1991 i64 number_of_tasks = 0; 1992 createAllocateSmallArray(group_stack); 1993 pushG(group_stack, group); 1994 forEachSmallArray(text_array, L) { 1995 castS(l, L); 1996 // count space indents 1997 i64 indent = 0; 1998 char *s = ssGet(l); 1999 range(i, lenG(l)) { 2000 if (s[i] != ' ') { 2001 break; 2002 } 2003 indent++; 2004 } 2005 if (indent < lenG(group_stack)-1) { 2006 // remove groups from stack if same level or higher levels 2007 delG(group_stack, -(lenG(group_stack)-1 - indent), 0); 2008 } 2009 trimG(l); 2010 const char *tid = add_text(getG(group_stack, rtChar,-1), l); 2011 create_group(tid); 2012 pushG(group_stack, tid); 2013 number_of_tasks++; 2014 finishG(l); 2015 } 2016 2017 sprintf(return_add_many_groups_from_text_array, "created %ld groups in group %s", number_of_tasks, group); 2018 edi_log(return_add_many_groups_from_text_array); 2019 return return_add_many_groups_from_text_array; 2020 } 2021 2022 /* create group from text file 2023 * @param[in] group task id 2024 * @param[in] text_file with group structure 2025 * @ingroup EDI_CORE 2026 */ 2027 const char *add_many_groups_from_text(const char *group, const char *text_file) { 2028 2029 // load file and call add_many_groups_from_text_array 2030 createAllocateSmallArray(text_array); 2031 readFileG(text_array, text_file); 2032 const char *r = add_many_groups_from_text_array(group, text_array); 2033 terminateG(text_array); 2034 2035 return r; 2036 } 2037 2038 /** string returned from export_task_to_a_file */ 2039 static char return_export_task_to_a_file[8192]; 2040 2041 /** copy description to path using first line of description as filename 2042 * @return path and filname 2043 * @param[in] tid task id 2044 * @param[in] path destination directory for task description 2045 * @ingroup EDI_CORE 2046 */ 2047 const char *export_task_to_a_file(const char *tid, const char *path) { 2048 createTaskPath(dPath, "description.txt"); 2049 createAllocateSmallArray(des); 2050 readFileG(des, dPath); 2051 smallStringt *fn = getNDupG(des, rtSmallStringt, 0); 2052 trimG(fn); 2053 delG(des, 0, 1); 2054 sprintf(return_export_task_to_a_file, "%s/%s", path, ssGet(fn)); 2055 // remove eventual double // 2056 uniqSlash((char *)&return_export_task_to_a_file); 2057 2058 writeFileG(des, return_export_task_to_a_file); 2059 return return_export_task_to_a_file; 2060 } 2061 2062 /** print description of task tid 2063 * @return list of strings 2064 * @param[in] tid task id 2065 * @param[in] titleFunc Function that changes first description line 2066 * @ingroup EDI_CORE 2067 * In GUI, titleFunc (passThroughTitle function) keeps original title line. 2068 */ 2069 // python call: def display_task tid, titleFunc=generate_task_string_with_tid 2070 smallArrayt *display_task(const char *tid, smallStringt *titleFunc(const char *, smallStringt *)) { 2071 createTaskPath(task_path, "description.txt"); 2072 createAllocateSmallArray(description); 2073 if (isBlankG(tid)) { 2074 // tid is invalid, return empty description 2075 pushG(description, ""); 2076 return description; 2077 } 2078 readFileG(description, task_path); 2079 2080 // print tid, status and first line 2081 smallStringt *s = getG(description, rtSmallStringt, 0); 2082 if (s) { 2083 setNFreeG(description, 0, titleFunc(tid, s)); 2084 } 2085 else { 2086 // empty description, push empty line 2087 pushG(description, ""); 2088 } 2089 finishG(s); 2090 return description; 2091 } 2092 2093 2094 /** string returned frm find_group_containing_task */ 2095 static char return_find_group_containing_task[64]; 2096 2097 /** find group containing task tid in groups folder 2098 * @return tid 2099 * @param[in] tid task id 2100 * @ingroup EDI_CORE 2101 */ 2102 const char *find_group_containing_task(const char *tid) { 2103 if (eqG(tid, "root")) { 2104 // root has not parent group, return root 2105 return tid; 2106 } 2107 return_find_group_containing_task[0] = 0; 2108 //#:define walk_groups 2109 // reuse previously created group list, to save time 2110 if (not lenG(group_directory_file_list)) { 2111 terminateG(group_directory_file_list); 2112 ; group_directory_file_list = walkDirG(rtSmallArrayt, data_location_groups); 2113 } 2114 smallArrayt *groups_and_tasks = group_directory_file_list; 2115 forEachSmallArray(groups_and_tasks, GT) { 2116 castS(t, GT); 2117 smallArrayt *ts = splitG(t, "/"); 2118 if (endsWithG(t, tid) and not eqG(tid, getG(ts, rtChar, -2))) { 2119 strcpy(return_find_group_containing_task, getG(ts, rtChar, -2)); 2120 } 2121 terminateG(ts); 2122 finishG(GT); 2123 } 2124 //#:end 2125 if (return_find_group_containing_task[0] == 0) { 2126 printf("\n\nEDI_ERROR: Database inconsistent - run: find %s|grep %s and remove reference.\n", data_location, tid); 2127 strcpy(return_find_group_containing_task, "error"); 2128 } 2129 return return_find_group_containing_task; 2130 } 2131 2132 /** find group containing task tid in groups folder and print 2133 * @return list of strings 2134 * @param[in] tid task id 2135 * @ingroup EDI_CORE 2136 * Shows path in tree and title for each group in path 2137 */ 2138 smallArrayt *show_group_for_task(const char *tid) { 2139 createAllocateSmallArray(r); 2140 2141 // set group string to be displayed on first line 2142 char *group_s = " "; 2143 if (is_this_task_a_group(tid)) { 2144 group_s = "GROUP"; 2145 } 2146 if (is_linked(tid)) { 2147 group_s = " LINK"; 2148 } 2149 2150 //#:walk_groups 2151 // reuse previously created group list, to save time 2152 if (not lenG(group_directory_file_list)) { 2153 terminateG(group_directory_file_list); 2154 group_directory_file_list = walkDirG(rtSmallArrayt, data_location_groups); 2155 } 2156 smallArrayt *groups_and_tasks = group_directory_file_list; 2157 createAllocateSmallString(treePath); 2158 forEachSmallArray(groups_and_tasks, GT) { 2159 castS(t, GT); 2160 smallArrayt *ts = splitG(t, "/"); 2161 if (endsWithG(t, tid) and not eqG(tid, getG(ts, rtChar, -2))) { 2162 char *group = return_find_group_containing_task; 2163 strcpy(return_find_group_containing_task, getG(ts, rtChar, -2)); 2164 const char *tree_path = find_group_in_tree(group); 2165 2166 // p is data_location folder/tree 2167 smallStringt *s = allocG(data_location_tree); 2168 smallArrayt *p_l = splitG(s, "/"); 2169 terminateG(s); 2170 sliceG(p_l, -2, 0); 2171 smallStringt *p = joinG(p_l, "/"); 2172 terminateG(p_l); 2173 2174 // if empty then command is run in tree root 2175 setFromG(treePath, tree_path); 2176 p_l = splitG(treePath, ssGet(p)); 2177 if (lenG(getG(p_l, rtChar, -1))) { 2178 // print path of tids: tid/tid... 2179 s = getNDupG(p_l, rtSmallStringt, -1); 2180 // remove / 2181 delG(s, 0, 1); 2182 pushG(r, s); 2183 smallArrayt *g_l = splitG(s, "/"); 2184 finishG(s); 2185 createAllocateSmallArray(group_titles_in_path); 2186 forEachSmallArray(g_l, G) { 2187 castS(g, G); 2188 pushNFreeG(group_titles_in_path, get_task_title(ssGet(g))); 2189 finishG(g); 2190 } 2191 terminateG(g_l); 2192 // print title/title... 2193 pushNFreeG(r, joinG(group_titles_in_path, "/")); 2194 terminateG(group_titles_in_path); 2195 } 2196 terminateManyG(p, p_l); 2197 p = get_task_title(group); 2198 pushNFreeG(r, generate_group_string_with_tid(group, p)); 2199 terminateG(p); 2200 pushG(r, ""); 2201 } 2202 terminateG(ts); 2203 finishG(GT); 2204 } 2205 terminateG(treePath); 2206 2207 // Print media and attachments 2208 // media type 2209 smallArrayt *m = get_media(tid); 2210 char *media = getG(m, rtChar, 0); 2211 // attachment list 2212 smallArrayt *attachments = get_attachments(tid); 2213 2214 // Print task, colors, group list 2215 smallArrayt *color = get_forground_color(tid); 2216 char *fc = toStringG(color); 2217 terminateG(color); 2218 color = get_background_color(tid); 2219 char *bc = toStringG(color); 2220 // Figure out creation time and modification times for description and status 2221 smallArrayt *tctime = get_creation_date(tid); 2222 char *task_ctime = getG(tctime, rtChar, 0); 2223 2224 createTaskPath(task_path, "description.txt"); 2225 char *description_mtime = timeToS(getModificationTimeG(task_path)); 2226 genTaskPath(task_path, "status"); 2227 char *status_mtime = timeToS(getModificationTimeG(task_path)); 2228 smallStringt *tt = get_task_title(tid); 2229 prependG(tt, " "); 2230 prependG(tt, group_s); 2231 smallStringt *s = generate_task_string_with_tid(tid, tt); 2232 createAllocateSmallArray(info); 2233 pushG (info, "Tasks:"); 2234 pushG (info, ssGet(s)); 2235 pushG (info, ""); 2236 pushG (info, ""); 2237 terminateManyG(tt, s); 2238 pushNFreeG (info, appendG("Media type: ", media)); 2239 pushG (info, ""); 2240 pushG (info, "Attachments:"); 2241 appendNSmashG (info, attachments); 2242 pushG (info, ""); 2243 pushG (info, ""); 2244 pushNFreeG (info, formatS("foreground color: %s", fc)); 2245 pushNFreeG (info, formatS("background color: %s", bc)); 2246 pushNFreeG (info, formatS("Creation time: %s", task_ctime)); 2247 pushNFreeG (info, formatS("Last description change: %s", description_mtime)); 2248 pushNFreeG (info, formatS("Last status change: %s", status_mtime)); 2249 pushG (info, ""); 2250 pushG (info, "Group list"); 2251 appendNSmashG (info, r); 2252 r = info; 2253 terminateManyG(m, color, tctime); 2254 freeManyS(fc, bc, description_mtime, status_mtime); 2255 return r; 2256 } 2257 2258 /** string returned from find_group_in_tree */ 2259 static char return_find_group_in_tree[8192]; 2260 2261 /** find group in tree folder 2262 * @return path_in_tree 2263 * @param[in] tid task id 2264 * @ingroup EDI_CORE 2265 */ 2266 const char *find_group_in_tree(const char *group) { 2267 char *path_in_tree; 2268 if (eqG(group, "root")) { 2269 path_in_tree = data_location_tree; 2270 } 2271 else { 2272 path_in_tree = return_find_group_in_tree; 2273 path_in_tree[0] = 0; 2274 2275 // list all group paths in tree 2276 /* if platform.system() = 'Windows' */ 2277 /* folders = [] */ 2278 /* folders append data_location_tree */ 2279 /* for dir, subdirs, files in os.walk(data_location_tree) */ 2280 /* for sd in subdirs */ 2281 /* folders append dir+os.sep + sd */ 2282 /* else */ 2283 smallArrayt *folders = walkDirAllG(rtSmallArrayt, data_location_tree); 2284 enumerateSmallArray(folders, F, i) { 2285 castS(f, F); 2286 trimG(f); 2287 setNFreePG(folders, i, f); 2288 } 2289 smallArrayt *groups = folders; 2290 // find the group in group paths 2291 forEachSmallArray(groups, G) { 2292 castS(g, G); 2293 char *s = basename(ssGet(g)); 2294 if (eqG(s, group)) 2295 strcpy(path_in_tree, ssGet(g)); 2296 finishG(g); 2297 } 2298 terminateG(folders); 2299 } 2300 return path_in_tree; 2301 } 2302 2303 /** Determines if a task has multiple references 2304 * @return integer 0 or 1 2305 * @param[in] tid task id 2306 * @ingroup EDI_CORE 2307 */ 2308 bool is_linked(const char *tid) { 2309 bool status = false; 2310 createTaskPath(task_linked_groups_path, "groups/"); 2311 // check if tid/groups exists 2312 if (fileExists(task_linked_groups_path)) { 2313 smallArrayt *groups = readDirG(rtSmallArrayt, task_linked_groups_path); 2314 // check if task is linked to more than 1 group 2315 if (lenG(groups) > 1) { 2316 status = true; 2317 } 2318 terminateG(groups); 2319 } 2320 return status; 2321 } 2322 2323 /** convert task to group 2324 * @return list of stings when there is an error 2325 * @param[in] tid task id 2326 * @ingroup EDI_CORE 2327 */ 2328 smallArrayt *create_group(const char *tid) { 2329 createAllocateSmallArray(r); 2330 // convert task to group only when task is not linked 2331 if (is_linked(tid)) { 2332 pushG(r, "Converting linked task to group removes links. The task groups are:"); 2333 appendNSmashG(r, show_group_for_task(tid)); 2334 2335 // delete tid/groups because groups are not linked 2336 createTaskPath(task_linked_groups_path, "groups/"); 2337 smallArrayt *groups = readDirG(rtSmallArrayt, task_linked_groups_path); 2338 // remove all links except for the first group 2339 forEachSmallArray(groups, G) { 2340 castS(g, G); 2341 delete_linked_task(ssGet(g),tid); 2342 finishG(g); 2343 } 2344 if (fileExistsG(task_linked_groups_path)) { 2345 rmAll(task_linked_groups_path); 2346 } 2347 pushNFreeG(r, formatS("Created group %s in %s", tid, getG(groups, rtChar,0))); 2348 terminateG(groups); 2349 } 2350 // create new group in groups folder 2351 char p[8192]; 2352 sprintf(p, "%s/%s", data_location_groups, tid); 2353 mkdirParentsG(p); 2354 // First task in group is group title task 2355 const char *order_id = baseconvert(0); 2356 sprintf(p, "%s/%s%s", generate_group_path(tid), order_id, tid); 2357 FILE *f = fopen(p, "w"); 2358 fclose(f); 2359 2360 // Add group in tree 2361 // update group list 2362 emptyG(group_directory_file_list); 2363 const char *group = find_group_containing_task(tid); 2364 if (eqG(group, "root")) { 2365 sprintf(p, "%s/%s", data_location_tree, tid); 2366 mkdirParentsG(p); 2367 // git support 2368 sprintf(p, "%s/%s/.keepDir", data_location_tree, tid); 2369 f = fopen(p, "w"); 2370 fclose(f); 2371 } 2372 else { 2373 const char *s = find_group_in_tree(group); 2374 sprintf(p, "%s/%s", s, tid); 2375 mkdirParentsG(p); 2376 // git support 2377 sprintf(p, "%s/%s/.keepDir", s, tid); 2378 f = fopen(p, "w"); 2379 fclose(f); 2380 } 2381 2382 // Change status active to void by default 2383 // To avoid filtering groups 2384 if (eqG(get_status(tid), TASK_STATUS[TASK_STATUS_ACTIVE])) { 2385 // set status to void 2386 set_status(tid,TASK_STATUS_VOID); 2387 } 2388 sprintf(p, "created group %s", tid); 2389 edi_log(p); 2390 return r; 2391 } 2392 2393 /** string returned from convert_group_to_task */ 2394 static char return_convert_group_to_task[1]; 2395 2396 /** convert group to task 2397 * @param[in] group group to convert to task 2398 * @ingroup EDI_CORE 2399 */ 2400 const char *convert_group_to_task(const char *group) { 2401 char *r = return_convert_group_to_task; 2402 r[0] = 0; 2403 2404 // list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder 2405 smallArrayt *groups_and_tasks = walkDirG(rtSmallArrayt, data_location_groups); 2406 2407 char *convert_group_status = "delete"; 2408 char groupslash[ORDER_ID_LENGTH + ID_LENGTH + 1]; 2409 sprintf(groupslash, "%s/", group); 2410 forEachSmallArray(groups_and_tasks, T2) { 2411 castS(t2, T2); 2412 // search a task in group that is not tid 2413 char *t2task = basename(ssGet(t2)); 2414 if (hasG(t2, groupslash) and (not eqG(group, t2task))) 2415 convert_group_status = "There is another task in the group, keeping group."; 2416 r = convert_group_status; 2417 // just need to find one task 2418 break; 2419 finishG(t2); 2420 } 2421 terminateG(groups_and_tasks); 2422 if (eqG(convert_group_status, "delete") and (not hasG(group, "root"))) { 2423 // Delete group title and group folder 2424 rmAllG(generate_group_path(group)); 2425 // Delete group in tree 2426 const char *treepath = find_group_in_tree(group); 2427 if (not isEmptyG(treepath)) { 2428 rmAll(treepath); 2429 } 2430 // change state from void to active 2431 set_status(group,TASK_STATUS_ACTIVE); 2432 char log[100]; 2433 sprintf(log, "converted group %s to task", group); 2434 edi_log(log); 2435 } 2436 return r; 2437 } 2438 2439 /** string returned from delete_task */ 2440 static char return_delete_task[ORDER_ID_LENGTH + ID_LENGTH + 1]; 2441 2442 /** delete task tid in all groups 2443 * @return group id 2444 * @param[in] tid task id 2445 * @ingroup EDI_CORE 2446 */ 2447 const char *delete_task(const char *tid) { 2448 // Delete task in tasks 2449 rmAll(generate_task_path(tid)); 2450 2451 // Delete task reference in groups 2452 // list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder 2453 smallArrayt *groups_and_tasks = walkDirG(rtSmallArrayt, data_location_groups); 2454 2455 // Identify if task is a group 2456 bool is_a_group = is_this_task_a_group(tid); 2457 2458 // Return group task, it changes when first task is deleted 2459 char *group_id = return_delete_task; 2460 group_id[0] = 0; 2461 // list groups to reorder at the end 2462 createAllocateSmallArray(reorder_groups); 2463 forEachSmallArray(groups_and_tasks, T) { 2464 castS(t, T); 2465 // Delete reference in upper group, not the title task of the group 2466 smallArrayt *ta = splitG(t, "/"); 2467 char thisGroup[8192]; 2468 strcpy(thisGroup, getG(ta, rtChar, -2)); 2469 terminateG(ta); 2470 if ((endsWithG(t, tid)) and (not eqG(tid, thisGroup))) { 2471 // group is the group for task tid 2472 strcpy(group_id, thisGroup); 2473 // Delete task reference in group 2474 rmAllG(t); 2475 pushG(reorder_groups, group_id); 2476 if (is_a_group) { 2477 // First task becomes a group, add a reference in group group 2478 // list tasks in order in tid group 2479 smallArrayt *group_tasks = readDirG(rtSmallArrayt, generate_group_path(tid)); 2480 // Delete emtpy group or first task in group becomes the group title. 2481 if (lenG(group_tasks) == 1) { 2482 rmAllG(generate_group_path(tid)); 2483 // Delete group in tree 2484 // when a group is deleted, subgroups are automatically deleted in the tree 2485 const char *s = find_group_in_tree(tid); 2486 if (not isEmptyG(s)) { 2487 rmAllG(s); 2488 } 2489 } 2490 else { 2491 // Remove order_id, keep task id only 2492 char *first_task = getG(group_tasks, rtChar,1) + ORDER_ID_LENGTH; 2493 strcpy(group_id, first_task); 2494 // Create an entry in group at the same position as tid had 2495 ta = splitG(t, "/"); 2496 char *order_id = copyRngG(getG(ta, rtChar, -1), 0, ORDER_ID_LENGTH); 2497 terminateG(ta); 2498 char groupPath[8192]; 2499 sprintf(groupPath, "%s/%s%s", generate_group_path(group_id), order_id, first_task); 2500 free(order_id); 2501 FILE *f = fopen(groupPath, "w"); 2502 fclose(f); 2503 2504 // Delete group task of group of more then 2 tasks, first task becomes a group 2505 // delete orderidtaskid 2506 sprintf(groupPath, "%s/%s", generate_group_path(tid), getG(group_tasks, rtChar, 0)); 2507 rmAllG(groupPath); 2508 const char *path = generate_group_path(tid); 2509 char src[8192]; 2510 sprintf(src, "%s/%s", path, getG(group_tasks, rtChar, 1)); 2511 char dst[8192]; 2512 sprintf(dst, "%s/%s%s", path, baseconvert(0), first_task); 2513 shRename(src, dst); 2514 // reorder tasks to remove gap between group title task and first task 2515 smallArrayt *tasks = readDirG(rtSmallArrayt, path); 2516 enumerateSmallArray(tasks, T, n) { 2517 castS(t, T); 2518 char src[8192]; 2519 sprintf(src, "%s/%s", path, ssGet(t)); 2520 char dst[8192]; 2521 sprintf(dst, "%s/%s%s", path, baseconvert(n), ssGet(t)+ORDER_ID_LENGTH); 2522 shRename(src, dst); 2523 finishG(t); 2524 } 2525 terminateG(tasks); 2526 // rename group in groups folder 2527 shRename(path, generate_group_path(first_task)); 2528 // rename group in tree folder 2529 const char *group_tree_path = find_group_in_tree(tid); 2530 smallStringt *s = allocG(group_tree_path); 2531 smallArrayt *group_tree_path_l = splitG(s, "/"); 2532 terminateG(s); 2533 setG(group_tree_path_l, -1, first_task); 2534 smallStringt *new_group_tree_path = joinG(group_tree_path_l, "/"); 2535 shRename(group_tree_path, ssGet(new_group_tree_path)); 2536 terminateManyG(new_group_tree_path, group_tree_path_l); 2537 } 2538 terminateG(group_tasks); 2539 } 2540 } 2541 finishG(t); 2542 } 2543 terminateG(groups_and_tasks); 2544 2545 // reorder tasks to remove gaps in reorder_groups 2546 forEachSmallArray(reorder_groups, T) { 2547 castS(tidg, T); 2548 const char *path = generate_group_path(ssGet(tidg)); 2549 smallArrayt *tasks = readDirG(rtSmallArrayt, path); 2550 enumerateSmallArray(tasks, TD, n) { 2551 castS(t, TD); 2552 char src[8192]; 2553 sprintf(src, "%s/%s", path, ssGet(t)); 2554 char dst[8192]; 2555 sprintf(dst, "%s/%s%s", path, baseconvert(n), ssGet(t)+ORDER_ID_LENGTH); 2556 shRename(src, dst); 2557 finishG(t); 2558 } 2559 terminateG(tasks); 2560 finishG(tidg); 2561 } 2562 2563 // Return group task 2564 smallStringt *s = joinG(reorder_groups, " "); 2565 char *log = formatS("deleted %s in %s", tid, ssGet(s)); 2566 edi_log(log); 2567 free(log); 2568 terminateManyG(reorder_groups, s); 2569 return group_id; 2570 } 2571 2572 /** string returned from delete_linked_task */ 2573 static char return_delete_linked_task[ORDER_ID_LENGTH + ID_LENGTH + 1]; 2574 2575 /** Delete task only if it is linked in one group 2576 * @return group id 2577 * @param[in] group task id 2578 * @param[in] tid task id 2579 * @ingroup EDI_CORE 2580 */ 2581 const char *delete_linked_task(const char *group, const char *tid) { 2582 char *group_id = return_delete_linked_task; 2583 strcpy(group_id, group); 2584 2585 if (not is_linked(tid)) { 2586 group_id = (char *) delete_task(tid); 2587 } 2588 else { 2589 // Delete task reference in group 2590 const char *path = generate_group_path(group); 2591 char p[8192]; 2592 2593 // find task in group: ORDER_IDTASK_ID 2594 smallArrayt *tasks = readDirG(rtSmallArrayt, path); 2595 forEachSmallArray(tasks, T) { 2596 castS(t, T); 2597 if (eqG(ssGet(t)+ORDER_ID_LENGTH, tid)) { 2598 sprintf(p, "%s%s", path, ssGet(t)); 2599 rmAllG(p); 2600 } 2601 finishG(t); 2602 } 2603 terminateG(tasks); 2604 2605 // delete group in tid/groups 2606 sprintf(p, "%s/groups/%s", generate_task_path(tid), group); 2607 rmAllG(p); 2608 2609 // reorder tasks to remove gaps in group 2610 tasks = readDirG(rtSmallArrayt, path); 2611 enumerateSmallArray(tasks, T, n) { 2612 castS(t, T); 2613 char src[8192]; 2614 sprintf(src, "%s/%s", path, ssGet(t)); 2615 char dst[8192]; 2616 sprintf(dst, "%s/%s%s", path, baseconvert(n), ssGet(t)+ORDER_ID_LENGTH); 2617 shRename(src, dst); 2618 finishG(t); 2619 } 2620 terminateG(tasks); 2621 2622 sprintf(p, "deleted %s in group %s", tid, group); 2623 edi_log(p); 2624 } 2625 2626 return group_id; 2627 } 2628 2629 /** delete group tid 2630 * @return group id 2631 * @param[in] tid task id 2632 * @ingroup EDI_CORE 2633 */ 2634 const char *delete_group(const char *tid) { 2635 // Delete tasks in group 2636 smallArrayt *group_tasks = readDirG(rtSmallArrayt, generate_group_path(tid)); 2637 if ((lenG(group_tasks) == 0) and eqG(tid, "root")) { 2638 // the database is already empty 2639 return tid; 2640 } 2641 if (not eqG(tid, "root")) { 2642 // root group doesnt have a title 2643 // Remove group title from the loop to delete tasks only and then group 2644 delG(group_tasks, 0, 1); 2645 } 2646 2647 // Delete tasks and groups recursively 2648 forEachSmallArray(group_tasks, T) { 2649 castS(t, T); 2650 // Remove order_id, keep task id only 2651 char *oid = ssGet(t)+ORDER_ID_LENGTH; 2652 if (not is_this_task_a_group(oid)) { 2653 // delete tasks that are linked only once 2654 delete_linked_task(tid, oid); 2655 } 2656 else { 2657 delete_group(oid); 2658 } 2659 finishG(t); 2660 } 2661 terminateG(group_tasks); 2662 2663 if (not eqG(tid, "root")) { 2664 // never delete root group 2665 return delete_task(tid); 2666 } 2667 else { 2668 // return root and keep root task 2669 return tid; 2670 } 2671 } 2672 2673 /** edit task with vi 2674 * @param[in] tid task id 2675 * @ingroup EDI_CORE 2676 */ 2677 void edit_task(const char *tid) { 2678 createTaskPath(dPath, "description.txt"); 2679 char c[24576]; 2680 sprintf(c, "%s %s", editor, dPath); 2681 system(c); 2682 sprintf(c, "edited %s", tid); 2683 edi_log(c); 2684 } 2685 2686 /** move task from group at at_pos to to_pos and reorder 2687 * @return list of stings when there is an error 2688 * @param[in] group task id 2689 * @param[in] at_pos selected position 2690 * @param[in] to_pos insert position 2691 * @ingroup EDI_CORE 2692 */ 2693 smallArrayt *change_task_order(const char *group, i64 at_pos, i64 to_pos) { 2694 // List tasks in group 2695 createAllocateSmallArray(r); 2696 char path[8192]; 2697 strcpy(path, generate_group_path(group)); 2698 smallArrayt *tasks = readDirG(rtSmallArrayt, path); 2699 2700 // Verify position 2701 // Get task 2702 char *orderid_and_tid = getG(tasks, rtChar, at_pos); 2703 if (not orderid_and_tid) { 2704 pushNFreeG(r, formatS("%ld is an invalid position.", at_pos)); 2705 goto end; 2706 } 2707 if (to_pos == 0) { 2708 char *tid = orderid_and_tid+ORDER_ID_LENGTH; 2709 // do not move linked tasks to position 0 2710 if (is_linked(tid)) { 2711 pushG(r, "Converting linked task to group removes links. The task groups are:"); 2712 appendNSmashG(r, show_group_for_task(tid)); 2713 2714 // delete tid/groups because groups are not linked 2715 createTaskPath(task_linked_groups_path, "groups/"); 2716 smallArrayt *groups = readDirG(rtSmallArrayt, task_linked_groups_path); 2717 // remove all links except for the first group 2718 forEachSmallArray(groups, G) { 2719 castS(g, G); 2720 if (not eqG(g, group)) { 2721 delete_linked_task(ssGet(g),tid); 2722 } 2723 finishG(g); 2724 } 2725 terminateG(groups); 2726 if (fileExists(task_linked_groups_path)) { 2727 rmAll(task_linked_groups_path); 2728 } 2729 const char *parent_group = find_group_containing_task(group); 2730 pushNFreeG(r, formatS("Created group %s in %s",tid,parent_group)); 2731 } 2732 // do not move groups to position 0 2733 if (is_this_task_a_group(tid)) { 2734 pushG(r, "Having a group in group title is not supported."); 2735 goto end; 2736 } 2737 } 2738 // Insert task at to_pos 2739 char *to_pos_tid = NULL; 2740 if (to_pos > at_pos) { 2741 // +1 because at_pos reference will be deleted and will shift to_pos reference 2742 to_pos += 1; 2743 injectSG(tasks, to_pos, orderid_and_tid); 2744 // Delete task at at_pos 2745 delG(tasks, at_pos, at_pos+1); 2746 } 2747 else { 2748 // rename group title, when to_pos in 0 2749 if ((to_pos == 0) and (not eqG(group, "root"))) { 2750 to_pos_tid = getG(tasks, rtChar, to_pos)+ORDER_ID_LENGTH; 2751 } 2752 2753 injectSG(tasks, to_pos, orderid_and_tid); 2754 // Delete task at at_pos+1 because the new position is before at_pos 2755 // at_pos was shifted when to_pos reference was added above 2756 delG(tasks, at_pos+1, at_pos+2); 2757 } 2758 2759 //:define reorder_tasks 2760 // Move tasks 2761 enumerateSmallArray(tasks, T, n) { 2762 castS(t, T); 2763 char src[8192]; 2764 sprintf(src, "%s/%s", path, ssGet(t)); 2765 char dst[8192]; 2766 sprintf(dst, "%s/%s%s", path, baseconvert(n), ssGet(t)+ORDER_ID_LENGTH); 2767 shRename(src, dst); 2768 finishG(t); 2769 } 2770 //:end 2771 2772 // rename group title, when to_pos in 0 2773 if ((to_pos == 0) and (not eqG(group, "root"))) { 2774 // rename group in parent group 2775 // group is tid for parent group and to_pos_tid is equal to group input parameter because to_pos is 0 2776 const char *groupF = find_group_containing_task(group); 2777 // to_pos_orderid_and_tid is group in parent group 2778 smallStringt *to_pos_orderid_and_tid; 2779 smallArrayt *parent_group_tasks = readDirG(rtSmallArrayt, generate_group_path(groupF)); 2780 forEachSmallArray(parent_group_tasks, T) { 2781 castS(t, T); 2782 if (eqG(ssGet(t)+ORDER_ID_LENGTH, to_pos_tid)) { 2783 to_pos_orderid_and_tid = dupG(t); 2784 } 2785 finishG(t); 2786 } 2787 terminateG(parent_group_tasks); 2788 // remove order_id, keep task id only for new group 2789 char *group_id = orderid_and_tid+ORDER_ID_LENGTH; 2790 // create an entry in parent group at the same position as to_pos tid had 2791 smallStringt *order_id = copyRngG(to_pos_orderid_and_tid, 0, ORDER_ID_LENGTH); 2792 char p[8192]; 2793 sprintf(p, "%s/%s%s", generate_group_path(groupF), order_id,group_id); 2794 FILE *f = fopen(p, "w"); 2795 fclose(f); 2796 2797 // delete group task of group 2798 // delete orderidtaskid 2799 sprintf(p, "%s/%s", generate_group_path(groupF), ssGet(to_pos_orderid_and_tid)); 2800 rmAllG(p); 2801 2802 // rename group in groups folder 2803 strcpy(p, generate_group_path(to_pos_tid)); 2804 shRename(p, generate_group_path(group_id)); 2805 // rename group in tree folder 2806 const char *group_tree_path = find_group_in_tree(to_pos_tid); 2807 char **group_tree_path_l = splitG(group_tree_path, "/"); 2808 setG(group_tree_path_l, -1, group_id); 2809 char *new_group_tree_path = joinG(group_tree_path_l, "/"); 2810 freeG(group_tree_path_l); 2811 shRename(group_tree_path, new_group_tree_path); 2812 free(new_group_tree_path); 2813 2814 // rename group in linked task and former linked task with a 'groups' folder in tasks database folder 2815 smallArrayt *group_tasks = readDirG(rtSmallArrayt, generate_group_path(group_id)); 2816 forEachSmallArray(group_tasks, G) { 2817 castS(group_task, G); 2818 char tPath[8192]; 2819 sprintf(tPath, "%s/groups", generate_task_path(ssGet(group_task)+ORDER_ID_LENGTH)); 2820 if (fileExists(tPath)) { 2821 // search for to_pos_tid (group input parameter) in task path groups folder 2822 smallArrayt *link_groups = readDirG(rtSmallArrayt, tPath); 2823 forEachSmallArray(link_groups, LG) { 2824 castS(lg, LG); 2825 if (eqG(to_pos_tid, lg)) { 2826 // rename group to new group id since the title changed 2827 char src[8192]; 2828 sprintf(src, "%s/%s", tPath, to_pos_tid); 2829 char dst[8192]; 2830 sprintf(dst, "%s/%s", tPath, group_id); 2831 shRename(src, dst); 2832 } 2833 finishG(lg); 2834 } 2835 terminateG(link_groups); 2836 } 2837 finishG(G); 2838 } 2839 terminateG(group_tasks); 2840 } 2841 2842 char log[2048]; 2843 sprintf(log, "changed task order %s to %s in group %s", at_pos, to_pos, group); 2844 edi_log(log); 2845 end: 2846 terminateG(tasks); 2847 return r; 2848 } 2849 2850 /** move selected tasks in Easydoneit desktop from group to to_pos and reorder 2851 * @return list of stings when there is an error 2852 * @param[in] group task id 2853 * @param[in] selected_tasks selected tasks in GUI list (array of ints) 2854 * @param[in] to_pos insert position 2855 * @ingroup EDI_CORE 2856 */ 2857 void change_task_order_desktop(const char *group,smallArrayt *selected_tasks, i64 to_pos) { 2858 // List tasks in group 2859 const char *path = generate_group_path(group); 2860 smallArrayt *tasks = readDirG(rtSmallArrayt, path); 2861 2862 createAllocateSmallArray(orderid_and_tids); 2863 forEachSmallArray(selected_tasks, T) { 2864 cast(smallIntt *, t, T); 2865 pushG(orderid_and_tids, getG(tasks, rtChar, getG(t, rtI64, unusedV))); 2866 finishG(t); 2867 } 2868 sortG(orderid_and_tids); 2869 2870 smallArrayt *task1 = copyRngG(tasks, 0, to_pos); 2871 smallArrayt *task2 = copyRngG(tasks, to_pos, 0); 2872 2873 // remove selected tasks from group list 2874 createAllocateSmallArray(task1m); 2875 forEachSmallArray(task1, I) { 2876 castS(i, I); 2877 if (not binarySearchG(orderid_and_tids, ssGet(i))) { 2878 pushG(task1m, i); 2879 } 2880 finishG(i); 2881 } 2882 createAllocateSmallArray(task2m); 2883 forEachSmallArray(task2, I) { 2884 castS(i, I); 2885 if (not binarySearchG(orderid_and_tids, ssGet(i))) { 2886 pushG(task2m, i); 2887 } 2888 finishG(i); 2889 } 2890 terminateManyG(task1, task2); 2891 2892 emptyG(tasks); 2893 catG(tasks, task1m, orderid_and_tids, task2m); 2894 smashManyO(task1m, orderid_and_tids, task2m); 2895 2896 //:reorder_tasks 2897 // Move tasks 2898 {enumerateSmallArray(tasks, T, n) { 2899 castS(t, T); 2900 char src[8192]; 2901 sprintf(src, "%s/%s", path, ssGet(t)); 2902 char dst[8192]; 2903 sprintf(dst, "%s/%s%s", path, baseconvert(n), ssGet(t)+ORDER_ID_LENGTH); 2904 shRename(src, dst); 2905 finishG(t); 2906 }} 2907 terminateG(tasks); 2908 2909 {enumerateSmallArray(orderid_and_tids, T, n) { 2910 castS(t, T); 2911 sliceG(t, ORDER_ID_LENGTH, 0); 2912 setPG(orderid_and_tids, n, t); 2913 finishG(t); 2914 }} 2915 smallStringt *log = joinG(orderid_and_tids, " "); 2916 terminateG(orderid_and_tids); 2917 char *s = formatS("changed task order (%s) to %ld in group %s", ssGet(log), to_pos, group); 2918 terminateG(log); 2919 edi_log(s); 2920 free(s); 2921 } 2922 2923 /** string returned from move_task_to_a_group */ 2924 static char return_move_task_to_a_group[2048]; 2925 2926 /** move task to group 2927 * @return tid 2928 * @param[in] tgroup task id, select a task in this group 2929 * @param[in] tid task id 2930 * @param[in] group task id, destination group 2931 * @ingroup EDI_CORE 2932 */ 2933 const char *move_task_to_a_group(const char *tgroup, const char *tid, const char *group) { 2934 2935 // find task tid in tgroup and remove task 2936 char path[8192]; 2937 strcpy(path, generate_group_path(tgroup)); 2938 smallArrayt *tasks = readDirG(rtSmallArrayt, path); 2939 char task_in_group[ORDER_ID_LENGTH + ID_LENGTH +1]; 2940 task_in_group[0] = 0; 2941 enumerateSmallArray(tasks, T, n) { 2942 castS(t, T); 2943 // remove task from tasks to reorder tgroup 2944 if (eqG(ssGet(t)+ORDER_ID_LENGTH, tid)) { 2945 strcpy(task_in_group, ssGet(t)); 2946 delG(tasks, n, n+1); 2947 break; 2948 } 2949 finishG(t); 2950 } 2951 if (isEmptyG(task_in_group)) { 2952 sprintf(return_move_task_to_a_group, "%s not found in %s", tid, tgroup); 2953 return return_move_task_to_a_group; 2954 } 2955 2956 // Move group in tree 2957 if (is_this_task_a_group(tid)) { 2958 char treePath[8192]; 2959 strcpy(treePath, find_group_in_tree(group)); 2960 char *treeTid = strdup(find_group_in_tree(tid)); 2961 sliceG(&treeTid, 0, -ID_LENGTH-1); 2962 if (eqG(treeTid, treePath)) { 2963 // prevent moving a group on itself, in tree and moving group title position to parent group 2964 free(treeTid); 2965 return tid; 2966 } 2967 free(treeTid); 2968 shRename(find_group_in_tree(tid), treePath); 2969 } 2970 2971 // Remove task in source group 2972 sprintf(path, "%s/%s", path, task_in_group); 2973 rmAll(path); 2974 2975 //:reorder_tasks 2976 // Move tasks 2977 {enumerateSmallArray(tasks, T, n) { 2978 castS(t, T); 2979 char src[8192]; 2980 sprintf(src, "%s/%s", path, ssGet(t)); 2981 char dst[8192]; 2982 sprintf(dst, "%s/%s%s", path, baseconvert(n), ssGet(t)+ORDER_ID_LENGTH); 2983 shRename(src, dst); 2984 finishG(t); 2985 }} 2986 terminateG(tasks); 2987 2988 // check that tid is not already linked in destination group 2989 if (is_linked(tid)) { 2990 createTaskPath(gPath, "groups/"); 2991 smallArrayt *link_groups = readDirG(rtSmallArrayt, gPath); 2992 if (hasG(link_groups, group)) { 2993 // removed task reference from tgroup. Remove tgroup reference in task. There is already a tid reference in group, nothing more to do 2994 char p[8192]; 2995 sprintf(p, "%s/%s", gPath, tgroup); 2996 rmAllG(p); 2997 terminateG(link_groups); 2998 return tid; 2999 } 3000 terminateG(link_groups); 3001 } 3002 3003 // Create reference in destination group 3004 add_task_to_group_folder(tid, group); 3005 3006 // linked tasks, remove source group and add destination group 3007 if (is_linked(tid)) { 3008 // remove source tgroup from tid/groups 3009 createTaskPath(gPath, "groups/"); 3010 char p[8192]; 3011 sprintf(p, "%s/%s", gPath, tgroup); 3012 rmAllG(p); 3013 sprintf(p, "%s/%s", gPath, group); 3014 FILE *f = fopen(p, "w"); 3015 fclose(f); 3016 } 3017 3018 char log[256]; 3019 sprintf(log, "moved %s in group %s to group %s", tid, tgroup, group); 3020 edi_log(log); 3021 return tid; 3022 } 3023 3024 /** string returned from copy_task_to_a_group */ 3025 static char return_copy_task_to_a_group[ORDER_ID_LENGTH + ID_LENGTH +1]; 3026 3027 /** copy task to group with new tid 3028 * @return new tid 3029 * @param[in] tid task id 3030 * @param[in] group task id, destination group 3031 * @ingroup EDI_CORE 3032 */ 3033 const char *copy_task_to_a_group(const char *tid, const char *group) { 3034 // Generate new tid 3035 strcpy(return_copy_task_to_a_group, generate_id()); 3036 char *newtid = return_copy_task_to_a_group; 3037 // Copy task in tasks 3038 char newtidPath[8192]; 3039 strcpy(newtidPath, generate_task_path(newtid)); 3040 // add / to copy files in task 3041 char *tPath = (char*) generate_task_path(tid); 3042 strcat(tPath, "/"); 3043 copy(tPath, newtidPath); 3044 // delete tid/groups because new task is not linked 3045 char task_linked_groups_path[8192]; 3046 sprintf(task_linked_groups_path, "%s/groups/", newtidPath); 3047 if (fileExistsG(task_linked_groups_path)) { 3048 rmAllG(task_linked_groups_path); 3049 } 3050 // Copy group in groups and in tree 3051 if (is_this_task_a_group(tid)) { 3052 // create new group 3053 copy(generate_group_path(tid), newtidPath); 3054 // Change group title task to newtid 3055 char tPath[8192]; 3056 sprintf(tPath, "%s/%s%s", newtidPath, baseconvert(0), tid); 3057 char ntPath[8192]; 3058 sprintf(tPath, "%s/%s%s", newtidPath, baseconvert(0), newtid); 3059 shRename(tPath, ntPath); 3060 3061 // delete tasks to be recreated with new tid 3062 char p[8192]; 3063 smallArrayt *tasks = readDirG(rtSmallArrayt ,generate_group_path(newtid)); 3064 forEachSmallArray(tasks, T) { 3065 castS(t, T); 3066 sprintf(p, "%s/%s", generate_group_path(newtid), ssGet(t)); 3067 rmAllG(p); 3068 finishG(t); 3069 } 3070 terminateG(tasks); 3071 3072 // Add group in tree 3073 if (eqG(group, "root")) { 3074 sprintf(p, "%s/%s", data_location_tree, newtid); 3075 mkdirParentsG(p); 3076 } 3077 else { 3078 sprintf(p, "%s/%s", find_group_in_tree(group), newtid); 3079 } 3080 mkdirParentsG(p); 3081 } 3082 3083 3084 // Add reference in group 3085 // Create reference in destination group 3086 add_task_to_group_folder(newtid, group); 3087 3088 if (is_this_task_a_group(tid)) { 3089 // walk in group 3090 // list items in group 3091 smallArrayt *tasks = readDirG(rtSmallArrayt, generate_group_path(tid)); 3092 3093 // add group found in first group 3094 forEachSmallArray(tasks, T) { 3095 castS(t, T); 3096 // Check tasks that are not title task in a group 3097 if (not eqG(ssGet(t) + ORDER_ID_LENGTH, tid)) 3098 // copy_task_to_a_group recursively 3099 copy_task_to_a_group(ssGet(t) + ORDER_ID_LENGTH,newtid); 3100 finishG(t); 3101 } 3102 terminateG(tasks); 3103 } 3104 3105 sprintf(newtidPath, "copied %s to group %s, created %s", tid,group,newtid); 3106 edi_log(newtidPath); 3107 return newtid; 3108 } 3109 3110 /** string returned from generate_task_path_in_database */ 3111 static char return_generate_task_path_in_database[8192]; 3112 3113 /** create task path in database using database name 3114 * @return path to task in tasks 3115 * @param[in] tid task id 3116 * @param[in] location database name in data section of easydoneit.ini 3117 * @ingroup EDI_CORE 3118 */ 3119 const char *generate_task_path_in_database(const char *tid, const char *location) { 3120 char *locPath = getG(selected_d, rtChar, location); 3121 sprintf(return_generate_task_path_in_database, "%s/tasks/%s/", locPath, tid); 3122 return return_generate_task_path_in_database; 3123 } 3124 3125 /** string returned from generate_group_path_in_database */ 3126 static char return_generate_group_path_in_database[8192]; 3127 3128 /** create group path in database using database name 3129 * @return path to group in groups 3130 * @param[in] tid task id 3131 * @param[in] location database name in data section of easydoneit.ini 3132 * @ingroup EDI_CORE 3133 */ 3134 const char *generate_group_path_in_database(const char *tid, const char *location) { 3135 char *locPath = getG(selected_d, rtChar, location); 3136 sprintf(return_generate_group_path_in_database, "%s/groups/%s/", locPath, tid); 3137 return return_generate_group_path_in_database; 3138 } 3139 3140 /** string returned from find_group_in_tree_in_database */ 3141 static char return_find_group_in_tree_in_database[8192]; 3142 3143 /** find group in tree folder in database 3144 * @return path to group in tree 3145 * @param[in] group task id 3146 * @param[in] location database name in data section of easydoneit.ini 3147 * @ingroup EDI_CORE 3148 */ 3149 const char *find_group_in_tree_in_database(const char *group, const char *location) { 3150 char location_tree[8192]; 3151 char *locPath = getG(selected_d, rtChar, location); 3152 sprintf(location_tree, "%s/tree", locPath); 3153 char *path_in_tree = return_find_group_in_tree_in_database; 3154 path_in_tree[0] = 0; 3155 3156 if (eqG(group, "root")) { 3157 strcpy(return_find_group_in_tree_in_database, location_tree); 3158 } 3159 else { 3160 // list all group paths in tree 3161 /* if platform.system() = 'Windows' */ 3162 /* folders = [] */ 3163 /* folders append location_tree */ 3164 /* for dir, subdirs, files in os.walk(location_tree) */ 3165 /* for sd in subdirs */ 3166 /* folders append dir+os.sep + sd */ 3167 /* else */ 3168 smallArrayt *folders = walkDirAllG(rtSmallArrayt, location_tree); 3169 enumerateSmallArray(folders, F, i) { 3170 castS(f, F); 3171 trimG(f); 3172 setNFreePG(folders, i, f); 3173 } 3174 smallArrayt *groups = folders; 3175 // find the group in group paths 3176 forEachSmallArray(groups, G) { 3177 castS(g, G); 3178 char *s = basename(ssGet(g)); 3179 if (eqG(s, group)) 3180 strcpy(path_in_tree, ssGet(g)); 3181 finishG(g); 3182 } 3183 terminateG(folders); 3184 } 3185 return path_in_tree; 3186 } 3187 3188 /** add task with new tid in selected database 3189 * @param[in] tid task id 3190 * @param[in] group task id 3191 * @param[in] location database name in data section of easydoneit.ini 3192 * @ingroup EDI_CORE 3193 */ 3194 void add_task_to_group_folder_in_database(const char *tid, const char *group, const char* location) { 3195 // Create an entry in group 3196 smallArrayt *tasks = readDirG(rtSmallArrayt, generate_group_path_in_database(group,location)); 3197 // Add +1 to last order_id to have the task last in the list 3198 const char *order_id; 3199 if (lenG(tasks)) { 3200 smallStringt *s = getG(tasks, rtSmallStringt, -1); 3201 smallStringt *pos = copyRngG(s, 0, ORDER_ID_LENGTH); 3202 finishG(s); 3203 order_id = baseconvert(baseconvert_to_dec(ssGet(pos))+1); 3204 terminateG(pos); 3205 } 3206 else { 3207 // start at 0 when group is empty 3208 order_id = baseconvert(0); 3209 } 3210 terminateG(tasks); 3211 char p[8192]; 3212 sprintf(p, "%s/%s%s", generate_group_path_in_database(group,location), order_id, tid); 3213 FILE *f = fopen(p, "w"); 3214 fclose(f); 3215 } 3216 3217 /** string returned from copy_task_to_database */ 3218 static char return_copy_task_to_database[ORDER_ID_LENGTH + ID_LENGTH +1]; 3219 3220 /** copy task to group in selected database with new tid 3221 * @return new tid 3222 * @param[in] tid task id 3223 * @param[in] group task id 3224 * @param[in] location database name in data section of easydoneit.ini 3225 * @ingroup EDI_CORE 3226 */ 3227 const char *copy_task_to_database(const char *tid, const char *location, const char *group) { 3228 // Generate new tid 3229 char *newtid; 3230 newtid = return_copy_task_to_database; 3231 strcpy(newtid, generate_id()); 3232 // Copy task in tasks 3233 // add / to copy files in task 3234 char *tidPath = (char*) generate_task_path(tid); 3235 strcat(tidPath, "/"); 3236 copy(tidPath,generate_task_path_in_database(newtid,location)); 3237 // delete tid/groups because new task is not linked 3238 char task_linked_groups_path[8192]; 3239 sprintf(task_linked_groups_path, "%s/groups/", generate_task_path_in_database(newtid,location)); 3240 if (fileExists(task_linked_groups_path)) { 3241 rmAllG(task_linked_groups_path); 3242 } 3243 // Copy group in groups and in tree 3244 if (is_this_task_a_group(tid)) { 3245 // create new group 3246 // add / to copy files in task 3247 tidPath = (char*) generate_task_path(tid); 3248 strcat(tidPath, "/"); 3249 copy(tidPath,generate_group_path_in_database(newtid,location)); 3250 // Change group title task to newtid 3251 char tPath[8192]; 3252 sprintf(tPath, "%s/%s%s", generate_group_path_in_database(newtid,location), baseconvert(0), tid); 3253 char ntPath[8192]; 3254 sprintf(ntPath, "%s/%s%s", generate_group_path_in_database(newtid,location), baseconvert(0), newtid); 3255 shRename(tPath, ntPath); 3256 3257 // delete tasks to be recreated with new tid 3258 smallArrayt *tasks = readDirG(rtSmallArrayt, generate_group_path_in_database(newtid,location)); 3259 forEachSmallArray(tasks, T) { 3260 castS(t, T); 3261 sprintf(tPath, "%s/%s", generate_group_path_in_database(newtid,location), ssGet(t)); 3262 rmAll(tPath); 3263 finishG(t); 3264 } 3265 terminateG(tasks); 3266 3267 // Add group in tree 3268 char location_tree[8192]; 3269 char *locPath = getG(selected_d, rtChar, location); 3270 sprintf(location_tree, "%s/tree", locPath); 3271 if (eqG(group, "root")) { 3272 sprintf(tPath, "%s/%s", location_tree, newtid); 3273 mkdirParentsG(tPath); 3274 } 3275 else { 3276 sprintf(tPath, "%s/%s", find_group_in_tree_in_database(group,location), newtid); 3277 mkdirParentsG(tPath); 3278 } 3279 } 3280 3281 3282 // Add reference in group 3283 // Create reference in destination group 3284 add_task_to_group_folder_in_database(newtid, group, location); 3285 3286 if (is_this_task_a_group(tid)) { 3287 // walk in group 3288 // list items in group 3289 smallArrayt *tasks = readDirG(rtSmallArrayt, generate_group_path(tid)); 3290 3291 // add group found in first group 3292 forEachSmallArray(tasks, T) { 3293 castS(t, T); 3294 // Check tasks that are not title task in a group 3295 if (eqG(ssGet(t)+ORDER_ID_LENGTH, tid)) { 3296 // copy_task_to_database recursively 3297 copy_task_to_database(ssGet(t)+ORDER_ID_LENGTH,location,newtid); 3298 } 3299 finishG(t); 3300 } 3301 terminateG(tasks); 3302 } 3303 return newtid; 3304 } 3305 3306 /** move task to database/group 3307 * @param[in] tgroup group id for task tid 3308 * @param[in] tid task id 3309 * @param[in] location database name in data section of easydoneit.ini 3310 * @param[in] group destination group id 3311 * @ingroup EDI_CORE 3312 * copy and delete 3313 */ 3314 const char *move_task_to_a_group_to_database(const char *tgroup, const char *tid, const char *location, const char *group) { 3315 const char *newtid = copy_task_to_database(tid,location,group); 3316 if (is_this_task_a_group(tid)) { 3317 delete_group(tid); 3318 } 3319 else { 3320 delete_linked_task(tgroup,tid); 3321 } 3322 return newtid; 3323 } 3324 3325 /** add reference to tid in group 3326 * @param[in] tid task id 3327 * @param[in] group destination group id 3328 * @ingroup EDI_CORE 3329 * Used in edi ln to link tasks 3330 */ 3331 void add_task_reference_to_a_group(const char *tid, smallStringt *group) { 3332 char p[8192]; 3333 // Check if selected item is a task 3334 if (not is_this_task_a_group(tid)) { 3335 // add group to task folder 3336 createTaskPath(task_path, "groups"); 3337 FILE *f; 3338 if (not fileExists(task_path)) { 3339 mkdirParentsG(task_path); 3340 3341 // add first group when task is linked to tid/groups/ 3342 const char *first_group = find_group_containing_task(tid); 3343 sprintf(p, "%s/%s", task_path, first_group); 3344 f = fopen(p, "w"); 3345 fclose(f); 3346 } 3347 sprintf(p, "%s/%s", task_path, ssGet(group)); 3348 f = fopen(p, "w"); 3349 fclose(f); 3350 3351 // add task to group 3352 add_task_to_group_folder(tid, ssGet(group)); 3353 } 3354 3355 sprintf(p, "linked %s to group %s", tid, ssGet(group)); 3356 edi_log(p); 3357 } 3358 3359 /** set status for tid 3360 * @param[in] tid task id 3361 * @param[in] status_number index in edi_core.TASK_STATUS 3362 * @ingroup EDI_CORE 3363 */ 3364 void set_status(const char *tid, i16 status_number) { 3365 // Change status 3366 createTaskPath(tpath, "status"); 3367 writeFileG(TASK_STATUS[status_number], tpath); 3368 sprintf(tpath, "set status for %s to %s", tid, TASK_STATUS_TRIM[status_number]); 3369 edi_log(tpath); 3370 } 3371 3372 /* ## set all tasks in group to active */ 3373 /* # @param[in] tid task id */ 3374 /* # @ingroup EDI_CORE */ 3375 /* def reset_group_status tid */ 3376 /* group_tasks = os.listdir(generate_group_path(tid)) */ 3377 /* for t in group_tasks */ 3378 /* if (not is_this_task_a_group(t[ORDER_ID_LENGTH:])) or (is_this_task_a_group(t[ORDER_ID_LENGTH:]) and (get_status(t[ORDER_ID_LENGTH:]) != TASK_STATUS[TASK_STATUS_VOID])) */ 3379 /* set_status(t[ORDER_ID_LENGTH:],TASK_STATUS_ACTIVE) */ 3380 /* edi_log('reset group %s'%tid) */ 3381 /* */ 3382 /** search string in tasks folder 3383 * @param[in] search query string 3384 * @ingroup EDI_CORE 3385 */ 3386 smallArrayt *search_string(const char *search){ 3387 smallArrayt *tasks = walkDirG(rtSmallArrayt, data_location_tasks); 3388 3389 // search in descriptions only 3390 createAllocateSmallArray(grep_r); 3391 forEachSmallArray(tasks, TID) { 3392 castS(tido, TID); 3393 if (not eqG(tido, "root")) { 3394 // search in visible tasks only (filter enable status) 3395 const char *task_status = get_status(ssGet(tido)); 3396 if (getG(status_filters_d, rtI32, task_status) == ENABLE) { 3397 /* if platform.system() = 'Windows' */ 3398 /* // in windows, grep in python */ 3399 /* f = open generate_task_path(tid)+os.sep+'description.txt' */ 3400 /* f_lines = f readlines */ 3401 /* f close */ 3402 /* grep_lines = [] */ 3403 /* for l in f_lines */ 3404 /* if search lower in l lower */ 3405 /* grep_lines append l */ 3406 /* else */ 3407 char *tid = ssGet(tido); 3408 createTaskPath(dPath, "description.txt"); 3409 createAllocateSmallArray(grep_lines); 3410 char c[16384]; 3411 sprintf(c, "grep -i \"%s\" %s", search, dPath); 3412 execO(c, grep_lines, NULL); 3413 // add tid and filename to results 3414 sprintf(dPath, "/%s/description.txt:", tid); 3415 forEachSmallArray(grep_lines, H) { 3416 castS(h, H); 3417 createAllocateSmallArray(a); 3418 pushG(a, dPath); 3419 pushNFreeG(a, h); 3420 pushG(a, tid); 3421 pushNFreeG(grep_r, a); 3422 } 3423 terminateG(grep_lines); 3424 } 3425 } 3426 finishG(tido); 3427 } 3428 3429 // r lists all hits 3430 createAllocateSmallArray(r); 3431 forEachSmallArray(grep_r, L) { 3432 cast(smallArrayt *, l_l, L); 3433 // replace filename with tid and title (first line in file) 3434 // read first line in file 3435 char *hit_filename = getG(l_l, rtChar, 0); 3436 char task_path[8192]; 3437 sprintf(task_path, "%s/%s", data_location_tasks, hit_filename); 3438 FILE *f = fopen(task_path, "r"); 3439 char *title = readLineG(rtChar, f); 3440 trimG(&title); 3441 fclose(f); 3442 // set tid 3443 char *tid = getG(l_l, rtChar, 2); 3444 char *group = " "; 3445 if (is_this_task_a_group(tid)) { 3446 group = "GROUP"; 3447 } 3448 if (is_linked(tid)) { 3449 group = " LINK"; 3450 } 3451 char l[16384]; 3452 sprintf(l, "%s %s - %s:%s", tid,group,title, getG(l_l, rtChar, 1)); 3453 free(title); 3454 pushG(r, l); 3455 finishG(l_l); 3456 } 3457 terminateG(grep_r); 3458 3459 return r; 3460 } 3461 3462 /** search string in group 3463 * @param[in] group task id 3464 * @param[in] search query string 3465 * @ingroup EDI_CORE 3466 */ 3467 smallArrayt *search_string_in_group(const char *group, const char *search) { 3468 3469 smallArrayt *tasks = readDirG(rtSmallArrayt, generate_group_path(group)); 3470 3471 // r lists all hits 3472 createAllocateSmallArray(r); 3473 forEachSmallArray(tasks, O) { 3474 castS(orderidtid, O); 3475 // tid for current task in group 3476 char *tid = ssGet(orderidtid)+ORDER_ID_LENGTH; 3477 // search in visible tasks only (filter enable status) 3478 const char *task_status = get_status(tid); 3479 createAllocateSmallArray(grep_r); 3480 if (getG(status_filters_d, rtI32, task_status) == ENABLE) { 3481 /* if platform.system() = 'Windows' */ 3482 /* // in windows, grep in python */ 3483 /* f = open generate_task_path(tid)+os.sep+'description.txt' */ 3484 /* f_lines = f readlines */ 3485 /* f close */ 3486 /* grep_lines = [] */ 3487 /* for l in f_lines */ 3488 /* if search lower in l lower */ 3489 /* grep_lines append l */ 3490 /* else */ 3491 createTaskPath(dPath, "description.txt"); 3492 createAllocateSmallArray(grep_lines); 3493 char c[16384]; 3494 sprintf(c, "grep -i \"%s\" %s", search, dPath); 3495 execO(c, grep_lines, NULL); 3496 // add tid and filename to results 3497 sprintf(dPath, "/%s/description.txt", tid); 3498 forEachSmallArray(grep_lines, H) { 3499 castS(h, H); 3500 createAllocateSmallArray(a); 3501 pushG(a, dPath); 3502 pushG(a, ssGet(h)); 3503 pushG(a, tid); 3504 pushNFreeG(grep_r, a); 3505 finishG(h); 3506 } 3507 terminateG(grep_lines); 3508 } 3509 3510 forEachSmallArray(grep_r, L) { 3511 cast(smallArrayt *, l_l, L); 3512 // replace filename with tid and title (first line in file) 3513 // read first line in file 3514 char *hit_filename = getG(l_l, rtChar, 0); 3515 char task_path[8192]; 3516 sprintf(task_path, "%s/%s", data_location_tasks, hit_filename); 3517 FILE *f = fopen(task_path, "r"); 3518 if (!f) { 3519 pFuncError 3520 shEPrintfS("The path was: \"%s\"\n", task_path); 3521 } 3522 char *title = readLineG(rtChar, f); 3523 trimG(&title); 3524 fclose(f); 3525 // set tid 3526 tid = getG(l_l, rtChar, 2); 3527 char *group = " "; 3528 if (is_this_task_a_group(tid)) { 3529 group = "GROUP"; 3530 } 3531 if (is_linked(tid)) { 3532 group = " LINK"; 3533 } 3534 char l[16384]; 3535 sprintf(l, "%s %s - %s:%s", tid,group,title, getG(l_l, rtChar, 1)); 3536 free(title); 3537 pushG(r, l); 3538 finishG(l_l); 3539 } 3540 terminateG(grep_r); 3541 finishG(orderidtid); 3542 } 3543 terminateG(tasks); 3544 return r; 3545 } 3546 3547 /** search string in tree 3548 * @param[in] group task id 3549 * @param[in] search query string 3550 * @ingroup EDI_CORE 3551 */ 3552 smallArrayt *search_string_in_tree(const char *group, const char *search) { 3553 // walk_group is the list of groups to visit. FIFO 3554 createAllocateSmallArray(r); 3555 createAllocateSmallArray(walk_group); 3556 pushG(walk_group, group); 3557 // the while loop goes through all the group that are found 3558 while (lenG(walk_group)) { 3559 // list items in first group 3560 smallArrayt *tasks = readDirG(rtSmallArrayt ,generate_group_path(getG(walk_group, rtChar, 0))); 3561 appendNSmashG(r, search_string_in_group(getG(walk_group, rtChar,0), search)); 3562 3563 // add group found in first group 3564 forEachSmallArray(tasks, T) { 3565 castS(t, T); 3566 // Check tasks that are not title task in a group 3567 if (not eqG(ssGet(t)+ORDER_ID_LENGTH, getG(walk_group, rtChar, 0)) and is_this_task_a_group(ssGet(t) + ORDER_ID_LENGTH)) { 3568 pushG(walk_group, ssGet(t)+ORDER_ID_LENGTH); 3569 } 3570 finishG(t); 3571 } 3572 terminateG(tasks); 3573 3574 // remove first group to list items in next group 3575 delG(walk_group, 0, 1); 3576 } 3577 terminateG(walk_group); 3578 return r; 3579 } 3580 3581 /* ## build tree recursively */ 3582 /* # @param[in] group group path in tree starting with os.sep */ 3583 /* # @return group_tree list of groups in tree starting from group */ 3584 /* # @ingroup EDI_CORE */ 3585 /* def build_group_tree group */ 3586 /* # list groups */ 3587 /* # call build_group_tree recursively */ 3588 /* */ 3589 /* # list groups */ 3590 /* group_path = generate_group_path(group.split(os.sep)[-1]) */ 3591 /* */ 3592 /* groups = sorted(os.listdir(group_path)) */ 3593 /* */ 3594 /* if group != 'root' */ 3595 /* # first task is group title */ 3596 /* del groups[0] */ 3597 /* else */ 3598 /* # empty string for root */ 3599 /* group = '' */ 3600 /* */ 3601 /* only_groups = [] */ 3602 /* for g in groups */ 3603 /* if is_this_task_a_group(g[ORDER_ID_LENGTH:]) */ 3604 /* # complete tree path in only_groups */ 3605 /* only_groups append '%s%s%s' % (group, os.sep, g[ORDER_ID_LENGTH:]) */ 3606 /* */ 3607 /* # call build_group_tree recursively */ 3608 /* group_tree = [] */ 3609 /* if only_groups */ 3610 /* for g in only_groups */ 3611 /* group_tree append g */ 3612 /* group_tree += build_group_tree(g) */ 3613 /* return group_tree */ 3614 /* */ 3615 /* ## build tree in order */ 3616 /* # @param[in] group group path in tree or root */ 3617 /* # @return group_tree list of groups in tree starting from group */ 3618 /* # @ingroup EDI_CORE */ 3619 /* def build_tree group */ 3620 /* tidtree = build_group_tree(group) */ 3621 /* return tidtree */ 3622 /* */ 3623 /* ## show tree */ 3624 /* # @ingroup EDI_CORE */ 3625 /* # print all trees: group tids and titles */ 3626 /* def show_tree */ 3627 /* r = [] */ 3628 /* */ 3629 /* tidtree = build_tree('root') */ 3630 /* */ 3631 /* # remove '/' from path */ 3632 /* tidtree = [i[1:] for i in tidtree] */ 3633 /* */ 3634 /* # print titles path\n group tid path\n\n */ 3635 /* for l in tidtree */ 3636 /* # find title for group tids */ 3637 /* group_titles_in_path = [] */ 3638 /* for g in l strip.split(os.sep) */ 3639 /* group_titles_in_path append get_task_title(g) */ 3640 /* if user_interface = 'web' */ 3641 /* # convert / to - to be able to create links correctly */ 3642 /* group_titles_in_path = [i.replace(os.sep, '-') for i in group_titles_in_path] */ 3643 /* # create string title/title... */ 3644 /* group_titles_in_path_s = '/'.join(group_titles_in_path) */ 3645 /* r append '%s\n'%group_titles_in_path_s */ 3646 /* r append '%s\n'%l */ 3647 /* */ 3648 /* return r */ 3649 /* */ 3650 /* ## group statistics */ 3651 /* # @ingroup EDI_CORE */ 3652 /* # print statistics for a group recursively */ 3653 /* def group_statistics group */ 3654 /* global stats */ 3655 /* global stats_total */ 3656 /* global stats_creation_dates */ 3657 /* global stats_overtime */ 3658 /* */ 3659 /* # compute total number of tasks in group */ 3660 /* path = generate_group_path(group) */ 3661 /* */ 3662 /* tasks = [] */ 3663 /* # remove group title */ 3664 /* for i in os.listdir(path) */ 3665 /* if not baseconvert(0) in i[:ORDER_ID_LENGTH] */ 3666 /* tasks append i[ORDER_ID_LENGTH:] */ 3667 /* */ 3668 /* stats_total += len(tasks) */ 3669 /* */ 3670 /* # compute number of tasks in each state, groups and links */ 3671 /* for tid in tasks */ 3672 /* task_status = get_status(tid) */ 3673 /* stats[task_status] += 1 */ 3674 /* if is_this_task_a_group(tid) */ 3675 /* stats[' Group'] += 1 */ 3676 /* group_statistics(tid) */ 3677 /* if is_linked(tid) */ 3678 /* stats[' Linked'] += 1 */ 3679 /* */ 3680 /* #stats_creation_dates */ 3681 /* # Figure out creation time and modification time for description */ 3682 /* task_ctime = get_creation_date(tid)[1] */ 3683 /* */ 3684 /* task_path = generate_task_path(tid) */ 3685 /* (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(task_path+os.sep+'description.txt') */ 3686 /* # 'if' below to be compatible with first database format */ 3687 /* if task_ctime = 0 */ 3688 /* # ctime not available */ 3689 /* task_ctime = mtime */ 3690 /* stats_creation_dates append task_ctime */ 3691 /* */ 3692 /* (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(task_path+os.sep+'status') */ 3693 /* */ 3694 /* cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime)) */ 3695 /* sdate = time.strftime("%Y-%m-%d", time.localtime(mtime)) */ 3696 /* if not stats_overtime.has_key(cdate) */ 3697 /* # initialize date dict */ 3698 /* stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) */ 3699 /* stats_overtime[cdate]['Creation'] += 1 */ 3700 /* if not stats_overtime.has_key(sdate) */ 3701 /* # initialize date dict */ 3702 /* stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) */ 3703 /* stats_overtime[sdate][task_status] += 1 */ 3704 /* #end */ 3705 /* */ 3706 /* ## statistics */ 3707 /* # @ingroup EDI_CORE */ 3708 /* # print statistics for a group or a database */ 3709 /* # compute speed */ 3710 /* def statistics group */ 3711 /* global stats */ 3712 /* global stats_total */ 3713 /* global stats_creation_dates */ 3714 /* global stats_overtime */ 3715 /* r = [] */ 3716 /* */ 3717 /* # initialize stats dictionary */ 3718 /* stat_keys = [TASK_STATUS[i] for i in range(len(TASK_STATUS))] */ 3719 /* stat_keys append ' Group' */ 3720 /* stat_keys append ' Linked' */ 3721 /* state_amounts = [0 for i in range(len(stat_keys))] */ 3722 /* stats = dict(zip(stat_keys,state_amounts)) */ 3723 /* */ 3724 /* if group = 'for database' */ 3725 /* # compute total number of tasks, excluding root */ 3726 /* path = data_location_tasks */ 3727 /* tasks = [] */ 3728 /* for i in os.listdir(path) */ 3729 /* if i != 'root' */ 3730 /* tasks append i */ 3731 /* */ 3732 /* # compute number of tasks in each state, groups and links */ 3733 /* for tid in tasks */ 3734 /* task_status = get_status(tid) */ 3735 /* stats[task_status] += 1 */ 3736 /* if is_this_task_a_group(tid) */ 3737 /* stats[' Group'] += 1 */ 3738 /* if is_linked(tid) */ 3739 /* stats[' Linked'] += 1 */ 3740 /* */ 3741 /* #:define stats_creation_dates */ 3742 /* # Figure out creation time and modification time for description */ 3743 /* task_ctime = get_creation_date(tid)[1] */ 3744 /* */ 3745 /* task_path = generate_task_path(tid) */ 3746 /* (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(task_path+os.sep+'description.txt') */ 3747 /* if task_ctime = 0 */ 3748 /* # ctime not available */ 3749 /* task_ctime = mtime */ 3750 /* stats_creation_dates append task_ctime */ 3751 /* */ 3752 /* (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat(task_path+os.sep+'status') */ 3753 /* */ 3754 /* cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime)) */ 3755 /* sdate = time.strftime("%Y-%m-%d", time.localtime(mtime)) */ 3756 /* if not stats_overtime.has_key(cdate) */ 3757 /* # initialize date dict */ 3758 /* stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) */ 3759 /* stats_overtime[cdate]['Creation'] += 1 */ 3760 /* if not stats_overtime.has_key(sdate) */ 3761 /* # initialize date dict */ 3762 /* stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))])) */ 3763 /* stats_overtime[sdate][task_status] += 1 */ 3764 /* #:end */ 3765 /* */ 3766 /* stats_total = len(tasks) */ 3767 /* else */ 3768 /* # compute total number of tasks in group */ 3769 /* path = generate_group_path(group) */ 3770 /* */ 3771 /* tasks = [] */ 3772 /* for i in os.listdir(path) */ 3773 /* if group = 'root' */ 3774 /* tasks append i[ORDER_ID_LENGTH:] */ 3775 /* else */ 3776 /* # the group task is not counted in the statistics */ 3777 /* if not baseconvert(0) in i[:ORDER_ID_LENGTH] */ 3778 /* tasks append i[ORDER_ID_LENGTH:] */ 3779 /* */ 3780 /* stats_total = len(tasks) */ 3781 /* */ 3782 /* # compute number of tasks in each state, groups and links, recursively */ 3783 /* for tid in tasks */ 3784 /* task_status = get_status(tid) */ 3785 /* stats[task_status] += 1 */ 3786 /* if is_this_task_a_group(tid) */ 3787 /* stats[' Group'] += 1 */ 3788 /* group_statistics(tid) */ 3789 /* if is_linked(tid) */ 3790 /* stats[' Linked'] += 1 */ 3791 /* */ 3792 /* #:stats_creation_dates */ 3793 /* */ 3794 /* if not stats_total */ 3795 /* r append '0 task in statistics.' */ 3796 /* return r */ 3797 /* */ 3798 /* # csv format, not used for now */ 3799 /* #col_names = ['Tasks'] + sorted(stats.keys()) */ 3800 /* #col_names = [i strip for i in col_names] */ 3801 /* #print ','.join(col_names) */ 3802 /* #csv = '%d'%len(tasks) */ 3803 /* #for k in sorted(stats.keys()) */ 3804 /* # csv += ',%d'%stats[k] */ 3805 /* #r append csv */ 3806 /* #r append '' */ 3807 /* */ 3808 /* r append 'Number of items: %d\n'%stats_total */ 3809 /* for k in sorted(stats.keys()) */ 3810 /* r append '%s - %d'%(k, stats[k]) */ 3811 /* r append '' */ 3812 /* */ 3813 /* # speed */ 3814 /* start_time = sorted(stats_creation_dates)[0] */ 3815 /* now = time.time() */ 3816 /* done_and_inactive = stats[TASK_STATUS[TASK_STATUS_DONE]] + stats[TASK_STATUS[TASK_STATUS_INACTIVE]] */ 3817 /* if not done_and_inactive */ 3818 /* r append 'Nothing is done or inactive' */ 3819 /* else */ 3820 /* # subtract tasks in state void, because tasks in void state are informative or groups */ 3821 /* number_of_tasks = stats_total - stats[TASK_STATUS[TASK_STATUS_VOID]] */ 3822 /* remaining_tasks = number_of_tasks - done_and_inactive */ 3823 /* remaining_time = (now - start_time) / float(done_and_inactive) * remaining_tasks */ 3824 /* r append 'Number of tasks (excluding voids): %d'%number_of_tasks */ 3825 /* r append 'Remaining tasks: %d'%remaining_tasks */ 3826 /* r append 'Remaining days: %.3f'%(remaining_time/86400) */ 3827 /* r append 'Start date: %s'%time.strftime("%Y-%m-%d", time.localtime(start_time)) */ 3828 /* r append "Today's date: %s"%time.strftime("%Y-%m-%d", time.localtime(now)) */ 3829 /* r append 'Finish date: %s'%time.strftime("%Y-%m-%d", time.localtime(now + remaining_time)) */ 3830 /* */ 3831 /* # create csv stats from stats_overtime */ 3832 /* f = open data_location+os.sep+'stats.csv' w */ 3833 /* f write 'date,%s\n'%','.join([i strip for i in STATS_OVERTIME_KEYS]) */ 3834 /* */ 3835 /* for d in sorted(stats_overtime keys) */ 3836 /* f write '%s,%s\n'%(d, ','.join([str(stats_overtime[d][i]) for i in STATS_OVERTIME_KEYS])) */ 3837 /* f close */ 3838 /* */ 3839 /* # create creation, done and inactive stats */ 3840 /* f = open data_location+os.sep+'stats_creation_done_inactive.csv' w */ 3841 /* f write 'date,Creation,Done/Inactive\n' */ 3842 /* */ 3843 /* for d in sorted(stats_overtime keys) */ 3844 /* f write '%s,%d,%d\n'%(d, stats_overtime[d]['Creation'], stats_overtime[d][TASK_STATUS[TASK_STATUS_DONE]] + stats_overtime[d][TASK_STATUS[TASK_STATUS_INACTIVE]]) */ 3845 /* f close */ 3846 /* */ 3847 /* r append '' */ 3848 /* return r */ 3849 /* */ 3850 /* ## create secret */ 3851 /* # @ingroup EDI_CORE */ 3852 /* # encrypts description.txt in tid */ 3853 /* # the title remains clear */ 3854 /* def create_secret tid */ 3855 /* */ 3856 /* # encrypt description.txt to description.txt.gpg */ 3857 /* tdir = generate_task_path(tid) +os.sep */ 3858 /* path = tdir+'description.txt' */ 3859 /* #:define encrypt */ 3860 /* os.system 'gpg -c --cipher-algo AES256 %s' % path */ 3861 /* # delete clear text */ 3862 /* os.rename(path, tdir+'tmp') */ 3863 /* # create clear title */ 3864 /* os.system 'head -n 1 %s > %s' % (tdir+'tmp', path) */ 3865 /* os.remove tdir+'tmp' */ 3866 /* #:end */ 3867 /* */ 3868 /* ## edit secret */ 3869 /* # @ingroup EDI_CORE */ 3870 /* # decrypts description.txt.gpg to description.txt */ 3871 /* # starts text editor */ 3872 /* # encrypts description.txt in tid */ 3873 /* # the title remains clear */ 3874 /* def edit_secret tid */ 3875 /* */ 3876 /* # delete clear description.txt (holding the title) */ 3877 /* tdir = generate_task_path(tid) +os.sep */ 3878 /* path = tdir+'description.txt' */ 3879 /* os.remove path */ 3880 /* os.system 'gpg %s' % (path+'.gpg') */ 3881 /* os.remove path+'.gpg' */ 3882 /* # start text editor */ 3883 /* edit_task(tid) */ 3884 /* #:encrypt */ 3885 /* */ 3886 /* ## for your eyes only */ 3887 /* # @ingroup EDI_CORE */ 3888 /* # print secret and hide passwords */ 3889 /* def fyeo tid */ 3890 /* */ 3891 /* # decrypt description.txt.gpg to stdout */ 3892 /* f = os.popen 'gpg -d %s' % generate_task_path(tid) +os.sep+'description.txt.gpg' */ 3893 /* t = f readlines */ 3894 /* f close */ 3895 /* for l in t */ 3896 /* # hide text after the word password on lines starting with password */ 3897 /* if l lower[:8] = 'password' */ 3898 /* L = l strip.split(' ') */ 3899 /* p = L[0] */ 3900 /* del L[0] */ 3901 /* s = ' '.join(L) */ 3902 /* print '%s \033[1;8m\033[1;41m%s\033[0;0m' %(p, s) */ 3903 /* else */ 3904 /* # print normal text */ 3905 /* print l rstrip */ 3906 /* */ 3907 3908 /** 3909 * list text for all tasks in list 3910 * @param 3911 * tid group to show 3912 * @return 3913 * array containing all task descriptions in group 3914 */ 3915 smallArrayt *listTasksInList(smallArrayt *ls) { 3916 createAllocateSmallArray(r); 3917 forEachSmallArray(ls, T) { 3918 cast(smallDictt*, d, T); 3919 if (not eqG(getG(d, rtChar, "tid"), "")) { 3920 smallArrayt *desc = display_task(getG(d, rtChar, "tid"), passThroughTitle); 3921 pushG(r, "---"); 3922 appendNSmashG(r, desc); 3923 } 3924 finishG(d); 3925 } 3926 return r; 3927 } 3928 3929 /** 3930 * list text for all tasks in group tid 3931 * @param 3932 * tid group to show 3933 * @return 3934 * array containing all task descriptions in group 3935 */ 3936 smallArrayt *listTasksInGroup(const char *tid) { 3937 createAllocateSmallArray(r); 3938 if (is_this_task_a_group(tid)) { 3939 // list_group... 3940 smallArrayt *ls = list_group(tid); 3941 forEachSmallArray(ls, T) { 3942 cast(smallDictt*, d, T); 3943 if (not eqG(getG(d, rtChar, "tid"), "")) { 3944 smallArrayt *desc = display_task(getG(d, rtChar, "tid"), passThroughTitle); 3945 pushG(r, "---"); 3946 appendNSmashG(r, desc); 3947 } 3948 finishG(d); 3949 } 3950 terminateG(ls); 3951 } 3952 return r; 3953 } 3954 3955 /** 3956 * list text in bookmarks 3957 * @return 3958 * array containing all task descriptions in the bookmarks 3959 */ 3960 smallArrayt *listTasksInBookmarks(void) { 3961 createAllocateSmallArray(r); 3962 forEachSmallArray(bookmarks, T) { 3963 castS(s, T); 3964 smallArrayt *desc = display_task(ssGet(s), passThroughTitle); 3965 pushG(r, "---"); 3966 appendNSmashG(r, desc); 3967 finishG(s); 3968 } 3969 return r; 3970 } 3971 3972 /** clear edi_core buffers 3973 * @ingroup EDI_DESKTOP 3974 */ 3975 void edi_core_clear_previous_run(void) { 3976 emptyG(group_directory_file_list); 3977 if (agenda) { 3978 emptyG(agenda); 3979 } 3980 } 3981 3982 /** search tid in all selected databases 3983 * @return r empty when tid is not found, 'tid exists' when found 3984 * @param[in] tid task or group id 3985 * @ingroup EDI_DESKTOP 3986 * The setup of the database for tid is kept 3987 */ 3988 bool tid_exists(const char *tid) { 3989 bool r = false; 3990 // clear edi_core variable because the database setup can change 3991 edi_core_clear_previous_run(); 3992 forEachSmallArray(selected_path, P) { 3993 castS(path, P); 3994 freeManyS(data_location, data_location_tasks, data_location_groups, data_location_tree); 3995 data_location = dupG(ssGet(path)); 3996 3997 data_location_tasks = appendG(data_location, "/tasks"); 3998 data_location_groups = appendG(data_location, "/groups"); 3999 data_location_tree = appendG(data_location, "/tree"); 4000 4001 if (not fileExists(data_location)) { 4002 //strcpy(result_select_database, data_location); 4003 //strcat(result_select_database, " is unreachable"); 4004 finishG(P); 4005 break; 4006 } 4007 4008 if (fileExists(generate_task_path(tid))) { 4009 r = true; 4010 finishG(P); 4011 break; 4012 } 4013 finishG(P); 4014 } 4015 return r; 4016 } 4017 4018 /** search tid in all selected databases 4019 * @param[in] tid task or group id 4020 * @ingroup EDI_DESKTOP 4021 * The setup of the database for tid is kept. 4022 * Same as tid_exists without return value. 4023 */ 4024 bool setup_data_location_for_tid(const char *tid) { 4025 return tid_exists(tid); 4026 } 4027 4028 /** save current database path in saved_data_location 4029 * @ingroup EDI_DESKTOP 4030 */ 4031 void save_edi_core_data_location(void) { 4032 //debug print 'save' 4033 free(saved_data_location); 4034 saved_data_location = dupG(data_location); 4035 } 4036 4037 /** restore saved_data_location 4038 * @ingroup EDI_DESKTOP 4039 */ 4040 void restore_edi_core_data_location(void) { 4041 //debug print 'restore' 4042 4043 freeManyS(data_location, data_location_tasks, data_location_groups, data_location_tree); 4044 data_location = saved_data_location; 4045 saved_data_location = NULL; 4046 4047 data_location_tasks = appendG(data_location, "/tasks"); 4048 data_location_groups = appendG(data_location, "/groups"); 4049 data_location_tree = appendG(data_location, "/tree"); 4050 4051 // clear edi_core variable because the database setup can change 4052 edi_core_clear_previous_run(); 4053 } 4054 4055 char return_getDatabaseNameFromPath[8192]; 4056 4057 const char *getDatabaseNameFromPath(const char *database_path) { 4058 return_getDatabaseNameFromPath[0] = 0; 4059 forEachSmallDict(selected_d, k, V) { 4060 castS(v, V); 4061 if (eqG(v, database_path)) { 4062 strcpy(return_getDatabaseNameFromPath, k); 4063 finishG(v); 4064 break; 4065 } 4066 finishG(v); 4067 } 4068 listFreeS(libsheepyInternalKeys); 4069 return return_getDatabaseNameFromPath; 4070 } 4071 4072 // test core functions 4073 void test(void) { 4074 // test 4075 group_directory_file_list = walkDirG(rtSmallArrayt, data_location_groups); 4076 logG(group_directory_file_list); 4077 return; 4078 smallArrayt *ls = list_group("root"); 4079 4080 forEachSmallArray(ls, L) { 4081 cast(smallDictt*, d, L) 4082 putsG(d); 4083 finishG(d); 4084 } 4085 forEachSmallArray(ls, L) { 4086 cast(smallDictt*, d, L) 4087 putsG(getG(d, rtChar, "title")); 4088 finishG(d); 4089 } 4090 terminateG(ls); 4091 4092 smallArrayt *desc = display_task("RUkjgkUSsDYREKqM", generate_task_string_with_tid); 4093 logG(desc); 4094 terminateG(desc); 4095 4096 /* t1 = add_task('root', 'task1.txt') */ 4097 /* add_task('root', 'task2.txt') */ 4098 /* g3 = add_task('root', 'task3.txt') */ 4099 /* */ 4100 /* create_group(g3) */ 4101 /* t4 = add_task(g3, 'task4.txt') */ 4102 /* */ 4103 /* list_group('root') */ 4104 /* list_group(g3) */ 4105 /* */ 4106 /* //display_task(t1) */ 4107 /* //display_task(t4) */ 4108 /* */ 4109 /* // Delete task in a group of 2 tasks */ 4110 /* print '// Delete task in a group of 2 tasks' */ 4111 /* delete_task(t4) */ 4112 /* */ 4113 /* list_group('root') */ 4114 /* */ 4115 /* create_group(g3) */ 4116 /* t4 = add_task(g3, 'task4.txt') */ 4117 /* */ 4118 /* list_group('root') */ 4119 /* */ 4120 /* // Delete group first task */ 4121 /* print '// Delete group first task' */ 4122 /* delete_task(g3) */ 4123 /* */ 4124 /* list_group('root') */ 4125 /* */ 4126 /* // Delete group task of a group with more than 2 tasks */ 4127 /* print '// Delete group task of a group with more than 2 tasks' */ 4128 /* delete_task(t4) */ 4129 /* */ 4130 /* g3 = add_task('root', 'task3.txt') */ 4131 /* create_group(g3) */ 4132 /* t4 = add_task(g3, 'task4.txt') */ 4133 /* t2 = add_task(g3, 'task2.txt') */ 4134 /* */ 4135 /* list_group('root') */ 4136 /* list_group(g3) */ 4137 /* new_group_id = delete_task(g3) */ 4138 /* print 'New group id %s'%new_group_id */ 4139 /* */ 4140 /* list_group('root') */ 4141 /* list_group(new_group_id) */ 4142 /* */ 4143 /* // Delete group */ 4144 /* print '// Delete group' */ 4145 /* delete_group(new_group_id) */ 4146 /* */ 4147 /* list_group('root') */ 4148 /* */ 4149 /* print baseconvert(100) */ 4150 /* print baseconvert_to_dec(baseconvert(100)) */ 4151 /* print */ 4152 /* */ 4153 /* //edit_task(t1) */ 4154 /* */ 4155 /* // Change order */ 4156 /* print '// Change order' */ 4157 /* g3 = add_task('root', 'task3.txt') */ 4158 /* change_task_order('root',1,0) */ 4159 /* change_task_order('root',0,2) */ 4160 /* */ 4161 /* list_group('root') */ 4162 /* */ 4163 /* // Change status */ 4164 /* print '// Change status' */ 4165 /* create_group(g3) */ 4166 /* t4 = add_task(g3, 'task4.txt') */ 4167 /* t2 = add_task(g3, 'task2.txt') */ 4168 /* */ 4169 /* list_group('root') */ 4170 /* */ 4171 /* set_status(t1,TASK_STATUS_DONE) */ 4172 /* set_status(t4,TASK_STATUS_DONE) */ 4173 /* */ 4174 /* list_group('root') */ 4175 /* list_group(g3) */ 4176 /* */ 4177 /* // Reset status */ 4178 /* */ 4179 /* reset_group_status(g3) */ 4180 /* */ 4181 /* list_group('root') */ 4182 /* list_group(g3) */ 4183 /* */ 4184 /* reset_group_status('root') */ 4185 /* */ 4186 /* list_group('root') */ 4187 /* list_group(g3) */ 4188 /* */ 4189 /* // Create a group in group */ 4190 /* print '// Create a group in group' */ 4191 /* create_group(t4) */ 4192 /* t2 = add_task(t4,'task2.txt') */ 4193 /* */ 4194 /* list_group('root') */ 4195 /* list_group(g3) */ 4196 /* list_group(t4) */ 4197 /* */ 4198 /* // Delete task in a group of 2 tasks not in root */ 4199 /* print '// Delete task in a group of 2 tasks not in root' */ 4200 /* delete_task(t2) */ 4201 /* */ 4202 /* list_group('root') */ 4203 /* list_group(g3) */ 4204 /* */ 4205 /* // Delete group with groups in it */ 4206 /* print '// Delete group with groups in it' */ 4207 /* create_group(t4) */ 4208 /* t2 = add_task(t4,'task2.txt') */ 4209 /* */ 4210 /* list_group('root') */ 4211 /* list_group(g3) */ 4212 /* list_group(t4) */ 4213 /* */ 4214 /* delete_group(g3) */ 4215 /* */ 4216 /* list_group('root') */ 4217 /* */ 4218 /* // Search string */ 4219 /* print '// Search string' */ 4220 /* */ 4221 /* search_string('AND') */ 4222 /* */ 4223 /* // List tree */ 4224 /* print '// List tree' */ 4225 /* g3 = add_task('root', 'task3.txt') */ 4226 /* create_group(g3) */ 4227 /* t4 = add_task(g3, 'task4.txt') */ 4228 /* t2 = add_task(g3, 'task2.txt') */ 4229 /* create_group(t4) */ 4230 /* t2 = add_task(t4,'task2.txt') */ 4231 /* */ 4232 /* list_tree('root') */ 4233 /* */ 4234 /* // Show group for a task */ 4235 /* print '// Show group for a task' */ 4236 /* */ 4237 /* print 'Show group for %s - %s' %(t2,get_task_title(t2)) */ 4238 /* show_group_for_task(t2) */ 4239 /* print 'Show group for %s - %s' %(t4,get_task_title(t4)) */ 4240 /* show_group_for_task(t4) */ 4241 /* print 'Show group for %s - %s' %(t1,get_task_title(t1)) */ 4242 /* show_group_for_task(t1) */ 4243 4244 // create task 4245 //create_task(t4) 4246 //list_group(t4) 4247 } 4248 4249 /** start - always called at startup. 4250 * @ingroup EDI_CORE 4251 * @param[in] interface selects current user interface. cli loads the configuration from user home, web loads the configuration located in edi_web folder. 4252 * creates default .easydoneit.ini<br> 4253 * creates database folders<br> 4254 * loads .easydoneit.ini 4255 */ 4256 void start(char *interface) { 4257 // steps 4258 // initialize variables 4259 // create default config 4260 // load config from inipath 4261 4262 // initialize variables 4263 add_top_or_bottom = strdup("bottom"); 4264 status_filters = allocG(rtSmallArrayt); 4265 //static i16 no_color[4] = {-1,-1,-1,255}; 4266 //static i16 status_fgColors[6][4] = {{0,0,0,255},{0,255,0,255},{255,128,0,255},{255,0,0,255},{192,192,192,255},{0,0,0,255}}; 4267 no_color = allocG(rtSmallArrayt); 4268 pushG(no_color, -1); 4269 pushG(no_color, -1); 4270 pushG(no_color, -1); 4271 pushG(no_color, 255); 4272 status_fgColors = allocG(rtSmallArrayt); 4273 smallArrayt *c = allocG(rtSmallArrayt); 4274 pushG(c, 0); 4275 pushG(c, 0); 4276 pushG(c, 0); 4277 pushG(c, 255); 4278 pushNFreeG(status_fgColors, dupG(c)); 4279 setG(c, 0, 0); 4280 setG(c, 1, 255); 4281 setG(c, 2, 0); 4282 setG(c, 3, 255); 4283 pushNFreeG(status_fgColors, dupG(c)); 4284 setG(c, 0, 255); 4285 setG(c, 1, 128); 4286 setG(c, 2, 0); 4287 setG(c, 3, 255); 4288 pushNFreeG(status_fgColors, dupG(c)); 4289 setG(c, 0, 255); 4290 setG(c, 1, 0); 4291 setG(c, 2, 0); 4292 setG(c, 3, 255); 4293 pushNFreeG(status_fgColors, dupG(c)); 4294 setG(c, 0, 192); 4295 setG(c, 1, 192); 4296 setG(c, 2, 192); 4297 setG(c, 3, 255); 4298 pushNFreeG(status_fgColors, dupG(c)); 4299 setG(c, 0, 0); 4300 setG(c, 1, 0); 4301 setG(c, 2, 0); 4302 setG(c, 3, 255); 4303 pushNFreeG(status_fgColors, dupG(c)); 4304 status_bgColors = allocG(rtSmallArrayt); 4305 range(i, COUNT_ELEMENTS(TASK_STATUS)-1) { 4306 pushNFreeG(status_bgColors, dupG(no_color)); 4307 } 4308 //logVarG(no_color); 4309 //logVarG(status_fgColors); 4310 //logVarG(status_bgColors); 4311 group_directory_file_list = allocG(rtSmallArrayt); 4312 4313 inipath = "~/.easydoneit.ini"; 4314 inipath = expandHomeG(inipath); 4315 4316 user_interface = interface; 4317 4318 if (not eqG(user_interface, "tui")) { 4319 puts(user_interface); 4320 puts(BLD RED "Not supported." RST); 4321 XFAILURE 4322 } 4323 else { 4324 if (not fileExists(inipath)) { 4325 // create default config 4326 createAllocateSmallArray(newcfg); 4327 pushG(newcfg, "[data]"); 4328 pushG(newcfg, "location=~/easydoneit_data"); 4329 pushG(newcfg, "1=~/easydoneit_data"); 4330 pushG(newcfg, ""); 4331 pushG(newcfg, "[locations]"); 4332 pushG(newcfg, "selected=1"); 4333 pushG(newcfg, "default_add_in=1"); 4334 pushG(newcfg, ""); 4335 pushG(newcfg, "[filters]"); 4336 // enable all status filters 4337 char buf[1024]; 4338 forEachS(TASK_STATUS, nfilter) { 4339 // strip to remove spaces in status strings 4340 char *filter = lowerS(nfilter); 4341 trimG(&filter); 4342 sprintf(buf, "%s= %s", filter, STATUS_FILTER_STATES[0]); 4343 free(filter); 4344 pushG(newcfg, buf); 4345 } 4346 pushG(newcfg, ""); 4347 pushG(newcfg, "[colors]"); 4348 enumerateS(TASK_STATUS, nfilter, n) { 4349 char *filter = lowerS(nfilter); 4350 trimG(&filter); 4351 smallArrayt *c = getG(status_fgColors, rtSmallArrayt, n); 4352 sprintf(buf, "%s_fgColor=%d,%d,%d,%d", filter, getG(c, rtI32, 0), getG(c, rtI32, 1), getG(c, rtI32, 2), getG(c, rtI32, 3)); 4353 finishG(c); 4354 free(filter); 4355 pushG(newcfg, buf); 4356 } 4357 {enumerateS(TASK_STATUS, nfilter, n) { 4358 char *filter = lowerS(nfilter); 4359 trimG(&filter); 4360 smallArrayt *c = getG(status_bgColors, rtSmallArrayt, n); 4361 sprintf(buf, "%s_bgColor=%d,%d,%d,%d", filter, getG(c, rtI32, 0), getG(c, rtI32, 1), getG(c, rtI32, 2), getG(c, rtI32, 3)); 4362 finishG(c); 4363 free(filter); 4364 pushG(newcfg, buf); 4365 }} 4366 4367 pushG(newcfg, "\n[settings]"); 4368 pushG(newcfg, "editor=vi"); 4369 4370 writeFileG(newcfg, inipath); 4371 terminateG(newcfg); 4372 } 4373 } 4374 4375 // load config from inipath 4376 ini = parseIni(inipath); 4377 4378 smallDictt *data = getG(ini, rtSmallDictt, "data"); 4379 smallDictt *locations = getG(ini, rtSmallDictt, "locations"); 4380 smallDictt *filters = getG(ini, rtSmallDictt, "filters"); 4381 smallDictt *colors = getG(ini, rtSmallDictt, "colors"); 4382 smallDictt *settings = getG(ini, rtSmallDictt, "settings"); 4383 smallDictt *desktop = getG(ini, rtSmallDictt, "desktop"); 4384 4385 //logVarG(filters); 4386 4387 // convert ~ to home path 4388 data_location = expandHomeG(getG(data, rtChar,"location")); 4389 4390 // load available databases 4391 char **data_keys = keysG(data); 4392 smallArrayt *data_section = valuesG(data); 4393 // remove location which is a path to a database 4394 // clear databases for reentrant testing 4395 databases = allocG(rtSmallArrayt); 4396 enumerateSmallArray(data_section, D, i) { 4397 if (not eqG(data_keys[i], "location")) { 4398 pushNFreeG(databases, dupG(D)); 4399 } 4400 free(D); 4401 } 4402 //logVarG(databases); 4403 4404 // load add_top_or_bottom 4405 char **location_keys = keysG(locations); 4406 if (hasG(location_keys, "add_top_or_bottom")) { 4407 add_top_or_bottom = getNDupG(locations, rtChar,"add_top_or_bottom"); 4408 } 4409 4410 // load selected database paths 4411 smallStringt *sselected = getNDupG(locations, rtSmallStringt, "selected"); 4412 selected = splitG(sselected, ","); 4413 terminateG(sselected); 4414 default_add_in = getNDupG(locations,rtChar, "default_add_in"); 4415 selected_path = allocG(rtSmallArrayt); 4416 forEachSmallArray(selected, D) { 4417 castS(d, D); 4418 pushNFreeG(selected_path, expandHomeG(getG(data, rtChar, ssGet(d)))); 4419 free(D); 4420 } 4421 4422 selected_d = allocG(rtSmallDictt); 4423 zipG(selected_d, selected, selected_path); 4424 //logVarG(selected_path); 4425 //logVarG(selected_d); 4426 4427 // load autolink groups 4428 if (hasG(location_keys, "autolink")) { 4429 smallStringt *autolink_cfg = getG(locations, rtSmallStringt,"autolink"); 4430 autolink = allocG(rtSmallArrayt); 4431 rangeStep(i, lenG(autolink_cfg), ID_LENGTH+1) { 4432 smallStringt *s = copyRngG(autolink_cfg, i, i+ID_LENGTH); 4433 pushNFreeG(autolink, s); 4434 } 4435 finishG(autolink_cfg); 4436 } 4437 //logVarG(autolink); 4438 4439 // load list groups 4440 if (hasG(location_keys, "list")) { 4441 smallStringt *list_cfg = getG(locations, rtSmallStringt, "list"); 4442 list_of_groups = splitG(list_cfg, ","); 4443 } 4444 4445 // load status filters and status colors 4446 char **config_filter_names = keysG(filters); 4447 char **config_color_names = keysG(colors); 4448 enumerateS(TASK_STATUS, nfilter, n) { 4449 // strip to remove spaces in status strings 4450 // check that task_status filter is in config file, to avoid problems between config file versions 4451 char *filter = lowerS(nfilter); 4452 trimG(&filter); 4453 pushG(&TASK_STATUS_TRIM, filter); 4454 if (hasG(config_filter_names, filter)) { 4455 smallStringt *s = getG(filters, rtSmallStringt, filter); 4456 if (eqG(s, STATUS_FILTER_STATES[DISABLE])) { 4457 pushG(status_filters, DISABLE); 4458 } 4459 else { 4460 pushG(status_filters, ENABLE); 4461 } 4462 finishG(s); 4463 } 4464 char *fc = appendG(filter, "_fgcolor"); 4465 if (hasG(config_color_names, fc)) { 4466 smallStringt *s = getG(colors, rtSmallStringt, fc); 4467 smallArrayt *a = splitG(s, ","); 4468 4469 // convert a strings to int 4470 enumerateSmallArray(a, CP, ci) { 4471 castS(cp, CP); 4472 setG(a, ci, parseIntG(cp)); 4473 finishG(cp); 4474 } 4475 setNFreeG(status_fgColors, n, a); 4476 finishG(s); 4477 } 4478 free(fc); 4479 fc = appendG(filter, "_bgcolor"); 4480 if (hasG(config_color_names, fc)) { 4481 smallStringt *s = getG(colors, rtSmallStringt, fc); 4482 smallArrayt *a = splitG(s, ","); 4483 4484 // convert a strings to int 4485 enumerateSmallArray(a, CP, ci) { 4486 castS(cp, CP); 4487 setG(a, ci, parseIntG(cp)); 4488 finishG(cp); 4489 } 4490 setNFreeG(status_bgColors, n, a); 4491 finishG(s); 4492 } 4493 free(fc); 4494 } 4495 //logVarG(status_fgColors); 4496 //logVarG(status_bgColors); 4497 4498 // Set text editor 4499 editor = getNDupG(settings, rtChar,"editor"); 4500 4501 // set user name and email 4502 char **settings_keys = keysG(settings); 4503 if (hasG(settings_keys, "username")) { 4504 user = getNDupG(settings, rtChar, "username"); 4505 } 4506 else { 4507 user = strdup("Unknown"); 4508 } 4509 if (hasG(settings, "useremail")) { 4510 email = getNDupG(settings, rtChar, "useremail"); 4511 } 4512 else { 4513 emptyS(email); 4514 } 4515 4516 if (desktop) { 4517 smallStringt *s = getG(desktop, rtSmallStringt, "bookmarks"); 4518 bookmarks = splitG(s, "|"); 4519 finishG(s); 4520 } 4521 else { 4522 // no desktop section in ini file, empty bookmarks 4523 bookmarks = allocG(rtSmallArrayt); 4524 } 4525 4526 // init 4527 init(); 4528 4529 listFreeManyS(data_keys, location_keys, settings_keys, config_filter_names, config_color_names); 4530 finishManyG(data, locations, filters, colors, settings, desktop); 4531 }