[IMP] tools: added disclaimer on detect_ip_addr
[odoo/odoo.git] / bin / tools / misc.py
index 2984fb0..43a283d 100644 (file)
@@ -3,6 +3,7 @@
 #
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+#    Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
 #
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
@@ -32,6 +33,10 @@ import zipfile
 import release
 import socket
 import re
+from itertools import islice
+import threading
+from which import which
+
 
 if sys.version_info[:2] < (2, 4):
     from threadinglocal import local
@@ -50,82 +55,78 @@ def init_db(cr):
     cr.commit()
 
     for i in addons.get_modules():
-        terp_file = addons.get_module_resource(i, '__terp__.py')
         mod_path = addons.get_module_path(i)
         if not mod_path:
             continue
-        info = False
-        if os.path.isfile(terp_file) or os.path.isfile(mod_path+'.zip'):
-            info = eval(file_open(terp_file).read())
-        if info:
-            categs = info.get('category', 'Uncategorized').split('/')
-            p_id = None
-            while categs:
-                if p_id is not None:
-                    cr.execute('select id \
-                            from ir_module_category \
-                            where name=%s and parent_id=%s', (categs[0], p_id))
-                else:
-                    cr.execute('select id \
-                            from ir_module_category \
-                            where name=%s and parent_id is NULL', (categs[0],))
-                c_id = cr.fetchone()
-                if not c_id:
-                    cr.execute('select nextval(\'ir_module_category_id_seq\')')
-                    c_id = cr.fetchone()[0]
-                    cr.execute('insert into ir_module_category \
-                            (id, name, parent_id) \
-                            values (%s, %s, %s)', (c_id, categs[0], p_id))
-                else:
-                    c_id = c_id[0]
-                p_id = c_id
-                categs = categs[1:]
-
-            active = info.get('active', False)
-            installable = info.get('installable', True)
-            if installable:
-                if active:
-                    state = 'to install'
-                else:
-                    state = 'uninstalled'
+
+        info = addons.load_information_from_description_file(i)
+
+        if not info:
+            continue
+        categs = info.get('category', 'Uncategorized').split('/')
+        p_id = None
+        while categs:
+            if p_id is not None:
+                cr.execute('select id \
+                           from ir_module_category \
+                           where name=%s and parent_id=%s', (categs[0], p_id))
             else:
-                state = 'uninstallable'
-            cr.execute('select nextval(\'ir_module_module_id_seq\')')
-            id = cr.fetchone()[0]
-            cr.execute('insert into ir_module_module \
-                    (id, author, website, name, shortdesc, description, \
-                        category_id, state, certificate) \
-                    values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
-                id, info.get('author', ''),
-                info.get('website', ''), i, info.get('name', False),
-                info.get('description', ''), p_id, state, info.get('certificate')))
-            cr.execute('insert into ir_model_data \
-                (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
-                    'module_meta_information', 'ir.module.module', i, id, True))
-            dependencies = info.get('depends', [])
-            for d in dependencies:
-                cr.execute('insert into ir_module_module_dependency \
-                        (module_id,name) values (%s, %s)', (id, d))
-            cr.commit()
+                cr.execute('select id \
+                           from ir_module_category \
+                           where name=%s and parent_id is NULL', (categs[0],))
+            c_id = cr.fetchone()
+            if not c_id:
+                cr.execute('select nextval(\'ir_module_category_id_seq\')')
+                c_id = cr.fetchone()[0]
+                cr.execute('insert into ir_module_category \
+                        (id, name, parent_id) \
+                        values (%s, %s, %s)', (c_id, categs[0], p_id))
+            else:
+                c_id = c_id[0]
+            p_id = c_id
+            categs = categs[1:]
+
+        active = info.get('active', False)
+        installable = info.get('installable', True)
+        if installable:
+            if active:
+                state = 'to install'
+            else:
+                state = 'uninstalled'
+        else:
+            state = 'uninstallable'
+        cr.execute('select nextval(\'ir_module_module_id_seq\')')
+        id = cr.fetchone()[0]
+        cr.execute('insert into ir_module_module \
+                (id, author, website, name, shortdesc, description, \
+                    category_id, state, certificate) \
+                values (%s, %s, %s, %s, %s, %s, %s, %s, %s)', (
+            id, info.get('author', ''),
+            info.get('website', ''), i, info.get('name', False),
+            info.get('description', ''), p_id, state, info.get('certificate') or None))
+        cr.execute('insert into ir_model_data \
+            (name,model,module, res_id, noupdate) values (%s,%s,%s,%s,%s)', (
+                'module_meta_information', 'ir.module.module', i, id, True))
+        dependencies = info.get('depends', [])
+        for d in dependencies:
+            cr.execute('insert into ir_module_module_dependency \
+                    (module_id,name) values (%s, %s)', (id, d))
+        cr.commit()
 
 def find_in_path(name):
-    if os.name == "nt":
-        sep = ';'
-    else:
-        sep = ':'
-    path = [dir for dir in os.environ['PATH'].split(sep)
-            if os.path.isdir(dir)]
-    for dir in path:
-        val = os.path.join(dir, name)
-        if os.path.isfile(val) or os.path.islink(val):
-            return val
-    return None
+    try:
+        return which(name)
+    except IOError:
+        return None
 
 def find_pg_tool(name):
+    path = None
     if config['pg_path'] and config['pg_path'] != 'None':
-        return os.path.join(config['pg_path'], name)
-    else:
-        return find_in_path(name)
+        path = config['pg_path']
+    try:
+        return which(name, path=path)
+    except IOError:
+        return None
 
 def exec_pg_command(name, *args):
     prog = find_pg_tool(name)
@@ -327,23 +328,23 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
     ## (c) Fry-IT, www.fry-it.com, 2007
     ## <peter@fry-it.com>
     ## download here: http://www.peterbe.com/plog/html2plaintext
-    
-    
+
+
     """ from an HTML text, convert the HTML to plain text.
-    If @body_id is provided then this is the tag where the 
+    If @body_id is provided then this is the tag where the
     body (not necessarily <body>) starts.
     """
     try:
         from BeautifulSoup import BeautifulSoup, SoupStrainer, Comment
     except:
         return html
-            
+
     urls = []
     if body_id is not None:
         strainer = SoupStrainer(id=body_id)
     else:
         strainer = SoupStrainer('body')
-    
+
     soup = BeautifulSoup(html, parseOnlyThese=strainer, fromEncoding=encoding)
     for link in soup.findAll('a'):
         title = link.renderContents()
@@ -351,7 +352,7 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
             urls.append(dict(url=url, tag=str(link), title=title))
 
     html = soup.__str__()
-            
+
     url_index = []
     i = 0
     for d in urls:
@@ -368,11 +369,11 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
     html = html.replace('<h2>','**').replace('</h2>','**')
     html = html.replace('<h1>','**').replace('</h1>','**')
     html = html.replace('<em>','/').replace('</em>','/')
-    
 
-    # the only line breaks we respect is those of ending tags and 
+
+    # the only line breaks we respect is those of ending tags and
     # breaks
-    
+
     html = html.replace('\n',' ')
     html = html.replace('<br>', '\n')
     html = html.replace('<tr>', '\n')
@@ -381,7 +382,7 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
     html = html.replace(' ' * 2, ' ')
 
 
-    # for all other tags we failed to clean up, just remove then and 
+    # for all other tags we failed to clean up, just remove then and
     # complain about them on the stderr
     def desperate_fixer(g):
         #print >>sys.stderr, "failed to clean up %s" % str(g.group())
@@ -395,13 +396,22 @@ def html2plaintext(html, body_id=None, encoding='utf-8'):
     for i, url in enumerate(url_index):
         if i == 0:
             html += '\n\n'
-        html += '[%s] %s\n' % (i+1, url)       
+        html += '[%s] %s\n' % (i+1, url)
     return html
 
 def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
                attach=None, openobject_id=False, ssl=False, debug=False, subtype='plain', x_headers=None, priority='3'):
 
-    """Send an email."""
+    """Send an email.
+
+    Arguments:
+
+    `email_from`: A string used to fill the `From` header, if falsy,
+                  config['email_from'] is used instead.  Also used for
+                  the `Reply-To` header if `reply_to` is not provided
+
+    `email_to`: a sequence of addresses to send the mail to.
+    """
     import smtplib
     from email.MIMEText import MIMEText
     from email.MIMEBase import MIMEBase
@@ -410,32 +420,33 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
     from email.Utils import formatdate, COMMASPACE
     from email.Utils import formatdate, COMMASPACE
     from email import Encoders
-    import netsvc    
+    import netsvc
 
     if x_headers is None:
         x_headers = {}
 
-    if not ssl:
-        ssl = config.get('smtp_ssl', False)
+    if not ssl: ssl = config.get('smtp_ssl', False)
 
-    if not email_from and not config['email_from']:
-        raise Exception("No Email sender by default, see config file")
+    if not (email_from or config['email_from']):
+        raise ValueError("Sending an email requires either providing a sender "
+                         "address or having configured one")
 
-    if not email_from:
-        email_from = config.get('email_from', False)
+    if not email_from: email_from = config.get('email_from', False)
 
-    if not email_cc:
-        email_cc = []
-    if not email_bcc:
-        email_bcc = []
+    if not email_cc: email_cc = []
+    if not email_bcc: email_bcc = []
+    if not body: body = u''
+    try: email_body = body.encode('utf-8')
+    except (UnicodeEncodeError, UnicodeDecodeError):
+        email_body = body
 
-    if not attach:
-        try:
-            msg = MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
-        except:
-            msg = MIMEText(body or '',_subtype=subtype,_charset='utf-8')
-    else:
-        msg = MIMEMultipart()
+    try:
+        email_text = MIMEText(email_body.encode('utf8') or '',_subtype=subtype,_charset='utf-8')
+    except:
+        email_text = MIMEText(email_body or '',_subtype=subtype,_charset='utf-8')
+
+    if attach: msg = MIMEMultipart()
+    else: msg = email_text
 
     msg['Subject'] = Header(ustr(subject), 'utf-8')
     msg['From'] = email_from
@@ -455,21 +466,18 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
     msg['X-Generated-By'] = 'OpenERP (http://www.openerp.com)'
     msg['X-OpenERP-Server-Host'] = socket.gethostname()
     msg['X-OpenERP-Server-Version'] = release.version
-    if priority:
-        msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
+    msg['X-Priority'] = priorities.get(priority, '3 (Normal)')
 
     # Add dynamic X Header
-    for key, value in x_headers.items():
+    for key, value in x_headers.iteritems():
         msg['X-OpenERP-%s' % key] = str(value)
+        msg['%s' % key] = str(value)
 
     if openobject_id:
         msg['Message-Id'] = "<%s-openobject-%s@%s>" % (time.time(), openobject_id, socket.gethostname())
 
     if attach:
-        try:
-            msg.attach(MIMEText(body.encode('utf8') or '',_subtype=subtype,_charset='utf-8'))
-        except:
-            msg.attach(MIMEText(body or '', _charset='utf-8', _subtype=subtype) )
+        msg.attach(email_text)
         for (fname,fcontent) in attach:
             part = MIMEBase('application', "octet-stream")
             part.set_payload( fcontent )
@@ -494,8 +502,8 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
             return True
         except Exception,e:
             netsvc.Logger().notifyChannel('email_send (maildir)', netsvc.LOG_ERROR, e)
-            return False    
-    
+            return False
+
     try:
         oldstderr = smtplib.stderr
         s = smtplib.SMTP()
@@ -504,7 +512,7 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
             if debug:
                 smtplib.stderr = WriteToLogger()
 
-            s.set_debuglevel(int(bool(debug)))  # 0 or 1            
+            s.set_debuglevel(int(bool(debug)))  # 0 or 1
             s.connect(smtp_server, config['smtp_port'])
             if ssl:
                 s.ehlo()
@@ -512,7 +520,7 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
                 s.ehlo()
 
             if config['smtp_user'] or config['smtp_password']:
-                s.login(config['smtp_user'], config['smtp_password'])            
+                s.login(config['smtp_user'], config['smtp_password'])
             s.sendmail(email_from,
                        flatten([email_to, email_cc, email_bcc]),
                        msg.as_string()
@@ -743,13 +751,13 @@ class cache(object):
             if *args and **kwargs are both empty, clear all the keys related to this database
         """
         if not args and not kwargs:
-            keys_to_del = [key for key in self.cache if key[0][1] == dbname]
+            keys_to_del = [key for key in self.cache.keys() if key[0][1] == dbname]
         else:
             kwargs2 = self._unify_args(*args, **kwargs)
-            keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache]
+            keys_to_del = [key for key, _ in self._generate_keys(dbname, kwargs2) if key in self.cache.keys()]
 
         for key in keys_to_del:
-            del self.cache[key]
+            self.cache.pop(key)
 
     @classmethod
     def clean_caches_for_db(cls, dbname):
@@ -771,9 +779,9 @@ class cache(object):
             if time.time()-int(self.timeout) > self.lasttime:
                 self.lasttime = time.time()
                 t = time.time()-int(self.timeout)
-                old_keys = [key for key in self.cache if self.cache[key][1] < t]
+                old_keys = [key for key in self.cache.keys() if self.cache[key][1] < t]
                 for key in old_keys:
-                    del self.cache[key]
+                    self.cache.pop(key)
 
             kwargs2 = self._unify_args(*args, **kwargs)
 
@@ -810,6 +818,22 @@ class cache(object):
 def to_xml(s):
     return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
 
+def get_encodings():
+    yield 'utf8'
+    from locale import getpreferredencoding
+    prefenc = getpreferredencoding()
+    if prefenc:
+        yield prefenc
+
+        prefenc = {
+            'latin1': 'latin9',
+            'iso-8859-1': 'iso8859-15',
+            'cp1252': '1252',
+        }.get(prefenc.lower())
+        if prefenc:
+            yield prefenc
+
+
 def ustr(value):
     """This method is similar to the builtin `str` method, except
     it will return Unicode string.
@@ -819,29 +843,25 @@ def ustr(value):
     @rtype: unicode
     @return: unicode string
     """
+    orig = value
+    if isinstance(value, Exception):
+        return exception_to_unicode(value)
 
     if isinstance(value, unicode):
         return value
 
-    if hasattr(value, '__unicode__'):
+    try:
         return unicode(value)
-
-    if not isinstance(value, str):
-        value = str(value)
-
-    try: # first try utf-8
-        return unicode(value, 'utf-8')
     except:
         pass
 
-    try: # then extened iso-8858
-        return unicode(value, 'iso-8859-15')
-    except:
-        pass
+    for ln in get_encodings():
+        try:
+            return unicode(value, ln)
+        except:
+            pass
+    raise UnicodeError('unable de to convert %r' % (orig,))
 
-    # else use default system locale
-    from locale import getlocale
-    return unicode(value, getlocale()[1])
 
 def exception_to_unicode(e):
     if (sys.version_info[:2] < (2,6)) and hasattr(e, 'message'):
@@ -876,29 +896,15 @@ if not hasattr(__builtin__, 'any'):
     __builtin__.any = any
     del any
 
-get_iso = {'ca_ES':'ca',
-'cs_CZ': 'cs',
-'et_EE': 'et',
-'sv_SE': 'sv',
-'sq_AL': 'sq',
-'uk_UA': 'uk',
-'vi_VN': 'vi',
-'af_ZA': 'af',
-'be_BY': 'be',
-'ja_JP': 'ja',
-'ko_KR': 'ko'
-}
-
 def get_iso_codes(lang):
-    if lang in get_iso:
-        lang = get_iso[lang]
-    elif lang.find('_') != -1:
+    if lang.find('_') != -1:
         if lang.split('_')[0] == lang.split('_')[1].lower():
             lang = lang.split('_')[0]
     return lang
 
 def get_languages():
     languages={
+        'ab_RU': u'Abkhazian (RU)',
         'ar_AR': u'Arabic / الْعَرَبيّة',
         'bg_BG': u'Bulgarian / български',
         'bs_BS': u'Bosnian / bosanski jezik',
@@ -913,29 +919,48 @@ def get_languages():
         'es_AR': u'Spanish (AR) / Español (AR)',
         'es_ES': u'Spanish / Español',
         'et_EE': u'Estonian / Eesti keel',
+        'fa_IR': u'Persian / فارس',
         'fi_FI': u'Finland / Suomi',
         'fr_BE': u'French (BE) / Français (BE)',
         'fr_CH': u'French (CH) / Français (CH)',
         'fr_FR': u'French / Français',
+        'gl_ES': u'Galician / Galego',
+        'gu_IN': u'Gujarati / India',
+        'hi_IN': u'Hindi / India',
         'hr_HR': u'Croatian / hrvatski jezik',
         'hu_HU': u'Hungarian / Magyar',
         'id_ID': u'Indonesian / Bahasa Indonesia',
         'it_IT': u'Italian / Italiano',
+        'iu_CA': u'Inuktitut / Canada',
+        'ja_JP': u'Japanese / Japan',
+        'ko_KP': u'Korean / Korea, Democratic Peoples Republic of',
+        'ko_KR': u'Korean / Korea, Republic of',
         'lt_LT': u'Lithuanian / Lietuvių kalba',
+        'lv_LV': u'Latvian / Latvia',
+        'ml_IN': u'Malayalam / India',
+        'mn_MN': u'Mongolian / Mongolia',
+        'nb_NO': u'Norwegian Bokmål / Norway',
         'nl_NL': u'Dutch / Nederlands',
         'nl_BE': u'Dutch (Belgium) / Nederlands (Belgïe)',
+        'oc_FR': u'Occitan (post 1500) / France',
         'pl_PL': u'Polish / Język polski',
         'pt_BR': u'Portugese (BR) / português (BR)',
         'pt_PT': u'Portugese / português',
         'ro_RO': u'Romanian / limba română',
         'ru_RU': u'Russian / русский язык',
-        'sl_SL': u'Slovenian / slovenščina',
+        'si_LK': u'Sinhalese / Sri Lanka',
+        'sl_SI': u'Slovenian / slovenščina',
+        'sk_SK': u'Slovak / Slovenský jazyk',
         'sq_AL': u'Albanian / Shqipëri',
+        'sr_RS': u'Serbian / Serbia',
         'sv_SE': u'Swedish / svenska',
+        'te_IN': u'Telugu / India',
         'tr_TR': u'Turkish / Türkçe',
         'vi_VN': u'Vietnam / Cộng hòa xã hội chủ nghĩa Việt Nam',
         'uk_UA': u'Ukrainian / украї́нська мо́ва',
+        'ur_PK': u'Urdu / Pakistan',
         'zh_CN': u'Chinese (CN) / 简体中文',
+        'zh_HK': u'Chinese (HK)',
         'zh_TW': u'Chinese (TW) / 正體字',
         'th_TH': u'Thai / ภาษาไทย',
         'tlh_TLH': u'Klingon',
@@ -1103,6 +1128,16 @@ icons = map(lambda x: (x,x), ['STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_
 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph',
+'terp-check','terp-go-month','terp-go-year','terp-go-today','terp-document-new','terp-camera_test',
+'terp-emblem-important','terp-gtk-media-pause','terp-gtk-stop','terp-gnome-cpu-frequency-applet+',
+'terp-dialog-close','terp-gtk-jump-to-rtl','terp-gtk-jump-to-ltr','terp-accessories-archiver',
+'terp-stock_align_left_24','terp-stock_effects-object-colorize','terp-go-home','terp-gtk-go-back-rtl',
+'terp-gtk-go-back-ltr','terp-personal','terp-personal-','terp-personal+','terp-accessories-archiver-minus',
+'terp-accessories-archiver+','terp-stock_symbol-selection','terp-call-start','terp-dolar',
+'terp-face-plain','terp-folder-blue','terp-folder-green','terp-folder-orange','terp-folder-yellow',
+'terp-gdu-smart-failing','terp-go-week','terp-gtk-select-all','terp-locked','terp-mail-forward',
+'terp-mail-message-new','terp-mail-replied','terp-rating-rated','terp-stage','terp-stock_format-scientific',
+'terp-dolar_ok!','terp-idea','terp-stock_format-default','terp-mail-','terp-mail_delete'
 ])
 
 def extract_zip_file(zip_file, outdirectory):
@@ -1124,6 +1159,11 @@ def extract_zip_file(zip_file, outdirectory):
     zf.close()
 
 def detect_ip_addr():
+    """Try a very crude method to figure out a valid external
+       IP or hostname for the current machine. Don't rely on this
+       for binding to an interface, but it could be used as basis
+       for constructing a remote URL to the server.
+    """
     def _detect_ip_addr():
         from array import array
         import socket
@@ -1174,9 +1214,9 @@ def detect_ip_addr():
 # RATIONALE BEHIND TIMESTAMP CALCULATIONS AND TIMEZONE MANAGEMENT:
 #  The server side never does any timestamp calculation, always
 #  sends them in a naive (timezone agnostic) format supposed to be
-#  expressed within the server timezone, and expects the clients to 
+#  expressed within the server timezone, and expects the clients to
 #  provide timestamps in the server timezone as well.
-#  It stores all timestamps in the database in naive format as well, 
+#  It stores all timestamps in the database in naive format as well,
 #  which also expresses the time in the server timezone.
 #  For this reason the server makes its timezone name available via the
 #  common/timezone_get() rpc method, which clients need to read
@@ -1255,10 +1295,39 @@ def detect_server_timezone():
     return 'UTC'
 
 
+def split_every(n, iterable, piece_maker=tuple):
+    """Splits an iterable into length-n pieces. The last piece will be shorter
+       if ``n`` does not evenly divide the iterable length.
+       @param ``piece_maker``: function to build the pieces
+       from the slices (tuple,list,...)
+    """
+    iterator = iter(iterable)
+    piece = piece_maker(islice(iterator, n))
+    while piece:
+        yield piece
+        piece = piece_maker(islice(iterator, n))
+
 if __name__ == '__main__':
     import doctest
     doctest.testmod()
 
+class upload_data_thread(threading.Thread):
+    def __init__(self, email, data, type):
+        self.args = [('email',email),('type',type),('data',data)]
+        super(upload_data_thread,self).__init__()
+    def run(self):
+        try:
+            import urllib
+            args = urllib.urlencode(self.args)
+            fp = urllib.urlopen('http://www.openerp.com/scripts/survey.php', args)
+            fp.read()
+            fp.close()
+        except:
+            pass
 
+def upload_data(email, data, type='SURVEY'):
+    a = upload_data_thread(email, data, type)
+    a.start()
+    return True
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: