💾 Archived View for gmi.noulin.net › gitRepositories › easydoneit-cli › file › edi_core.py.gmi captured on 2024-08-18 at 17:57:10. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

easydoneit-cli

Log

Files

Refs

README

LICENSE

edi_core.py (85124B)

     1 #! /usr/bin/env python
     2 # -*- coding: latin-1 -*-
     3 ## @package edi_core
     4 #  Core module
     5 #
     6 #
     7 # Copyright (C) 2014 Spartatek AB
     8 #
     9 # contact@spartatek.se
    10 # http://spartatek.se
    11 #
    12 # EASYDONEIT CLI is free software: you can redistribute it and/or modify
    13 # it under the terms of the GNU Genereric Public License as published by
    14 # the Free Software Foundation, either version 3 of the License, or
    15 # (at your option) any later version.
    16 #
    17 # EASYDONIT CLI is distributed in the hope that it will be usesul,
    18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
    19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    20 # GNU Genereral Public License for more details.
    21 #
    22 # You should have received a copy of the GNU General Public License
    23 # along with this program. If not, see
    24 # GNU licences http://www.gnu.org/licenses
    25 
    26 import sys
    27 import re
    28 import time
    29 import shutil
    30 import os
    31 import ConfigParser
    32 from string import ascii_lowercase
    33 from string import ascii_uppercase
    34 from string import digits
    35 import random
    36 import stat
    37 import getpass
    38 
    39 from edi_common import *
    40 
    41 ## 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.
    42 # Values: 'cli', 'web'
    43 user_interface       = 'cli'
    44 
    45 data_location        = ''
    46 data_location_tasks  = ''
    47 data_location_groups = ''
    48 data_location_tree   = ''
    49 
    50 ## Available databases
    51 databases            = []
    52 
    53 ## list selected databases
    54 selected             = []
    55 selected_path        = []
    56 ## default database where tasks are created
    57 default_add_in       = ''
    58 
    59 ## add new tasks in 'bottom' of group or 'top' of group
    60 add_top_or_bottom    = 'bottom'
    61 
    62 ## Autlink groups (array of tids)
    63 autolink             = ''
    64 
    65 ## list of groups for edi ls -L, -La and -Lx (array of tids)
    66 list_of_groups       = ''
    67 
    68 ## characters for task ids
    69 ID_BASE              = sorted(list(digits)+list(ascii_lowercase)+list(ascii_uppercase)+['_',','])
    70 ID_BASE_STRING       = ''.join(ID_BASE)
    71 ## Length of task id
    72 ID_LENGTH            = 16
    73 ## Length of order id in groups
    74 ORDER_ID_LENGTH      = 8
    75 BASE                 = len(ID_BASE)
    76 
    77 TASK_STATUS          = ['  Active',
    78                         '    Done',
    79                         ' Ongoing',
    80                         ' Pending',
    81                         'Inactive',
    82                         '    Void']
    83 TASK_STATUS_ACTIVE   = 0
    84 TASK_STATUS_DONE     = 1
    85 TASK_STATUS_ONGOING  = 2
    86 TASK_STATUS_PENDING  = 3
    87 TASK_STATUS_INACTIVE = 4
    88 TASK_STATUS_VOID     = 5
    89 
    90 ## Sort order for sort_task_attributes function, ongoing, active, pending, done, inactive, void
    91 SORT_TASK_ORDER      = [2,0,3,1,4,5]
    92 
    93 LIST_OPTIONS         = ['tids',
    94                         'positions',
    95                         'html']
    96 list_option          = 'tids'
    97 
    98 STATUS_FILTER_STATES = ['enable','disable']
    99 
   100 ## enables all status filters
   101 status_filters       = [STATUS_FILTER_STATES[0] for i in range(len(TASK_STATUS))]
   102 ## status filter dictionary, initialized after loading ini file
   103 status_filters_d     = {}
   104 
   105 ## colors
   106 no_color             = (-1,-1,-1,255)
   107 #status_fgColors      = [(0,0,0,255) for i in range(len(TASK_STATUS))]
   108 status_fgColors      = [(0,0,0,255),(0,255,0,255),(255,128,0,255),(160,32,240,255),(192,192,192,255),(0,0,0,255)]
   109 status_fgColors_d    = {}
   110 ## no background color by default
   111 status_bgColors      = [no_color for i in range(len(TASK_STATUS))]
   112 status_fgColors_d    = {}
   113 
   114 ## text editor in terminal - easydoneit.ini [settings] EDITOR
   115 editor               = 'vi'
   116 
   117 ## Agenda generated by edi.in_cli
   118 agenda               = []
   119 
   120 ## user name
   121 user                 = ''
   122 
   123 ## user email
   124 email                = ''
   125 
   126 ## stats for statistics function
   127 stats                = {}
   128 
   129 ## total number of tasks for statistics function
   130 stats_total          = 0
   131 
   132 ## creation dates of tasks in statistics, used to find out oldest task
   133 stats_creation_dates = []
   134 
   135 ## timely state changes and creation dates
   136 # Type: dict of dict of integers
   137 # keys are dates
   138 # each dates is a dictionary with STATS_OVERTIME_KEYS keys
   139 # each key is the amount for states and creation
   140 stats_overtime       = {}
   141 
   142 ## timely state changes and creation dates
   143 STATS_OVERTIME_KEYS  = [TASK_STATUS[i] for i in range(len(TASK_STATUS))] + ['Creation']
   144 
   145 ## Group directory, cache result to speed up color functions
   146 group_directory_file_list = []
   147 
   148 ## initializes path to data.
   149 # creates directories<br>
   150 # creates root group and task<br>
   151 # @ingroup EDI_CORE
   152 def init():
   153         # Set global variables
   154         global data_location_tasks
   155         global data_location_groups
   156         global data_location_tree
   157         global status_filters
   158         global status_filters_d
   159         global status_fgColors
   160         global status_fgColors_d
   161         global status_bgColors
   162         global status_bgColors_d
   163 
   164         status_filters_d     = dict(zip(TASK_STATUS,status_filters))
   165         status_fgColors_d    = dict(zip(TASK_STATUS,status_fgColors))
   166         status_bgColors_d    = dict(zip(TASK_STATUS,status_bgColors))
   167 
   168         data_location_tasks  = '%s/tasks'%data_location
   169         data_location_groups = '%s/groups'%data_location
   170         data_location_tree   = '%s/tree'%data_location
   171 
   172         if not os.path.exists(data_location_tasks):
   173                 if not os.path.exists(data_location):
   174                         os.mkdir(data_location)
   175                 os.mkdir(data_location_tasks)
   176                 os.mkdir(data_location_groups)
   177                 os.mkdir(data_location_tree)
   178                 os.mkdir('%s/root'%data_location_groups)
   179 
   180                 # Create root description in tasks
   181                 task_path = '%s/root'%data_location_tasks
   182                 os.mkdir(task_path)
   183                 # copy root description from script directory
   184                 shutil.copy('%s/root.txt'%os.path.abspath(os.path.dirname(sys.argv[0])),'%s/description.txt'%task_path)
   185 
   186                 # Create status
   187                 f         = open('%s/status'%task_path,'w')
   188                 f.write(TASK_STATUS[TASK_STATUS_VOID])
   189                 f.close()
   190 
   191 ## log actions in database - data_location/log.txt
   192 # @param[in] s string without user name and timestamp
   193 # @ingroup EDI_CORE
   194 def edi_log(s):
   195         global user
   196         global email
   197         f = open('%s/log.txt'%data_location,'a')
   198         f.write('%s - %s - %s\n'%(time.strftime("%Y-%m-%d %H:%M"), '%s <%s>'%(user,email),s.strip()))
   199         f.close()
   200 
   201 
   202 ## select location database
   203 # @param[in] location database name
   204 # @ingroup EDI_CORE
   205 def select_database(location):
   206         global data_location
   207         global data_location_tasks
   208         global data_location_groups
   209         global data_location_tree
   210 
   211         z = dict(zip(selected, selected_path))
   212         data_location        = os.path.expanduser(z[location])
   213         data_location_tasks  = '%s/tasks'%data_location
   214         data_location_groups = '%s/groups'%data_location
   215         data_location_tree   = '%s/tree'%data_location
   216         if not os.path.exists(data_location):
   217                 return '%s is unreachable.'%data_location
   218         else:
   219                 return ''
   220 
   221 ## Mix foreground colors from task, groups and default colors.
   222 # @return color array
   223 # @param[in] tid task id
   224 # @ingroup EDI_CORE
   225 # Collect all defined colors in link groups.
   226 # For each group, the group gets the first color defined in the tree<br>
   227 # compute average color
   228 def mix_fgcolors(tid):
   229         # mix colors
   230         # collect all defined colors in groups to root
   231         colors = []
   232         # root has no parent group, dont search parent group color
   233         if tid != 'root':
   234                 # find all groups - link and parent group
   235                 if is_linked(tid):
   236                         groups = os.listdir('%s/groups/' % generate_task_path(tid))
   237                 else:
   238                         groups = [find_group_containing_task(tid)]
   239 
   240                 # walk parent groups until a color or root is found
   241                 while groups:
   242                         status_color = 'search'
   243                         if os.path.isfile('%s/fgColor'%generate_task_path(groups[0])):
   244                                 f       = open('%s/fgColor'%generate_task_path(groups[0]))
   245                                 c       = tuple([int(i) for i in f.readline().split(',')])
   246                                 f.close()
   247                                 # -1 means no_color, mix colors
   248                                 if c[0] != -1:
   249                                         colors.append(c)
   250                                         status_color = 'found color'
   251                         if status_color == 'search':
   252                                 parent_group = find_group_containing_task(groups[0])
   253                                 if parent_group != 'root':
   254                                         groups.append(parent_group)
   255                         del groups[0]
   256 
   257                 # compute average color
   258                 if colors:
   259                         color = [0,0,0,0]
   260                         for c in colors:
   261                                 for i in range(len(c)):
   262                                         color[i] += c[i]
   263                         colorCount = len(colors)
   264                         color      = tuple([i/colorCount for i in color])
   265         if not colors:
   266                 # no defined colors, use default color for status
   267                 task_status = get_status(tid)
   268                 color       = status_fgColors_d[task_status]
   269         return color
   270 
   271 ## get color for task tid
   272 # @return color array
   273 # @param[in] tid task id
   274 # @ingroup EDI_CORE
   275 # when no color is set, mix colors from groups or use defaults
   276 def get_forground_color(tid):
   277         color = no_color
   278 
   279         if os.path.isfile('%s/fgColor'%generate_task_path(tid)):
   280                 f       = open('%s/fgColor'%generate_task_path(tid))
   281                 color   = tuple([int(i) for i in f.readline().split(',')])
   282                 f.close()
   283                 # -1 means no_color, mix colors
   284                 if color[0] == -1:
   285                         color = mix_fgcolors(tid)
   286         else:
   287                 color   = mix_fgcolors(tid)
   288         return color
   289 
   290 ## set color for task tid
   291 # @return color array
   292 # @param[in] tid task id
   293 # @param[in] color_s color array
   294 # @ingroup EDI_CORE
   295 def set_forground_color(tid,color_s):
   296         f       = open('%s/fgColor'%generate_task_path(tid),'w')
   297         f.write(color_s)
   298         f.close()
   299         edi_log('set foreground color in %s to %s'%(tid,color_s))
   300 
   301 ## Mix background colors from task, groups and default colors.
   302 # @return color array
   303 # @param[in] tid task id
   304 # @ingroup EDI_CORE
   305 # Collect all defined colors in link groups.
   306 # For each group, the group gets the first color defined in the tree<br>
   307 # compute average color
   308 def mix_bgcolors(tid):
   309         # mix colors
   310         # collect all defined colors in groups to root
   311         colors = []
   312         # root has no parent group, dont search parent group color
   313         if tid != 'root':
   314                 # find all groups - link and parent group
   315                 if is_linked(tid):
   316                         groups = os.listdir('%s/groups/' % generate_task_path(tid))
   317                 else:
   318                         groups = [find_group_containing_task(tid)]
   319 
   320                 # walk parent groups until a color or root is found
   321                 while groups:
   322                         status_color = 'search'
   323                         if os.path.isfile('%s/bgColor'%generate_task_path(groups[0])):
   324                                 f       = open('%s/bgColor'%generate_task_path(groups[0]))
   325                                 c       = tuple([int(i) for i in f.readline().split(',')])
   326                                 f.close()
   327                                 # -1 means no_color, mix colors
   328                                 if c[0] != -1:
   329                                         colors.append(c)
   330                                         status_color = 'found color'
   331                         if status_color == 'search':
   332                                 parent_group = find_group_containing_task(groups[0])
   333                                 if parent_group != 'root':
   334                                         groups.append(parent_group)
   335                         del groups[0]
   336 
   337                 # compute average color
   338                 if colors:
   339                         color = [0,0,0,0]
   340                         for c in colors:
   341                                 for i in range(len(c)):
   342                                         color[i] += c[i]
   343                         colorCount = len(colors)
   344                         color      = tuple([i/colorCount for i in color])
   345         if not colors:
   346                 # no defined colors, use default color for status
   347                 task_status = get_status(tid)
   348                 color       = status_bgColors_d[task_status]
   349         return color
   350 
   351 ## get color for task tid
   352 # @return color array
   353 # @param[in] tid task id
   354 # @ingroup EDI_CORE
   355 # when no color is set, mix colors from groups or use defaults
   356 def get_background_color(tid):
   357         color = no_color
   358 
   359         if os.path.isfile('%s/bgColor'%generate_task_path(tid)):
   360                 f       = open('%s/bgColor'%generate_task_path(tid))
   361                 color   = tuple([int(i) for i in f.readline().split(',')])
   362                 f.close()
   363                 # -1 means no_color, mix colors
   364                 if color[0] == -1:
   365                         color = mix_bgcolors(tid)
   366         else:
   367                 color   = mix_bgcolors(tid)
   368         return color
   369 
   370 ## set color for task tid
   371 # @return color array
   372 # @param[in] tid task id
   373 # @param[in] color_s color array
   374 # @ingroup EDI_CORE
   375 def set_background_color(tid,color_s):
   376         f       = open('%s/bgColor'%generate_task_path(tid),'w')
   377         f.write(color_s)
   378         f.close()
   379         edi_log('set background color in %s to %s'%(tid,color_s))
   380 
   381 ## converts color to hex format
   382 # @return color hex string
   383 # @param[in] color color array
   384 # @ingroup EDI_CORE
   385 def color_to_hex(color):
   386         c = []
   387         for n in color:
   388                 if n == -1:
   389                         # -1 is no color, white
   390                         c.append(255)
   391                 else:
   392                         c.append(n)
   393         color = tuple(c)
   394         return '%02x%02x%02x' % (color[0],color[1],color[2])
   395 
   396 ## filesystem path for task in database tasks
   397 # @return path
   398 # @param[in] tid task id
   399 # @ingroup EDI_CORE
   400 def generate_task_path(tid):
   401         return '%s/%s'%(data_location_tasks,tid)
   402 
   403 ## filesystem path for group in database groups
   404 # @return path
   405 # @param[in] tid task id
   406 # @ingroup EDI_CORE
   407 def generate_group_path(tid):
   408         return '%s/%s/'%(data_location_groups,tid)
   409 
   410 ## get status for tid
   411 # @return status string
   412 # @param[in] tid task id
   413 # @ingroup EDI_CORE
   414 def get_status(tid):
   415         # open status file
   416         try:
   417                 f      = open('%s/status'%generate_task_path(tid))
   418                 status = f.readline()
   419                 f.close()
   420         except:
   421                 print '%s is an invalid task.'%generate_task_path(tid)
   422                 status = TASK_STATUS[TASK_STATUS_VOID]
   423         return status
   424 
   425 ## creates a string with task id, status and string parameter
   426 # @return list of strings
   427 # @param[in] tid task id
   428 # @param[in] s task title string
   429 # @ingroup EDI_CORE
   430 # Displays a task depending on edi_core.list_option
   431 def generate_task_string_with_tid(tid,s):
   432         status = get_status(tid)
   433         if list_option == 'tids':
   434                 # %*s to dynamically adjust id length
   435                 r             = '%*s - %s -   %s'%(ID_LENGTH,tid,status,s)
   436         if list_option == 'positions':
   437                 if is_this_task_a_group(tid):
   438                         # print tid for groups in the list
   439                         r             = '%*s - %s -   %s'%(ID_LENGTH,tid,status,s)
   440                 else:
   441                         tid           = ' '
   442                         r             = '%*s   %s -   %s'%(ID_LENGTH,tid,status,s)
   443         if list_option == 'md':
   444                 # print title and description
   445                 f             = open('%s/description.txt'%generate_task_path(tid))
   446                 # remove title line
   447                 description   = ''.join(f.readlines()[1:])
   448                 f.close()
   449 
   450                 tid           = ' '
   451                 # add <br> for long titles
   452                 r             = '\n---\n#%s<br>\n%s'%(s,description)
   453         if list_option == 'rst':
   454                 # print title and description
   455                 f             = open('%s/description.txt'%generate_task_path(tid))
   456                 # remove title line
   457                 description_l = f.readlines()[1:]
   458                 f.close()
   459 
   460                 # convert urls to link
   461                 NOBRACKET = r'[^\]\[]*'
   462                 BRK = ( r'\[('
   463                         + (NOBRACKET + r'(\[')*6
   464                         + (NOBRACKET+ r'\])*')*6
   465                         + NOBRACKET + r')\]' )
   466                 NOIMG = r'(?<!\!)'
   467                 LINK_RE = NOIMG + BRK + \
   468                 r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
   469                 # [text](url) or [text](<url>) or [text](url "title")
   470 
   471                 compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE)
   472 
   473                 for line_index,l in enumerate(description_l):
   474                         if 'http' in l:
   475                                 try:
   476                                         match      = compiled_re.match(l)
   477                                         url        = match.group(9)
   478                                         text       = match.group(2)
   479                                         before_url = match.group(1)
   480                                         #title      = match.group(13)
   481                                         after_url  = match.group(14)
   482 
   483                                         l = '%s`%s<%s>`_%s'% (before_url, text, url, after_url)
   484                                 except:
   485                                         # not used, information
   486                                         link_status = 'WARNING: the link is not in format [title](url)'
   487                         description_l[line_index] = l
   488 
   489                 description   = ''.join(description_l)
   490 
   491                 tid           = ' '
   492                 # remove position and type (GROUP, LINK) from title
   493                 title         = s[11:].lstrip()
   494                 r             = '%s\n'%title + '='*len(title) + '\n%s\n\n'%description
   495         if list_option == 'html':
   496                 #<tr bgcolor="#FEFFCC">
   497                 #  <td class="subject">          <font color="#000000">t1</font></td>
   498                 #  <td class="description">          <font color="#000000">&nbsp;</font></td>
   499                 #</tr>
   500 #:define read_description
   501                 # convert < to &lt; and > &gt; to ignore html tags in title line
   502                 s             = s.replace('<','&lt;').replace('>','&gt;')
   503                 f             = open('%s/description.txt'%generate_task_path(tid))
   504                 # remove title line, convert < to &lt; and > &gt; to ignore html tags
   505                 description_l = ['%s<br>'%i.rstrip().replace('<','&lt;').replace('>','&gt;') for i in f.readlines()[1:]]
   506                 f.close()
   507 
   508                 # convert urls to link
   509                 NOBRACKET = r'[^\]\[]*'
   510                 BRK = ( r'\[('
   511                         + (NOBRACKET + r'(\[')*6
   512                         + (NOBRACKET+ r'\])*')*6
   513                         + NOBRACKET + r')\]' )
   514                 NOIMG = r'(?<!\!)'
   515                 LINK_RE = NOIMG + BRK + \
   516                 r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
   517                 # [text](url) or [text](<url>) or [text](url "title")
   518 
   519                 compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE)
   520 
   521                 for line_index,l in enumerate(description_l):
   522                         if 'http' in l:
   523                                 try:
   524                                         match      = compiled_re.match(l)
   525                                         url        = match.group(9)
   526                                         text       = match.group(2)
   527                                         before_url = match.group(1)
   528                                         #title      = match.group(13)
   529                                         after_url  = match.group(14)
   530 
   531                                         l = '%s<a href="%s">%s</a>%s'% (before_url, url, text, after_url)
   532                                 except:
   533                                         # not used, information
   534                                         link_status = 'WARNING: the link is not in format [title](url)'
   535                         description_l[line_index] = l
   536 
   537                 description   = '\n'.join(description_l)
   538 #:end
   539 
   540                 if is_this_task_a_group(tid) and user_interface == 'web':
   541                         subject       = '<a href="edi_web.py?tid=%s">%s -   %s</a>'%(tid,status,s)
   542                 else:
   543                         subject       = '%s -   %s'%(status,s)
   544                 fg            = color_to_hex(get_forground_color(tid))
   545                 bg            = color_to_hex(get_background_color(tid))
   546                 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)
   547         return r
   548 
   549 ## creates a string with task id, status and string parameter
   550 # @return list of strings
   551 # @param[in] tid task id
   552 # @param[in] s task title string
   553 # @ingroup EDI_CORE
   554 # Displays a group depending on edi_core.list_option
   555 def generate_group_string_with_tid(tid,s):
   556         global agenda
   557 
   558         status = get_status(tid)
   559         if (list_option == 'tids') or (list_option == 'positions'):
   560                 # %*s to dynamically adjust id length
   561                 r           = '%*s - %s - %s'%(ID_LENGTH,tid,status,s)
   562         if list_option == 'md':
   563                 tid         = ' '
   564                 # print group title only
   565                 r           = '#%s'%s.replace('  0 - GROUP','')
   566                 if agenda:
   567                         r += '\n---\n# Agenda\n%s' % '\n\n'.join(agenda)
   568         if list_option == 'rst':
   569                 tid         = ' '
   570                 # print group title only
   571                 title       = s.replace('  0 - GROUP','')
   572                 r           = '='*len(title) + '\n%s\n'%title + '='*len(title) + '\n\n'
   573                 if agenda:
   574                         r += 'Agenda\n======\n%s\n\n' % '\n'.join(agenda)
   575                 else:
   576                         r += '\n.. contents::\n\n'
   577         if list_option == 'html':
   578                 #<tr bgcolor="#FEFFCC">
   579                 #  <td class="subject">          <font color="#000000">t1</font></td>
   580                 #  <td class="description">          <font color="#000000">&nbsp;</font></td>
   581                 #</tr>
   582                 # display description in html
   583 #:read_description
   584                 # convert < to &lt; and > &gt; to ignore html tags in title line
   585                 s             = s.replace('<','&lt;').replace('>','&gt;')
   586                 f             = open('%s/description.txt'%generate_task_path(tid))
   587                 # remove title line, convert < to &lt; and > &gt; to ignore html tags
   588                 description_l = ['%s<br>'%i.rstrip().replace('<','&lt;').replace('>','&gt;') for i in f.readlines()[1:]]
   589                 f.close()
   590 
   591                 # convert urls to link
   592                 NOBRACKET = r'[^\]\[]*'
   593                 BRK = ( r'\[('
   594                         + (NOBRACKET + r'(\[')*6
   595                         + (NOBRACKET+ r'\])*')*6
   596                         + NOBRACKET + r')\]' )
   597                 NOIMG = r'(?<!\!)'
   598                 LINK_RE = NOIMG + BRK + \
   599                 r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
   600                 # [text](url) or [text](<url>) or [text](url "title")
   601 
   602                 compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE)
   603 
   604                 for line_index,l in enumerate(description_l):
   605                         if 'http' in l:
   606                                 try:
   607                                         match      = compiled_re.match(l)
   608                                         url        = match.group(9)
   609                                         text       = match.group(2)
   610                                         before_url = match.group(1)
   611                                         #title      = match.group(13)
   612                                         after_url  = match.group(14)
   613 
   614                                         l = '%s<a href="%s">%s</a>%s'% (before_url, url, text, after_url)
   615                                 except:
   616                                         # not used, information
   617                                         link_status = 'WARNING: the link is not in format [title](url)'
   618                         description_l[line_index] = l
   619 
   620                 description   = '\n'.join(description_l)
   621 
   622                 subject     = '%s - %s'%(status, s)
   623                 fg          = color_to_hex(get_forground_color(tid))
   624                 bg          = color_to_hex(get_background_color(tid))
   625                 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)
   626         return r
   627 
   628 ## determines if task is a group
   629 # @return empty string or 'this task is a group'
   630 # @param[in] tid task id
   631 # @ingroup EDI_CORE
   632 def is_this_task_a_group(tid):
   633         # Identify if task is a group
   634         is_a_group       = ''
   635         # list groups
   636         groups           = os.listdir(data_location_groups)
   637         if tid in groups:
   638                 is_a_group = 'this task is a group'
   639         return is_a_group
   640 
   641 ## get task title
   642 # @return first line of task description
   643 # @param[in] tid task id
   644 # @ingroup EDI_CORE
   645 def get_task_title(tid):
   646         task_path = generate_task_path(tid)
   647         f         = open('%s/description.txt'%task_path)
   648         r         = f.readline().strip()
   649         f.close()
   650         return r
   651 
   652 ## get creation date
   653 # @return array of date string and unix time
   654 # @param[in] tid task id
   655 # @ingroup EDI_CORE
   656 def get_creation_date(tid):
   657         r = []
   658         # Figure out creation time and modification times for description and status
   659         task_path         = generate_task_path(tid)
   660         if not os.path.exists('%s/ctime.txt'%task_path):
   661                 task_ctime        = 'Not available'
   662                 r.append(task_ctime)
   663                 r.append(0)
   664         else:
   665                 f = open('%s/ctime.txt'%task_path)
   666                 task_ctime        = f.readline()
   667                 f.close()
   668                 r.append(task_ctime)
   669                 r.append(time.mktime(time.strptime(task_ctime)))
   670 
   671         return r
   672 
   673 ## get media
   674 # @return media file name
   675 # @param[in] tid task id
   676 # @ingroup EDI_CORE
   677 def get_media(tid):
   678         r = ['None']
   679         task_path = generate_task_path(tid)
   680         if os.path.exists('%s/media'%task_path):
   681                 files = os.listdir('%s/media'%task_path)
   682                 r     = [files[0].split('.')[0], '%s/media/%s'%(task_path,files[0])]
   683         return r
   684 
   685 ## set media, one media file per task
   686 # @return media file name
   687 # @param[in] tid task id
   688 # @param[in] filename media file to copy to task
   689 # @ingroup EDI_CORE
   690 def set_media(tid,filename):
   691         r = '%s is not a supported media file.' % filename
   692 
   693         task_path = generate_task_path(tid)
   694         if filename.strip()[-3:] == 'wav':
   695 #:define create_media_folder
   696                 if not os.path.exists('%s/media'%task_path):
   697                         # create media folder
   698                         os.mkdir('%s/media'%task_path)
   699 #:end
   700                 else:
   701                         # media exists, check type
   702                         files = os.listdir('%s/media'%task_path)
   703                         if not files[0][-3:] == 'wav':
   704                                 r = '%s is not a sound.'%tid
   705                                 return r
   706                 # sound file
   707                 shutil.copy(filename,'%s/media/sound.wav'%task_path)
   708                 r = 'sound.wav'
   709         if filename.strip()[-3:] == 'jpg':
   710 #:create_media_folder
   711                 if not os.path.exists('%s/media'%task_path):
   712                         # create media folder
   713                         os.mkdir('%s/media'%task_path)
   714                 else:
   715                         # media exists, check type
   716                         files = os.listdir('%s/media'%task_path)
   717                         if not files[0][-3:] == 'jpg':
   718                                 r = '%s is not an image.'%tid
   719                                 return r
   720                 # image file
   721                 shutil.copy(filename,'%s/media/image.jpg'%task_path)
   722                 r = 'image.jpg'
   723         edi_log('added media %s in task %s'%(filename,tid))
   724         return r
   725 
   726 ## get attachments
   727 # @return attachment file names
   728 # @param[in] tid task id
   729 # @ingroup EDI_CORE
   730 def get_attachments(tid):
   731         r = ['None']
   732         task_path = generate_task_path(tid)
   733         if os.path.exists('%s/attachments'%task_path):
   734                 files = os.listdir('%s/attachments'%task_path)
   735                 r     = ['%s/attachments/%s'%(task_path,i) for i in files]
   736         return r
   737 
   738 ## set attachments
   739 # @return attachment file names
   740 # @param[in] tid task id
   741 # @param[in] array of filenames
   742 # @ingroup EDI_CORE
   743 # With *, set_attachments copies multiple files at once
   744 def set_attachments(tid,filenames):
   745         task_path = generate_task_path(tid)
   746         if not os.path.exists('%s/attachments'%task_path):
   747                 # create attachment folder
   748                 os.mkdir('%s/attachments'%task_path)
   749 
   750         r         = []
   751         for fn in filenames:
   752                 try:
   753                         shutil.copy(fn,'%s/attachments/'%task_path)
   754                         edi_log('added attachment %s in task %s'%(fn,tid))
   755                         # Remove path from fn, keep only filename
   756                         r.append('%s/attachments/%s'%(task_path,fn.split(os.sep)[-1]))
   757                 except:
   758                         r.append('Failed to copy %s'%fn)
   759         return r
   760 
   761 ## sort task attributes (ls in edi cli)
   762 # @return sorted task list by state
   763 # @param[in] result from list_group
   764 # @ingroup EDI_CORE
   765 def sort_task_attributes(task_attributes):
   766         r = []
   767         # Keep head group on top
   768         if task_attributes[0]['head'] == 'head group':
   769                 r.append(task_attributes[0])
   770                 del task_attributes[0]
   771         for s in SORT_TASK_ORDER:
   772                 for t in task_attributes:
   773                         if t['status'] == TASK_STATUS[s]:
   774                                 r.append(t)
   775         return r
   776 
   777 ## sort function for sorting an array of tasks by date from newest to oldest
   778 # @return compare result
   779 # @param[in] result from list_group
   780 # @ingroup EDI_CORE
   781 def sortdatefunc(x,y):
   782         return cmp(y['ctime'],x['ctime'])
   783 
   784 ## sort task attributes (ls in edi cli)
   785 # @return sorted task list by date
   786 # @param[in] result from list_group
   787 # @ingroup EDI_CORE
   788 def sort_task_attributes_by_date(task_attributes):
   789         r = []
   790         # Keep head group on top
   791         if task_attributes[0]['head'] == 'head group':
   792                 r.append(task_attributes[0])
   793                 del task_attributes[0]
   794 
   795         # -1 to exclude the empty line
   796         a = task_attributes[:-1]
   797         a.sort(sortdatefunc)
   798         #print task_attributes
   799         r += a
   800         r.append(task_attributes[-1])
   801         return r
   802 
   803 ## list id - status - group - first line from description
   804 # @return list of tasks and groups title
   805 # @param[in] tid task id
   806 # @ingroup EDI_CORE
   807 # items in groups have the format 'ORDER_ID''TASK_ID'<br>
   808 # task 0 is group tittle<br>
   809 #<br>
   810 # result array elements:<br>
   811 # {head :string, tid :string, position :value, group :string, title :string, status :string}<br>
   812 # head tells if the element is a group title.<br>
   813 #<br>
   814 # example:<br>
   815 #fTTB1KRWfDpoSR1_ -   Active -   GROUP Motivational Interviewing<br>
   816 #62ZvFA_q0pCZFr0Y -   Active -         news<br>
   817 def list_group(tid):
   818         result      = []
   819         # list visible groups only
   820         task_status = get_status(tid)
   821         if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
   822                 # List task titles in group
   823                 group_path     = generate_group_path(tid)
   824 
   825                 # List groups to indicate when a task is a group
   826                 # Identify if task is a group
   827                 groups         = os.listdir(data_location_groups)
   828                 # End Identify if task is a group
   829 
   830                 # print items in tid in order
   831                 for n,fn in enumerate(sorted(os.listdir(group_path))):
   832                         # print position in list
   833                         current_postion = baseconvert_to_dec(fn[:ORDER_ID_LENGTH])
   834 
   835                         # Identify if task is a group
   836                         # Remove order_id, keep task id only
   837                         group = '     '
   838                         if fn[ORDER_ID_LENGTH:] in groups:
   839                                 group = 'GROUP'
   840                         if is_linked(fn[ORDER_ID_LENGTH:]):
   841                                 group = ' LINK'
   842                         # End Identify if task is a group
   843                         # Get task title
   844                         # Remove order_id, keep task id only
   845                         task_path = generate_task_path(fn[ORDER_ID_LENGTH:])
   846                         f         = open('%s/description.txt'%task_path)
   847                         # Remove order_id, keep task id only
   848                         if (not n) and (not tid == 'root'):
   849                                 # First task is group title
   850                                 # filter status, keep task if status filter is enabled
   851                                 task_status = get_status(fn[ORDER_ID_LENGTH:])
   852                                 if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
   853                                         task_d             = {}
   854                                         task_d['head']     = 'head group'
   855                                         task_d['tid']      = fn[ORDER_ID_LENGTH:]
   856                                         task_d['position'] = current_postion
   857                                         task_d['group']    = group
   858                                         task_d['title']    = f.readline().strip()
   859                                         task_d['status']   = get_status(task_d['tid'])
   860                                         task_d['ctime']    = get_creation_date(task_d['tid'])[1]
   861                                         result.append(task_d)
   862                         else:
   863                                 # filter status, keep task if status filter is enabled
   864                                 task_status = get_status(fn[ORDER_ID_LENGTH:])
   865                                 if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
   866                                         task_d             = {}
   867                                         task_d['head']     = 'element'
   868                                         task_d['tid']      = fn[ORDER_ID_LENGTH:]
   869                                         task_d['position'] = current_postion
   870                                         task_d['group']    = group
   871                                         task_d['title']    = f.readline().strip()
   872                                         task_d['status']   = get_status(task_d['tid'])
   873                                         task_d['ctime']    = get_creation_date(task_d['tid'])[1]
   874                                         result.append(task_d)
   875                         f.close()
   876                 task_d             = {}
   877                 task_d['head']     = 'empty line'
   878                 task_d['tid']      = ''
   879                 task_d['position'] = 0
   880                 task_d['group']    = ''
   881                 task_d['title']    = ''
   882                 task_d['status']   = ''
   883                 task_d['ctime']    = 0
   884                 result.append(task_d)
   885         return result
   886 
   887 ## lists all items in the tid group
   888 # @return list of tasks and groups title
   889 # @param[in] tid task id
   890 # @ingroup EDI_CORE
   891 def list_tree(tid):
   892         result     = []
   893         # walk_group is the list of groups to visit. FIFO
   894         walk_group = [tid]
   895         # the while loop goes through all the group that are found
   896         while walk_group:
   897                 # list visible groups only
   898                 task_status = get_status(walk_group[0])
   899                 if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
   900                         # list items in first group
   901                         tasks      = sorted(os.listdir(generate_group_path(walk_group[0])))
   902                         result    += list_group(walk_group[0])
   903 
   904                         # add group found in first group
   905                         for t in tasks:
   906                                 # Check tasks that are not title task in a group
   907                                 if (t[ORDER_ID_LENGTH:] != walk_group[0]) and is_this_task_a_group(t[ORDER_ID_LENGTH:]):
   908                                         walk_group.append(t[ORDER_ID_LENGTH:])
   909 
   910                 # remove first group to list items in next group
   911                 del walk_group[0]
   912         return result
   913 
   914 ## generates a random task id
   915 # @return new task id
   916 # @ingroup EDI_CORE
   917 def generate_id():
   918         return ''.join([random.choice(ID_BASE) for _ in range(ID_LENGTH)])
   919 
   920 ## converts decimal number to BASE (base 65)
   921 # @return string representing a number in base BASE
   922 # @param[in] n integer
   923 # @ingroup EDI_CORE
   924 def baseconvert(n):
   925         s = ""
   926         while 1:
   927                 r = n % BASE
   928                 s = ID_BASE[r] + s
   929                 n = n / BASE
   930                 if n == 0:
   931                         break
   932 
   933         s = s.rjust(ORDER_ID_LENGTH, ',')
   934         return s
   935 
   936 ## converts BASE number to decimal
   937 # @return n integer
   938 # @param[in] n string representing a number in base BASE
   939 # @ingroup EDI_CORE
   940 def baseconvert_to_dec(n):
   941         r     = 0
   942         power = 1
   943         for digit in reversed(list(n)):
   944                 r     += ID_BASE_STRING.find(digit) * power
   945                 power *= BASE
   946         return r
   947 
   948 ## add task to group folder in database groups
   949 # @param[in] tid task id
   950 # @param[in] group task id
   951 # @ingroup EDI_CORE
   952 # Tasks are added at the top or bottom of the list.
   953 def add_task_to_group_folder(tid,group):
   954         # Create an entry in group
   955         tasks              = sorted(os.listdir(generate_group_path(group)))
   956         # Add +1 to last order_id to have the task last in the list
   957         if tasks:
   958                 if (len(tasks) == 1) or (add_top_or_bottom == 'bottom'):
   959                         # add tasks in bottom
   960                         order_id = baseconvert(baseconvert_to_dec(tasks[-1][:ORDER_ID_LENGTH])+1)
   961                 else:
   962                         # add tasks on top
   963                         # temporary orderid #
   964                         orderid_and_tid = '#' * ORDER_ID_LENGTH + tid
   965                         if group == 'root':
   966                                 to_pos = 0
   967                         else:
   968                                 # add new tasks after group title
   969                                 to_pos = 1
   970 
   971                         tasks           = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:]
   972 
   973                         # Move tasks
   974                         path            = generate_group_path(group)
   975                         for n,t in enumerate(tasks):
   976                                 if n == to_pos:
   977                                         # set orderid on top
   978                                         order_id = baseconvert(n)
   979                                 else:
   980                                         os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
   981 
   982         else:
   983                 # start at 0 when group is empty
   984                 order_id = baseconvert(0)
   985         tid_in_groups_path = '%s/%s%s'%(generate_group_path(group),order_id,tid)
   986         # remove double slash that can come up
   987         tid_in_groups_path = tid_in_groups_path.replace('//','/')
   988         f                  = open(tid_in_groups_path,'w')
   989         f.close()
   990 
   991         # return tid_in_groups_path to easily add new tasks to group_directory_file_list and save time
   992         return tid_in_groups_path
   993 
   994 ## creates a task and opens vi
   995 # @param[in] group task id
   996 # @ingroup EDI_CORE
   997 def create_task(group):
   998         # Open text editor
   999         # create task in tasks folder
  1000 #:define create_task_part1
  1001         tid        =  generate_id()
  1002 
  1003         # Save text in tasks
  1004         task_path  = generate_task_path(tid)
  1005         os.mkdir(task_path)
  1006 #:end
  1007         os.system('%s %s/description.txt' % (editor,task_path))
  1008         if not os.path.isfile('%s/description.txt' % task_path):
  1009                 # file doesnt exist, abort task creation
  1010                 shutil.rmtree(task_path)
  1011                 return 'no new task, the description is empty'
  1012 
  1013         # Save creation time
  1014 #:define save_task_creation_time
  1015         f          = open('%s/ctime.txt'%task_path,'w')
  1016         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path)
  1017         task_mtime = time.ctime(mtime)
  1018         f.write(task_mtime)
  1019         f.close()
  1020 #:end
  1021 
  1022         # create status, active by default
  1023 #:define create_task_part2
  1024         # Create status
  1025         f          = open('%s/status'%task_path,'w')
  1026         f.write(TASK_STATUS[TASK_STATUS_ACTIVE])
  1027         f.close()
  1028 
  1029         tid_in_groups_path = add_task_to_group_folder(tid, group)
  1030 
  1031         global group_directory_file_list
  1032         if group_directory_file_list:
  1033                 group_directory_file_list.append(tid_in_groups_path)
  1034 
  1035         # autolink
  1036         global autolink
  1037         if autolink:
  1038                 for g in autolink:
  1039                         if is_this_task_a_group(g):
  1040                                 # link to groups existing in current database
  1041                                 add_task_reference_to_a_group(tid,g)
  1042 #:end
  1043         edi_log('created %s in group %s'%(tid,group))
  1044         return tid
  1045 
  1046 ## create task and copy text file
  1047 # @param[in] group task id
  1048 # @param[in] text_file filename of text file to copy
  1049 # @ingroup EDI_CORE
  1050 def add_task(group,text_file):
  1051 #:create_task_part1
  1052         tid        =  generate_id()
  1053 
  1054         # Save text in tasks
  1055         task_path  = generate_task_path(tid)
  1056         os.mkdir(task_path)
  1057         shutil.copy(text_file,'%s/description.txt'%task_path)
  1058 #:save_task_creation_time
  1059         f          = open('%s/ctime.txt'%task_path,'w')
  1060         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path)
  1061         task_mtime = time.ctime(mtime)
  1062         f.write(task_mtime)
  1063         f.close()
  1064 #:create_task_part2
  1065         # Create status
  1066         f          = open('%s/status'%task_path,'w')
  1067         f.write(TASK_STATUS[TASK_STATUS_ACTIVE])
  1068         f.close()
  1069 
  1070         tid_in_groups_path = add_task_to_group_folder(tid, group)
  1071 
  1072         global group_directory_file_list
  1073         if group_directory_file_list:
  1074                 group_directory_file_list.append(tid_in_groups_path)
  1075 
  1076         # autolink
  1077         global autolink
  1078         if autolink:
  1079                 for g in autolink:
  1080                         if is_this_task_a_group(g):
  1081                                 # link to groups existing in current database
  1082                                 add_task_reference_to_a_group(tid,g)
  1083         edi_log('created %s in group %s'%(tid,group))
  1084         return tid
  1085 
  1086 ## create task with description text
  1087 # @param[in] group task id
  1088 # @param[in] text string
  1089 # @ingroup EDI_CORE
  1090 def add_text(group,text):
  1091 #:create_task_part1
  1092         tid        =  generate_id()
  1093 
  1094         # Save text in tasks
  1095         task_path  = generate_task_path(tid)
  1096         os.mkdir(task_path)
  1097         f = open('%s/description.txt'%task_path,'w')
  1098         f.write(text)
  1099         f.close()
  1100 #:save_task_creation_time
  1101         f          = open('%s/ctime.txt'%task_path,'w')
  1102         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path)
  1103         task_mtime = time.ctime(mtime)
  1104         f.write(task_mtime)
  1105         f.close()
  1106 #:create_task_part2
  1107         # Create status
  1108         f          = open('%s/status'%task_path,'w')
  1109         f.write(TASK_STATUS[TASK_STATUS_ACTIVE])
  1110         f.close()
  1111 
  1112         tid_in_groups_path = add_task_to_group_folder(tid, group)
  1113 
  1114         global group_directory_file_list
  1115         if group_directory_file_list:
  1116                 group_directory_file_list.append(tid_in_groups_path)
  1117 
  1118         # autolink
  1119         global autolink
  1120         if autolink:
  1121                 for g in autolink:
  1122                         if is_this_task_a_group(g):
  1123                                 # link to groups existing in current database
  1124                                 add_task_reference_to_a_group(tid,g)
  1125         edi_log('created %s in group %s'%(tid,group))
  1126         return tid
  1127 
  1128 ## create task and copy text file, filename is task title
  1129 # @param[in] group task id
  1130 # @param[in] text_file filename of text file to copy
  1131 # @ingroup EDI_CORE
  1132 def add_task_and_filename(group,text_file):
  1133         tid = add_task(group,text_file)
  1134 
  1135         # Add file name on first line
  1136         f   = open('%s/description.txt'%generate_task_path(tid))
  1137         des = f.readlines()
  1138         f.close()
  1139         f   = open('%s/description.txt'%generate_task_path(tid),'w')
  1140         # save text filename only
  1141         f.write('%s\n'%text_file.split('/')[-1])
  1142         for l in des:
  1143                 f.write(l)
  1144         f.close()
  1145 
  1146         return tid
  1147 
  1148 ## create many tasks from a text file
  1149 # @param[in] group task id
  1150 # @param[in] text_file filename of text file to copy
  1151 # @ingroup EDI_CORE
  1152 def add_many_tasks(group,text_file):
  1153         r               = ''
  1154 
  1155         # Copy a task to text string
  1156         # Remove '#' from first line, first character position
  1157         # Add task to database
  1158         f               = open(text_file)
  1159         status          = 'start'
  1160         text            = ''
  1161         number_of_tasks = 0
  1162         for l in f.readlines():
  1163                 if l.rstrip() == '---':
  1164                         if status != 'start':
  1165                                 # add task to database
  1166                                 add_text(group,text)
  1167                                 number_of_tasks += 1
  1168                         text   = ''
  1169                         status = 'start'
  1170                 else:
  1171                         if (status == 'start') and (l[0] == '#'):
  1172                                 l    = l[1:]
  1173                         text   += l
  1174                         status = 'writing task'
  1175         f.close()
  1176         if status == 'writing task':
  1177                 # add task to database
  1178                 add_text(group,text)
  1179                 number_of_tasks += 1
  1180 
  1181         edi_log('created %s tasks in group %s'%(number_of_tasks,group))
  1182         r               = 'created %s tasks in group %s'%(number_of_tasks,group)
  1183         return r
  1184 
  1185 ## create many one line tasks from a text file
  1186 # @param[in] group task id
  1187 # @param[in] text_file filename of text file to copy
  1188 # @ingroup EDI_CORE
  1189 def add_many_one_line_tasks(group,text_file):
  1190         r               = []
  1191 
  1192         f               = open(text_file)
  1193         number_of_tasks = 0
  1194         for l in f.readlines():
  1195                 add_text(group,l.strip())
  1196                 number_of_tasks += 1
  1197         f.close()
  1198 
  1199         edi_log('created %s tasks in group %s'%(number_of_tasks,group))
  1200         r               = 'created %s tasks in group %s'%(number_of_tasks,group)
  1201         return r
  1202 
  1203 ## create group from text file
  1204 # @param[in] group task id
  1205 # @param[in] text_file with group structure
  1206 # @ingroup EDI_CORE
  1207 def add_many_groups_from_text(group,text_file):
  1208         r               = []
  1209 
  1210         f               = open(text_file)
  1211         number_of_tasks = 0
  1212         group_stack     = [group]
  1213         for l in f.readlines():
  1214                 # count space indents
  1215                 l_l = l.split(' ')
  1216                 indent = 0
  1217                 for s in l_l:
  1218                         if s:
  1219                                 break
  1220                         indent += 1
  1221                 if indent < len(group_stack)-1:
  1222                         # remove groups from stack if same level or higher levels
  1223                         del group_stack[-(len(group_stack)-1 - indent):]
  1224                 l   = l.strip()
  1225                 tid = add_text(group_stack[-1],l)
  1226                 create_group(tid)
  1227                 group_stack.append(tid)
  1228                 number_of_tasks += 1
  1229         f.close()
  1230 
  1231         edi_log('created %s groups in group %s'%(number_of_tasks,group))
  1232         r               = 'created %s groups in group %s'%(number_of_tasks,group)
  1233         return r
  1234 
  1235 ## copy description to path using first line of description as filename
  1236 # @return path and filname
  1237 # @param[in] tid task id
  1238 # @param[in] path destination directory for task description
  1239 # @ingroup EDI_CORE
  1240 def export_task_to_a_file(tid,path):
  1241         f   = open('%s/description.txt'%generate_task_path(tid))
  1242         fn  = f.readline().strip()
  1243         des = f.readlines()
  1244         f.close()
  1245         f   = open('%s/%s'%(path,fn),'w')
  1246         for l in des:
  1247                 f.write(l)
  1248         f.close()
  1249         r = '%s/%s'%(path,fn)
  1250         # remove eventual double //
  1251         return r.replace(os.sep*2,os.sep)
  1252 
  1253 ## print description of task tid
  1254 # @return list of strings
  1255 # @param[in] tid task id
  1256 # @ingroup EDI_CORE
  1257 def display_task(tid):
  1258         task_path = generate_task_path(tid)
  1259         f           = open('%s/description.txt'%task_path)
  1260         description = f.readlines()
  1261         f.close()
  1262 
  1263         # print tid, status and first line
  1264         description[0] = generate_task_string_with_tid(tid,description[0])
  1265         return description
  1266 
  1267 ## find group containing task tid in groups folder
  1268 # @return tid
  1269 # @param[in] tid task id
  1270 # @ingroup EDI_CORE
  1271 def find_group_containing_task(tid):
  1272         if tid == 'root':
  1273                 # root has not parent group, return root
  1274                 return tid
  1275 #:define walk_groups
  1276         global data_location_groups
  1277         # reuse previously created group list, to save time
  1278         global group_directory_file_list
  1279         if not group_directory_file_list:
  1280                 group_directory_file_list = ffind(data_location_groups)
  1281         groups_and_tasks = group_directory_file_list
  1282         for t in groups_and_tasks:
  1283                 if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]):
  1284                         group = t.split('/')[-2]
  1285 #:end
  1286         try:
  1287                 a = group
  1288         except:
  1289                 print '\n\nEDI_ERROR: Database inconsistent - run: find %s|grep %s and remove reference.\n'%(data_location, tid)
  1290                 group = 'error'
  1291         return group
  1292 
  1293 ## find group containing task tid in groups folder and print
  1294 # @return list of strings
  1295 # @param[in] tid task id
  1296 # @ingroup EDI_CORE
  1297 # Shows path in tree and title for each group in path
  1298 def show_group_for_task(tid):
  1299         global data_location_tree
  1300         r                 = []
  1301 
  1302         # set group string to be displayed on first line
  1303         group_s           = '     '
  1304         if is_this_task_a_group(tid):
  1305                 group_s = 'GROUP'
  1306         if is_linked(tid):
  1307                 group_s = ' LINK'
  1308 
  1309 #:walk_groups
  1310         global data_location_groups
  1311         # reuse previously created group list, to save time
  1312         global group_directory_file_list
  1313         if not group_directory_file_list:
  1314                 group_directory_file_list = ffind(data_location_groups)
  1315         groups_and_tasks = group_directory_file_list
  1316         for t in groups_and_tasks:
  1317                 if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]):
  1318                         group = t.split('/')[-2]
  1319                         tree_path         = find_group_in_tree(group)
  1320                         # p is data_location folder/tree
  1321                         p_l               = data_location_tree.split('/')[-2:]
  1322                         p                 = '/'.join(p_l)
  1323                         # if empty then command is run in tree root
  1324                         if tree_path.split(p)[-1]:
  1325                                 # print path of tids: tid/tid...
  1326                                 r.append(tree_path.split(p)[-1][1:])
  1327                                 group_titles_in_path = []
  1328                                 for g in tree_path.split(p)[-1][1:].split('/'):
  1329                                         group_titles_in_path.append(get_task_title(g))
  1330                                 # print title/title...
  1331                                 r.append('/'.join(group_titles_in_path))
  1332                         r.append(generate_group_string_with_tid(group,get_task_title(group)))
  1333                         r.append('')
  1334 
  1335         # Print media and attachments
  1336         # media type
  1337         media             = get_media(tid)[0]
  1338         # attachment list
  1339         attachments       = get_attachments(tid)
  1340 
  1341         # Print task, colors, group list
  1342         color             = get_forground_color(tid)
  1343         fc                = '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3])
  1344         color             = get_background_color(tid)
  1345         bc                = '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3])
  1346         # Figure out creation time and modification times for description and status
  1347         task_ctime        = get_creation_date(tid)[0]
  1348 
  1349         task_path         = generate_task_path(tid)
  1350         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
  1351         description_mtime = time.ctime(mtime)
  1352         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)
  1353         status_mtime      = time.ctime(mtime)
  1354         r                 = ['Task:\n%s\n\n'%generate_task_string_with_tid( tid,'%s %s'%(group_s, get_task_title(tid)) )] + ['Media type: %s'%media] + ['\nAttachments:'] + attachments + ['\n\nforeground color:        %s\nbackground color:        %s\nCreation time:           %s\nLast description change: %s\nLast status change:      %s\n\nGroup list:\n' % (fc, bc, task_ctime, description_mtime, status_mtime)] + r
  1355         return r
  1356 
  1357 ## find group in tree folder
  1358 # @return path_in_tree
  1359 # @param[in] tid task id
  1360 # @ingroup EDI_CORE
  1361 def find_group_in_tree(group):
  1362         if group == 'root':
  1363                 path_in_tree = data_location_tree
  1364         else:
  1365                 path_in_tree = ''
  1366                 # list all group paths in tree
  1367                 f            = os.popen('find %s'%data_location_tree)
  1368                 groups       = [i.strip() for i in f.readlines()]
  1369                 f.close()
  1370                 # remove data_location_tree from paths
  1371                 del groups[0]
  1372                 # find the group in group paths
  1373                 for g in groups:
  1374                         if g.split('/')[-1] == group:
  1375                                 path_in_tree = g
  1376         return path_in_tree
  1377 
  1378 ## Determines if a task has multiple references
  1379 # @return integer 0 or 1
  1380 # @param[in] tid task id
  1381 # @ingroup EDI_CORE
  1382 def is_linked(tid):
  1383         status = 0
  1384         task_linked_groups_path = '%s/groups/' % generate_task_path(tid)
  1385         # check if tid/groups exists
  1386         if os.path.exists(task_linked_groups_path):
  1387                 groups = os.listdir(task_linked_groups_path)
  1388                 # check if task is linked to more than 1 group
  1389                 if len(groups) > 1:
  1390                         status = 1
  1391         return status
  1392 
  1393 ## convert task to group
  1394 # @return list of stings when there is an error
  1395 # @param[in] tid task id
  1396 # @ingroup EDI_CORE
  1397 def create_group(tid):
  1398         r                         = []
  1399         # convert task to group only when task is not linked
  1400         if is_linked(tid):
  1401                 r.append('Converting linked task to group removes links. The task groups are:')
  1402                 r += show_group_for_task(tid)
  1403 
  1404                 # delete tid/groups because groups are not linked
  1405                 task_linked_groups_path = '%s/groups/' % generate_task_path(tid)
  1406                 groups                  = os.listdir(task_linked_groups_path)
  1407                 # remove all links except for the first group
  1408                 for g in groups[1:]:
  1409                         delete_linked_task(g,tid)
  1410                 if os.path.exists(task_linked_groups_path):
  1411                         shutil.rmtree(task_linked_groups_path)
  1412                 r.append('Created group %s in %s'%(tid,groups[0]))
  1413         # create new group in groups folder
  1414         os.mkdir('%s/%s'%(data_location_groups,tid))
  1415         # First task in group is group title task
  1416         order_id                  = baseconvert(0)
  1417         f                         = open('%s/%s%s'%(generate_group_path(tid),order_id,tid),'w')
  1418         f.close()
  1419 
  1420         # Add group in tree
  1421         # update group list
  1422         global group_directory_file_list
  1423         group_directory_file_list = []
  1424         group                     = find_group_containing_task(tid)
  1425         if group == 'root':
  1426                 os.mkdir('%s/%s'%(data_location_tree,tid))
  1427         else:
  1428                 os.mkdir('%s/%s'%(find_group_in_tree(group),tid))
  1429 
  1430         # Change status active to void by default
  1431         # To avoid filtering groups
  1432         if get_status(tid) == TASK_STATUS[TASK_STATUS_ACTIVE]:
  1433                 # set status to void
  1434                 set_status(tid,TASK_STATUS_VOID)
  1435         edi_log('created group %s'%tid)
  1436         return r
  1437 
  1438 ## convert group to task
  1439 # @param[in] group group to convert to task
  1440 # @ingroup EDI_CORE
  1441 def convert_group_to_task(group):
  1442         r = []
  1443 
  1444         # list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder
  1445         groups_and_tasks = ffind(data_location_groups)
  1446 
  1447         convert_group_status = 'delete'
  1448         for t2 in groups_and_tasks:
  1449                 # search a task in group that is not tid
  1450                 if ('%s/'%group in t2) and (group not in t2.split('/')[-1]):
  1451                         convert_group_status = 'There is another task in the group, keep group.'
  1452                         r.append(convert_group_status)
  1453         if (convert_group_status == 'delete') and (not 'root' in group):
  1454                 # Delete group title and group folder
  1455                 os.remove('%s/%s%s'%(generate_group_path(group),baseconvert(0),group))
  1456                 os.rmdir(generate_group_path(group))
  1457                 # Delete group in tree
  1458                 if find_group_in_tree(group):
  1459                         shutil.rmtree(find_group_in_tree(group))
  1460                 # change state from void to active
  1461                 set_status(group,TASK_STATUS_ACTIVE)
  1462                 edi_log('converted group %s to task'%group)
  1463         return r
  1464 
  1465 ## delete task tid in all groups
  1466 # @return group id
  1467 # @param[in] tid task id
  1468 # @ingroup EDI_CORE
  1469 def delete_task(tid):
  1470         # save original tid parameter for log
  1471         tid_param        = tid
  1472         # Delete task in tasks
  1473         shutil.rmtree(generate_task_path(tid))
  1474 
  1475         # Delete task reference in groups
  1476         # list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder
  1477         groups_and_tasks = ffind(data_location_groups)
  1478 
  1479         # Identify if task is a group
  1480         is_a_group       = is_this_task_a_group(tid)
  1481 
  1482         # Return group task, it changes when first task is deleted
  1483         group_id         = ''
  1484         # list groups to reorder at the end
  1485         reorder_groups   = []
  1486         for t in groups_and_tasks:
  1487                 # Delete reference in upper group, not the title task of the group
  1488                 if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]):
  1489                         # group is the group for task tid
  1490                         group    = t.split('/')[-2]
  1491                         group_id = group
  1492                         # Delete task reference in group
  1493                         os.remove(t)
  1494                         reorder_groups.append(group_id)
  1495                         if is_a_group:
  1496                                 # First task becomes a group, add a reference in group group
  1497                                 # list tasks in order in tid group
  1498                                 group_tasks = sorted(os.listdir(generate_group_path(tid)))
  1499                                 # Delete emtpy group or first task in group becomes the group title.
  1500                                 if len(group_tasks) == 1:
  1501                                         shutil.rmtree(generate_group_path(tid))
  1502                                         # Delete group in tree
  1503                                         # when a group is deleted, subgroups are automatically deleted in the tree
  1504                                         if find_group_in_tree(tid):
  1505                                                 shutil.rmtree(find_group_in_tree(tid))
  1506                                 else:
  1507                                         # Remove order_id, keep task id only
  1508                                         first_task  = group_tasks[1][ORDER_ID_LENGTH:]
  1509                                         group_id    = first_task
  1510                                         # Create an entry in group at the same position as tid had
  1511                                         order_id    = t.split('/')[-1][:ORDER_ID_LENGTH]
  1512                                         f           = open('%s/%s%s'%(generate_group_path(group),order_id,first_task),'w')
  1513                                         f.close()
  1514 
  1515                                         # Delete group task of group of more then 2 tasks, first task becomes a group
  1516                                         # delete orderidtaskid
  1517                                         os.remove('%s/%s'%(generate_group_path(tid),group_tasks[0]))
  1518                                         os.rename('%s/%s'%(generate_group_path(tid),group_tasks[1]),'%s/%s%s'%(generate_group_path(tid),baseconvert(0),first_task))
  1519                                         # reorder tasks to remove gap between group title task and first task
  1520                                         path        = generate_group_path(tid)
  1521                                         tasks       = sorted(os.listdir(path))
  1522                                         for n,t in enumerate(tasks):
  1523                                                 os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
  1524                                         # rename group in groups folder
  1525                                         os.rename(generate_group_path(tid),generate_group_path(first_task))
  1526                                         # rename group in tree folder
  1527                                         group_tree_path       = find_group_in_tree(tid)
  1528                                         group_tree_path_l     = group_tree_path.split('/')
  1529                                         group_tree_path_l[-1] = first_task
  1530                                         new_group_tree_path   = '/'.join(group_tree_path_l)
  1531                                         os.rename(group_tree_path,new_group_tree_path)
  1532 
  1533         # reorder tasks to remove gaps in reorder_groups
  1534         for tid in reorder_groups:
  1535                 path        = generate_group_path(tid)
  1536                 tasks       = sorted(os.listdir(path))
  1537                 for n,t in enumerate(tasks):
  1538                         os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
  1539 
  1540         # Return group task
  1541         edi_log('deleted %s in %s'%(tid_param,' '.join(reorder_groups)))
  1542         return group_id
  1543 
  1544 ## Delete task only if it is linked in one group
  1545 # @return group id
  1546 # @param[in] group task id
  1547 # @param[in] tid task id
  1548 # @ingroup EDI_CORE
  1549 def delete_linked_task(group,tid):
  1550         group_id = group
  1551         if not is_linked(tid):
  1552                 group_id = delete_task(tid)
  1553         else:
  1554                 # Delete task reference in group
  1555                 # find task in group: ORDER_IDTASK_ID
  1556                 tasks = os.listdir(generate_group_path(group))
  1557                 for t in tasks:
  1558                         if t[ORDER_ID_LENGTH:] == tid:
  1559                                 os.remove('%s%s' % (generate_group_path(group), t))
  1560 
  1561                 # delete group in tid/groups
  1562                 os.remove('%s/groups/%s' % (generate_task_path(tid), group))
  1563 
  1564                 # reorder tasks to remove gaps in group
  1565                 path  = generate_group_path(group)
  1566                 tasks = os.listdir(path)
  1567                 for n,t in enumerate(tasks):
  1568                         os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
  1569 
  1570                 edi_log('deleted %s in group %s'%(tid,group))
  1571 
  1572         return group_id
  1573 
  1574 ## delete group tid
  1575 # @return group id
  1576 # @param[in] tid task id
  1577 # @ingroup EDI_CORE
  1578 def delete_group(tid):
  1579         # Delete tasks in group
  1580         group_tasks = sorted(os.listdir(generate_group_path(tid)))
  1581         if (len(group_tasks) == 0) and (tid == 'root'):
  1582                 # the database is already empty
  1583                 return tid
  1584         if tid != 'root':
  1585                 # root group doesnt have a title
  1586                 # Remove group title from the loop to delete tasks only and then group
  1587                 del group_tasks[0]
  1588 
  1589         # Delete tasks and groups recursively
  1590         for t in group_tasks:
  1591                 # Remove order_id, keep task id only
  1592                 oid = t[ORDER_ID_LENGTH:]
  1593                 if not is_this_task_a_group(oid):
  1594                         # delete tasks that are linked only once
  1595                         delete_linked_task(tid, oid)
  1596                 else:
  1597                         delete_group(oid)
  1598 
  1599         if tid != 'root':
  1600                 # never delete root group
  1601                 return delete_task(tid)
  1602         else:
  1603                 # return root and keep root task
  1604                 return tid
  1605 
  1606 ## edit task with vi
  1607 # @param[in] tid task id
  1608 # @ingroup EDI_CORE
  1609 def edit_task(tid):
  1610         os.system('%s %s/description.txt' % (editor, generate_task_path(tid)))
  1611         edi_log('edited %s'%tid)
  1612 
  1613 ## move task from group at at_pos to to_pos and reorder
  1614 # @return list of stings when there is an error
  1615 # @param[in] group task id
  1616 # @param[in] at_pos selected position
  1617 # @param[in] to_pos insert position
  1618 # @ingroup EDI_CORE
  1619 def change_task_order(group,at_pos,to_pos):
  1620         # save original group parameter for log
  1621         group_param     = group
  1622         # List tasks in group
  1623         r               = []
  1624         path            = generate_group_path(group)
  1625         tasks           = sorted(os.listdir(path))
  1626 
  1627         # Verify position
  1628         # Get task
  1629         try:
  1630                 orderid_and_tid = tasks[at_pos]
  1631         except:
  1632                 r.append('%d is an invalid position.'%at_pos)
  1633                 return r
  1634         if to_pos == 0:
  1635                 tid = orderid_and_tid[ORDER_ID_LENGTH:]
  1636                 # do not move linked tasks to position 0
  1637                 if is_linked(tid):
  1638                         r.append('Converting linked task to group removes links. The task groups are:')
  1639                         r += show_group_for_task(tid)
  1640 
  1641                         # delete tid/groups because groups are not linked
  1642                         task_linked_groups_path = '%s/groups/' % generate_task_path(tid)
  1643                         groups                  = os.listdir(task_linked_groups_path)
  1644                         # remove all links except for the first group
  1645                         for g in groups:
  1646                                 if not g == group:
  1647                                         delete_linked_task(g,tid)
  1648                         if os.path.exists(task_linked_groups_path):
  1649                                 shutil.rmtree(task_linked_groups_path)
  1650                         parent_group             = find_group_containing_task(group)
  1651                         r.append('Created group %s in %s'%(tid,parent_group))
  1652                 # do not move groups to position 0
  1653                 if is_this_task_a_group(tid):
  1654                         r.append('Having a group in group title is not supported.')
  1655                         return r
  1656         # Insert task at to_pos
  1657         if to_pos > at_pos:
  1658                 # +1 because at_pos reference will be deleted and will shift to_pos reference
  1659                 to_pos += 1
  1660                 tasks   = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:]
  1661                 # Delete task at at_pos
  1662                 del tasks[at_pos]
  1663         else:
  1664                 # rename group title, when to_pos in 0
  1665                 if (to_pos == 0) and (not group == 'root'):
  1666                         to_pos_tid = tasks[to_pos][ORDER_ID_LENGTH:]
  1667 
  1668                 tasks   = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:]
  1669                 # Delete task at at_pos+1 because the new position is before at_pos
  1670                 # at_pos was shifted when to_pos reference was added above
  1671                 del tasks[at_pos+1]
  1672 
  1673 #:define reorder_tasks
  1674         # Move tasks
  1675         for n,t in enumerate(tasks):
  1676                 os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
  1677 #:end
  1678 
  1679         # rename group title, when to_pos in 0
  1680         if (to_pos == 0) and (not group == 'root'):
  1681                 # rename group in parent group
  1682                 # group is tid for parent group and to_pos_tid is equal to group input parameter because to_pos is 0
  1683                 group = find_group_containing_task(group)
  1684                 # to_pos_orderid_and_tid is group in parent group
  1685                 parent_group_tasks = os.listdir(generate_group_path(group))
  1686                 for t in parent_group_tasks:
  1687                         if t[ORDER_ID_LENGTH:] == to_pos_tid:
  1688                                 to_pos_orderid_and_tid = t
  1689                 # remove order_id, keep task id only for new group
  1690                 group_id    = orderid_and_tid[ORDER_ID_LENGTH:]
  1691                 # create an entry in parent group at the same position as to_pos tid had
  1692                 order_id    = to_pos_orderid_and_tid[:ORDER_ID_LENGTH]
  1693                 f           = open('%s/%s%s'%(generate_group_path(group),order_id,group_id),'w')
  1694                 f.close()
  1695 
  1696                 # delete group task of group
  1697                 # delete orderidtaskid
  1698                 os.remove('%s/%s'%(generate_group_path(group),to_pos_orderid_and_tid))
  1699 
  1700                 # rename group in groups folder
  1701                 os.rename(generate_group_path(to_pos_tid),generate_group_path(group_id))
  1702                 # rename group in tree folder
  1703                 group_tree_path       = find_group_in_tree(to_pos_tid)
  1704                 group_tree_path_l     = group_tree_path.split('/')
  1705                 group_tree_path_l[-1] = group_id
  1706                 new_group_tree_path   = '/'.join(group_tree_path_l)
  1707                 os.rename(group_tree_path,new_group_tree_path)
  1708 
  1709                 # rename group in linked task and former linked task with a 'groups' folder in tasks database folder
  1710                 group_tasks = os.listdir(generate_group_path(group_id))
  1711                 for group_task in group_tasks:
  1712                         if os.path.exists('%s/groups' % generate_task_path(group_task[ORDER_ID_LENGTH:])):
  1713                                 # search for to_pos_tid (group input parameter) in task path groups folder
  1714                                 link_groups = os.listdir('%s/groups' % generate_task_path(group_task[ORDER_ID_LENGTH:]))
  1715                                 for lg in link_groups:
  1716                                         if to_pos_tid == lg:
  1717                                                 # rename group to new group id since the title changed
  1718                                                 os.rename('%s/groups/%s' % (generate_task_path(group_task[ORDER_ID_LENGTH:]),to_pos_tid), '%s/groups/%s' % (generate_task_path(group_task[ORDER_ID_LENGTH:]),group_id))
  1719 
  1720         edi_log('changed task order %s to %s in group %s'%(at_pos, to_pos, group_param))
  1721         return r
  1722 
  1723 
  1724 ## move task to group
  1725 # @return tid
  1726 # @param[in] tgroup task id, select a task in this group
  1727 # @param[in] tid task id
  1728 # @param[in] group task id, destination group
  1729 # @ingroup EDI_CORE
  1730 def move_task_to_a_group(tgroup,tid,group):
  1731 
  1732         # find task tid in tgroup and remove task
  1733         path            = generate_group_path(tgroup)
  1734         tasks             = sorted(os.listdir(path))
  1735         task_in_group   = ''
  1736         for n,t in enumerate(tasks):
  1737                 # remove task from tasks to reorder tgroup
  1738                 if t[ORDER_ID_LENGTH:] == tid:
  1739                         task_in_group = t
  1740                         del tasks[n]
  1741                         break
  1742         if not task_in_group:
  1743                 return '%s not found in %s'%(tid,tgroup)
  1744 
  1745         # Move group in tree
  1746         if is_this_task_a_group(tid):
  1747                 if find_group_in_tree(tid)[:-ID_LENGTH-1] == find_group_in_tree(group):
  1748                         # prevent moving a group on itself, in tree and moving group title position to parent group
  1749                         return tid
  1750                 shutil.move(find_group_in_tree(tid),find_group_in_tree(group))
  1751 
  1752         # Remove task in source group
  1753         os.remove('%s/%s'%(path,task_in_group))
  1754 
  1755 #:reorder_tasks
  1756         # Move tasks
  1757         for n,t in enumerate(tasks):
  1758                 os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
  1759         # check that tid is not already linked in destination group
  1760         if is_linked(tid):
  1761                 link_groups = os.listdir('%s/groups/' % generate_task_path(tid))
  1762                 if group in link_groups:
  1763                         # removed task reference from tgroup. Remove tgroup reference in task. There is already a tid reference in group, nothing more to do
  1764                         os.remove('%s/groups/%s' % (generate_task_path(tid), tgroup))
  1765                         return tid
  1766 
  1767         # Create reference in destination group
  1768         add_task_to_group_folder(tid, group)
  1769 
  1770         # linked tasks, remove source group and add destination group
  1771         if is_linked(tid):
  1772                 # remove source tgroup from tid/groups
  1773                 os.remove('%s/groups/%s' % (generate_task_path(tid), tgroup))
  1774                 f         = open('%s/groups/%s'%(generate_task_path(tid), group),'w')
  1775                 f.close()
  1776 
  1777         edi_log('moved %s in group %s to group %s'%(tid,tgroup,group))
  1778         return tid
  1779 
  1780 ## copy task to group with new tid
  1781 # @return new tid
  1782 # @param[in] tid task id
  1783 # @param[in] group task id, destination group
  1784 # @ingroup EDI_CORE
  1785 def copy_task_to_a_group(tid,group):
  1786         # Generate new tid
  1787         newtid = generate_id()
  1788         # Copy task in tasks
  1789         shutil.copytree(generate_task_path(tid),generate_task_path(newtid))
  1790         # delete tid/groups because new task is not linked
  1791         task_linked_groups_path = '%s/groups/' % generate_task_path(newtid)
  1792         if os.path.exists(task_linked_groups_path):
  1793                 shutil.rmtree(task_linked_groups_path)
  1794         # Copy group in groups and in tree
  1795         if is_this_task_a_group(tid):
  1796                 # create new group
  1797                 shutil.copytree(generate_group_path(tid),generate_group_path(newtid))
  1798                 # Change group title task to newtid
  1799                 shutil.move('%s/%s%s'%(generate_group_path(newtid),baseconvert(0),tid),'%s/%s%s'%(generate_group_path(newtid),baseconvert(0),newtid))
  1800 
  1801                 # delete tasks to be recreated with new tid
  1802                 tasks = sorted(os.listdir(generate_group_path(newtid)))
  1803                 del tasks[0]
  1804                 for t in tasks:
  1805                         os.remove('%s/%s'%(generate_group_path(newtid),t))
  1806 
  1807                 # Add group in tree
  1808                 if group == 'root':
  1809                         os.mkdir('%s/%s'%(data_location_tree,newtid))
  1810                 else:
  1811                         os.mkdir('%s/%s'%(find_group_in_tree(group),newtid))
  1812 
  1813 
  1814         # Add reference in group
  1815         tmp_tid = tid
  1816         tid     = newtid
  1817         # Create reference in destination group
  1818         add_task_to_group_folder(tid, group)
  1819         tid     = tmp_tid
  1820 
  1821         if is_this_task_a_group(tid):
  1822                 # walk in group
  1823                 # list items in group
  1824                 tasks      = sorted(os.listdir(generate_group_path(tid)))
  1825 
  1826                 # add group found in first group
  1827                 for t in tasks:
  1828                         # Check tasks that are not title task in a group
  1829                         if t[ORDER_ID_LENGTH:] != tid:
  1830                                 # copy_task_to_a_group recursively
  1831                                 copy_task_to_a_group(t[ORDER_ID_LENGTH:],newtid)
  1832         edi_log('copied %s to group %s, created %s'%(tid,group,newtid))
  1833         return newtid
  1834 
  1835 ## create task path in database using database name
  1836 # @return path to task in tasks
  1837 # @param[in] tid task id
  1838 # @param[in] location database name in data section of easydoneit.ini
  1839 # @ingroup EDI_CORE
  1840 def generate_task_path_in_database(tid,location):
  1841         global selected
  1842         global selected_path
  1843 
  1844         z              = dict(zip(selected, selected_path))
  1845         location_tasks = '%s/tasks'%z[location]
  1846         return '%s/%s'%(location_tasks,tid)
  1847 
  1848 ## create group path in database using database name
  1849 # @return path to group in groups
  1850 # @param[in] tid task id
  1851 # @param[in] location database name in data section of easydoneit.ini
  1852 # @ingroup EDI_CORE
  1853 def generate_group_path_in_database(tid,location):
  1854         global selected
  1855         global selected_path
  1856 
  1857         z              = dict(zip(selected, selected_path))
  1858         location_groups = '%s/groups'%z[location]
  1859         return '%s/%s/'%(location_groups,tid)
  1860 
  1861 ## find group in tree folder in database
  1862 # @return path to group in tree
  1863 # @param[in] group task id
  1864 # @param[in] location database name in data section of easydoneit.ini
  1865 # @ingroup EDI_CORE
  1866 def find_group_in_tree_in_database(group,location):
  1867         global selected
  1868         global selected_path
  1869 
  1870         z              = dict(zip(selected, selected_path))
  1871         location_tree  = '%s/tree'%z[location]
  1872 
  1873         if group == 'root':
  1874                 path_in_tree = location_tree
  1875         else:
  1876                 path_in_tree = ''
  1877                 # list all group paths in tree
  1878                 f            = os.popen('find %s'%location_tree)
  1879                 groups       = [i.strip() for i in f.readlines()]
  1880                 f.close()
  1881                 # remove data_location_tree from paths
  1882                 del groups[0]
  1883                 # find the group in group paths
  1884                 for g in groups:
  1885                         if g.split('/')[-1] == group:
  1886                                 path_in_tree = g
  1887         return path_in_tree
  1888 
  1889 ## add task with new tid in selected database
  1890 # @param[in] tid task id
  1891 # @param[in] group task id
  1892 # @param[in] location database name in data section of easydoneit.ini
  1893 # @ingroup EDI_CORE
  1894 def add_task_to_group_folder_in_database(tid,group,location):
  1895         # Create an entry in group
  1896         tasks     = sorted(os.listdir(generate_group_path_in_database(group,location)))
  1897         # Add +1 to last order_id to have the task last in the list
  1898         if tasks:
  1899                 order_id = baseconvert(baseconvert_to_dec(tasks[-1][:ORDER_ID_LENGTH])+1)
  1900         else:
  1901                 # start at 0 when group is empty
  1902                 order_id = baseconvert(0)
  1903         f         = open('%s/%s%s'%(generate_group_path_in_database(group,location),order_id,tid),'w')
  1904         f.close()
  1905 
  1906 ## copy task to group in selected database with new tid
  1907 # @return new tid
  1908 # @param[in] tid task id
  1909 # @param[in] group task id
  1910 # @param[in] location database name in data section of easydoneit.ini
  1911 # @ingroup EDI_CORE
  1912 def copy_task_to_database(tid,location,group):
  1913         # Generate new tid
  1914         newtid = generate_id()
  1915         # Copy task in tasks
  1916         shutil.copytree(generate_task_path(tid),generate_task_path_in_database(newtid,location))
  1917         # delete tid/groups because new task is not linked
  1918         task_linked_groups_path = '%s/groups/' % generate_task_path_in_database(newtid,location)
  1919         if os.path.exists(task_linked_groups_path):
  1920                 shutil.rmtree(task_linked_groups_path)
  1921         # Copy group in groups and in tree
  1922         if is_this_task_a_group(tid):
  1923                 # create new group
  1924                 shutil.copytree(generate_group_path(tid),generate_group_path_in_database(newtid,location))
  1925                 # Change group title task to newtid
  1926                 shutil.move('%s/%s%s'%(generate_group_path_in_database(newtid,location),baseconvert(0),tid),'%s/%s%s'%(generate_group_path_in_database(newtid,location),baseconvert(0),newtid))
  1927 
  1928                 # delete tasks to be recreated with new tid
  1929                 tasks = sorted(os.listdir(generate_group_path_in_database(newtid,location)))
  1930                 del tasks[0]
  1931                 for t in tasks:
  1932                         os.remove('%s/%s'%(generate_group_path_in_database(newtid,location),t))
  1933 
  1934                 # Add group in tree
  1935                 global selected
  1936                 global selected_path
  1937 
  1938                 z              = dict(zip(selected, selected_path))
  1939                 location_tree  = '%s/tree'%z[location]
  1940                 if group == 'root':
  1941                         os.mkdir('%s/%s'%(location_tree,newtid))
  1942                 else:
  1943                         os.mkdir('%s/%s'%(find_group_in_tree_in_database(group,location),newtid))
  1944 
  1945 
  1946         # Add reference in group
  1947         # Create reference in destination group
  1948         add_task_to_group_folder_in_database(newtid, group, location)
  1949 
  1950         if is_this_task_a_group(tid):
  1951                 # walk in group
  1952                 # list items in group
  1953                 tasks      = sorted(os.listdir(generate_group_path(tid)))
  1954 
  1955                 # add group found in first group
  1956                 for t in tasks:
  1957                         # Check tasks that are not title task in a group
  1958                         if t[ORDER_ID_LENGTH:] != tid:
  1959                                 # copy_task_to_database recursively
  1960                                 copy_task_to_database(t[ORDER_ID_LENGTH:],location,newtid)
  1961         return newtid
  1962 
  1963 ## move task to database/group
  1964 # @param[in] tgroup group id for task tid
  1965 # @param[in] tid task id
  1966 # @param[in] location database name in data section of easydoneit.ini
  1967 # @param[in] group destination group id
  1968 # @ingroup EDI_CORE
  1969 # copy and delete
  1970 def move_task_to_a_group_to_database(tgroup,tid,location,group):
  1971         newtid = copy_task_to_database(tid,location,group)
  1972         if is_this_task_a_group(tid):
  1973                 delete_group(tid)
  1974         else:
  1975                 delete_linked_task(tgroup,tid)
  1976         return newtid
  1977 
  1978 ## add reference to tid in group
  1979 # @param[in] tid task id
  1980 # @param[in] group destination group id
  1981 # @ingroup EDI_CORE
  1982 # Used in edi ln to link tasks
  1983 def add_task_reference_to_a_group(tid,group):
  1984         prints = []
  1985         if is_this_task_a_group(tid):
  1986                 prints.append('Select a task to link instead of a group')
  1987         else:
  1988                 # add group to task folder
  1989                 task_path = generate_task_path(tid)
  1990                 if not os.path.exists('%s/groups/' % task_path):
  1991                         os.mkdir('%s/groups/' % task_path)
  1992 
  1993                         # add first group when task is linked to tid/groups/
  1994                         first_group = find_group_containing_task(tid)
  1995                         f         = open('%s/groups/%s'%(task_path,first_group),'w')
  1996                         f.close()
  1997                 f         = open('%s/groups/%s'%(task_path,group),'w')
  1998                 f.close()
  1999 
  2000                 # add task to group
  2001                 add_task_to_group_folder(tid, group)
  2002 
  2003         edi_log('linked %s to group %s'%(tid,group))
  2004         return prints
  2005 
  2006 ## set status for tid
  2007 # @param[in] tid task id
  2008 # @param[in] status_number index in edi_core.TASK_STATUS
  2009 # @ingroup EDI_CORE
  2010 def set_status(tid,status_number):
  2011         # Change status
  2012         f         = open('%s/status'%generate_task_path(tid),'w')
  2013         f.write(TASK_STATUS[status_number])
  2014         f.close()
  2015         edi_log('set status for %s to %s'%(tid,TASK_STATUS[status_number].strip()))
  2016 
  2017 ## set all tasks in group to active
  2018 # @param[in] tid task id
  2019 # @ingroup EDI_CORE
  2020 def reset_group_status(tid):
  2021         group_tasks = os.listdir(generate_group_path(tid))
  2022         for t in group_tasks:
  2023                 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])):
  2024                         set_status(t[ORDER_ID_LENGTH:],TASK_STATUS_ACTIVE)
  2025         edi_log('reset group %s'%tid)
  2026 
  2027 ## search string in tasks folder
  2028 # @param[in] search query string
  2029 # @ingroup EDI_CORE
  2030 def search_string(search):
  2031         global data_location_tasks
  2032         global status_filters_d
  2033         global STATUS_FILTER_STATES
  2034 
  2035         tasks  = sorted(os.listdir(data_location_tasks))
  2036 
  2037         # search in descriptions only
  2038         all_grep_r = []
  2039         for tid in tasks:
  2040                 if tid != 'root':
  2041                         # search in visible tasks only (filter enable status)
  2042                         task_status = get_status(tid)
  2043                         if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
  2044                                 f           = os.popen('grep -i -R "%s" %s/description.txt'%(search,generate_task_path(tid)))
  2045                                 # add tid and filename to results
  2046                                 grep_r      = ['/%s/description.txt:%s'%(tid,i) for i in f.readlines()]
  2047                                 f.close()
  2048                                 all_grep_r  += grep_r
  2049 
  2050         grep_r     = all_grep_r
  2051 
  2052         # r lists all hits
  2053         r      = []
  2054         for l in grep_r:
  2055                 # replace filename with tid and title (first line in file)
  2056                 # read first line in file
  2057                 l_l          = l.split(':')
  2058                 hit_filename = l_l[0]
  2059                 f            = open('%s%s'%(data_location_tasks,hit_filename))
  2060                 title        = f.readline().strip()
  2061                 f.close()
  2062                 # set tid
  2063                 tid          = hit_filename.split('/')[1]
  2064                 group = '     '
  2065                 if is_this_task_a_group(tid):
  2066                         group = 'GROUP'
  2067                 if is_linked(tid):
  2068                         group = ' LINK'
  2069                 l_l[0]       = '%s %s - %s'%(tid,group,title)
  2070                 l            = ':'.join(l_l)
  2071                 r.append(l)
  2072 
  2073         return r
  2074 
  2075 ## search string in group
  2076 # @param[in] group task id
  2077 # @param[in] search query string
  2078 # @ingroup EDI_CORE
  2079 def search_string_in_group(group,search):
  2080 
  2081         tasks  = sorted(os.listdir(generate_group_path(group)))
  2082 
  2083         # r lists all hits
  2084         r      = []
  2085         for orderidtid in tasks:
  2086                 # tid for current task in group
  2087                 tid         = orderidtid[ORDER_ID_LENGTH:]
  2088                 # search in visible tasks only (filter enable status)
  2089                 task_status = get_status(tid)
  2090                 grep_r      = []
  2091                 if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
  2092                         f           = os.popen('grep -i -R "%s" %s/description.txt'%(search,generate_task_path(tid)))
  2093                         # add tid and filename to results
  2094                         grep_r      = ['/%s/description.txt:%s'%(tid,i) for i in f.readlines()]
  2095                         f.close()
  2096 
  2097                 for l in grep_r:
  2098                         # replace filename with tid and title (first line in file)
  2099                         # read first line in file
  2100                         l_l          = l.split(':')
  2101                         hit_filename = l_l[0]
  2102                         f            = open('%s%s'%(data_location_tasks,hit_filename))
  2103                         title        = f.readline().strip()
  2104                         f.close()
  2105                         # set tid
  2106                         tid          = hit_filename.split('/')[1]
  2107                         group = '     '
  2108                         if is_this_task_a_group(tid):
  2109                                 group = 'GROUP'
  2110                         if is_linked(tid):
  2111                                 group = ' LINK'
  2112                         l_l[0]       = '%s %s - %s'%(tid,group,title)
  2113                         l            = ':'.join(l_l)
  2114                         r.append(l)
  2115 
  2116         return r
  2117 
  2118 ## search string in tree
  2119 # @param[in] group task id
  2120 # @param[in] search query string
  2121 # @ingroup EDI_CORE
  2122 def search_string_in_tree(group,search):
  2123         # walk_group is the list of groups to visit. FIFO
  2124         r          = []
  2125         walk_group = [group]
  2126         # the while loop goes through all the group that are found
  2127         while walk_group:
  2128                 # list items in first group
  2129                 tasks      = sorted(os.listdir(generate_group_path(walk_group[0])))
  2130                 r += search_string_in_group(walk_group[0],search)
  2131 
  2132                 # add group found in first group
  2133                 for t in tasks:
  2134                         # Check tasks that are not title task in a group
  2135                         if (t[ORDER_ID_LENGTH:] != walk_group[0]) and is_this_task_a_group(t[ORDER_ID_LENGTH:]):
  2136                                 walk_group.append(t[ORDER_ID_LENGTH:])
  2137 
  2138                 # remove first group to list items in next group
  2139                 del walk_group[0]
  2140         return r
  2141 
  2142 ## show tree
  2143 #  @ingroup EDI_CORE
  2144 # print all trees: group tids and titles
  2145 def show_tree():
  2146         r       = []
  2147         f       = os.popen('cd %s;find .'%data_location_tree)
  2148         # remove first empty line
  2149         tidtree = f.readlines() [1:]
  2150         f.close()
  2151 
  2152         # remove './' from path
  2153         tidtree = [i[2:] for i in tidtree]
  2154 
  2155         # print titles path\n group tid path\n\n
  2156         for l in tidtree:
  2157                 # find title for group tids
  2158                 group_titles_in_path = []
  2159                 for g in l.strip().split(os.sep):
  2160                         group_titles_in_path.append(get_task_title(g))
  2161                 if user_interface == 'web':
  2162                         # convert / to - to be able to create links correctly
  2163                         group_titles_in_path = [i.replace('/', '-') for i in group_titles_in_path]
  2164                 # create string title/title...
  2165                 group_titles_in_path_s = '/'.join(group_titles_in_path)
  2166                 r.append('%s\n'%group_titles_in_path_s)
  2167                 r.append('%s\n'%l)
  2168 
  2169         return r
  2170 
  2171 ## group statistics
  2172 #  @ingroup EDI_CORE
  2173 # print statistics for a group recursively
  2174 def group_statistics(group):
  2175         global stats
  2176         global stats_total
  2177         global stats_creation_dates
  2178         global stats_overtime
  2179 
  2180         # compute total number of tasks in group
  2181         path = generate_group_path(group)
  2182 
  2183         tasks         = []
  2184         # remove group title
  2185         for i in os.listdir(path):
  2186                 if not baseconvert(0) in i[:ORDER_ID_LENGTH]:
  2187                         tasks.append(i[ORDER_ID_LENGTH:])
  2188 
  2189         stats_total   += len(tasks)
  2190 
  2191         # compute number of tasks in each state, groups and links
  2192         for tid in tasks:
  2193                 task_status        = get_status(tid)
  2194                 stats[task_status] += 1
  2195                 if is_this_task_a_group(tid):
  2196                         stats['   Group'] += 1
  2197                         group_statistics(tid)
  2198                 if is_linked(tid):
  2199                         stats['  Linked'] += 1
  2200 
  2201                 #stats_creation_dates
  2202                 # Figure out creation time and modification time for description
  2203                 task_ctime         = get_creation_date(tid)[1]
  2204 
  2205                 task_path         = generate_task_path(tid)
  2206                 (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
  2207                 # 'if' below to be compatible with first database format
  2208                 if task_ctime == 0:
  2209                         # ctime not available
  2210                         task_ctime = mtime
  2211                 stats_creation_dates.append(task_ctime)
  2212 
  2213                 (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)
  2214 
  2215                 cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime))
  2216                 sdate = time.strftime("%Y-%m-%d", time.localtime(mtime))
  2217                 if not stats_overtime.has_key(cdate):
  2218                         # initialize date dict
  2219                         stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
  2220                 stats_overtime[cdate]['Creation'] += 1
  2221                 if not stats_overtime.has_key(sdate):
  2222                         # initialize date dict
  2223                         stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
  2224                 stats_overtime[sdate][task_status] += 1
  2225                 #end
  2226 
  2227 ## statistics
  2228 #  @ingroup EDI_CORE
  2229 # print statistics for a group or a database
  2230 # compute speed
  2231 def statistics(group):
  2232         global stats
  2233         global stats_total
  2234         global stats_creation_dates
  2235         global stats_overtime
  2236         r = []
  2237 
  2238         # initialize stats dictionary
  2239         stat_keys     = [TASK_STATUS[i] for i in range(len(TASK_STATUS))]
  2240         stat_keys.append('   Group')
  2241         stat_keys.append('  Linked')
  2242         state_amounts = [0 for i in range(len(stat_keys))]
  2243         stats         = dict(zip(stat_keys,state_amounts))
  2244 
  2245         if group == 'for database':
  2246                 # compute total number of tasks, excluding root
  2247                 path          = data_location_tasks
  2248                 tasks         = []
  2249                 for i in os.listdir(path):
  2250                         if i != 'root':
  2251                                 tasks.append(i)
  2252 
  2253                 # compute number of tasks in each state, groups and links
  2254                 for tid in tasks:
  2255                         task_status        = get_status(tid)
  2256                         stats[task_status] += 1
  2257                         if is_this_task_a_group(tid):
  2258                                 stats['   Group'] += 1
  2259                         if is_linked(tid):
  2260                                 stats['  Linked'] += 1
  2261 
  2262 #:define stats_creation_dates
  2263                         # Figure out creation time and modification time for description
  2264                         task_ctime         = get_creation_date(tid)[1]
  2265 
  2266                         task_path         = generate_task_path(tid)
  2267                         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
  2268                         if task_ctime == 0:
  2269                                 # ctime not available
  2270                                 task_ctime = mtime
  2271                         stats_creation_dates.append(task_ctime)
  2272 
  2273                         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)
  2274 
  2275                         cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime))
  2276                         sdate = time.strftime("%Y-%m-%d", time.localtime(mtime))
  2277                         if not stats_overtime.has_key(cdate):
  2278                                 # initialize date dict
  2279                                 stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
  2280                         stats_overtime[cdate]['Creation'] += 1
  2281                         if not stats_overtime.has_key(sdate):
  2282                                 # initialize date dict
  2283                                 stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
  2284                         stats_overtime[sdate][task_status] += 1
  2285 #:end
  2286 
  2287                 stats_total   = len(tasks)
  2288         else:
  2289                 # compute total number of tasks in group
  2290                 path = generate_group_path(group)
  2291 
  2292                 tasks         = []
  2293                 for i in os.listdir(path):
  2294                         if group == 'root':
  2295                                 tasks.append(i[ORDER_ID_LENGTH:])
  2296                         else:
  2297                                 # the group task is not counted in the statistics
  2298                                 if not baseconvert(0) in i[:ORDER_ID_LENGTH]:
  2299                                         tasks.append(i[ORDER_ID_LENGTH:])
  2300 
  2301                 stats_total   = len(tasks)
  2302 
  2303                 # compute number of tasks in each state, groups and links, recursively
  2304                 for tid in tasks:
  2305                         task_status        = get_status(tid)
  2306                         stats[task_status] += 1
  2307                         if is_this_task_a_group(tid):
  2308                                 stats['   Group'] += 1
  2309                                 group_statistics(tid)
  2310                         if is_linked(tid):
  2311                                 stats['  Linked'] += 1
  2312 
  2313 #:stats_creation_dates
  2314                         # Figure out creation time and modification time for description
  2315                         task_ctime         = get_creation_date(tid)[1]
  2316 
  2317                         task_path         = generate_task_path(tid)
  2318                         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
  2319                         if task_ctime == 0:
  2320                                 # ctime not available
  2321                                 task_ctime = mtime
  2322                         stats_creation_dates.append(task_ctime)
  2323 
  2324                         (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)
  2325 
  2326                         cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime))
  2327                         sdate = time.strftime("%Y-%m-%d", time.localtime(mtime))
  2328                         if not stats_overtime.has_key(cdate):
  2329                                 # initialize date dict
  2330                                 stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
  2331                         stats_overtime[cdate]['Creation'] += 1
  2332                         if not stats_overtime.has_key(sdate):
  2333                                 # initialize date dict
  2334                                 stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
  2335                         stats_overtime[sdate][task_status] += 1
  2336 
  2337         if not stats_total:
  2338                 r.append('0 task in statistics.')
  2339                 return r
  2340 
  2341         # csv format, not used for now
  2342         #col_names = ['Tasks'] + sorted(stats.keys())
  2343         #col_names = [i strip for i in col_names]
  2344         #print ','.join(col_names)
  2345         #csv = '%d'%len(tasks)
  2346         #for k in sorted(stats.keys())
  2347         #        csv += ',%d'%stats[k]
  2348         #r append csv
  2349         #r append ''
  2350 
  2351         r.append('Number of items: %d\n'%stats_total)
  2352         for k in sorted(stats.keys()):
  2353                 r.append('%s - %d'%(k, stats[k]))
  2354         r.append('')
  2355 
  2356         # speed
  2357         start_time        = sorted(stats_creation_dates)[0]
  2358         now               = time.time()
  2359         done_and_inactive = stats[TASK_STATUS[TASK_STATUS_DONE]] + stats[TASK_STATUS[TASK_STATUS_INACTIVE]]
  2360         if not done_and_inactive:
  2361                 r.append('Nothing is done or inactive')
  2362         else:
  2363                 # subtract tasks in state void, because tasks in void state are informative or groups
  2364                 number_of_tasks   = stats_total - stats[TASK_STATUS[TASK_STATUS_VOID]]
  2365                 remaining_tasks   = number_of_tasks - done_and_inactive
  2366                 remaining_time    = (now - start_time) / float(done_and_inactive) * remaining_tasks
  2367                 r.append('Number of tasks (excluding voids): %d'%number_of_tasks)
  2368                 r.append('Remaining tasks:                   %d'%remaining_tasks)
  2369                 r.append('Remaining days:                    %.3f'%(remaining_time/86400))
  2370                 r.append('Start date:                        %s'%time.strftime("%Y-%m-%d", time.localtime(start_time)))
  2371                 r.append("Today's date:                      %s"%time.strftime("%Y-%m-%d", time.localtime(now)))
  2372                 r.append('Finish date:                       %s'%time.strftime("%Y-%m-%d", time.localtime(now + remaining_time)))
  2373 
  2374         # create csv stats from stats_overtime
  2375         f = open('%s/stats.csv'%data_location,'w')
  2376         f.write('date,%s\n'%','.join([i.strip() for i in STATS_OVERTIME_KEYS]))
  2377 
  2378         for d in sorted(stats_overtime.keys()):
  2379                 f.write('%s,%s\n'%(d, ','.join([str(stats_overtime[d][i]) for i in STATS_OVERTIME_KEYS])))
  2380         f.close()
  2381 
  2382         # create creation, done and inactive stats
  2383         f = open('%s/stats_creation_done_inactive.csv'%data_location,'w')
  2384         f.write('date,Creation,Done/Inactive\n')
  2385 
  2386         for d in sorted(stats_overtime.keys()):
  2387                 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]]))
  2388         f.close()
  2389 
  2390         r.append('')
  2391         return r
  2392 
  2393 # test core functions
  2394 #def test
  2395 #        # test
  2396 #        list_group('root')
  2397 #
  2398 #        t1 = add_task('root', 'task1.txt')
  2399 #        add_task('root', 'task2.txt')
  2400 #        g3 = add_task('root', 'task3.txt')
  2401 #
  2402 #        create_group(g3)
  2403 #        t4 = add_task(g3, 'task4.txt')
  2404 #
  2405 #        list_group('root')
  2406 #        list_group(g3)
  2407 #
  2408 #        #display_task(t1)
  2409 #        #display_task(t4)
  2410 #
  2411 #        # Delete task in a group of 2 tasks
  2412 #        print '# Delete task in a group of 2 tasks'
  2413 #        delete_task(t4)
  2414 #
  2415 #        list_group('root')
  2416 #
  2417 #        create_group(g3)
  2418 #        t4 = add_task(g3, 'task4.txt')
  2419 #
  2420 #        list_group('root')
  2421 #
  2422 #        # Delete group first task
  2423 #        print '# Delete group first task'
  2424 #        delete_task(g3)
  2425 #
  2426 #        list_group('root')
  2427 #
  2428 #        # Delete group task of a group with more than 2 tasks
  2429 #        print '# Delete group task of a group with more than 2 tasks'
  2430 #        delete_task(t4)
  2431 #
  2432 #        g3 = add_task('root', 'task3.txt')
  2433 #        create_group(g3)
  2434 #        t4 = add_task(g3, 'task4.txt')
  2435 #        t2 = add_task(g3, 'task2.txt')
  2436 #
  2437 #        list_group('root')
  2438 #        list_group(g3)
  2439 #        new_group_id = delete_task(g3)
  2440 #        print 'New group id %s'%new_group_id
  2441 #
  2442 #        list_group('root')
  2443 #        list_group(new_group_id)
  2444 #
  2445 #        # Delete group
  2446 #        print '# Delete group'
  2447 #        delete_group(new_group_id)
  2448 #
  2449 #        list_group('root')
  2450 #
  2451 #        print baseconvert(100)
  2452 #        print baseconvert_to_dec(baseconvert(100))
  2453 #        print
  2454 #
  2455 #        #edit_task(t1)
  2456 #
  2457 #        # Change order
  2458 #        print '# Change order'
  2459 #        g3 = add_task('root', 'task3.txt')
  2460 #        change_task_order('root',1,0)
  2461 #        change_task_order('root',0,2)
  2462 #
  2463 #        list_group('root')
  2464 #
  2465 #        # Change status
  2466 #        print '# Change status'
  2467 #        create_group(g3)
  2468 #        t4 = add_task(g3, 'task4.txt')
  2469 #        t2 = add_task(g3, 'task2.txt')
  2470 #
  2471 #        list_group('root')
  2472 #
  2473 #        set_status(t1,TASK_STATUS_DONE)
  2474 #        set_status(t4,TASK_STATUS_DONE)
  2475 #
  2476 #        list_group('root')
  2477 #        list_group(g3)
  2478 #
  2479 #        # Reset status
  2480 #
  2481 #        reset_group_status(g3)
  2482 #
  2483 #        list_group('root')
  2484 #        list_group(g3)
  2485 #
  2486 #        reset_group_status('root')
  2487 #
  2488 #        list_group('root')
  2489 #        list_group(g3)
  2490 #
  2491 #        # Create a group in group
  2492 #        print '# Create a group in group'
  2493 #        create_group(t4)
  2494 #        t2 = add_task(t4,'task2.txt')
  2495 #
  2496 #        list_group('root')
  2497 #        list_group(g3)
  2498 #        list_group(t4)
  2499 #
  2500 #        # Delete task in a group of 2 tasks not in root
  2501 #        print '# Delete task in a group of 2 tasks not in root'
  2502 #        delete_task(t2)
  2503 #
  2504 #        list_group('root')
  2505 #        list_group(g3)
  2506 #
  2507 #        # Delete group with groups in it
  2508 #        print '# Delete group with groups in it'
  2509 #        create_group(t4)
  2510 #        t2 = add_task(t4,'task2.txt')
  2511 #
  2512 #        list_group('root')
  2513 #        list_group(g3)
  2514 #        list_group(t4)
  2515 #
  2516 #        delete_group(g3)
  2517 #
  2518 #        list_group('root')
  2519 #
  2520 #        # Search string
  2521 #        print '# Search string'
  2522 #
  2523 #        search_string('AND')
  2524 #
  2525 #        # List tree
  2526 #        print '# List tree'
  2527 #        g3 = add_task('root', 'task3.txt')
  2528 #        create_group(g3)
  2529 #        t4 = add_task(g3, 'task4.txt')
  2530 #        t2 = add_task(g3, 'task2.txt')
  2531 #        create_group(t4)
  2532 #        t2 = add_task(t4,'task2.txt')
  2533 #
  2534 #        list_tree('root')
  2535 #
  2536 #        # Show group for a task
  2537 #        print '# Show group for a task'
  2538 #
  2539 #        print 'Show group for %s - %s' %(t2,get_task_title(t2))
  2540 #        show_group_for_task(t2)
  2541 #        print 'Show group for %s - %s' %(t4,get_task_title(t4))
  2542 #        show_group_for_task(t4)
  2543 #        print 'Show group for %s - %s' %(t1,get_task_title(t1))
  2544 #        show_group_for_task(t1)
  2545 
  2546         # create task
  2547         #create_task(t4)
  2548         #list_group(t4)
  2549 
  2550 ## start - always called at startup.
  2551 # @ingroup EDI_CORE
  2552 # @param[in] interface selects current user interface. cli loads the configuration from user home, web loads the configuration located in edi_web folder.
  2553 # creates default .easydoneit.ini<br>
  2554 # creates database folders<br>
  2555 # loads .easydoneit.ini
  2556 def start(interface='cli'):
  2557         global user_interface
  2558         user_interface = interface
  2559 
  2560         if interface=='web':
  2561                 # Do not try to access ~/.easydoneit.ini
  2562                 inipath = '.easydoneit.ini'
  2563         else:
  2564                 inipath = '~/.easydoneit.ini'
  2565                 # create ~/.easydoneit.ini if it doesnt exist, (not in web interface mode)
  2566                 if not os.path.isfile(os.path.expanduser('~/.easydoneit.ini')):
  2567                         f = open(os.path.expanduser('~/.easydoneit.ini'),'w')
  2568                         f.write('[data]\n')
  2569                         f.write('location=~/easydoneit_data\n')
  2570                         f.write('1=~/easydoneit_data\n')
  2571                         f.write('\n')
  2572                         f.write('[locations]\n')
  2573                         f.write('selected=1\n')
  2574                         f.write('default_add_in=1\n')
  2575                         f.write('\n')
  2576                         f.write('[filters]\n')
  2577                         # enable all status filters
  2578                         fi = zip(TASK_STATUS,status_filters)
  2579                         for name,nfilter in fi:
  2580                                 # strip to remove spaces in status strings
  2581                                 f.write("%s=%s\n"%(name.strip(),nfilter))
  2582                         f.write('\n')
  2583                         f.write('[colors]\n')
  2584                         co = zip(TASK_STATUS,status_fgColors)
  2585                         for name,color in co:
  2586                                 f.write('%s_fgColor=%d,%d,%d,%d\n' % (name.strip(),color[0],color[1],color[2],color[3]))
  2587                         co = zip(TASK_STATUS,status_bgColors)
  2588                         for name,color in co:
  2589                                 f.write('%s_bgColor=%d,%d,%d,%d\n' % (name.strip(),color[0],color[1],color[2],color[3]))
  2590 
  2591                         f.write('\n[settings]\n')
  2592                         f.write('editor=vi\n')
  2593 
  2594                         f.close()
  2595                         # default permissions 600 rw for user no access for group and world
  2596                         os.chmod(os.path.expanduser('~/.easydoneit.ini'),stat.S_IRUSR|stat.S_IWUSR)
  2597 
  2598         # load config from inipath
  2599         config              = ConfigParser.ConfigParser()
  2600         config.readfp(open(os.path.expanduser(inipath)))
  2601 
  2602         # convert ~ to home path
  2603         global data_location
  2604         data_location       = os.path.expanduser(config.get('data','location'))
  2605 
  2606         # load available databases
  2607         global databases
  2608         data_section        = config.items('data')
  2609         # remove location which is a path to a database
  2610         DATABASE_NAME = 0
  2611         DATABASE_PATH = 1
  2612         # clear databases for reentrant testing
  2613         databases = []
  2614         for d in data_section:
  2615                 if d[DATABASE_NAME] != 'location':
  2616                         databases.append(d)
  2617 
  2618         # load add_top_or_bottom
  2619         if 'add_top_or_bottom' in dict(config.items('locations')).keys():
  2620                 global add_top_or_bottom
  2621                 add_top_or_bottom = config.get('locations','add_top_or_bottom')
  2622 
  2623         # load selected database paths
  2624         global selected
  2625         global default_add_in
  2626         global selected_path
  2627         selected            = config.get('locations','selected').split(',')
  2628         default_add_in      = config.get('locations','default_add_in')
  2629         for d in selected:
  2630                 selected_path.append(os.path.expanduser(config.get('data',d)))
  2631 
  2632         # load autolink groups
  2633         if 'autolink' in dict(config.items('locations')).keys():
  2634                 global autolink
  2635                 autolink_cfg        = config.get('locations','autolink')
  2636                 autolink            = [ autolink_cfg[i:i+ID_LENGTH] for i in range(0,len(autolink_cfg), ID_LENGTH+1)]
  2637 
  2638         # load list groups
  2639         if 'list' in dict(config.items('locations')).keys():
  2640                 global list_of_groups
  2641                 list_cfg        = config.get('locations','list')
  2642                 list_of_groups  = [ list_cfg[i:i+ID_LENGTH] for i in range(0,len(list_cfg), ID_LENGTH+1)]
  2643 
  2644         # load status filters and status colors
  2645         config_filter_names = dict(config.items('filters')).keys()
  2646         config_color_names  = dict(config.items('colors')).keys()
  2647         for n,nfilter in enumerate(TASK_STATUS):
  2648                 # strip to remove spaces in status strings
  2649                 # check that task_status filter is in config file, to avoid problems between config file versions
  2650                 if nfilter.strip().lower() in config_filter_names:
  2651                         status_filters[n]  = config.get('filters',nfilter.strip())
  2652                 if '%s_fgcolor'%nfilter.strip().lower() in config_color_names:
  2653                         status_fgColors[n] = tuple([int(i) for i in config.get('colors','%s_fgcolor'%nfilter.strip()).split(',')])
  2654                 if '%s_bgcolor'%nfilter.strip().lower() in config_color_names:
  2655                         status_bgColors[n] = tuple([int(i) for i in config.get('colors','%s_bgcolor'%nfilter.strip()).split(',')])
  2656 
  2657         # Set text editor
  2658         global editor
  2659         editor              = config.get('settings','editor')
  2660 
  2661         # set user name and email
  2662         global user
  2663         if 'username' in dict(config.items('settings')).keys():
  2664                 user  = config.get('settings','username')
  2665         else:
  2666                 user  = getpass.getuser()
  2667         if 'useremail' in dict(config.items('settings')).keys():
  2668                 global email
  2669                 email = config.get('settings','useremail')
  2670 
  2671         # init
  2672         init()
  2673