From 32e6fb7df16c8e690762760cd1a14f4f74e1373f Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Fri, 20 Sep 2019 17:19:10 -0500 Subject: [PATCH 1/2] Fix in Windows --- easymacro.py | 77 ++++++++++++++++++++++----------- files/ZAZFavorites_v0.1.0.oxt | Bin 51441 -> 51601 bytes source/ZAZFavorites.py | 3 +- source/pythonpath/easymacro.py | 77 ++++++++++++++++++++++----------- 4 files changed, 105 insertions(+), 52 deletions(-) diff --git a/easymacro.py b/easymacro.py index dd65e9e..c572a01 100644 --- a/easymacro.py +++ b/easymacro.py @@ -134,7 +134,7 @@ MENUS_APP = { } -FILE_NAME_DEBUG = 'zaz-debug.log' +FILE_NAME_DEBUG = 'debug.odt' FILE_NAME_CONFIG = 'zaz-{}.json' LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' LOG_DATE = '%d/%m/%Y %H:%M:%S' @@ -195,7 +195,10 @@ def catch_exception(f): try: return f(*args, **kwargs) except Exception as e: - log.error(f.__name__, exc_info=True) + name = f.__name__ + if IS_WIN: + debug(traceback.format_exc()) + log.error(name, exc_info=True) return func @@ -203,6 +206,7 @@ class LogWin(object): def __init__(self, doc): self.doc = doc + # ~ self.doc.Title = FILE_NAME_DEBUG def write(self, info): text = self.doc.Text @@ -219,14 +223,10 @@ def info(data): def debug(info): if IS_WIN: - # ~ app = LOApp(self.ctx, self.sm, self.desktop, self.toolkit) - # ~ doc = app.getDoc(FILE_NAME_DEBUG) - # ~ if not doc: - # ~ doc = app.newDoc(WRITER) - # ~ out = OutputDoc(doc) - # ~ sys.stdout = out - # ~ pprint(info) - doc = LogWin(new_doc('writer').obj) + doc = get_document(FILE_NAME_DEBUG) + if doc is None: + doc = new_doc('writer') + doc = LogWin(doc.obj) doc.write(info) return @@ -254,20 +254,19 @@ def run_in_thread(fn): return run -def get_config(key='', prefix='config'): +def get_config(key='', default=None, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) - values = {} + values = None path = join(get_config_path('UserConfig'), path_json) if not exists_path(path): - return values + return default with open(path, 'r', encoding='utf-8') as fh: data = fh.read() - if data: - values = json.loads(data) + values = json.loads(data) if key: - return values.get(key, None) + return values.get(key, default) return values @@ -275,7 +274,7 @@ def get_config(key='', prefix='config'): def set_config(key, value, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) path = join(get_config_path('UserConfig'), path_json) - values = get_config(prefix=prefix) + values = get_config(default={}, prefix=prefix) values[key] = value with open(path, 'w', encoding='utf-8') as fh: json.dump(values, fh, ensure_ascii=False, sort_keys=True, indent=4) @@ -516,6 +515,10 @@ class LODocument(object): def path(self): return _path_system(self.obj.getURL()) + @property + def statusbar(self): + return self._cc.getStatusIndicator() + @property def visible(self): w = self._cc.getFrame().getContainerWindow() @@ -1419,16 +1422,28 @@ def _get_class_doc(obj): return classes[type_doc](obj) -def get_document(): +def get_document(title=''): doc = None desktop = get_desktop() - try: + if not title: doc = _get_class_doc(desktop.getCurrentComponent()) - except Exception as e: - log.error(e) + return doc + + for d in desktop.getComponents(): + if d.Title == title: + doc = d + break return doc +def get_documents(): + docs = [] + desktop = get_desktop() + for doc in desktop.getComponents(): + docs.append(_get_class_doc(doc)) + return docs + + def get_selection(): return get_document().selection @@ -1592,6 +1607,14 @@ def new_doc(type_doc=CALC): return _get_class_doc(doc) +def new_db(path): + dbc = create_instance('com.sun.star.sdb.DatabaseContext') + db = dbc.createInstance() + db.URL = 'sdbc:embedded:firebird' # hsqldb + db.DatabaseDocument.storeAsURL(_path_url(path), ()) + return _get_class_doc(db) + + def open_doc(path, **kwargs): """ Open document in path Usually options: @@ -1944,7 +1967,8 @@ def insert_menu(type_doc, name_menu, **kwargs): index_menu = _get_index_menu(menu, command) if index_menu: msg = 'Exists: %s' % command - debug(msg) + if not IS_WIN: + debug(msg) return 0 sub_menu = kwargs.get('Submenu', ()) @@ -1987,7 +2011,8 @@ def remove_menu(type_doc, name_menu, command): index = _get_index_menu(menu, command) if not index: - debug('Not exists: %s' % command) + if not IS_WIN: + debug('Not exists: %s' % command) return False _store_menu(ui, menus, menu, index, remove=True) @@ -1999,7 +2024,8 @@ def _get_app_submenus(menus, count=0): data = property_to_dict(menu) cmd = data.get('CommandURL', '') msg = ' ' * count + '├─' + cmd - debug(msg) + if not IS_WIN: + debug(msg) submenu = data.get('ItemDescriptorContainer', None) if not submenu is None: _get_app_submenus(submenu, count + 1) @@ -2015,7 +2041,8 @@ def get_app_menus(name_app, index=-1): if index == -1: for menu in menus: data = property_to_dict(menu) - debug(data.get('CommandURL', '')) + if not IS_WIN: + debug(data.get('CommandURL', '')) else: menus = property_to_dict(menus[index])['ItemDescriptorContainer'] _get_app_submenus(menus) diff --git a/files/ZAZFavorites_v0.1.0.oxt b/files/ZAZFavorites_v0.1.0.oxt index e6a41af65ce16bad4c192cdf8598f0f3a58e8ed4..09db96c20f0442c3643cf61283c7509065d96485 100644 GIT binary patch delta 15888 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 VaE5`-6GXs`2{b6Jk^f!#{{TvOQbYg% delta 15705 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 uX#azH{r}OTlcL5%K Date: Fri, 27 Sep 2019 14:57:32 -0500 Subject: [PATCH 2/2] Update easymacro --- CHANGELOG | 5 + README.md | 5 +- VERSION | 2 +- conf.py | 2 +- easymacro.py | 847 +++++++++++++++++++++++++++------ files/ZAZFavorites_v0.2.0.oxt | Bin 0 -> 55398 bytes source/description.xml | 2 +- source/pythonpath/easymacro.py | 847 +++++++++++++++++++++++++++------ 8 files changed, 1422 insertions(+), 288 deletions(-) create mode 100644 files/ZAZFavorites_v0.2.0.oxt diff --git a/CHANGELOG b/CHANGELOG index b7b582f..3b183ba 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +v 0.2.0 [27-sep-2019] +--------------------- + - Update easymacro.py + + v 0.1.0 [19-sep-2019] --------------------- - Initial version diff --git a/README.md b/README.md index 7082f2e..16d67fa 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ BCH: `1RPLWHJW34p7pMQV1ft4x7eWhAYw69Dsb` BTC: `3Fe4JuADrAK8Qs7GDAxbSXR8E54avwZJLW` +PayPal :( donate ATT elmau DOT net -* [See the wiki](https://gitlab.com/mauriciobaeza/zaz-favorite/wikis/home) + + +* [Look the wiki](https://gitlab.com/mauriciobaeza/zaz-favorite/wikis/home) * [Mira la wiki](https://gitlab.com/mauriciobaeza/zaz-favorite/wikis/home_es) diff --git a/VERSION b/VERSION index 49b49e4..5faa42c 100644 --- a/VERSION +++ b/VERSION @@ -1,2 +1,2 @@ -0.1.0 +0.2.0 diff --git a/conf.py b/conf.py index 4820939..0543de4 100644 --- a/conf.py +++ b/conf.py @@ -26,7 +26,7 @@ import logging TYPE_EXTENSION = 1 # ~ https://semver.org/ -VERSION = '0.1.0' +VERSION = '0.2.0' # ~ Your great extension name, not used spaces NAME = 'ZAZFavorites' diff --git a/easymacro.py b/easymacro.py index c572a01..3bf53b9 100644 --- a/easymacro.py +++ b/easymacro.py @@ -17,11 +17,12 @@ # ~ You should have received a copy of the GNU General Public License # ~ along with ZAZ. If not, see . - +import base64 import ctypes import datetime import errno import getpass +import hashlib import json import logging import os @@ -34,19 +35,30 @@ import sys import tempfile import threading import time +import traceback import zipfile from collections import OrderedDict from collections.abc import MutableMapping -from datetime import datetime from functools import wraps from operator import itemgetter from pathlib import Path, PurePath from pprint import pprint +from string import Template from subprocess import PIPE +import smtplib +from smtplib import SMTPException, SMTPAuthenticationError +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.utils import formatdate +from email import encoders +import mailbox + import uno import unohelper +from com.sun.star.util import Time, Date, DateTime from com.sun.star.beans import PropertyValue from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES @@ -61,12 +73,20 @@ from com.sun.star.lang import XEventListener from com.sun.star.awt import XActionListener from com.sun.star.awt import XMouseListener +try: + from fernet import Fernet, InvalidToken + CRYPTO = True +except ImportError: + CRYPTO = False + MSG_LANG = { 'es': { 'OK': 'Aceptar', 'Cancel': 'Cancelar', 'Select file': 'Seleccionar archivo', + 'Incorrect user or password': 'Nombre de usuario o contraseña inválidos', + 'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail', } } @@ -95,7 +115,8 @@ TYPE_DOC = { 'writer': 'com.sun.star.text.TextDocument', 'impress': 'com.sun.star.presentation.PresentationDocument', 'draw': 'com.sun.star.drawing.DrawingDocument', - 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', + # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', + 'base': 'com.sun.star.sdb.DocumentDataSource', 'math': 'com.sun.star.formula.FormulaProperties', 'basic': 'com.sun.star.script.BasicIDE', } @@ -134,6 +155,11 @@ MENUS_APP = { } +EXT = { + 'pdf': 'pdf', +} + + FILE_NAME_DEBUG = 'debug.odt' FILE_NAME_CONFIG = 'zaz-{}.json' LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -145,10 +171,16 @@ logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) log = logging.getLogger(__name__) +_start = 0 +_stop_thread = {} +TIMEOUT = 10 + + CTX = uno.getComponentContext() SM = CTX.getServiceManager() +# ~ Export ok def create_instance(name, with_context=False): if with_context: instance = SM.createInstanceWithContext(name, CTX) @@ -178,6 +210,7 @@ NAME = TITLE = _get_app_config('ooName', 'org.openoffice.Setup/Product') VERSION = _get_app_config('ooSetupVersion', 'org.openoffice.Setup/Product') +# ~ Export ok def mri(obj): m = create_instance('mytools.Mri') if m is None: @@ -206,26 +239,29 @@ class LogWin(object): def __init__(self, doc): self.doc = doc - # ~ self.doc.Title = FILE_NAME_DEBUG + self.doc.Title = FILE_NAME_DEBUG def write(self, info): text = self.doc.Text cursor = text.createTextCursor() cursor.gotoEnd(False) - text.insertString(cursor, str(info), 0) + text.insertString(cursor, str(info) + '\n\n', 0) return +# ~ Export ok def info(data): log.info(data) return +# ~ Export ok def debug(info): if IS_WIN: doc = get_document(FILE_NAME_DEBUG) if doc is None: - doc = new_doc('writer') + # ~ doc = new_doc('writer') + return doc = LogWin(doc.obj) doc.write(info) return @@ -234,14 +270,16 @@ def debug(info): return +# ~ Export ok def error(info): log.error(info) return +# ~ Export ok def save_log(path, data): with open(path, 'a') as out: - out.write('{} -{}- '.format(str(datetime.now())[:19], LOG_NAME)) + out.write('{} -{}- '.format(str(now())[:19], LOG_NAME)) pprint(data, stream=out) return @@ -254,6 +292,11 @@ def run_in_thread(fn): return run +def now(): + return datetime.datetime.now() + + +# ~ Export ok def get_config(key='', default=None, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) values = None @@ -271,6 +314,7 @@ def get_config(key='', default=None, prefix='config'): return values +# ~ Export ok def set_config(key, value, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) path = join(get_config_path('UserConfig'), path_json) @@ -278,9 +322,10 @@ def set_config(key, value, prefix='config'): values[key] = value with open(path, 'w', encoding='utf-8') as fh: json.dump(values, fh, ensure_ascii=False, sort_keys=True, indent=4) - return True + return +# ~ Export ok def sleep(seconds): time.sleep(seconds) return @@ -297,6 +342,7 @@ def _(msg): return MSG_LANG[L][msg] +# ~ Export ok def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'): """ Create message box type_msg: infobox, warningbox, errorbox, querybox, messbox @@ -308,15 +354,18 @@ def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infob return mb.execute() +# ~ Export ok def question(message, title=TITLE): res = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') return res == YES +# ~ Export ok def warning(message, title=TITLE): return msgbox(message, title, type_msg='warningbox') +# ~ Export ok def errorbox(message, title=TITLE): return msgbox(message, title, type_msg='errorbox') @@ -325,10 +374,20 @@ def get_desktop(): return create_instance('com.sun.star.frame.Desktop', True) +# ~ Export ok def get_dispatch(): return create_instance('com.sun.star.frame.DispatchHelper') +# ~ Export ok +def call_dispatch(url, args=()): + frame = get_document().frame + dispatch = get_dispatch() + dispatch.executeDispatch(frame, url, '', 0, args) + return + + +# ~ Export ok def get_temp_file(): delete = True if IS_WIN: @@ -348,6 +407,7 @@ def _path_system(path): return path +# ~ Export ok def exists_app(name): try: dn = subprocess.DEVNULL @@ -357,33 +417,20 @@ def exists_app(name): return False return True -# ~ Delete -# ~ def exists(path): - # ~ return Path(path).exists() + +# ~ Export ok def exists_path(path): return Path(path).exists() +# ~ Export ok def get_type_doc(obj): - # ~ services = { - # ~ 'calc': 'com.sun.star.sheet.SpreadsheetDocument', - # ~ 'writer': 'com.sun.star.text.TextDocument', - # ~ 'impress': 'com.sun.star.presentation.PresentationDocument', - # ~ 'draw': 'com.sun.star.drawing.DrawingDocument', - # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', - # ~ 'math': 'com.sun.star.formula.FormulaProperties', - # ~ 'basic': 'com.sun.star.script.BasicIDE', - # ~ } for k, v in TYPE_DOC.items(): if obj.supportsService(v): return k return '' -# ~ def _properties(values): - # ~ p = [PropertyValue(Name=n, Value=v) for n, v in values.items()] - # ~ return tuple(p) - def dict_to_property(values, uno_any=False): ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) if uno_any: @@ -396,82 +443,12 @@ def property_to_dict(values): return d -# ~ Third classes - - -# ~ https://github.com/psf/requests/blob/v2.22.0/requests/structures.py -class CaseInsensitiveDict(MutableMapping): - """A case-insensitive ``dict``-like object. - Implements all methods and operations of - ``MutableMapping`` as well as dict's ``copy``. Also - provides ``lower_items``. - All keys are expected to be strings. The structure remembers the - case of the last key to be set, and ``iter(instance)``, - ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` - will contain case-sensitive keys. However, querying and contains - testing is case insensitive:: - cid = CaseInsensitiveDict() - cid['Accept'] = 'application/json' - cid['aCCEPT'] == 'application/json' # True - list(cid) == ['Accept'] # True - For example, ``headers['content-encoding']`` will return the - value of a ``'Content-Encoding'`` response header, regardless - of how the header name was originally stored. - If the constructor, ``.update``, or equality comparison - operations are given keys that have equal ``.lower()``s, the - behavior is undefined. - """ - - def __init__(self, data=None, **kwargs): - self._store = OrderedDict() - if data is None: - data = {} - self.update(data, **kwargs) - - def __setitem__(self, key, value): - # Use the lowercased key for lookups, but store the actual - # key alongside the value. - self._store[key.lower()] = (key, value) - - def __getitem__(self, key): - return self._store[key.lower()][1] - - def __delitem__(self, key): - del self._store[key.lower()] - - def __iter__(self): - return (casedkey for casedkey, mappedvalue in self._store.values()) - - def __len__(self): - return len(self._store) - - def lower_items(self): - """Like iteritems(), but with all lowercase keys.""" - return ( - (lowerkey, keyval[1]) - for (lowerkey, keyval) - in self._store.items() - ) - - def __eq__(self, other): - if isinstance(other, Mapping): - other = CaseInsensitiveDict(other) - else: - return NotImplemented - # Compare insensitively - return dict(self.lower_items()) == dict(other.lower_items()) - - # Copy is required - def copy(self): - return CaseInsensitiveDict(self._store.values()) - - def __repr__(self): - return str(dict(self.items())) +def array_to_dict(values): + d = {r[0]: r[1] for r in values} + return d # ~ Custom classes - - class LODocument(object): def __init__(self, obj): @@ -480,7 +457,10 @@ class LODocument(object): def _init_values(self): self._type_doc = get_type_doc(self.obj) - self._cc = self.obj.getCurrentController() + if self._type_doc == 'base': + self._cc = self.obj.DatabaseDocument.getCurrentController() + else: + self._cc = self.obj.getCurrentController() return @property @@ -563,6 +543,30 @@ class LODocument(object): self._cc.insertTransferable(transferable) return self.obj.getCurrentSelection() + def to_pdf(self, path, **kwargs): + path_pdf = path + if path: + if is_dir(path): + _, _, n, _ = get_info_path(self.path) + path_pdf = join(path, '{}.{}'.format(n, EXT['pdf'])) + else: + path_pdf = replace_ext(self.path, EXT['pdf']) + + filter_name = '{}_pdf_Export'.format(self.type) + filter_data = dict_to_property(kwargs, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + args = dict_to_property(args) + try: + self.obj.storeToURL(_path_url(path_pdf), args) + except Exception as e: + error(e) + path_pdf = '' + + return path_pdf + class LOCalc(LODocument): @@ -713,7 +717,6 @@ class LODrawImpress(LODocument): def draw_page(self): return self._cc.getCurrentPage() - @catch_exception def insert_image(self, path, **kwargs): w = kwargs.get('width', 3000) h = kwargs.get('Height', 1000) @@ -1124,6 +1127,10 @@ class UnoListBox(UnoBaseObject): super().__init__(obj) self._data = [] + @property + def type(self): + return 'listbox' + @property def value(self): return self.obj.SelectedItem @@ -1384,7 +1391,6 @@ class LODialog(object): return _path_url(path) return '' - @catch_exception def add_control(self, properties): tipo = properties.pop('Type').lower() @@ -1422,6 +1428,7 @@ def _get_class_doc(obj): return classes[type_doc](obj) +# ~ Export ok def get_document(title=''): doc = None desktop = get_desktop() @@ -1433,14 +1440,22 @@ def get_document(title=''): if d.Title == title: doc = d break - return doc + + if doc is None: + return + + return _get_class_doc(doc) -def get_documents(): +# ~ Export ok +def get_documents(custom=True): docs = [] desktop = get_desktop() for doc in desktop.getComponents(): - docs.append(_get_class_doc(doc)) + if custom: + docs.append(_get_class_doc(doc)) + else: + docs.append(doc) return docs @@ -1478,6 +1493,7 @@ def set_properties(model, properties): return +# ~ Export ok def get_config_path(name='Work'): """ Return de path name in config @@ -1487,6 +1503,7 @@ def get_config_path(name='Work'): return _path_system(getattr(path, name)) +# ~ Export ok def get_file(init_dir='', multiple=False, filters=()): """ init_folder: folder default open @@ -1505,32 +1522,109 @@ def get_file(init_dir='', multiple=False, filters=()): file_picker.setDisplayDirectory(init_dir) file_picker.setMultiSelectionMode(multiple) + path = '' if filters: file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) if file_picker.execute(): + path = _path_system(file_picker.getSelectedFiles()[0]) if multiple: - return [_path_system(f) for f in file_picker.getSelectedFiles()] - return _path_system(file_picker.getSelectedFiles()[0]) + path = [_path_system(f) for f in file_picker.getSelectedFiles()] - return '' + return path +# ~ Export ok +def get_path(init_dir='', filters=()): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = get_config_path() + init_dir = _path_url(init_dir) + file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + path = _path_system(file_picker.getSelectedFiles()[0]) + return path + + +# ~ Export ok +def get_dir(init_dir=''): + folder_picker = create_instance('com.sun.star.ui.dialogs.FolderPicker') + if not init_dir: + init_dir = get_config_path() + init_dir = _path_url(init_dir) + folder_picker.setDisplayDirectory(init_dir) + + path = '' + if folder_picker.execute(): + path = _path_system(folder_picker.getDirectory()) + return path + + +# ~ Export ok def get_info_path(path): path, filename = os.path.split(path) name, extension = os.path.splitext(filename) return (path, filename, name, extension) +# ~ Export ok +def read_file(path, mode='r', array=False): + data = '' + with open(path, mode) as f: + if array: + data = tuple(f.read().splitlines()) + else: + data = f.read() + return data + + +# ~ Export ok +def save_file(path, mode='w', data=None): + with open(path, mode) as f: + f.write(data) + return + + +# ~ Export ok +def to_json(path, data): + with open(path, 'w') as f: + f.write(json.dumps(data, indent=4, sort_keys=True)) + return + + +# ~ Export ok +def from_json(path): + with open(path) as f: + data = json.loads(f.read()) + return data + + def get_path_extension(id): pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') path = _path_system(pip.getPackageLocation(id)) return path -def inputbox(message, default='', title=TITLE): +# ~ Export ok +def inputbox(message, default='', title=TITLE, echochar=''): class ControllersInput(object): @@ -1569,6 +1663,8 @@ def inputbox(message, default='', title=TITLE): 'Width': 190, 'Height': 15, } + if echochar: + args['EchoChar'] = ord(echochar[0]) dlg.add_control(args) dlg.txt_value.move(dlg.lbl_msg) @@ -1601,12 +1697,15 @@ def inputbox(message, default='', title=TITLE): return '' -def new_doc(type_doc=CALC): +# ~ Export ok +def new_doc(type_doc=CALC, **kwargs): path = 'private:factory/s{}'.format(type_doc) - doc = get_desktop().loadComponentFromURL(path, '_default', 0, ()) + opt = dict_to_property(kwargs) + doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) return _get_class_doc(doc) +# ~ Export ok def new_db(path): dbc = create_instance('com.sun.star.sdb.DatabaseContext') db = dbc.createInstance() @@ -1615,6 +1714,7 @@ def new_db(path): return _get_class_doc(db) +# ~ Export ok def open_doc(path, **kwargs): """ Open document in path Usually options: @@ -1629,7 +1729,6 @@ def open_doc(path, **kwargs): http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html """ path = _path_url(path) - # ~ opt = _properties(kwargs) opt = dict_to_property(kwargs) doc = get_desktop().loadComponentFromURL(path, '_blank', 0, opt) if doc is None: @@ -1638,6 +1737,7 @@ def open_doc(path, **kwargs): return _get_class_doc(doc) +# ~ Export ok def open_file(path): if IS_WIN: os.startfile(path) @@ -1646,37 +1746,45 @@ def open_file(path): return +# ~ Export ok def join(*paths): return os.path.join(*paths) +# ~ Export ok def is_dir(path): return Path(path).is_dir() +# ~ Export ok def is_file(path): return Path(path).is_file() +# ~ Export ok def get_file_size(path): return Path(path).stat().st_size +# ~ Export ok def is_created(path): return is_file(path) and bool(get_file_size(path)) +# ~ Export ok def replace_ext(path, extension): path, _, name, _ = get_info_path(path) return '{}/{}.{}'.format(path, name, extension) -def zip_names(path): +# ~ Export ok +def zip_content(path): with zipfile.ZipFile(path) as z: names = z.namelist() return names +# ~ Export ok def run(command, wait=False): # ~ debug(command) # ~ debug(shlex.split(command)) @@ -1731,6 +1839,7 @@ def _zippwd(source, target, pwd): return is_created(target) +# ~ Export ok def zip(source, target='', mode='w', pwd=''): if pwd: return _zippwd(source, target, pwd) @@ -1772,6 +1881,7 @@ def zip(source, target='', mode='w', pwd=''): return is_created(target) +# ~ Export ok def unzip(source, path='', members=None, pwd=None): if not path: path, _, _, _ = get_info_path(source) @@ -1784,6 +1894,7 @@ def unzip(source, path='', members=None, pwd=None): return True +# ~ Export ok def merge_zip(target, zips): try: with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: @@ -1798,18 +1909,20 @@ def merge_zip(target, zips): return True +# ~ Export ok def kill(path): p = Path(path) - if p.is_file(): - try: + try: + if p.is_file(): p.unlink() - except: - pass - elif p.is_dir(): - p.rmdir() + elif p.is_dir(): + shutil.rmtree(path) + except OSError as e: + log.error(e) return +# ~ Export ok def get_size_screen(): if IS_WIN: user32 = ctypes.windll.user32 @@ -1820,6 +1933,7 @@ def get_size_screen(): return res.strip() +# ~ Export ok def get_clipboard(): df = None text = '' @@ -1865,6 +1979,7 @@ class TextTransferable(unohelper.Base, XTransferable): return False +# ~ Export ok def set_clipboard(value): ts = TextTransferable(value) sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') @@ -1872,40 +1987,38 @@ def set_clipboard(value): return -def copy(doc=None): - if doc is None: - doc = get_document() - if hasattr(doc, 'frame'): - frame = doc.frame - else: - frame = doc.getCurrentController().getFrame() - dispatch = get_dispatch() - dispatch.executeDispatch(frame, '.uno:Copy', '', 0, ()) +# ~ Todo +def copy(): + call_dispatch('.uno:Copy') return +# ~ Export ok def get_epoch(): - now = datetime.datetime.now() - return int(time.mktime(now.timetuple())) + n = now() + return int(time.mktime(n.timetuple())) +# ~ Export ok def file_copy(source, target='', name=''): p, f, n, e = get_info_path(source) if target: p = target if name: + e = '' n = name path_new = join(p, '{}{}'.format(n, e)) shutil.copy(source, path_new) - return + return path_new -def get_files(path, ext='*'): - docs = [] +# ~ Export ok +def get_path_content(path, filters='*'): + paths = [] for folder, _, files in os.walk(path): - pattern = re.compile(r'\.{}'.format(ext), re.IGNORECASE) - docs += [join(folder, f) for f in files if pattern.search(f)] - return docs + pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE) + paths += [join(folder, f) for f in files if pattern.search(f)] + return paths def _get_menu(type_doc, name_menu): @@ -1967,8 +2080,7 @@ def insert_menu(type_doc, name_menu, **kwargs): index_menu = _get_index_menu(menu, command) if index_menu: msg = 'Exists: %s' % command - if not IS_WIN: - debug(msg) + debug(msg) return 0 sub_menu = kwargs.get('Submenu', ()) @@ -2011,8 +2123,7 @@ def remove_menu(type_doc, name_menu, command): index = _get_index_menu(menu, command) if not index: - if not IS_WIN: - debug('Not exists: %s' % command) + debug('Not exists: %s' % command) return False _store_menu(ui, menus, menu, index, remove=True) @@ -2024,8 +2135,7 @@ def _get_app_submenus(menus, count=0): data = property_to_dict(menu) cmd = data.get('CommandURL', '') msg = ' ' * count + '├─' + cmd - if not IS_WIN: - debug(msg) + debug(msg) submenu = data.get('ItemDescriptorContainer', None) if not submenu is None: _get_app_submenus(submenu, count + 1) @@ -2041,14 +2151,467 @@ def get_app_menus(name_app, index=-1): if index == -1: for menu in menus: data = property_to_dict(menu) - if not IS_WIN: - debug(data.get('CommandURL', '')) + debug(data.get('CommandURL', '')) else: menus = property_to_dict(menus[index])['ItemDescriptorContainer'] _get_app_submenus(menus) return menus +# ~ Export ok +def start(): + global _start + _start = now() + log.info(_start) + return + + +# ~ Export ok +def end(): + global _start + e = now() + return str(e - _start).split('.')[0] + + +# ~ Export ok +# ~ https://en.wikipedia.org/wiki/Web_colors +def get_color(value): + COLORS = { + 'aliceblue': 15792383, + 'antiquewhite': 16444375, + 'aqua': 65535, + 'aquamarine': 8388564, + 'azure': 15794175, + 'beige': 16119260, + 'bisque': 16770244, + 'black': 0, + 'blanchedalmond': 16772045, + 'blue': 255, + 'blueviolet': 9055202, + 'brown': 10824234, + 'burlywood': 14596231, + 'cadetblue': 6266528, + 'chartreuse': 8388352, + 'chocolate': 13789470, + 'coral': 16744272, + 'cornflowerblue': 6591981, + 'cornsilk': 16775388, + 'crimson': 14423100, + 'cyan': 65535, + 'darkblue': 139, + 'darkcyan': 35723, + 'darkgoldenrod': 12092939, + 'darkgray': 11119017, + 'darkgreen': 25600, + 'darkgrey': 11119017, + 'darkkhaki': 12433259, + 'darkmagenta': 9109643, + 'darkolivegreen': 5597999, + 'darkorange': 16747520, + 'darkorchid': 10040012, + 'darkred': 9109504, + 'darksalmon': 15308410, + 'darkseagreen': 9419919, + 'darkslateblue': 4734347, + 'darkslategray': 3100495, + 'darkslategrey': 3100495, + 'darkturquoise': 52945, + 'darkviolet': 9699539, + 'deeppink': 16716947, + 'deepskyblue': 49151, + 'dimgray': 6908265, + 'dimgrey': 6908265, + 'dodgerblue': 2003199, + 'firebrick': 11674146, + 'floralwhite': 16775920, + 'forestgreen': 2263842, + 'fuchsia': 16711935, + 'gainsboro': 14474460, + 'ghostwhite': 16316671, + 'gold': 16766720, + 'goldenrod': 14329120, + 'gray': 8421504, + 'grey': 8421504, + 'green': 32768, + 'greenyellow': 11403055, + 'honeydew': 15794160, + 'hotpink': 16738740, + 'indianred': 13458524, + 'indigo': 4915330, + 'ivory': 16777200, + 'khaki': 15787660, + 'lavender': 15132410, + 'lavenderblush': 16773365, + 'lawngreen': 8190976, + 'lemonchiffon': 16775885, + 'lightblue': 11393254, + 'lightcoral': 15761536, + 'lightcyan': 14745599, + 'lightgoldenrodyellow': 16448210, + 'lightgray': 13882323, + 'lightgreen': 9498256, + 'lightgrey': 13882323, + 'lightpink': 16758465, + 'lightsalmon': 16752762, + 'lightseagreen': 2142890, + 'lightskyblue': 8900346, + 'lightslategray': 7833753, + 'lightslategrey': 7833753, + 'lightsteelblue': 11584734, + 'lightyellow': 16777184, + 'lime': 65280, + 'limegreen': 3329330, + 'linen': 16445670, + 'magenta': 16711935, + 'maroon': 8388608, + 'mediumaquamarine': 6737322, + 'mediumblue': 205, + 'mediumorchid': 12211667, + 'mediumpurple': 9662683, + 'mediumseagreen': 3978097, + 'mediumslateblue': 8087790, + 'mediumspringgreen': 64154, + 'mediumturquoise': 4772300, + 'mediumvioletred': 13047173, + 'midnightblue': 1644912, + 'mintcream': 16121850, + 'mistyrose': 16770273, + 'moccasin': 16770229, + 'navajowhite': 16768685, + 'navy': 128, + 'oldlace': 16643558, + 'olive': 8421376, + 'olivedrab': 7048739, + 'orange': 16753920, + 'orangered': 16729344, + 'orchid': 14315734, + 'palegoldenrod': 15657130, + 'palegreen': 10025880, + 'paleturquoise': 11529966, + 'palevioletred': 14381203, + 'papayawhip': 16773077, + 'peachpuff': 16767673, + 'peru': 13468991, + 'pink': 16761035, + 'plum': 14524637, + 'powderblue': 11591910, + 'purple': 8388736, + 'red': 16711680, + 'rosybrown': 12357519, + 'royalblue': 4286945, + 'saddlebrown': 9127187, + 'salmon': 16416882, + 'sandybrown': 16032864, + 'seagreen': 3050327, + 'seashell': 16774638, + 'sienna': 10506797, + 'silver': 12632256, + 'skyblue': 8900331, + 'slateblue': 6970061, + 'slategray': 7372944, + 'slategrey': 7372944, + 'snow': 16775930, + 'springgreen': 65407, + 'steelblue': 4620980, + 'tan': 13808780, + 'teal': 32896, + 'thistle': 14204888, + 'tomato': 16737095, + 'turquoise': 4251856, + 'violet': 15631086, + 'wheat': 16113331, + 'white': 16777215, + 'whitesmoke': 16119285, + 'yellow': 16776960, + 'yellowgreen': 10145074, + } + + if isinstance(value, tuple): + return (value[0] << 16) + (value[1] << 8) + value[2] + + if isinstance(value, str) and value[0] == '#': + r, g, b = bytes.fromhex(value[1:]) + return (r << 16) + (g << 8) + b + + return COLORS.get(value.lower(), -1) + + +# ~ Export ok +def render(template, data): + s = Template(template) + return s.safe_substitute(**data) + + +def _to_date(value): + new_value = value + if isinstance(value, Time): + new_value = datetime.time(value.Hours, value.Minutes, value.Seconds) + elif isinstance(value, Date): + new_value = datetime.date(value.Year, value.Month, value.Day) + elif isinstance(value, DateTime): + new_value = datetime.datetime( + value.Year, value.Month, value.Day, + value.Hours, value.Minutes, value.Seconds) + return new_value + + +# ~ Export ok +def format(template, data): + """ + https://pyformat.info/ + """ + if isinstance(data, (str, int, float)): + # ~ print(template.format(data)) + return template.format(data) + + if isinstance(data, (Time, Date, DateTime)): + return template.format(_to_date(data)) + + if isinstance(data, tuple) and isinstance(data[0], tuple): + data = {r[0]: _to_date(r[1]) for r in data} + return template.format(**data) + + data = [_to_date(v) for v in data] + result = template.format(*data) + return result + + +def _call_macro(macro): + #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification + name = 'com.sun.star.script.provider.MasterScriptProviderFactory' + factory = create_instance(name, False) + + data = macro.copy() + if macro['language'] == 'Python': + data['module'] = '.py$' + elif macro['language'] == 'Basic': + data['module'] = '.{}.'.format(macro['module']) + if macro['location'] == 'user': + data['location'] = 'application' + else: + data['module'] = '.' + + args = macro.get('args', ()) + url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' + path = url.format(**data) + script = factory.createScriptProvider('').getScript(path) + return script.invoke(args, None, None)[0] + + +# ~ Export ok +def call_macro(macro): + in_thread = macro.pop('thread') + if in_thread: + t = threading.Thread(target=_call_macro, args=(macro,)) + t.start() + return + + return _call_macro(macro) + + +class TimerThread(threading.Thread): + + def __init__(self, event, seconds, macro): + threading.Thread.__init__(self) + self.stopped = event + self.seconds = seconds + self.macro = macro + + def run(self): + info('Timer started... {}'.format(self.macro['name'])) + while not self.stopped.wait(self.seconds): + _call_macro(self.macro) + info('Timer stopped... {}'.format(self.macro['name'])) + return + + +# ~ Export ok +def timer(name, seconds, macro): + global _stop_thread + _stop_thread[name] = threading.Event() + thread = TimerThread(_stop_thread[name], seconds, macro) + thread.start() + return + + +# ~ Export ok +def stop_timer(name): + global _stop_thread + _stop_thread[name].set() + del _stop_thread[name] + return + + +def _get_key(password): + digest = hashlib.sha256(password.encode()).digest() + key = base64.urlsafe_b64encode(digest) + return key + + +# ~ Export ok +def encrypt(data, password): + f = Fernet(_get_key(password)) + token = f.encrypt(data).decode() + return token + + +# ~ Export ok +def decrypt(token, password): + data = '' + f = Fernet(_get_key(password)) + try: + data = f.decrypt(token.encode()).decode() + except InvalidToken as e: + error('Invalid Token') + return data + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['pass']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\\n', '
') + return body + + def send(self, message): + file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + for path in message.get('files', ()): + _, fn, _, _ = get_info_path(path) + part = MIMEBase('application', 'octet-stream') + part.set_payload(read_file(path, 'rb')) + encoders.encode_base64(part) + part.add_header('Content-Disposition', file_name.format(fn)) + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +def _send_email(server, messages): + with SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + +def send_email(server, message): + messages = message + if isinstance(message, dict): + messages = (message,) + t = threading.Thread(target=_send_email, args=(server, messages)) + t.start() + return + + +def server_smtp_test(config): + with SmtpServer(config) as server: + if server.error: + error(server.error) + return server.error + + # ~ name = 'com.sun.star.configuration.ConfigurationProvider' # ~ cp = create_instance(name, True) # ~ node = PropertyValue(Name='nodepath', Value=NODE_SETTING) diff --git a/files/ZAZFavorites_v0.2.0.oxt b/files/ZAZFavorites_v0.2.0.oxt new file mode 100644 index 0000000000000000000000000000000000000000..acaa4662d69474449cc1902b3cc923779a530038 GIT binary patch literal 55398 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 - + Favorites files Archivos favoritos diff --git a/source/pythonpath/easymacro.py b/source/pythonpath/easymacro.py index c572a01..3bf53b9 100644 --- a/source/pythonpath/easymacro.py +++ b/source/pythonpath/easymacro.py @@ -17,11 +17,12 @@ # ~ You should have received a copy of the GNU General Public License # ~ along with ZAZ. If not, see . - +import base64 import ctypes import datetime import errno import getpass +import hashlib import json import logging import os @@ -34,19 +35,30 @@ import sys import tempfile import threading import time +import traceback import zipfile from collections import OrderedDict from collections.abc import MutableMapping -from datetime import datetime from functools import wraps from operator import itemgetter from pathlib import Path, PurePath from pprint import pprint +from string import Template from subprocess import PIPE +import smtplib +from smtplib import SMTPException, SMTPAuthenticationError +from email.mime.multipart import MIMEMultipart +from email.mime.base import MIMEBase +from email.mime.text import MIMEText +from email.utils import formatdate +from email import encoders +import mailbox + import uno import unohelper +from com.sun.star.util import Time, Date, DateTime from com.sun.star.beans import PropertyValue from com.sun.star.awt import MessageBoxButtons as MSG_BUTTONS from com.sun.star.awt.MessageBoxResults import YES @@ -61,12 +73,20 @@ from com.sun.star.lang import XEventListener from com.sun.star.awt import XActionListener from com.sun.star.awt import XMouseListener +try: + from fernet import Fernet, InvalidToken + CRYPTO = True +except ImportError: + CRYPTO = False + MSG_LANG = { 'es': { 'OK': 'Aceptar', 'Cancel': 'Cancelar', 'Select file': 'Seleccionar archivo', + 'Incorrect user or password': 'Nombre de usuario o contraseña inválidos', + 'Allow less secure apps in GMail': 'Activa: Permitir aplicaciones menos segura en GMail', } } @@ -95,7 +115,8 @@ TYPE_DOC = { 'writer': 'com.sun.star.text.TextDocument', 'impress': 'com.sun.star.presentation.PresentationDocument', 'draw': 'com.sun.star.drawing.DrawingDocument', - 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', + # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', + 'base': 'com.sun.star.sdb.DocumentDataSource', 'math': 'com.sun.star.formula.FormulaProperties', 'basic': 'com.sun.star.script.BasicIDE', } @@ -134,6 +155,11 @@ MENUS_APP = { } +EXT = { + 'pdf': 'pdf', +} + + FILE_NAME_DEBUG = 'debug.odt' FILE_NAME_CONFIG = 'zaz-{}.json' LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s' @@ -145,10 +171,16 @@ logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT, datefmt=LOG_DATE) log = logging.getLogger(__name__) +_start = 0 +_stop_thread = {} +TIMEOUT = 10 + + CTX = uno.getComponentContext() SM = CTX.getServiceManager() +# ~ Export ok def create_instance(name, with_context=False): if with_context: instance = SM.createInstanceWithContext(name, CTX) @@ -178,6 +210,7 @@ NAME = TITLE = _get_app_config('ooName', 'org.openoffice.Setup/Product') VERSION = _get_app_config('ooSetupVersion', 'org.openoffice.Setup/Product') +# ~ Export ok def mri(obj): m = create_instance('mytools.Mri') if m is None: @@ -206,26 +239,29 @@ class LogWin(object): def __init__(self, doc): self.doc = doc - # ~ self.doc.Title = FILE_NAME_DEBUG + self.doc.Title = FILE_NAME_DEBUG def write(self, info): text = self.doc.Text cursor = text.createTextCursor() cursor.gotoEnd(False) - text.insertString(cursor, str(info), 0) + text.insertString(cursor, str(info) + '\n\n', 0) return +# ~ Export ok def info(data): log.info(data) return +# ~ Export ok def debug(info): if IS_WIN: doc = get_document(FILE_NAME_DEBUG) if doc is None: - doc = new_doc('writer') + # ~ doc = new_doc('writer') + return doc = LogWin(doc.obj) doc.write(info) return @@ -234,14 +270,16 @@ def debug(info): return +# ~ Export ok def error(info): log.error(info) return +# ~ Export ok def save_log(path, data): with open(path, 'a') as out: - out.write('{} -{}- '.format(str(datetime.now())[:19], LOG_NAME)) + out.write('{} -{}- '.format(str(now())[:19], LOG_NAME)) pprint(data, stream=out) return @@ -254,6 +292,11 @@ def run_in_thread(fn): return run +def now(): + return datetime.datetime.now() + + +# ~ Export ok def get_config(key='', default=None, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) values = None @@ -271,6 +314,7 @@ def get_config(key='', default=None, prefix='config'): return values +# ~ Export ok def set_config(key, value, prefix='config'): path_json = FILE_NAME_CONFIG.format(prefix) path = join(get_config_path('UserConfig'), path_json) @@ -278,9 +322,10 @@ def set_config(key, value, prefix='config'): values[key] = value with open(path, 'w', encoding='utf-8') as fh: json.dump(values, fh, ensure_ascii=False, sort_keys=True, indent=4) - return True + return +# ~ Export ok def sleep(seconds): time.sleep(seconds) return @@ -297,6 +342,7 @@ def _(msg): return MSG_LANG[L][msg] +# ~ Export ok def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infobox'): """ Create message box type_msg: infobox, warningbox, errorbox, querybox, messbox @@ -308,15 +354,18 @@ def msgbox(message, title=TITLE, buttons=MSG_BUTTONS.BUTTONS_OK, type_msg='infob return mb.execute() +# ~ Export ok def question(message, title=TITLE): res = msgbox(message, title, MSG_BUTTONS.BUTTONS_YES_NO, 'querybox') return res == YES +# ~ Export ok def warning(message, title=TITLE): return msgbox(message, title, type_msg='warningbox') +# ~ Export ok def errorbox(message, title=TITLE): return msgbox(message, title, type_msg='errorbox') @@ -325,10 +374,20 @@ def get_desktop(): return create_instance('com.sun.star.frame.Desktop', True) +# ~ Export ok def get_dispatch(): return create_instance('com.sun.star.frame.DispatchHelper') +# ~ Export ok +def call_dispatch(url, args=()): + frame = get_document().frame + dispatch = get_dispatch() + dispatch.executeDispatch(frame, url, '', 0, args) + return + + +# ~ Export ok def get_temp_file(): delete = True if IS_WIN: @@ -348,6 +407,7 @@ def _path_system(path): return path +# ~ Export ok def exists_app(name): try: dn = subprocess.DEVNULL @@ -357,33 +417,20 @@ def exists_app(name): return False return True -# ~ Delete -# ~ def exists(path): - # ~ return Path(path).exists() + +# ~ Export ok def exists_path(path): return Path(path).exists() +# ~ Export ok def get_type_doc(obj): - # ~ services = { - # ~ 'calc': 'com.sun.star.sheet.SpreadsheetDocument', - # ~ 'writer': 'com.sun.star.text.TextDocument', - # ~ 'impress': 'com.sun.star.presentation.PresentationDocument', - # ~ 'draw': 'com.sun.star.drawing.DrawingDocument', - # ~ 'base': 'com.sun.star.sdb.OfficeDatabaseDocument', - # ~ 'math': 'com.sun.star.formula.FormulaProperties', - # ~ 'basic': 'com.sun.star.script.BasicIDE', - # ~ } for k, v in TYPE_DOC.items(): if obj.supportsService(v): return k return '' -# ~ def _properties(values): - # ~ p = [PropertyValue(Name=n, Value=v) for n, v in values.items()] - # ~ return tuple(p) - def dict_to_property(values, uno_any=False): ps = tuple([PropertyValue(Name=n, Value=v) for n, v in values.items()]) if uno_any: @@ -396,82 +443,12 @@ def property_to_dict(values): return d -# ~ Third classes - - -# ~ https://github.com/psf/requests/blob/v2.22.0/requests/structures.py -class CaseInsensitiveDict(MutableMapping): - """A case-insensitive ``dict``-like object. - Implements all methods and operations of - ``MutableMapping`` as well as dict's ``copy``. Also - provides ``lower_items``. - All keys are expected to be strings. The structure remembers the - case of the last key to be set, and ``iter(instance)``, - ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` - will contain case-sensitive keys. However, querying and contains - testing is case insensitive:: - cid = CaseInsensitiveDict() - cid['Accept'] = 'application/json' - cid['aCCEPT'] == 'application/json' # True - list(cid) == ['Accept'] # True - For example, ``headers['content-encoding']`` will return the - value of a ``'Content-Encoding'`` response header, regardless - of how the header name was originally stored. - If the constructor, ``.update``, or equality comparison - operations are given keys that have equal ``.lower()``s, the - behavior is undefined. - """ - - def __init__(self, data=None, **kwargs): - self._store = OrderedDict() - if data is None: - data = {} - self.update(data, **kwargs) - - def __setitem__(self, key, value): - # Use the lowercased key for lookups, but store the actual - # key alongside the value. - self._store[key.lower()] = (key, value) - - def __getitem__(self, key): - return self._store[key.lower()][1] - - def __delitem__(self, key): - del self._store[key.lower()] - - def __iter__(self): - return (casedkey for casedkey, mappedvalue in self._store.values()) - - def __len__(self): - return len(self._store) - - def lower_items(self): - """Like iteritems(), but with all lowercase keys.""" - return ( - (lowerkey, keyval[1]) - for (lowerkey, keyval) - in self._store.items() - ) - - def __eq__(self, other): - if isinstance(other, Mapping): - other = CaseInsensitiveDict(other) - else: - return NotImplemented - # Compare insensitively - return dict(self.lower_items()) == dict(other.lower_items()) - - # Copy is required - def copy(self): - return CaseInsensitiveDict(self._store.values()) - - def __repr__(self): - return str(dict(self.items())) +def array_to_dict(values): + d = {r[0]: r[1] for r in values} + return d # ~ Custom classes - - class LODocument(object): def __init__(self, obj): @@ -480,7 +457,10 @@ class LODocument(object): def _init_values(self): self._type_doc = get_type_doc(self.obj) - self._cc = self.obj.getCurrentController() + if self._type_doc == 'base': + self._cc = self.obj.DatabaseDocument.getCurrentController() + else: + self._cc = self.obj.getCurrentController() return @property @@ -563,6 +543,30 @@ class LODocument(object): self._cc.insertTransferable(transferable) return self.obj.getCurrentSelection() + def to_pdf(self, path, **kwargs): + path_pdf = path + if path: + if is_dir(path): + _, _, n, _ = get_info_path(self.path) + path_pdf = join(path, '{}.{}'.format(n, EXT['pdf'])) + else: + path_pdf = replace_ext(self.path, EXT['pdf']) + + filter_name = '{}_pdf_Export'.format(self.type) + filter_data = dict_to_property(kwargs, True) + args = { + 'FilterName': filter_name, + 'FilterData': filter_data, + } + args = dict_to_property(args) + try: + self.obj.storeToURL(_path_url(path_pdf), args) + except Exception as e: + error(e) + path_pdf = '' + + return path_pdf + class LOCalc(LODocument): @@ -713,7 +717,6 @@ class LODrawImpress(LODocument): def draw_page(self): return self._cc.getCurrentPage() - @catch_exception def insert_image(self, path, **kwargs): w = kwargs.get('width', 3000) h = kwargs.get('Height', 1000) @@ -1124,6 +1127,10 @@ class UnoListBox(UnoBaseObject): super().__init__(obj) self._data = [] + @property + def type(self): + return 'listbox' + @property def value(self): return self.obj.SelectedItem @@ -1384,7 +1391,6 @@ class LODialog(object): return _path_url(path) return '' - @catch_exception def add_control(self, properties): tipo = properties.pop('Type').lower() @@ -1422,6 +1428,7 @@ def _get_class_doc(obj): return classes[type_doc](obj) +# ~ Export ok def get_document(title=''): doc = None desktop = get_desktop() @@ -1433,14 +1440,22 @@ def get_document(title=''): if d.Title == title: doc = d break - return doc + + if doc is None: + return + + return _get_class_doc(doc) -def get_documents(): +# ~ Export ok +def get_documents(custom=True): docs = [] desktop = get_desktop() for doc in desktop.getComponents(): - docs.append(_get_class_doc(doc)) + if custom: + docs.append(_get_class_doc(doc)) + else: + docs.append(doc) return docs @@ -1478,6 +1493,7 @@ def set_properties(model, properties): return +# ~ Export ok def get_config_path(name='Work'): """ Return de path name in config @@ -1487,6 +1503,7 @@ def get_config_path(name='Work'): return _path_system(getattr(path, name)) +# ~ Export ok def get_file(init_dir='', multiple=False, filters=()): """ init_folder: folder default open @@ -1505,32 +1522,109 @@ def get_file(init_dir='', multiple=False, filters=()): file_picker.setDisplayDirectory(init_dir) file_picker.setMultiSelectionMode(multiple) + path = '' if filters: file_picker.setCurrentFilter(filters[0][0]) for f in filters: file_picker.appendFilter(f[0], f[1]) if file_picker.execute(): + path = _path_system(file_picker.getSelectedFiles()[0]) if multiple: - return [_path_system(f) for f in file_picker.getSelectedFiles()] - return _path_system(file_picker.getSelectedFiles()[0]) + path = [_path_system(f) for f in file_picker.getSelectedFiles()] - return '' + return path +# ~ Export ok +def get_path(init_dir='', filters=()): + """ + Options: http://api.libreoffice.org/docs/idl/ref/namespacecom_1_1sun_1_1star_1_1ui_1_1dialogs_1_1TemplateDescription.html + filters: Example + ( + ('XML', '*.xml'), + ('TXT', '*.txt'), + ) + """ + if not init_dir: + init_dir = get_config_path() + init_dir = _path_url(init_dir) + file_picker = create_instance('com.sun.star.ui.dialogs.FilePicker') + file_picker.setTitle(_('Select file')) + file_picker.setDisplayDirectory(init_dir) + file_picker.initialize((2,)) + if filters: + file_picker.setCurrentFilter(filters[0][0]) + for f in filters: + file_picker.appendFilter(f[0], f[1]) + + path = '' + if file_picker.execute(): + path = _path_system(file_picker.getSelectedFiles()[0]) + return path + + +# ~ Export ok +def get_dir(init_dir=''): + folder_picker = create_instance('com.sun.star.ui.dialogs.FolderPicker') + if not init_dir: + init_dir = get_config_path() + init_dir = _path_url(init_dir) + folder_picker.setDisplayDirectory(init_dir) + + path = '' + if folder_picker.execute(): + path = _path_system(folder_picker.getDirectory()) + return path + + +# ~ Export ok def get_info_path(path): path, filename = os.path.split(path) name, extension = os.path.splitext(filename) return (path, filename, name, extension) +# ~ Export ok +def read_file(path, mode='r', array=False): + data = '' + with open(path, mode) as f: + if array: + data = tuple(f.read().splitlines()) + else: + data = f.read() + return data + + +# ~ Export ok +def save_file(path, mode='w', data=None): + with open(path, mode) as f: + f.write(data) + return + + +# ~ Export ok +def to_json(path, data): + with open(path, 'w') as f: + f.write(json.dumps(data, indent=4, sort_keys=True)) + return + + +# ~ Export ok +def from_json(path): + with open(path) as f: + data = json.loads(f.read()) + return data + + def get_path_extension(id): pip = CTX.getValueByName('/singletons/com.sun.star.deployment.PackageInformationProvider') path = _path_system(pip.getPackageLocation(id)) return path -def inputbox(message, default='', title=TITLE): +# ~ Export ok +def inputbox(message, default='', title=TITLE, echochar=''): class ControllersInput(object): @@ -1569,6 +1663,8 @@ def inputbox(message, default='', title=TITLE): 'Width': 190, 'Height': 15, } + if echochar: + args['EchoChar'] = ord(echochar[0]) dlg.add_control(args) dlg.txt_value.move(dlg.lbl_msg) @@ -1601,12 +1697,15 @@ def inputbox(message, default='', title=TITLE): return '' -def new_doc(type_doc=CALC): +# ~ Export ok +def new_doc(type_doc=CALC, **kwargs): path = 'private:factory/s{}'.format(type_doc) - doc = get_desktop().loadComponentFromURL(path, '_default', 0, ()) + opt = dict_to_property(kwargs) + doc = get_desktop().loadComponentFromURL(path, '_default', 0, opt) return _get_class_doc(doc) +# ~ Export ok def new_db(path): dbc = create_instance('com.sun.star.sdb.DatabaseContext') db = dbc.createInstance() @@ -1615,6 +1714,7 @@ def new_db(path): return _get_class_doc(db) +# ~ Export ok def open_doc(path, **kwargs): """ Open document in path Usually options: @@ -1629,7 +1729,6 @@ def open_doc(path, **kwargs): http://api.libreoffice.org/docs/idl/ref/servicecom_1_1sun_1_1star_1_1document_1_1MediaDescriptor.html """ path = _path_url(path) - # ~ opt = _properties(kwargs) opt = dict_to_property(kwargs) doc = get_desktop().loadComponentFromURL(path, '_blank', 0, opt) if doc is None: @@ -1638,6 +1737,7 @@ def open_doc(path, **kwargs): return _get_class_doc(doc) +# ~ Export ok def open_file(path): if IS_WIN: os.startfile(path) @@ -1646,37 +1746,45 @@ def open_file(path): return +# ~ Export ok def join(*paths): return os.path.join(*paths) +# ~ Export ok def is_dir(path): return Path(path).is_dir() +# ~ Export ok def is_file(path): return Path(path).is_file() +# ~ Export ok def get_file_size(path): return Path(path).stat().st_size +# ~ Export ok def is_created(path): return is_file(path) and bool(get_file_size(path)) +# ~ Export ok def replace_ext(path, extension): path, _, name, _ = get_info_path(path) return '{}/{}.{}'.format(path, name, extension) -def zip_names(path): +# ~ Export ok +def zip_content(path): with zipfile.ZipFile(path) as z: names = z.namelist() return names +# ~ Export ok def run(command, wait=False): # ~ debug(command) # ~ debug(shlex.split(command)) @@ -1731,6 +1839,7 @@ def _zippwd(source, target, pwd): return is_created(target) +# ~ Export ok def zip(source, target='', mode='w', pwd=''): if pwd: return _zippwd(source, target, pwd) @@ -1772,6 +1881,7 @@ def zip(source, target='', mode='w', pwd=''): return is_created(target) +# ~ Export ok def unzip(source, path='', members=None, pwd=None): if not path: path, _, _, _ = get_info_path(source) @@ -1784,6 +1894,7 @@ def unzip(source, path='', members=None, pwd=None): return True +# ~ Export ok def merge_zip(target, zips): try: with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as t: @@ -1798,18 +1909,20 @@ def merge_zip(target, zips): return True +# ~ Export ok def kill(path): p = Path(path) - if p.is_file(): - try: + try: + if p.is_file(): p.unlink() - except: - pass - elif p.is_dir(): - p.rmdir() + elif p.is_dir(): + shutil.rmtree(path) + except OSError as e: + log.error(e) return +# ~ Export ok def get_size_screen(): if IS_WIN: user32 = ctypes.windll.user32 @@ -1820,6 +1933,7 @@ def get_size_screen(): return res.strip() +# ~ Export ok def get_clipboard(): df = None text = '' @@ -1865,6 +1979,7 @@ class TextTransferable(unohelper.Base, XTransferable): return False +# ~ Export ok def set_clipboard(value): ts = TextTransferable(value) sc = create_instance('com.sun.star.datatransfer.clipboard.SystemClipboard') @@ -1872,40 +1987,38 @@ def set_clipboard(value): return -def copy(doc=None): - if doc is None: - doc = get_document() - if hasattr(doc, 'frame'): - frame = doc.frame - else: - frame = doc.getCurrentController().getFrame() - dispatch = get_dispatch() - dispatch.executeDispatch(frame, '.uno:Copy', '', 0, ()) +# ~ Todo +def copy(): + call_dispatch('.uno:Copy') return +# ~ Export ok def get_epoch(): - now = datetime.datetime.now() - return int(time.mktime(now.timetuple())) + n = now() + return int(time.mktime(n.timetuple())) +# ~ Export ok def file_copy(source, target='', name=''): p, f, n, e = get_info_path(source) if target: p = target if name: + e = '' n = name path_new = join(p, '{}{}'.format(n, e)) shutil.copy(source, path_new) - return + return path_new -def get_files(path, ext='*'): - docs = [] +# ~ Export ok +def get_path_content(path, filters='*'): + paths = [] for folder, _, files in os.walk(path): - pattern = re.compile(r'\.{}'.format(ext), re.IGNORECASE) - docs += [join(folder, f) for f in files if pattern.search(f)] - return docs + pattern = re.compile(r'\.(?:{})$'.format(filters), re.IGNORECASE) + paths += [join(folder, f) for f in files if pattern.search(f)] + return paths def _get_menu(type_doc, name_menu): @@ -1967,8 +2080,7 @@ def insert_menu(type_doc, name_menu, **kwargs): index_menu = _get_index_menu(menu, command) if index_menu: msg = 'Exists: %s' % command - if not IS_WIN: - debug(msg) + debug(msg) return 0 sub_menu = kwargs.get('Submenu', ()) @@ -2011,8 +2123,7 @@ def remove_menu(type_doc, name_menu, command): index = _get_index_menu(menu, command) if not index: - if not IS_WIN: - debug('Not exists: %s' % command) + debug('Not exists: %s' % command) return False _store_menu(ui, menus, menu, index, remove=True) @@ -2024,8 +2135,7 @@ def _get_app_submenus(menus, count=0): data = property_to_dict(menu) cmd = data.get('CommandURL', '') msg = ' ' * count + '├─' + cmd - if not IS_WIN: - debug(msg) + debug(msg) submenu = data.get('ItemDescriptorContainer', None) if not submenu is None: _get_app_submenus(submenu, count + 1) @@ -2041,14 +2151,467 @@ def get_app_menus(name_app, index=-1): if index == -1: for menu in menus: data = property_to_dict(menu) - if not IS_WIN: - debug(data.get('CommandURL', '')) + debug(data.get('CommandURL', '')) else: menus = property_to_dict(menus[index])['ItemDescriptorContainer'] _get_app_submenus(menus) return menus +# ~ Export ok +def start(): + global _start + _start = now() + log.info(_start) + return + + +# ~ Export ok +def end(): + global _start + e = now() + return str(e - _start).split('.')[0] + + +# ~ Export ok +# ~ https://en.wikipedia.org/wiki/Web_colors +def get_color(value): + COLORS = { + 'aliceblue': 15792383, + 'antiquewhite': 16444375, + 'aqua': 65535, + 'aquamarine': 8388564, + 'azure': 15794175, + 'beige': 16119260, + 'bisque': 16770244, + 'black': 0, + 'blanchedalmond': 16772045, + 'blue': 255, + 'blueviolet': 9055202, + 'brown': 10824234, + 'burlywood': 14596231, + 'cadetblue': 6266528, + 'chartreuse': 8388352, + 'chocolate': 13789470, + 'coral': 16744272, + 'cornflowerblue': 6591981, + 'cornsilk': 16775388, + 'crimson': 14423100, + 'cyan': 65535, + 'darkblue': 139, + 'darkcyan': 35723, + 'darkgoldenrod': 12092939, + 'darkgray': 11119017, + 'darkgreen': 25600, + 'darkgrey': 11119017, + 'darkkhaki': 12433259, + 'darkmagenta': 9109643, + 'darkolivegreen': 5597999, + 'darkorange': 16747520, + 'darkorchid': 10040012, + 'darkred': 9109504, + 'darksalmon': 15308410, + 'darkseagreen': 9419919, + 'darkslateblue': 4734347, + 'darkslategray': 3100495, + 'darkslategrey': 3100495, + 'darkturquoise': 52945, + 'darkviolet': 9699539, + 'deeppink': 16716947, + 'deepskyblue': 49151, + 'dimgray': 6908265, + 'dimgrey': 6908265, + 'dodgerblue': 2003199, + 'firebrick': 11674146, + 'floralwhite': 16775920, + 'forestgreen': 2263842, + 'fuchsia': 16711935, + 'gainsboro': 14474460, + 'ghostwhite': 16316671, + 'gold': 16766720, + 'goldenrod': 14329120, + 'gray': 8421504, + 'grey': 8421504, + 'green': 32768, + 'greenyellow': 11403055, + 'honeydew': 15794160, + 'hotpink': 16738740, + 'indianred': 13458524, + 'indigo': 4915330, + 'ivory': 16777200, + 'khaki': 15787660, + 'lavender': 15132410, + 'lavenderblush': 16773365, + 'lawngreen': 8190976, + 'lemonchiffon': 16775885, + 'lightblue': 11393254, + 'lightcoral': 15761536, + 'lightcyan': 14745599, + 'lightgoldenrodyellow': 16448210, + 'lightgray': 13882323, + 'lightgreen': 9498256, + 'lightgrey': 13882323, + 'lightpink': 16758465, + 'lightsalmon': 16752762, + 'lightseagreen': 2142890, + 'lightskyblue': 8900346, + 'lightslategray': 7833753, + 'lightslategrey': 7833753, + 'lightsteelblue': 11584734, + 'lightyellow': 16777184, + 'lime': 65280, + 'limegreen': 3329330, + 'linen': 16445670, + 'magenta': 16711935, + 'maroon': 8388608, + 'mediumaquamarine': 6737322, + 'mediumblue': 205, + 'mediumorchid': 12211667, + 'mediumpurple': 9662683, + 'mediumseagreen': 3978097, + 'mediumslateblue': 8087790, + 'mediumspringgreen': 64154, + 'mediumturquoise': 4772300, + 'mediumvioletred': 13047173, + 'midnightblue': 1644912, + 'mintcream': 16121850, + 'mistyrose': 16770273, + 'moccasin': 16770229, + 'navajowhite': 16768685, + 'navy': 128, + 'oldlace': 16643558, + 'olive': 8421376, + 'olivedrab': 7048739, + 'orange': 16753920, + 'orangered': 16729344, + 'orchid': 14315734, + 'palegoldenrod': 15657130, + 'palegreen': 10025880, + 'paleturquoise': 11529966, + 'palevioletred': 14381203, + 'papayawhip': 16773077, + 'peachpuff': 16767673, + 'peru': 13468991, + 'pink': 16761035, + 'plum': 14524637, + 'powderblue': 11591910, + 'purple': 8388736, + 'red': 16711680, + 'rosybrown': 12357519, + 'royalblue': 4286945, + 'saddlebrown': 9127187, + 'salmon': 16416882, + 'sandybrown': 16032864, + 'seagreen': 3050327, + 'seashell': 16774638, + 'sienna': 10506797, + 'silver': 12632256, + 'skyblue': 8900331, + 'slateblue': 6970061, + 'slategray': 7372944, + 'slategrey': 7372944, + 'snow': 16775930, + 'springgreen': 65407, + 'steelblue': 4620980, + 'tan': 13808780, + 'teal': 32896, + 'thistle': 14204888, + 'tomato': 16737095, + 'turquoise': 4251856, + 'violet': 15631086, + 'wheat': 16113331, + 'white': 16777215, + 'whitesmoke': 16119285, + 'yellow': 16776960, + 'yellowgreen': 10145074, + } + + if isinstance(value, tuple): + return (value[0] << 16) + (value[1] << 8) + value[2] + + if isinstance(value, str) and value[0] == '#': + r, g, b = bytes.fromhex(value[1:]) + return (r << 16) + (g << 8) + b + + return COLORS.get(value.lower(), -1) + + +# ~ Export ok +def render(template, data): + s = Template(template) + return s.safe_substitute(**data) + + +def _to_date(value): + new_value = value + if isinstance(value, Time): + new_value = datetime.time(value.Hours, value.Minutes, value.Seconds) + elif isinstance(value, Date): + new_value = datetime.date(value.Year, value.Month, value.Day) + elif isinstance(value, DateTime): + new_value = datetime.datetime( + value.Year, value.Month, value.Day, + value.Hours, value.Minutes, value.Seconds) + return new_value + + +# ~ Export ok +def format(template, data): + """ + https://pyformat.info/ + """ + if isinstance(data, (str, int, float)): + # ~ print(template.format(data)) + return template.format(data) + + if isinstance(data, (Time, Date, DateTime)): + return template.format(_to_date(data)) + + if isinstance(data, tuple) and isinstance(data[0], tuple): + data = {r[0]: _to_date(r[1]) for r in data} + return template.format(**data) + + data = [_to_date(v) for v in data] + result = template.format(*data) + return result + + +def _call_macro(macro): + #~ https://wiki.openoffice.org/wiki/Documentation/DevGuide/Scripting/Scripting_Framework_URI_Specification + name = 'com.sun.star.script.provider.MasterScriptProviderFactory' + factory = create_instance(name, False) + + data = macro.copy() + if macro['language'] == 'Python': + data['module'] = '.py$' + elif macro['language'] == 'Basic': + data['module'] = '.{}.'.format(macro['module']) + if macro['location'] == 'user': + data['location'] = 'application' + else: + data['module'] = '.' + + args = macro.get('args', ()) + url = 'vnd.sun.star.script:{library}{module}{name}?language={language}&location={location}' + path = url.format(**data) + script = factory.createScriptProvider('').getScript(path) + return script.invoke(args, None, None)[0] + + +# ~ Export ok +def call_macro(macro): + in_thread = macro.pop('thread') + if in_thread: + t = threading.Thread(target=_call_macro, args=(macro,)) + t.start() + return + + return _call_macro(macro) + + +class TimerThread(threading.Thread): + + def __init__(self, event, seconds, macro): + threading.Thread.__init__(self) + self.stopped = event + self.seconds = seconds + self.macro = macro + + def run(self): + info('Timer started... {}'.format(self.macro['name'])) + while not self.stopped.wait(self.seconds): + _call_macro(self.macro) + info('Timer stopped... {}'.format(self.macro['name'])) + return + + +# ~ Export ok +def timer(name, seconds, macro): + global _stop_thread + _stop_thread[name] = threading.Event() + thread = TimerThread(_stop_thread[name], seconds, macro) + thread.start() + return + + +# ~ Export ok +def stop_timer(name): + global _stop_thread + _stop_thread[name].set() + del _stop_thread[name] + return + + +def _get_key(password): + digest = hashlib.sha256(password.encode()).digest() + key = base64.urlsafe_b64encode(digest) + return key + + +# ~ Export ok +def encrypt(data, password): + f = Fernet(_get_key(password)) + token = f.encrypt(data).decode() + return token + + +# ~ Export ok +def decrypt(token, password): + data = '' + f = Fernet(_get_key(password)) + try: + data = f.decrypt(token.encode()).decode() + except InvalidToken as e: + error('Invalid Token') + return data + + +class SmtpServer(object): + + def __init__(self, config): + self._server = None + self._error = '' + self._sender = '' + self._is_connect = self._login(config) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + @property + def is_connect(self): + return self._is_connect + + @property + def error(self): + return self._error + + def _login(self, config): + name = config['server'] + port = config['port'] + is_ssl = config['ssl'] + self._sender = config['user'] + hosts = ('gmail' in name or 'outlook' in name) + try: + if is_ssl and hosts: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + self._server.ehlo() + self._server.starttls() + self._server.ehlo() + elif is_ssl: + self._server = smtplib.SMTP_SSL(name, port, timeout=TIMEOUT) + self._server.ehlo() + else: + self._server = smtplib.SMTP(name, port, timeout=TIMEOUT) + + self._server.login(self._sender, config['pass']) + msg = 'Connect to: {}'.format(name) + debug(msg) + return True + except smtplib.SMTPAuthenticationError as e: + if '535' in str(e): + self._error = _('Incorrect user or password') + return False + if '534' in str(e) and 'gmail' in name: + self._error = _('Allow less secure apps in GMail') + return False + except smtplib.SMTPException as e: + self._error = str(e) + return False + except Exception as e: + self._error = str(e) + return False + return False + + def _body(self, msg): + body = msg.replace('\\n', '
') + return body + + def send(self, message): + file_name = 'attachment; filename={}' + email = MIMEMultipart() + email['From'] = self._sender + email['To'] = message['to'] + email['Cc'] = message.get('cc', '') + email['Subject'] = message['subject'] + email['Date'] = formatdate(localtime=True) + if message.get('confirm', False): + email['Disposition-Notification-To'] = email['From'] + email.attach(MIMEText(self._body(message['body']), 'html')) + + for path in message.get('files', ()): + _, fn, _, _ = get_info_path(path) + part = MIMEBase('application', 'octet-stream') + part.set_payload(read_file(path, 'rb')) + encoders.encode_base64(part) + part.add_header('Content-Disposition', file_name.format(fn)) + email.attach(part) + + receivers = ( + email['To'].split(',') + + email['CC'].split(',') + + message.get('bcc', '').split(',')) + try: + self._server.sendmail(self._sender, receivers, email.as_string()) + msg = 'Email sent...' + debug(msg) + if message.get('path', ''): + self.save_message(email, message['path']) + return True + except Exception as e: + self._error = str(e) + return False + return False + + def save_message(self, email, path): + mbox = mailbox.mbox(path, create=True) + mbox.lock() + try: + msg = mailbox.mboxMessage(email) + mbox.add(msg) + mbox.flush() + finally: + mbox.unlock() + return + + def close(self): + try: + self._server.quit() + msg = 'Close connection...' + debug(msg) + except: + pass + return + + +def _send_email(server, messages): + with SmtpServer(server) as server: + if server.is_connect: + for msg in messages: + server.send(msg) + else: + error(server.error) + return server.error + + +def send_email(server, message): + messages = message + if isinstance(message, dict): + messages = (message,) + t = threading.Thread(target=_send_email, args=(server, messages)) + t.start() + return + + +def server_smtp_test(config): + with SmtpServer(config) as server: + if server.error: + error(server.error) + return server.error + + # ~ name = 'com.sun.star.configuration.ConfigurationProvider' # ~ cp = create_instance(name, True) # ~ node = PropertyValue(Name='nodepath', Value=NODE_SETTING)