<commit_before>#!/bin/env python
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Name:         awc/controls/attachments.py
# Author:       <NAME> <<EMAIL>>
# Copyright:    (C) 2011 <NAME>. <NAME>, 122 18038 Sanremo (IM)
# ------------------------------------------------------------------------------
# This file is part of X4GA
# 
# X4GA is free software: you can redistribute it and/or modify
# it under the terms of the Affero GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# X4GA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with X4GA.  If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------


import sys, os, subprocess
def opj(x,y):
    return os.path.join(x,y).replace('\\', '/')

import wx
import wx.lib.colourdb as colourdb
import stormdb as adb

import awc.controls.attachbutton_wdr as wdr
import awc.util as awu
import awc.controls.dbgrid as dbg

#import twain
twain = None

from mx import DateTime

import locale
try:
    locale.setlocale(locale.LC_ALL, 'it')
except locale.Error:
    locale.setlocale(locale.LC_ALL)

import shutil

import awc.controls.images as images

import awc.thumbs as thumbs
import base64

import time

_colorsok = False

attach_dir = './attach_files'
FILES_PER_DIR = 1024


ATTACH_TYPE_FILE =  1
ATTACH_TYPE_TWAIN = 2
ATTACH_TYPE_URL =   3
ATTACH_TYPE_VOICE = 4
ATTACH_TYPE_NOTE =  5


PAGE_NOTES = 0
PAGE_FILE = 1
PAGE_TWAIN = -1
PAGE_URL = 2
PAGE_VOICE = -1


ATTACH_SIZEMAX = 1024*1024*16 #in bytes => 16 MB

if sys.platform.startswith('linux'):
    TEMP_DIR = '/tmp'
else:
    TEMP_DIR = os.getenv('temp')

TWAIN_TEMPFILE = opj(TEMP_DIR, "x4scan.bmp")

VOICE_MAXLEN =   60 #seconds
VOICE_TEMPFILE = opj(TEMP_DIR, "x4voice.mp3")
TEXT_TEMPFILE =  opj(TEMP_DIR, "x4text.txt")


__image_ext__ = ('bmp', 'jpg', 'jpeg', 'tif', 'tiff', 'gif')
__sound_ext__ = ('mp3', 'wav')

#__voicerec__ = True
#try:
#    import time, sys
#    import pymedia.audio.sound as sound
#    import pymedia.audio.acodec as acodec
#except ImportError:
#    __voicerec__ = False
#
#__voiceplay__ = True
#try:
#    import pymedia.audio.sound as sound
#    import pymedia.audio.acodec as acodec
#    import pymedia.muxer as muxer
#    if len(sound.getODevices()) == 0:
#        __voiceplay__ = False
#except:
#    __voiceplay__ = False


_evtATTACHSMODIFIED = wx.NewEventType()
EVT_ATTACHSMODIFIED = wx.PyEventBinder(_evtATTACHSMODIFIED, 1)
class AttachsModifiedEvent(wx.PyCommandEvent):
    pass


class _AttachTableMixin(adb.DbTable):
    
    def GetLastFolderNumber(self):
        last = 1
        if not os.path.exists(attach_dir):
            os.mkdir(attach_dir)
        dirs = [f for f in os.listdir(attach_dir) if os.path.isdir(f)]
        if dirs:
            dirs.sort()
            last = dirs[-1]
            last = last.replace('\\', '/')
            last = int(last.split('/')[-1])
        return last
    
    def GetNewFilePathAndName(self):
        path_number = self.GetLastFolderNumber()
        while True:
            path = self.GetFolderName(path_number)
            if not os.path.exists(path):
                os.mkdir(path)
            if len(os.listdir(path)) < FILES_PER_DIR:
                break
            path_number += 1
        now = DateTime.now()
        def f(x,n=2):
            return str(int(x)).zfill(n)
        file_name = '%s-%s-%s %s-%s-%s' % (f(now.year,4),
                                           f(now.month),
                                           f(now.day),
                                           f(now.hour),
                                           f(now.minute),
                                           f(now.second))
        return path_number, file_name
    
    def IsImage(self):
        if self.attach_type == ATTACH_TYPE_TWAIN:
            out = True
        elif self.attach_type == ATTACH_TYPE_FILE:
            out = (self.file or '').split('.')[-1].lower() in thumbs.IMAGE_EXTENSIONS
        else:
            out = False
        return out
    
    def IsNote(self):
        return self.attach_type == ATTACH_TYPE_NOTE
    
    def IsURL(self):
        return self.attach_type == ATTACH_TYPE_URL
    
    def IsVoice(self):
        return self.attach_type == ATTACH_TYPE_VOICE
    
    def IsTwain(self):
        return self.attach_type == ATTACH_TYPE_TWAIN
    
    def IsDocument(self):
        return self.attach_type == ATTACH_TYPE_FILE
    
    def GetFolderName(self, n=None):
        if n is None:
            n = self.folderno
        return opj(attach_dir, str(n).zfill(3))
    
    def GetFileName(self):
        return opj(self.GetFolderName(), self.file)
        
    def GetDefaultImageName(self):
        for a in self:
            if a.IsImage() and a.autotext:
                return self.GetFileName()
    
    def GetBase64EncodedImage(self, image=None):
        out = ''
        if not self.IsEmpty():
            if image is None:
                fn = self.GetFileName()
                if fn:
                    if os.path.exists(fn):
                        try:
                            f = open(self.GetFileName(), 'rb')
                            image = f.read()
                            f.close()
                        except Exception:
                            pass
            if image is not None:
                out = base64.b64encode(image)
        return out
    
    def GetBase64EncodedResizedImage(self, dimx, dimy, scale_test_func=None, force_jpeg=False):
        out = None
        if not self.IsEmpty():
            fn = self.GetFileName()
            if fn:
                if os.path.exists(fn):
                    try:
                        client_size = (dimx, dimy)
                        image = wx.Image(fn)
                        if callable(scale_test_func):
                            do = scale_test_func(image)
#                            if not do:
#                                out = self.GetBase64EncodedImage()
                        else:
                            do = True
                        if do:
                            image_size = image.GetSize()
                            proportions = (float(client_size[i]) / image_size[i]
                                           for i in range(len(image_size)))
                            proportion = min(proportions) # Use the smallest proportion.
                            # Can't use wx.Image.Shrink because it doesn't provide enough pixel
                            # granularity -- the shrinking has serious discontinuities.
                            new_size = (pixels * proportion for pixels in image_size)
                            image.Rescale(*new_size)
                        if do or force_jpeg:
                            import tempfile
                            tmpfile = tempfile.NamedTemporaryFile(suffix='.temp')
                            tmpname = tmpfile.name
                            tmpfile.close()
                            app = wx.GetApp()
                            if hasattr(app, 'AppendTempFile'):
                                wx.GetApp().AppendTempFile(tmpname)
                            image.SaveFile(tmpname, wx.BITMAP_TYPE_JPEG)
                            f = open(tmpname, 'rb')
                            buf = f.read()
                            f.close()
                            out = self.GetBase64EncodedImage(buf)
                    except Exception, e:
                        pass
        return out


# ------------------------------------------------------------------------------


class AttachTableMemo(_AttachTableMixin):
    
    def __init__(self, tabname, *args):
        _AttachTableMixin.__init__(self, tabname, 'allegati', writable=True,
                                   defaults={'attach_type': 1, 'hidden': 0})
        self.AddField('NULL', 'filecontent')
        self.AddFilter("FALSE")


# ------------------------------------------------------------------------------


class AttachTableList(_AttachTableMixin):
    
    def __init__(self, tabname, *args):
        mix = _AttachTableMixin
        mix.__init__(self, tabname, 'allegati', writable=False,\
                     fields="id,attscope,attkey,description,attach_type,"\
                     +"folderno,file,url,voiceatt_id,autotext,hidden")
        self.AddOrder("attach_type")
        self.AddOrder("datins")
        self.AddOrder("description")


# ------------------------------------------------------------------------------


class AttachmentButton(wx.Button):
    """
    Classe per la gestione degli allegati.
    
    Da qui è possibile inserire, modificare, eliminare e richiamare allegati di
    vario tipo.
    
    Gli allegati sono memorizzati sulla tabella 'allegati' e sono
    contestualizzati mediante i seguenti identificativi:
        - attscope    (char,10) Tipicamente il nome tabella a cui si riferiscono
        - attkey      (integer) Tipicamente l'id del record a cui si riferiscono
    
    A corredo di ogni allegato troviamo:
        - description (varchar,255) Descrizione dell'allegato
        - name        (varchar,255) Nome del documento o indirizzo url
        - content     (blob)        Contenuto dell'allegato
        - attach_type (integer)     Tipologia di allegato:
          1 = Documento
          2 = Immagine
          3 = URL
          4 = Nota vocale
          5 = Nota 
    
    Ad esempio, per allegare un documento alla scheda anagrafica di un cliente,
    pesupposto che il nome della tabella clienti sia 'clienti', avremo:
        attscope    = clienti (nome della tabella clienti)
        attkey      = 1234    (id del cliente nella tabella clienti)
        description = Condizioni contrattuali
        attach_type = 1 (documento)
        content     = <binary> - contenuto del file di Word.
    """

    def __init__(self, *args, **kwargs):
        
        wx.Button.__init__(self, *args, **kwargs)
        
        self._tabname = "allegati"
        self._attscope = None
        self._attkey = None
        self._canadd = True
        self._canedit = True
        self._autotext = None
        
        self._autotext_prefix = None
        self._autotext_suffix = None
        
        self._twain_sm = None
        self._twain_sd = None
        
        self._voice_playnow = True
        
        self.image_files = []
        
        self._spypanel = None
        self._autoscan = None
        
        #_attlist contiene le informazioni degli allegati senza il campo blob
        #che appesantisce la ricerca; è *sempre* allineato con il contenuto
        #della tabella allegati, anche dopo averne inserito di nuovi, la cui
        #scrittura sulla tabella è differita rispetto al loro inserimento nel
        #bottone - questo ne consente l'inserimento anche quando l'elemento a 
        #cui si riferiscono non ha ancora un proprio id.
        #ATTENZIONE
        #per la memorizzazione di nuovi allegati è necessario richiamare
        #l'apposito metodo Save - vedi specifiche
        self._attlist = AttachTableList(self._tabname)
        
        #_attmemo contiene *tutte* le informazioni dei soli allegati che si 
        #stanno inserendo - vedi considerazioni precedenti
        #ATTENZIONE
        #per la memorizzazione di nuovi allegati è necessario richiamare
        #l'apposito metodo Save - vedi specifiche
        self._attmemo = AttachTableMemo(self._tabname)
        
        self.Bind(wx.EVT_BUTTON, self._OnCallAttachMenu)
    
    def SetSpyPanel(self, panel):
        assert isinstance(panel, AttachmentSpyPanel)
        self._spypanel = panel
    
    def SetAutoScan(self, autoscan=True):
        self._autoscan = autoscan
    
    def GetDefaultImageName(self):
        return self._attlist.GetDefaultImageName()
    
    def SetAutoText(self, at):
        self._autotext = at
    
    def SetScope(self, scope):
        """
        Imposta lo 'scope' degli allegati, ovvero l'ambito nel quale la chiave
        a cui si riferiscono ha significato; tipicamente è il nome della 
        tabella sulla quale risiede il record a cui si riferiscono gli allegati.
        """
        self._attscope = scope
    
    def SetAutotextPS(self, prefix=None, suffix=None):
        self._autotext_prefix = prefix
        self._autotext_suffix = suffix
    
    def SetKey(self, key, save=False, delete=False):
        """
        Imposta la chiave numerica di relazione degli allegati; tipicamente è
        l'id del record della tabella indicata nello 'scope' (vedi SetScope)
        a cui si riferiscono gli allegati.
        """
        self._attkey = key
        if save:
            self.SaveAttachments()
        self._attmemo.Retrieve()
        if delete:
            self.DeleteAttachments()
        self.UpdateElements()
        self.UpdateButtonStatus()
    
    def SetPermissions(self, caninsert=True, canedit=True):
        """
        Imposta i diritti di inserimento e modifica degli allegati:
        SetPermissions(self, caninsert=True, canedit=True)
        """
        self._canadd = caninsert
        self._canedit = canedit
    
    def UpdateElements(self):
        """
        Aggiorna l'elenco degli allegati per la costruzione del menu alla
        pressione del bottone.
        """
        db = self._attlist
        db.ClearFilters()
        db.AddFilter("allegati.attscope=%s", self._attscope)
        db.AddFilter("allegati.attkey=%s", self._attkey)
        #db.AddFilter("allegati.datdel IS NULL")
        db.AddFilter("hidden<>1")        
        if db.Retrieve():
            sp = self._spypanel
            if sp is not None:
                sp.SetEmpty()
            at = self._autotext
            txt = ''
            for att in db:
                if sp is not None and att.IsImage() and att.autotext:
                    sp.SetFull()
                elif att.IsNote() and att.autotext and self._autotext:
                    file = att.GetFileName()
                    try:
                        h = open(file, 'r')
                        c = h.read()
                        h.close()
                        if txt:
                            txt += ' - '
                        txt += c
                    except:
                        pass
            if at is not None:
                text, sep = '', ' :: '
                if self._autotext_prefix:
                    text += self._autotext_prefix
                    if txt:
                        text += sep
                text += txt
                if self._autotext_suffix:
                    if txt:
                        text += sep
                    text += self._autotext_suffix
                at.SetText(text)
            n = db.RowsCount()
        else:
            n = 0
        font = self.GetFont()
        if n > 0:
            if n == 1:
                txt = "1 allegato"
            else:
                txt = "%d allegati" % n
            font.SetWeight(wx.FONTWEIGHT_BOLD)
        else:
            txt = "No allegati"
            font.SetWeight(wx.FONTWEIGHT_NORMAL)
        #self.Enable(n>0)
        self.SetFont(font)
        self.SetLabel(txt)
    
    def GetAutoNotes(self, db):
        txt = ''
        for att in db:
            if att.IsNote() and att.autotext:
                file = att.GetFileName()
                try:
                    h = open(file, 'r')
                    c = h.read()
                    h.close()
                    if txt:
                        txt += ' - '
                    txt += c
                except:
                    pass
        return txt
    
    def StoreNewAttach(self, att):
        out = True
        if att.filecontent:
            n, f = att.GetNewFilePathAndName()
            att.folderno = n
            path = att.GetFolderName()
            if not os.path.isdir(path):
                try:
                    os.mkdir(path)
                except:
                    out = False
            if att.file is None:
                ext = '.'
            else:
                ext = att.file
            filename = opj(path, '%s%s' % (f, ext))
            att.file = filename.split('/')[-1]
            att.size = len(att.filecontent)
            try:
                h = open(filename, 'wb')
                h.write(att.filecontent)
                h.close()
            except Exception, e:
                awu.MsgDialog(self, repr(e.args))
                out = False
        return out
    
    def LaunchAttachment(self, attid, saveonly=False):
        """
        Lancia l'allegato.
        """
        am = AttachTableList(self._tabname)
        if am.Get(attid) and am.OneRow():
            if   am.IsURL():
                if am.url:
                    try:
                        url = am.url
                        if not url.startswith('http://') and not url.startswith('https://'):
                            url = 'http://%s' % url
                        os.startfile(url)
                    except:
                        pass
                return
            file = am.GetFileName()
            if not saveonly:
                if am.IsVoice():
                    if am.hidden:
                        t = "Commento audio"
                    else:
                        t = am.description
                    self.CallVoicePlayer(file, start=self._voice_playnow,\
                                         title=t)
                else:
                    if not os.path.exists(file):
                        awu.MsgDialog(self, "File non trovato",
                                      style=wx.ICON_ERROR)
                        return
                    try:
                        os.startfile(file.replace('/','\\'))
                        if am.voiceatt_id is not None:
                            self._voice_playnow = False
                            self.LaunchAttachment(am.voiceatt_id)
                            self._voice_playnow = True
                        
                    except Exception, e:
                        if awu.MsgDialog(self,\
                                         """Impossibile aprire il file %s.\n"""\
                                         """Vuoi salvarlo?""" % file,\
                                         "Apertura allegato fallita",\
                                         style=wx.ICON_ERROR|\
                                         wx.YES_NO|wx.NO_DEFAULT) == wx.ID_YES:
                            saveonly = True
                        
                    except Exception, e:
                        awu.MsgDialog(self, repr(e.args))
            if saveonly:
                self.SaveAttachFileAs(file)
    
    def SaveAttachFileAs(self, tmp):
        out = False
        if '.' in tmp:
            ext = tmp[tmp.rindex(".")+1:]
            extinfo = "Tutti i files %s (*.%s)|*.%s" % (ext, ext, ext)
        else:
            ext = "*"
            extinfo = "Tutti i files (*.*)|*.*"
        file = tmp.replace("\\","/")
        if "/" in file:
            file = file[file.rindex("/")+1:]
        dlg = wx.FileDialog(None,\
                            message="Seleziona il file da salvare",\
                            defaultFile=file,\
                            wildcard=extinfo,\
                            style=wx.SAVE|wx.OVERWRITE_PROMPT)
        if dlg.ShowModal() == wx.ID_OK:
            try:
                shutil.copyfile(tmp, dlg.GetPath())
                out = True
            except Exception, e:
                awu.MsgDialog(self,\
                              """Impossibile scrivere su questo file\n"""\
                              """Errore: %s""" % repr(e.args))
        dlg.Destroy()
        return out

    def UpdateButtonStatus(self):
        global _colorsok
        if not _colorsok:
            colourdb.updateColourDB()
            _colorsok = True
        if self._attmemo.RowsCount() > 0:
            bg = wx.TheColourDatabase.Find("LIGHT CORAL")
        else:
            bg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE)
        self.SetBackgroundColour(bg)
    
    def ManageAttachments(self):
        dlg = AttachmentsGridDialog(self, -1, "Manutenzione allegati",\
                                    style=wx.DEFAULT_FRAME_STYLE\
                                    |wx.RESIZE_BORDER)
        if dlg.ShowModal() == 1:
            self.UpdateElements()
        dlg.Destroy()
    
    def MakeNewAttach(self, toscan=False):
        
        am = self._attmemo
        attno = am.RowsCount()
        am.CreateNewRow()
        am.autotext = 0
        dlg = AttachmentDetailsDialog(self, -1, "Inserimento nuovo allegato")
        dlg._twain_sm = self._twain_sm
        dlg._twain_sd = self._twain_sd
        force_autotext = 0
#        if toscan:
#            def cn(x):
#                return dlg.FindWindowByName(x)
#            cn('description').SetValue("Scansione Documento")
#            dlg.notebook.SetSelection(2)
#            force_autotext = 1
        
        am.attach_type = dlg.ShowModal()
        
        if am.attach_type >= ATTACH_TYPE_FILE:
            
            am.attscope =    self._attscope
            am.attkey =      self._attkey
            am.description = dlg.GetFieldValue('description')
            am.datins =      DateTime.now()
            
            if am.IsTwain():
                am.file = dlg._file
            
            file = None
            if am.IsDocument():
                file = dlg.GetFieldValue('file')
            elif am.IsTwain():
                file = dlg._file
            if file is not None:
                file = file.replace('\\','/')
                if "/" in file:
                    file = file.split('/')[-1]
                if not '.' in file:
                    file += '.'
                file = file.split('.')[-1]
                am.file = '.%s'%file
            
            if am.IsImage() or am.IsNote():
                am.autotext = int(dlg._autotext or force_autotext)
            
            if am.IsURL():
                am.url = dlg.GetFieldValue('url')
                
            elif am.IsNote():
                am.file = '.txt'
            
            if dlg._stream:
                if am.IsDocument():
                    if am.IsImage():
                        am.attach_type = ATTACH_TYPE_TWAIN
                am.filecontent = dlg._stream
            
            self.UpdateButtonStatus()
            event = AttachsModifiedEvent(_evtATTACHSMODIFIED, self.GetId())
            event.SetEventObject(self)
            event.m_id = self.GetId()
            event.SetId(event.m_id)
            self.GetParent().AddPendingEvent(event)
        
        dlg.Destroy()
    
    def SaveAttachments(self):
        out = True
        am = self._attmemo
        if am.RowsCount() > 0:
            if self._attkey is None:
                if am.RowsCount() == 1:
                    desc = "l'allegato inserito"
                else:
                    desc = "i %d allegati inseriti" % am.RowsCount()
                awu.MsgDialog(self,\
                              """Non è possibile memorizzare %s in quanto """\
                              """manca l'identificativo a cui riferirli""" % desc,\
                              style=wx.ICON_ERROR)
                out = False
            else:
                for a in am:
                    a.attkey = self._attkey
                    self.StoreNewAttach(am)
                    time.sleep(1)
                out = am.Save()
                if not out:
                    awu.MsgDialog(self,\
                                  """Problema durante il salvataggio """\
                                  """degli allegati inseriti\nErrore: %s"""\
                                  % repr(am.GetError()))
                self.UpdateElements()
                am.Retrieve()
        self.UpdateButtonStatus()
        return out
    
    def DeleteAttachments(self):
        out = True
        al = self._attlist
        if al.RowsCount() > 0:
            rows = [(a.id, a.folderno, a.file) for a in al if a.id is not None]
            db = al._info.db
            filerr = []
            for di, dn, df in rows:
                db.Execute("DELETE FROM %s WHERE id=%d" % (self._tabname, di))
                path = al.GetFolderName(dn)
                file = opj(path, df)
                try:
                    os.remove(file)
                except Exception, e:
                    filerr.append(file[:100])
            if filerr:
                awu.MsgDialog(self, <commit_msg>Perfezionamento Memorizzazione allegati<commit_after>#!/bin/env python
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Name:         awc/controls/attachments.py
# Author:       <NAME> <<EMAIL>>
# Copyright:    (C) 2011 <NAME>.r.l. <NAME>, 122 18038 Sanremo (IM)
# ------------------------------------------------------------------------------
# This file is part of X4GA
# 
# X4GA is free software: you can redistribute it and/or modify
# it under the terms of the Affero GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# X4GA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with X4GA.  If not, see <http://www.gnu.org/licenses/>.
# ------------------------------------------------------------------------------


import sys, os, subprocess
def opj(x,y):
    return os.path.join(x,y).replace('\\', '/')

import wx
import wx.lib.colourdb as colourdb
import stormdb as adb

import awc.controls.attachbutton_wdr as wdr
import awc.util as awu
import awc.controls.dbgrid as dbg

#import twain
twain = None

from mx import DateTime

import locale
try:
    locale.setlocale(locale.LC_ALL, 'it')
except locale.Error:
    locale.setlocale(locale.LC_ALL)

import shutil

import awc.controls.images as images

import awc.thumbs as thumbs
import base64

import time

_colorsok = False

attach_dir = './attach_files'
FILES_PER_DIR = 1024


ATTACH_TYPE_FILE =  1
ATTACH_TYPE_TWAIN = 2
ATTACH_TYPE_URL =   3
ATTACH_TYPE_VOICE = 4
ATTACH_TYPE_NOTE =  5


PAGE_NOTES = 0
PAGE_FILE = 1
PAGE_TWAIN = -1
PAGE_URL = 2
PAGE_VOICE = -1


ATTACH_SIZEMAX = 1024*1024*16 #in bytes => 16 MB

if sys.platform.startswith('linux'):
    TEMP_DIR = '/tmp'
else:
    TEMP_DIR = os.getenv('temp')

TWAIN_TEMPFILE = opj(TEMP_DIR, "x4scan.bmp")

VOICE_MAXLEN =   60 #seconds
VOICE_TEMPFILE = opj(TEMP_DIR, "x4voice.mp3")
TEXT_TEMPFILE =  opj(TEMP_DIR, "x4text.txt")


__image_ext__ = ('bmp', 'jpg', 'jpeg', 'tif', 'tiff', 'gif')
__sound_ext__ = ('mp3', 'wav')

#__voicerec__ = True
#try:
#    import time, sys
#    import pymedia.audio.sound as sound
#    import pymedia.audio.acodec as acodec
#except ImportError:
#    __voicerec__ = False
#
#__voiceplay__ = True
#try:
#    import pymedia.audio.sound as sound
#    import pymedia.audio.acodec as acodec
#    import pymedia.muxer as muxer
#    if len(sound.getODevices()) == 0:
#        __voiceplay__ = False
#except:
#    __voiceplay__ = False


_evtATTACHSMODIFIED = wx.NewEventType()
EVT_ATTACHSMODIFIED = wx.PyEventBinder(_evtATTACHSMODIFIED, 1)
class AttachsModifiedEvent(wx.PyCommandEvent):
    pass


class _AttachTableMixin(adb.DbTable):
    
    def GetLastFolderNumber(self):
        last = 1
        if not os.path.exists(attach_dir):
            os.mkdir(attach_dir)
        dirs = [f for f in os.listdir(attach_dir) if os.path.isdir(f)]
        if dirs:
            dirs.sort()
            last = dirs[-1]
            last = last.replace('\\', '/')
            last = int(last.split('/')[-1])
        return last
    
    def GetNewFilePathAndName(self):
        path_number = self.GetLastFolderNumber()
        while True:
            path = self.GetFolderName(path_number)
            if not os.path.exists(path):
                os.mkdir(path)
            if len(os.listdir(path)) < FILES_PER_DIR:
                break
            path_number += 1
        now = DateTime.now()
        def f(x,n=2):
            return str(int(x)).zfill(n)
        file_name = '%s-%s-%s %s-%s-%s' % (f(now.year,4),
                                           f(now.month),
                                           f(now.day),
                                           f(now.hour),
                                           f(now.minute),
                                           f(now.second))
        return path_number, file_name
    
    def IsImage(self):
        if self.attach_type == ATTACH_TYPE_TWAIN:
            out = True
        elif self.attach_type == ATTACH_TYPE_FILE:
            out = (self.file or '').split('.')[-1].lower() in thumbs.IMAGE_EXTENSIONS
        else:
            out = False
        return out
    
    def IsNote(self):
        return self.attach_type == ATTACH_TYPE_NOTE
    
    def IsURL(self):
        return self.attach_type == ATTACH_TYPE_URL
    
    def IsVoice(self):
        return self.attach_type == ATTACH_TYPE_VOICE
    
    def IsTwain(self):
        return self.attach_type == ATTACH_TYPE_TWAIN
    
    def IsDocument(self):
        return self.attach_type == ATTACH_TYPE_FILE
    
    def GetFolderName(self, n=None):
        if n is None:
            n = self.folderno
        return opj(attach_dir, str(n).zfill(3))
    
    def GetFileName(self):
        return opj(self.GetFolderName(), self.file)
        
    def GetDefaultImageName(self):
        for a in self:
            if a.IsImage() and a.autotext:
                return self.GetFileName()
    
    def GetBase64EncodedImage(self, image=None):
        out = ''
        if not self.IsEmpty():
            if image is None:
                fn = self.GetFileName()
                if fn:
                    if os.path.exists(fn):
                        try:
                            f = open(self.GetFileName(), 'rb')
                            image = f.read()
                            f.close()
                        except Exception:
                            pass
            if image is not None:
                out = base64.b64encode(image)
        return out
    
    def GetBase64EncodedResizedImage(self, dimx, dimy, scale_test_func=None, force_jpeg=False):
        out = None
        if not self.IsEmpty():
            fn = self.GetFileName()
            if fn:
                if os.path.exists(fn):
                    try:
                        client_size = (dimx, dimy)
                        image = wx.Image(fn)
                        if callable(scale_test_func):
                            do = scale_test_func(image)
#                            if not do:
#                                out = self.GetBase64EncodedImage()
                        else:
                            do = True
                        if do:
                            image_size = image.GetSize()
                            proportions = (float(client_size[i]) / image_size[i]
                                           for i in range(len(image_size)))
                            proportion = min(proportions) # Use the smallest proportion.
                            # Can't use wx.Image.Shrink because it doesn't provide enough pixel
                            # granularity -- the shrinking has serious discontinuities.
                            new_size = (pixels * proportion for pixels in image_size)
                            image.Rescale(*new_size)
                        if do or force_jpeg:
                            import tempfile
                            tmpfile = tempfile.NamedTemporaryFile(suffix='.temp')
                            tmpname = tmpfile.name
                            tmpfile.close()
                            app = wx.GetApp()
                            if hasattr(app, 'AppendTempFile'):
                                wx.GetApp().AppendTempFile(tmpname)
                            image.SaveFile(tmpname, wx.BITMAP_TYPE_JPEG)
                            f = open(tmpname, 'rb')
                            buf = f.read()
                            f.close()
                            out = self.GetBase64EncodedImage(buf)
                    except Exception, e:
                        pass
        return out


# ------------------------------------------------------------------------------


class AttachTableMemo(_AttachTableMixin):
    
    def __init__(self, tabname, *args):
        _AttachTableMixin.__init__(self, tabname, 'allegati', writable=True,
                                   defaults={'attach_type': 1, 'hidden': 0})
        self.AddField('NULL', 'filecontent')
        self.AddFilter("FALSE")


# ------------------------------------------------------------------------------


class AttachTableList(_AttachTableMixin):
    
    def __init__(self, tabname, *args):
        mix = _AttachTableMixin
        mix.__init__(self, tabname, 'allegati', writable=False,\
                     fields="id,attscope,attkey,description,attach_type,"\
                     +"folderno,file,url,voiceatt_id,autotext,hidden")
        self.AddOrder("attach_type")
        self.AddOrder("datins")
        self.AddOrder("description")


# ------------------------------------------------------------------------------


class AttachmentButton(wx.Button):
    """
    Classe per la gestione degli allegati.
    
    Da qui è possibile inserire, modificare, eliminare e richiamare allegati di
    vario tipo.
    
    Gli allegati sono memorizzati sulla tabella 'allegati' e sono
    contestualizzati mediante i seguenti identificativi:
        - attscope    (char,10) Tipicamente il nome tabella a cui si riferiscono
        - attkey      (integer) Tipicamente l'id del record a cui si riferiscono
    
    A corredo di ogni allegato troviamo:
        - description (varchar,255) Descrizione dell'allegato
        - name        (varchar,255) Nome del documento o indirizzo url
        - content     (blob)        Contenuto dell'allegato
        - attach_type (integer)     Tipologia di allegato:
          1 = Documento
          2 = Immagine
          3 = URL
          4 = Nota vocale
          5 = Nota 
    
    Ad esempio, per allegare un documento alla scheda anagrafica di un cliente,
    pesupposto che il nome della tabella clienti sia 'clienti', avremo:
        attscope    = clienti (nome della tabella clienti)
        attkey      = 1234    (id del cliente nella tabella clienti)
        description = Condizioni contrattuali
        attach_type = 1 (documento)
        content     = <binary> - contenuto del file di Word.
    """

    def __init__(self, *args, **kwargs):
        
        wx.Button.__init__(self, *args, **kwargs)
        
        self._tabname = "allegati"
        self._attscope = None
        self._attkey = None
        self._canadd = True
        self._canedit = True
        self._autotext = None
        
        self._autotext_prefix = None
        self._autotext_suffix = None
        
        self._twain_sm = None
        self._twain_sd = None
        
        self._voice_playnow = True
        
        self.image_files = []
        
        self._spypanel = None
        self._autoscan = None
        
        #_attlist contiene le informazioni degli allegati senza il campo blob
        #che appesantisce la ricerca; è *sempre* allineato con il contenuto
        #della tabella allegati, anche dopo averne inserito di nuovi, la cui
        #scrittura sulla tabella è differita rispetto al loro inserimento nel
        #bottone - questo ne consente l'inserimento anche quando l'elemento a 
        #cui si riferiscono non ha ancora un proprio id.
        #ATTENZIONE
        #per la memorizzazione di nuovi allegati è necessario richiamare
        #l'apposito metodo Save - vedi specifiche
        self._attlist = AttachTableList(self._tabname)
        
        #_attmemo contiene *tutte* le informazioni dei soli allegati che si 
        #stanno inserendo - vedi considerazioni precedenti
        #ATTENZIONE
        #per la memorizzazione di nuovi allegati è necessario richiamare
        #l'apposito metodo Save - vedi specifiche
        self._attmemo = AttachTableMemo(self._tabname)
        
        self.Bind(wx.EVT_BUTTON, self._OnCallAttachMenu)
    
    def SetSpyPanel(self, panel):
        assert isinstance(panel, AttachmentSpyPanel)
        self._spypanel = panel
    
    def SetAutoScan(self, autoscan=True):
        self._autoscan = autoscan
    
    def GetDefaultImageName(self):
        return self._attlist.GetDefaultImageName()
    
    def SetAutoText(self, at):
        self._autotext = at
    
    def SetScope(self, scope):
        """
        Imposta lo 'scope' degli allegati, ovvero l'ambito nel quale la chiave
        a cui si riferiscono ha significato; tipicamente è il nome della 
        tabella sulla quale risiede il record a cui si riferiscono gli allegati.
        """
        self._attscope = scope
    
    def SetAutotextPS(self, prefix=None, suffix=None):
        self._autotext_prefix = prefix
        self._autotext_suffix = suffix
    
    def SetKey(self, key, save=False, delete=False):
        """
        Imposta la chiave numerica di relazione degli allegati; tipicamente è
        l'id del record della tabella indicata nello 'scope' (vedi SetScope)
        a cui si riferiscono gli allegati.
        """
        self._attkey = key
        if save:
            self.SaveAttachments()
        self._attmemo.Retrieve()
        if delete:
            self.DeleteAttachments()
        self.UpdateElements()
        self.UpdateButtonStatus()
    
    def SetPermissions(self, caninsert=True, canedit=True):
        """
        Imposta i diritti di inserimento e modifica degli allegati:
        SetPermissions(self, caninsert=True, canedit=True)
        """
        self._canadd = caninsert
        self._canedit = canedit
    
    def UpdateElements(self):
        """
        Aggiorna l'elenco degli allegati per la costruzione del menu alla
        pressione del bottone.
        """
        db = self._attlist
        db.ClearFilters()
        db.AddFilter("allegati.attscope=%s", self._attscope)
        db.AddFilter("allegati.attkey=%s", self._attkey)
        #db.AddFilter("allegati.datdel IS NULL")
        db.AddFilter("hidden<>1")        
        if db.Retrieve():
            sp = self._spypanel
            if sp is not None:
                sp.SetEmpty()
            at = self._autotext
            txt = ''
            for att in db:
                if sp is not None and att.IsImage() and att.autotext:
                    sp.SetFull()
                elif att.IsNote() and att.autotext and self._autotext:
                    file = att.GetFileName()
                    try:
                        h = open(file, 'r')
                        c = h.read()
                        h.close()
                        if txt:
                            txt += ' - '
                        txt += c
                    except:
                        pass
            if at is not None:
                text, sep = '', ' :: '
                if self._autotext_prefix:
                    text += self._autotext_prefix
                    if txt:
                        text += sep
                text += txt
                if self._autotext_suffix:
                    if txt:
                        text += sep
                    text += self._autotext_suffix
                at.SetText(text)
            n = db.RowsCount()
        else:
            n = 0
        font = self.GetFont()
        if n > 0:
            if n == 1:
                txt = "1 allegato"
            else:
                txt = "%d allegati" % n
            font.SetWeight(wx.FONTWEIGHT_BOLD)
        else:
            txt = "No allegati"
            font.SetWeight(wx.FONTWEIGHT_NORMAL)
        #self.Enable(n>0)
        self.SetFont(font)
        self.SetLabel(txt)
    
    def GetAutoNotes(self, db):
        txt = ''
        for att in db:
            if att.IsNote() and att.autotext:
                file = att.GetFileName()
                try:
                    h = open(file, 'r')
                    c = h.read()
                    h.close()
                    if txt:
                        txt += ' - '
                    txt += c
                except:
                    pass
        return txt
    
    def StoreNewAttach(self, att):
        out = True
        if att.filecontent:
            n, f = att.GetNewFilePathAndName()
            att.folderno = n
            path = att.GetFolderName()
            if not os.path.isdir(path):
                try:
                    os.mkdir(path)
                except:
                    out = False
            if att.file is None:
                ext = '.'
            else:
                ext = att.file
            filename = opj(path, '%s%s' % (f, ext))
            att.file = filename.split('/')[-1]
            att.size = len(att.filecontent)
            try:
                h = open(filename, 'wb')
                h.write(att.filecontent)
                h.close()
            except Exception, e:
                awu.MsgDialog(self, repr(e.args))
                out = False
        return out
    
    def LaunchAttachment(self, attid, saveonly=False):
        """
        Lancia l'allegato.
        """
        am = AttachTableList(self._tabname)
        if am.Get(attid) and am.OneRow():
            if   am.IsURL():
                if am.url:
                    try:
                        url = am.url
                        if not url.startswith('http://') and not url.startswith('https://'):
                            url = 'http://%s' % url
                        os.startfile(url)
                    except:
                        pass
                return
            file = am.GetFileName()
            if not saveonly:
                if am.IsVoice():
                    if am.hidden:
                        t = "Commento audio"
                    else:
                        t = am.description
                    self.CallVoicePlayer(file, start=self._voice_playnow,\
                                         title=t)
                else:
                    if not os.path.exists(file):
                        awu.MsgDialog(self, "File non trovato",
                                      style=wx.ICON_ERROR)
                        return
                    try:
                        os.startfile(file.replace('/','\\'))
                        if am.voiceatt_id is not None:
                            self._voice_playnow = False
                            self.LaunchAttachment(am.voiceatt_id)
                            self._voice_playnow = True
                        
                    except Exception, e:
                        if awu.MsgDialog(self,\
                                         """Impossibile aprire il file %s.\n"""\
                                         """Vuoi salvarlo?""" % file,\
                                         "Apertura allegato fallita",\
                                         style=wx.ICON_ERROR|\
                                         wx.YES_NO|wx.NO_DEFAULT) == wx.ID_YES:
                            saveonly = True
                        
                    except Exception, e:
                        awu.MsgDialog(self, repr(e.args))
            if saveonly:
                self.SaveAttachFileAs(file)
    
    def SaveAttachFileAs(self, tmp):
        out = False
        if '.' in tmp:
            ext = tmp[tmp.rindex(".")+1:]
            extinfo = "Tutti i files %s (*.%s)|*.%s" % (ext, ext, ext)
        else:
            ext = "*"
            extinfo = "Tutti i files (*.*)|*.*"
        file = tmp.replace("\\","/")
        if "/" in file:
            file = file[file.rindex("/")+1:]
        dlg = wx.FileDialog(None,\
                            message="Seleziona il file da salvare",\
                            defaultFile=file,\
                            wildcard=extinfo,\
                            style=wx.SAVE|wx.OVERWRITE_PROMPT)
        if dlg.ShowModal() == wx.ID_OK:
            try:
                shutil.copyfile(tmp, dlg.GetPath())
                out = True
            except Exception, e:
                awu.MsgDialog(self,\
                              """Impossibile scrivere su questo file\n"""\
                              """Errore: %s""" % repr(e.args))
        dlg.Destroy()
        return out

    def UpdateButtonStatus(self):
        global _colorsok
        if not _colorsok:
            colourdb.updateColourDB()
            _colorsok = True
        if self._attmemo.RowsCount() > 0:
            bg = wx.TheColourDatabase.Find("LIGHT CORAL")
        else:
            bg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNFACE)
        self.SetBackgroundColour(bg)
    
    def ManageAttachments(self):
        dlg = AttachmentsGridDialog(self, -1, "Manutenzione allegati",\
                                    style=wx.DEFAULT_FRAME_STYLE\
                                    |wx.RESIZE_BORDER)
        if dlg.ShowModal() == 1:
            self.UpdateElements()
        dlg.Destroy()
    
    def MakeNewAttach(self, toscan=False):
        
        am = self._attmemo
        attno = am.RowsCount()
        am.CreateNewRow()
        am.autotext = 0
        dlg = AttachmentDetailsDialog(self, -1, "Inserimento nuovo allegato")
        dlg._twain_sm = self._twain_sm
        dlg._twain_sd = self._twain_sd
        force_autotext = 0
#        if toscan:
#            def cn(x):
#                return dlg.FindWindowByName(x)
#            cn('description').SetValue("Scansione Documento")
#            dlg.notebook.SetSelection(2)
#            force_autotext = 1
        
        am.attach_type = dlg.ShowModal()
        
        if am.attach_type >= ATTACH_TYPE_FILE:
            
            am.attscope =    self._attscope
            am.attkey =      self._attkey
            am.description = dlg.GetFieldValue('description')
            am.datins =      DateTime.now()
            
            if am.IsTwain():
                am.file = dlg._file
            
            file = None
            if am.IsDocument():
                file = dlg.GetFieldValue('file')
            elif am.IsTwain():
                file = dlg._file
            if file is not None:
                file = file.replace('\\','/')
                if "/" in file:
                    file = file.split('/')[-1]
                if not '.' in file:
                    file += '.'
                file = file.split('.')[-1]
                am.file = '.%s'%file
            
            if am.IsImage() or am.IsNote():
                am.autotext = int(dlg._autotext or force_autotext)
            
            if am.IsURL():
                am.url = dlg.GetFieldValue('url')
                
            elif am.IsNote():
                am.file = '.txt'
            
            if dlg._stream:
                if am.IsDocument():
                    if am.IsImage():
                        am.attach_type = ATTACH_TYPE_TWAIN
                am.filecontent = dlg._stream
            
            self.UpdateButtonStatus()
            event = AttachsModifiedEvent(_evtATTACHSMODIFIED, self.GetId())
            event.SetEventObject(self)
            event.m_id = self.GetId()
            event.SetId(event.m_id)
            self.GetParent().AddPendingEvent(event)
        
        dlg.Destroy()
    
    def SaveAttachments(self):
        wx.BeginBusyCursor()
        out = True
        am = self._attmemo
        if am.RowsCount() > 0:
            if self._attkey is None:
                if am.RowsCount() == 1:
                    desc = "l'allegato inserito"
                else:
                    desc = "i %d allegati inseriti" % am.RowsCount()
                awu.MsgDialog(self,\
                              """Non è possibile memorizzare %s in quanto """\
                              """manca l'identificativo a cui riferirli""" % desc,\
                              style=wx.ICON_ERROR)
                out = False
            else:
                for i, a in enumerate(am):
                    if i>0:
                        time.sleep(1)
                    a.attkey = self._attkey
                    self.StoreNewAttach(am)
                out = am.Save()
                if not out:
                    awu.MsgDialog(self,\
                                  """Problema durante il salvataggio """\
                                  """degli allegati inseriti\nErrore: %s"""\
                                  % repr(am.GetError()))
                self.UpdateElements()
                am.Retrieve()
        wx.EndBusyCursor()
        self.UpdateButtonStatus()
        return out
    
    def DeleteAttachments(self):
        out = True
        al = self._attlist
        if al.RowsCount() > 0:
            rows = [(a.id, a.folderno, a.file) for a in al if a.id is not None]
            db = al._info.db
            filerr = []
            for di, dn, df in rows:
                db.Execute("DELETE FROM %s WHERE id=%d" % (self._tabname, di))
                path = al.GetFolderName(dn)
                file = opj(path, df)
                try:
                    os.remove(file)
                except Exception, e:
                    filerr.append(file[:100])
            if filerr:
                awu.MsgDialog(self, 
{'fraction_non_alphanumeric': 0.10801247451121507, 'fraction_numerical': 0.004857862540482188, 'mean_word_length': 1.274806730331969, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 34, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 70, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1655808', 'n_tokens_mistral': 14283, 'n_tokens_neox': 13734, 'n_words': 3371}