[casetta] Files... for previous mail

[ Thread Index | Date Index | More lists.tuxfamily.org/casetta Archives ]



--
Fabien ANDRE aka Xion345
Linux User #418689 -- fabien.andre.g@xxxxxxxxxx -- xion345@xxxxxxxxxxxxx
...Unix, MS-DOS, and Windows NT (also known as the Good, the Bad, and the Ugly). ( Matt Welsh, Not dated )
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2007 Xion345 aka Fabien ANDRE <fabien.andre.g@xxxxxxxxxx>
# (c) 2006-2007 Thesa aka Florian Birée <florian.biree@xxxxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0dev
#
# changelog:
#
# # 0.3.0 version:
#
#   o First release (extract password function is from casetta_gtk code)
#   o Add the DOTALL flag in regular expressions
#   o Remove the find_password function (replaced by the improved 
#     find_prgm_metadata function)
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""Pictures manager for casetta"""

__author__ = "Fabien ANDRE, Florian Birée"
__version__ = "0.3.0dev"
__copyright__ = "Copyright (c) 2006-2007, Fabien ANDRE, Florian Birée"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"

import re

def find_prgm_list(rawback):
    """Return a list like [(name1, use_base1, offset1, password1),... ]
    
    Where name1 is the raw name of the first program ;
    use_base1 is True if the program use base calculation, False else ;
    offset1 is the offset of the first program (for find_prgm_data function) ;
    password1 is the raw password of the first program.
    """
    names_pattern = re.compile( """
    [\x00\xee]          # first character (0x00 except if the last prgm have a
                        # password. In this case, 0xee)
    ([a-zA-Z0-9\x99\xa9\xb9
    \xcd\xce\x89\x20'"~.{}\[\]] # A program name begins by a charater
    [a-zA-Z0-9
    \x99\xa9\xb9\xcd\xce\x89\x20'"~.{}\[\]\xff]
    {7})                # End of program name: 7 of the above charaters or 0xff
    
    .{2}                # 2 charaters
    [\x00]{2}           # 2 0x00 characters
    ([\x00\x01])        # 0x01 if using base calculation, 0x00 else
    (.{2})              # offset of the beginning of the prgm data 
                        # If a password is present:
    (?:[\x10]           # A 0x10 character (ie a password)
    ([a-zA-Z0-9\x99\xa9\xb9\xcd\xce\x89\x20'"~.{}\[\]] # Password (same as name)
    [a-zA-Z0-9\x99\xa9\xb9\xcd\xce\x89\x20'"~.{}\[\]\xff]{7})
    [\x00]{7}           # 7 0x00 characters
    )?
    """, re.VERBOSE | re.DOTALL)
    names_list = names_pattern.findall(rawback)
    return [(i[0].replace('\xff',''), # Clean the name
             bool(ord(i[1])),         # Boolean base attribut
             i[2],                    # offset
             i[3].replace('\xff', '') # Clean the password
            ) for i in names_list]

def find_prgm_data(offset, rawback):
    """Return raw data of a program in a backup with its offset"""
    #hex1 = hex(ord(offset[1]))
    #hex2 = hex(ord(offset[0]))
    #ad2 = int(hex1 + hex2.replace('0x',''), 16)
    ad2 = ord(offset[1]) * 0x100 + ord(offset[0])
    i = ad2
    prgm_data = ""
    
    while i > 0:
        prgm_data += (rawback[i])
        if rawback[i] == '\xff':
            break
        i -= 1
    return prgm_data
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2007 Florian Birée aka Thesa <florian.biree@xxxxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0dev
#
# changelog:
#
# # 0.3.0 version:
#
#   o First release
#     This code is hardly inspired by cafix softwares (cafix.sourceforge.com)
#     A lot of thanks for cafix coders.
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""cas (CASIOLINK format) management module"""

__author__ = "Florian Birée"
__version__ = "0.3.0dev"
__copyright__ = "Copyright (c) 2007, Florian Birée"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"

import datetime
import errors
import data as datacls

# Constant
PICTURE_PALLET = ['o', 'b', 'g', 'w']
SCREEN_PALLET = ['b', 'g', 'w', 'o']

# Special Data class
class End(datacls.Data):
    """Special data to end a transfer."""
    def __init__(self):
        """New end data"""
        datacls.Data.__init__(self)
        self.dType = 'end'

# Cas format functions
def build_header(data):
    """Return a cas header built from data"""
    if data.__class__ == End:
        header = 'END' + '\xff' * 45
    else:
        #header_len = 49
        data_len = len(data.raw_data) + 2
        #header = '\xff' * header_len
        if data.__class__ == datacls.Program:
            header = 'TXT\x00PG'
            header += chr(data_len / (256 ** 3))
            header += chr((data_len % 256 ** 3) / (256 ** 2))
            header += chr((data_len % 256 ** 2) / 256)
            header += chr(data_len % 256) # 0 -> 9
            header += data.name[:8] + '\xff' * (8 - len(data.name[:8])) #10->17
            header += '\xff' * 8
            header += data.password[:8] + '\xff' * (8 - len(data.password[:8]))
            if data.use_base:
                header += 'BN'
            else:
                header += 'NL'
            header += '\xff' * 12
        elif data.__class__ == datacls.Backup:
            header = data.raw_data[2048: 2048 + 48]
            header = header[:33] + '\x00\x10\x00\x00\x00\x00' + header[39:]
        elif data.dType == 'variable' and False: ## Xion345 : I think this can now be wiped/commented  ;-)
            # TO BE CHECKED
            header += 'VAL\x00'
            #for (my $a = 0; $a < length($self->{dataname}); $a++) {
            #	last if ($a > 8);
            #	my $bar = sprintf "0x%lx",
            #	ord(substr($self->{dataname}, $a, 1));
            #	$foo[10+$a] = $bar;
            #}
            #$foo[18] = "0x56"; # V
            #$foo[19] = "0x61"; # a
            #$foo[20] = "0x72"; # r
            #$foo[21] = "0x69"; # i
            #$foo[22] = "0x61"; # a
            #$foo[23] = "0x62"; # b
            #$foo[24] = "0x6c"; # l
            #$foo[25] = "0x65"; # e
            #$foo[26] = "0x52"; # R
            #$foo[27] = "0x0a"; # LF
            #if ($self->{subtype} == 13) {
            #	$foo[4] = "0x56"; # V
            #	$foo[5] = "0x4d"; # M
            #	$foo[6] = "0x00"; # NUL
            #	$foo[7] = "0x01"; # SOH
            #	$foo[8] = "0x00"; # NUL
            #	$foo[9] = "0x01"; # SOH
            #}
        elif data.__class__ == datacls.Variable:
            header = 'VAL'+'\x00'+'VM'+'\x00\x01\x00\x01'
            header += data.name
            header += '\xff'*7 + 'VariableR' + '\x0a'
            header += '\xff'*20
        elif data.__class__ == datacls.ViewVariable:
            header = 'VAL'+'\x00'+'WD'+'\x00\x01\x00\x01'
            header += data.name
            header += '\xff'*(8-len(data.name))
            header += 'V\x99Win'+str(data.belongs_to_file)
            header += '\xff'*2 + 'R\n'
            header += '\xff'*20
        elif data.__class__ == datacls.Matrix:
            header = 'VAL'+'\x00'+'MT'+'\x00'+chr(data.dimensions[0])
            header += '\x00'+chr(data.dimensions[1])+data.name
            header += '\xff'*11 + '\x52'+'\x0a'
            header += '\xff'*20
        elif data.__class__ == datacls.List:
            header = 'VAL'+'\x00'+'LT'+'\x00'+'\x01'
            header += '\x00'+chr(data.dimension)+data.name
            if data.belongs_to_file == None:
                header += '\xff'*10 
            else:
                header += '\xff'*2+'File '+str(data.belongs_to_file)+'\xff'*2
            header += '\x52'+'\x0a'+'\xff'*20
        elif data.__class__ == datacls.SimlEquationMatrix:
            header = 'VAL'+'\x00'+'SE'+'\x00'+chr(data.dimensions[0])
            header += '\x00'+chr(data.dimensions[1])
            header += data.name +'EquationR\n' + '\xff'*20  
        elif data.__class__ == datacls.PolyEquationMatrix:
            header = 'VAL'+'\x00'+'PE'+'\x00'+chr(data.dimensions[0])
            header += '\x00'+chr(data.dimensions[1])
            header += data.name +'EquationR\n' + '\xff'*20  
        elif data.__class__ == datacls.FMem:
            header = 'TXT' +'\x00'+'FM'
            header += '\x00'*3 + chr(data_len)
            header += data.name
            header += '\xff'*6+'F\x99Mem'+'\xff'*25 
        elif data.__class__ == datacls.Picture:
            header = 'IMG\x00PC'
            header += '\x00\x40\x00\x80' # len ?
            header += data.name
            header += '\xff' * 8
            header += 'DRUWF'
            header += '\x00\x04\x00\x01' # len ?
            header += '\xff' * 13
        else:
            raise errors.DataNoManaged([data.__class__])
    # h_crc (header checksum) calcul
    h_crc = 0
    for byte in header:
        h_crc = (h_crc + ord(byte)) % 256
    h_crc = (0 - h_crc) % 256
    header += chr(h_crc)
    return header

def get_header_format(header):
    """Return [header_len, header_type, sub_type]."""
    sub_type = None
    type_id = header[0:3]
    sub_id = header[4:6]
    if type_id == "MEM" and sub_id == "BU":
        header_type = 'backup'
        headerlen = 49
    elif type_id == "DD@":
        header_type = 'screencapture'
        headerlen = 39
        sub_type = 'mono'
    elif type_id == "DC@":
        header_type = 'screencapture'
        headerlen = 39
        sub_type = 'color'
#     elif type_id == "FNC":
#         datatype = 4
#         headerlen = 49
    elif type_id == "IMG" and sub_id == "PC":
        header_type = 'picture'
        headerlen = 49
    elif type_id == "TXT" and sub_id == "PG":
        header_type = 'program'
        headerlen = 49
    elif type_id == "VAL" and sub_id == "MT": # Matrix
        header_type = 'matrix'
        headerlen = 49
    elif type_id == "VAL" and sub_id == "VM": # Simple variable like A,B,...,Z
        header_type = 'var'                   # For cafix : format=8
        headerlen = 49                        # datatype=13 size=14   
    elif type_id == "VAL" and sub_id == "WD": # Variable used to define a view-window
        header_type = 'viewvar'
        headerlen = 49
    elif type_id == "VAL" and sub_id == "LT": # List
        header_type = 'list'
        headerlen = 49
    elif type_id == "VAL" and sub_id == "SE":
        header_type = 'simlequation-matrix'
        headerlen = 49
    elif type_id == "VAL" and sub_id == "PE":
        header_type = 'polyequation-matrix'
        headerlen = 49
    elif type_id == "TXT" and sub_id == "FM":
        header_type = 'function-memory'
        headerlen = 49
    elif type_id == "FNC" and sub_id == "GF":
        header_type = "graph-func"
        headerlen = 49
#     elif type_id == "VAL":
#         datatype = 8
#         headerlen = 49
#     elif type_id == "REQ":
#         datatype = 7
#         headerlen = 49
    elif type_id == "END":
        header_type = 'end'
        headerlen = 49
    else:
        header_type = 'unknown'
        headerlen = 49
    return [headerlen, header_type, sub_type]

def get_data_len(header, data_type, sub_type):
    """Return data len from the header"""
    std_len = ord(header[6]) * (256 ** 3) + ord(header[7]) * (256 ** 2) + \
              ord(header[8]) * 256 + ord(header[9]) - 2
    if data_type == 'program':
        data_len = std_len
    elif data_type == 'backup': # bck
        data_len = std_len
    elif data_type == 'end':
        data_len = 0
#     elif data_type == 'val' :
#         data_len = stdlen
#         if data_sub_type == 13:
#             data_len = 14
#         elif data_sub_type == 6:
#             data_len = 2 * (ord(header[8]) + ord(header[9])) - 2
    elif data_type == 'program':
        data_len = std_len
    elif data_type == 'matrix' or 'simlequation-matrix' or data_type == 'polyequation-matrix':
        # A matrix is a table with 2 dimensions
        # header[7] is the first dimension
        # header[9] is the second dimension
        # 16 bytes per value transfered 
        # 14 bytes for the value itself 
        # + 1 byte \x3a, transfer inititation 
        # + 1 byte CRC
        data_len=14*ord(header[7])*ord(header[9])
    elif data_type == 'list':
        # A list is a table with 1 dimension
        # header[7] is the first dimension
        # 16 bytes per value transfered
        # 49 byte for a sort of footer (so it is very similar to matrixes)
        data_len = 14*ord(header[7])
    elif data_type == 'var' or data_type == 'viewvar':
        data_len = 14
    elif data_type == 'function-memory':
        data_len = ord(header[9])-2
    elif data_type == "graph-func":
        data_len = ord(header[9])-2
    elif data_type == 'screencapture' and sub_type == 'color': 
        #screencapture color
        data_len = 3075
    elif data_type == 'screencapture' and sub_type == 'mono':
        #screencapture mono
        data_len = 1024
    elif data_type == 'picture': #image
        data_len = 4112
    else:
        data_len = std_len
    return data_len

def fill_metadata(data, header, add_date = True):
    """Fill a data object with metadata from header"""
    ### All data ###
    # Name:
    # lazy code : what append if \xff are followed by non ff characters ?
    # FIXME : Why not to use the name_rule ?
    data.name = header[10:18].replace('\xff', '')
    # Date:
    if add_date:
        data.date = datetime.date.today()
    ### Programs ###
    if data.__class__ == datacls.Program:
        # Password:
        data.password = header[26:34].replace('\xff', '')
        # Base:
        if header[34:36] == 'BN':
            data.use_base = True
        else:
            data.use_base = False
    ### Matrices ###
    elif data.__class__ == datacls.Matrix or data.__class__ == datacls.SimlEquationMatrix or data.__class__ == datacls.PolyEquationMatrix :
        # Dimensions
        data.dimensions = (ord(header[7]),ord(header[9]))
    ### Lists ###
    elif data.__class__ == datacls.List:
        # Dimension
        data.dimension = ord(header[7])
        # Belongs to a FileList ?
        if header[23] in [str(i+1) for i in range(6)]:
            data.belongs_to_file = int(header[23])
    
    elif data.__class__ == datacls.ViewVariable:
        if header[23] in [str(i+1) for i in range(6)]:
            data.belongs_to_file = int(header[23])
    elif data.__class__ == datacls.GraphFunc:
        # F-Type
        if header[10] != 'f':
            data.ftype = header[10]
        else:
            data.ftype = 'param'
        # Equal_type
        data.equal_type = header[17]
        # Color
        if header[29:31] == 'BL':
            data.color = 'blue'
        elif header[29:31] == 'OR':
            data.color = 'orange'
        elif header[29:31] == 'GN':
            data.color = 'green'
        # Selected ?
        if header[32:35] == 'SLD':
            data.selected = True 
    ### Pictures ###
    elif data.__class__ == datacls.Picture:
        data.pallet = PICTURE_PALLET
        data.color_byte = 3
        ### Screen capture ###
    elif data.__class__ == datacls.ScreenCapture:
        data.name = "Capture"
        data.pallet = SCREEN_PALLET
        data.color_byte = 0

def color_screencapture_to_raw(screen_capture):
    """Return picture raw data from raw color screen capture."""
    return screen_capture[:0x400 * 2 + 2] +\
           '\x03' + '\x00' * 0x400 +\
           screen_capture[0x400 * 2 + 2:]
           
def mono_screencapture_to_raw(screen_capture):
    """Return picture raw data from raw mono screen capture."""
    # Convert screencapture (invert up and down)
    columns = [''] * 16
    for index in range(len(screen_capture)):
        col = index / 64
        columns[col] += screen_capture[index]
    for col in range(len(columns)):
        byte_list = list(columns[col])
        byte_list.reverse()
        columns[col] = ''.join(byte_list)
    black_sheet = ''.join(columns)
    return '\x01' + black_sheet +\
           '\x02' + '\x00' * 0x400 +\
           '\x03' + '\x00' * 0x400 +\
           '\x04' + '\x00' * 0x400

# File format management classes
class CasFile:
    """Cas (Casiolink) file format manager."""
    
    name = 'cas'
    managed_data = [datacls.Program, datacls.Backup, datacls.Picture, \
                    datacls.ScreenCapture]
    read = True
    write = False
    list_ext = ['cas']
    def __init__(self, filename = None):
        """Make a cas file object with filename"""
        self.filename = filename
        
    def open_file(self):
        """Open a file in the cas Format"""
        # Build the FileData
        file_data = datacls.FileData()
        cas_file = open(self.filename, 'r')
        ctn = cas_file.read()
        cas_file.close()
        # Init var
        index = 0
        is_header = True
        header = ""
        header_len = 100
        data = None
        data_len = 100
        raw_data = ""
        # Loop on bytes in ctn
        while index < len(ctn):
            if is_header and ctn[index] != ':' and len(header) < header_len:
                header += ctn[index]
                if len(header) == 7:
                    # Get metadata
                    header_len, data_type, sub_type = \
                                get_header_format(header)
            elif is_header and len(header) == header_len:
                # Crc byte, init the data record
                is_header = False
                if data_type == 'end':
                    break
                data_len = get_data_len(header, data_type, sub_type)
                # Make the data
                if data_type == 'program':
                    data = file_data.new_record(datacls.Program)
                elif data_type == 'backup':
                    data = file_data.new_record(datacls.Backup)
                elif data_type == 'picture':
                    data = file_data.new_record(datacls.Picture)
                elif data_type == 'ScreenCapture':
                    data = file_data.new_record(datacls.ScreenCapture)
                else:
                    data = file_data.new_record(datacls.Data)
                fill_metadata(data, header, False)
            elif not is_header and len(raw_data) == 0 and ctn[index] == ':':
                # Not header, first :
                pass
            elif not is_header and len(raw_data) < data_len:
                # Raw_data
                if data_type == 'screencapture' and sub_type == 'color' \
                        and (len(raw_data) == 1024 or len(raw_data) == 2048):
                    # Jump two bytes (checksum and :)
                    index += 2
                if data_type == 'picture' and (len(raw_data) + 1) % 1028 == 0 \
                        and len(raw_data) != 0 and len(raw_data) + 1 < data_len:
                    # Jump two bytes (checksum and :)
                    index += 2
                raw_data += ctn[index]
            elif not is_header and len(raw_data) == data_len:
                # Data Crc, save raw_data (and make some convertions)
                if data_type == 'screencapture' and sub_type == 'color':
                    data.raw_data = color_screencapture_to_raw(raw_data)
                elif data_type == 'screencapture' and sub_type == 'mono':
                    data.raw_data = mono_screencapture_to_raw(raw_data)
                else:
                    data.raw_data = raw_data
                # Init vars
                is_header = True
                header = ""
                header_len = 100
                data = None
                raw_data = ""
            index += 1
        return file_data
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2006-2007 Florian Birée aka Thesa <florian.biree@xxxxxxxxxxx>
# (c) 2006 Achraf Cherti aka Asher256 <achrafcherti@xxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0dev
#
# changelog:
#
# # 0.3.0 version:
#
#   o First release (code from __init__.py)
#   o Add picture-specific data properties
#   o Add new classes for each data type
#   o Change methods in FileData to have a better behaviour
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""Data management classes for casetta"""

__author__ = "Florian Birée, Achraf Cherti"
__version__ = "0.3.0dev"
__copyright__ = "Copyright (c) 2006-2007, Florian Birée, Achraf Cherti"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"

import re
import catdic
import pictures
import backups
import errors
from math import log10

class Data:
    """Data manipulation class"""
    name_rule = r'^.'
    dType = 'unknown'
    def __init__(self, name = '', raw_data = '', data_type = 'unknown', \
            date = None):
        """Make an empty data"""
        self.name = name
        self.raw_data = raw_data
        self.dType = data_type
        self.date = date

    def __repr__(self):
        """Descript this data"""
        return "<Data '%s', type: '%s'>" % (self.name, self.dType)
    
    def copy(self):
        """Creat a new Data object from this object."""
        return Data(self.name, self.raw_data, self.dType, self.date)
    
    def get_name(self):
        """Return the name in an human readable format"""
        return catdic.text_encode(self.name, True)
        
    def set_name(self, new_name):
        """Set the name from a newcat-like string."""
        self.name = catdic.text_decode(new_name, True)

class Program(Data):
    """Class for a casio program"""
    name_rule = r'^.{1,8}$'
    dType = 'program'
    def __init__(self, name = '', raw_data = '', date = None, password = "", \
            use_base = False):
        """Make an empty program"""
        Data.__init__(self, name, raw_data, self.dType, date)
        # Program specific properties
        self.password = password
        self.use_base = use_base
    
    def copy(self):
        """Creat a new Program object from this object."""
        return Program(self.name, self.raw_data, self.date, \
                       self.password, self.use_base)

    def get_text(self):
        """Return the program in an human readable format"""
        return catdic.text_encode(self.raw_data)

    def set_text(self, text):
        """Set the program from a newcat-like string"""
        self.raw_data = catdic.text_decode(text)
    
    def get_password(self):
        """Return the password in an human readable format"""
        return catdic.text_encode(self.password, True)
        
    def set_password(self, new_password):
        """Set the password from a newcat-like string."""
        self.password = catdic.text_decode(new_password, True)

class Backup(Data):
    """Class for a casio backup"""
    name_rule = r'^.'
    dType = 'backup'
    def __init__(self, name = '', raw_data = '', date = None):
        """Make an empty backup"""
        Data.__init__(self, name, raw_data, self.dType, date)
    
#     def find_password(self, program_name):
#         """Return a list of possible passwords for a program_name"""
#         return backups.find_password(self.raw_data, program_name)
    
    def find_program_list(self):
        """Find the program list from the backup
        
        Return a list like [(name1, use_base1, offset1, password1),... ]
        
        Where name1 is the raw name of the first program ;
        use_base1 is True if the program use base calculation, False else ;
        offset1 is the offset of the location of the first program ;
        password1 is the raw password of the first program.
        """
        return backups.find_prgm_list(self.raw_data)
    
    def get_program_by_name(self, program_name):
        """Return raw data of program from its offset"""
        # Get metadata list
        metadata_list = self.find_program_list()
        # Find metadata for program_name
        for (name, use_base, offset, password) in metadata_list:
            if name == program_name:
                return Program(name = name,
                               raw_data = backups.find_prgm_data(offset, 
                                                                 self.raw_data),
                               use_base = use_base,
                               password = password)
    
    def copy(self):
        """Creat a new Backup object from this object."""
        return Backup(self.name, self.raw_data, self.date)

################# Management of various types of VARIABLES ###################
# Casio variables (I mean simple variables like A,B...Z, matrix and lists)
# are stored in a similar way.

### Low level functions ###

def get_simple_val(raw_line):
    """Returns the value of the variable in *decimal* format."""
    val=''
    # raw_line[4:10] is where figures are stored
    for i in raw_line[4:10]:
        if ord(i) >= 10:
            val += hex(ord(i)).replace('0x','')
        else:
            val += '0'+hex(ord(i)).replace('0x','')
    val=val[0]+'.'+val[1:]
    # raw_line[12]=special code // raw_line[13]=power
    # raw_line[12]==\x01 -> positive number + positive power
    # raw_line[12]==\x00 -> positive number + negative power
    # raw_line[12]==\x51 -> negative number + positive power
    # raw_line[12]==\x50 -> negative number + negative power
    
    if raw_line[12]=='\x01' or raw_line[12]=='\x51': 
        #Then value > 0 data[13] is positive power
        power= int(hex(ord(raw_line[13])).replace('0x',''))+1
    else: # Then value < 0 data[13] is a negative power
        power=-100+int(hex(ord(raw_line[13])).replace('0x',''))+1
    
    if raw_line[12]=='\x50' or raw_line[12]=='\x51':
        # Then we have a negative number
        return float(val)*(10**power)*-1
    else: # Then we have a positive number
        return float(val)*(10**power)
 
def set_simple_val(nbre):
    if abs(nbre) >= 9.99999999*(10**99) or abs(nbre) <= 9.99999999*(10**-99) and nbre != 0:
        return "Absolute value of number is too high/low and not supported by the calculator" 
    elif nbre==0:
        return '\x00\x01\x00\x01'+'\x00'*8+'\x01'+'\x00'
    raw_line = ''
    string_num=str(nbre)
    if 'e' in string_num:
        string_num=string_num.split('e')[0]
    string_num=string_num.replace('-','').replace('.','')
    string_num=re.sub('^0*','',string_num)
    #print '*'+string_num+'*'
    
    # Numbers are stored this way : 
    # [0|0] [0|1] [0|0] [0|1] [0|1stfig] [2ndFig|3rdFig] [4thFig|5thFig]
    # [6thFig|7thFig] [8thFig|9thFig] [10thFig|0] [0|0] [0|0] [special code]
    # [power]  
    # Each [x|x] block is one byte  
    
    # Each xxFig is one figure of the number you entered
    # So you can see that except for 5th and the 10th byte,
    # 2 Fig are are stored in each byte
        
        
    # Stores the first figure in the first byte
    raw_line += chr(int(string_num[0],16))
    i=1
        
    # Stores figures 2-8 in the corresponding bytes
    # or \x00 if figures are not present
    while i <= 7:
        len(string_num)
        if (i+2) <= len(string_num):
            sli = string_num[i:i+2]
            raw_line += chr(int(sli,16))
        elif i == len(string_num)-1:
            sli = string_num[i]
            raw_line += chr(int(sli+'0',16))
        else:
            raw_line += '\x00'
        i +=2
    # Stores the last figure or \x00
    if len(string_num)>=10:
        raw_line += chr(int(string_num[9]+'0',16))
    else:
        raw_line += '\x00'
    
    raw_line=raw_line+'\x00\x00'
    if abs(nbre)>=1 or string_num=='1':
        power=int(log10(abs(nbre)))
    else:
        power=int(log10(abs(nbre)))-1
    # Sets the correct code and power
    # See get_val for more info.
    if nbre >= 0 and power >=0 :    
        raw_line += '\x01'+ chr(int(str(power),16))
    elif nbre >= 0 and power < 0:
        raw_line += '\x00'+ chr(int(str(100+power),16))
    elif nbre < 0 and power >=0:
        raw_line += '\x51'+ chr(int(str(power),16))
    elif nbre < 0 and power <=0:
        raw_line += '\x50'+ chr(int(str(100+power),16))
    return raw_line

### Variables and ViewVariables management ###

class Variable(Data):
    """Class for a casio simple var like A,B...Z"""
    name_rule = r'^[A-Z]$'
    dType = 'variable'
    def __init__(self, name='', raw_data='', date=None):
        """Makes an empty variable"""
        Data.__init__(self, name, raw_data, self.dType, date)
    def get_val(self):
        """ Returns the value of raw_data under the form of a *decimal*
        floatting point number"""
        return get_simple_val(self.raw_data)
    def set_val(self, number):
        """Takes a decimal integer or floatting point number and puts
        its value in raw_data"""
        self.raw_data='\x00\x01\x00\x01'+set_simple_val(number)

class ViewVariable(Variable):
    """ Class for a variable used to define a view-window"""
    name_rule = r'^Xmin|Xmax|Xscl|Ymin|Ymax|Yscl|T\xcemin|T\xcemax|T\xceptch$'
    dType = 'view-variable'
    def __init__(self, name='', raw_data='', date=None, belongs_to_vwin = None ):
        """Makes an empty variable"""
        Data.__init__(self, name, raw_data, self.dType, date)
        self.belongs_to_vwin = belongs_to_vwin
    def copy(self):
        """ Create a new ViewVariable object from this object"""
        return ViewVariable(self.name, self.raw_data, self.date, self.belongs_to_vwin)

### Matrices and Lists ###

class Matrix(Data):
    """Class for a casio matrix"""
    name_rule = r'^Mat [A-Z]$'
    dType = 'matrix'
    def __init__(self, name='', raw_data='', date = None, dimensions=(0,0)):
        """Makes an empty matrix"""
        Data.__init__(self, name, raw_data, self.dType, date)
        self.dimensions=dimensions
    
    def set_dimensions(self, dimensions):
        if dimensions[0] > 0 and dimensions[0] <= 255 and dimensions[1] > 0 and dimensions[1] <= 255:
            self.dimensions = dimensions
        else:
            raise errors.BadMatrix(dimensions, 'Bad dimensions, maximum size for matrices is 255x255')
    
    def copy(self):
        """ Create a new Matrix object from this object """
        return Matrix(self.name, self.raw_data, self.date, self.dimensions)
    
    def get_raw_data_list(self):
        """ Returns a list of rawdata composed of  each value of the
        matrix. Useful when sending a matrix 
        (each value has to be transferred independently, 
        and has its own checksum"""
        raw_data_list=[]
        for i in range(len(self.raw_data)/14): # Counts the number of variables inside the matrix
                                                          # A variable is 14 octets long
            raw_data_list.append(self.raw_data[i*14:(i+1)*14]) # Add each variable to the list
        return raw_data_list

    def get_val(self):
        """ Returns values contained in the matrix in a 
        human-readable format using a list of list"""
        current_line=0 
        raw_data_list=self.get_raw_data_list()
        data_list=[[]]
        for i in raw_data_list:
            value=get_simple_val(i)
            if ord(i[1]) == (current_line + 2):
                data_list.append([])
                current_line += 1
            data_list[current_line].append(value)
        return data_list
    
    def set_val(self, py_list):
        """ Sets the the matrix content. Takes a python list of list"""
        ## ***IMPORTANT*** : The checking and the actual remplacement of 
        ## ---------------
        ## values are done into two different blocks to avoid
        ## the matrix to be partially modified, and not 
        ## completely if an error appears. Therefore, if
        ## an error is raised during the checking, you can BE SURE
        ## the list was not modifed at all.
        
        ## Some tests to check the py_list givent is valid
        ## FIXME : Maybe there's a cleaner way to do that !!!
        for line in py_list:
            if type(line) == list:
                if len(line)==len(py_list[0]):
                    for el in line:
                        if type(el) != float and type(el)!=int:
                            raise errors.BadMatrix( el, 'Matrices must' \
                        ' be composed of numbers only')
                else:
                    raise errors.BadMatrix(line, 'Not all lines have same length')
            else:
                raise errors.BadMatrix(line, 'You must give a list of lists')
        self.raw_data=''
        self.set_dimensions((len(py_list), len(py_list[0]))) # Update dimensions before modifing/creatind values
        ## Actually sets the matrix content
        for num_line in range(len(py_list)):
            for index in range(len(py_list[num_line])):
                coord1=num_line+1 # Add 1 because numbering starts at 0 in python
                                    # compared to 1 on the calculator
                coord2=index+1
                # Add raw_number (number + coords)
                self.raw_data += '\x00'+chr(coord1)+'\x00'+chr(coord2)+set_simple_val(py_list[num_line][index])

class List(Data):
    """Class for a casio list"""
    name_rule = r'^List [1-6]$'
    dType = 'list'
    def __init__(self, name='', raw_data='', date = None, dimension=0, belongs_to_file = None ):
        """Makes an empty list"""
        Data.__init__(self, name, raw_data, self.dType, date)
        self.dimension = dimension
        # This var is used to specify if the list belongs to a file
        self.belongs_to_file = belongs_to_file
    
    def set_dimension(self, dimension):
        if dimension >= 0 and dimension <= 255:
            self.dimension = dimension
        else:
            raise errors.BadList(dimensions, 'Bad dimensions, maximum size for lists is 255') 
    
    def copy(self):
        """ Create a new List object from this object """
        return List(self.name, self.raw_data, self.date, self.dimension, self.belongs_to_file)
    
    def get_raw_data_list(self):
        raw_data_list=[]
        for i in range(len(self.raw_data)/14):
            raw_data_list.append(self.raw_data[i*14:(i+1)*14]) 
        return raw_data_list
        
    def get_val(self):
        raw_data_list=self.get_raw_data_list()
        data_list=[]
        for i in raw_data_list:
            value=get_simple_val(i)
            data_list.append(value)
        return data_list
        
    def set_val(self,py_list):
        ## ***IMPORTANT*** : The checking and the actual remplacement of
        ## --------------- 
        ## values are done into two different blocks to avoid
        ## the list to be partially modified, and not 
        ## completely if an error appears. Therefore, if
        ## an error is raised during the checking, you can BE SURE
        ## the list was not modifed at all.
        
        ## A test to check the list is valid
        for el in py_list:
            if type(el) != int and type(el) != float:
                raise errors.BadList(el, 'The list must be composed of numbers only')
        
        self.raw_data=''
        self.set_dimension=len(py_list)
        
        ## Actually sets the list content
        for index in range(len(py_list)):
            self.raw_data += '\x00'+chr(index+1)+'\x00\x01'+set_simple_val(py_list[index])

class SimlEquationMatrix(Matrix):
    name_rule = r'^Mat Siml$'
    dType = 'simlequation-matrix'
    
    def __init__(self, name='', raw_data='', date = None, dimensions=(2,3)):
        Data.__init__(self, name, raw_data, self.dType, date)
        self.dimensions = dimensions
        
    def set_dimensions(self,dimensions):
        if dimensions in [(int(i)+2,int(i)+3) for i in range(5)]:
            self.dimensions = dimensions
        else:
            raise errors.BadMatrix(dimensions, 'Bad dimensions')

class PolyEquationMatrix(Matrix):
    name_rule = r'^Mat Poly$'
    dType = 'polyequation-matrix'
    
    def __init__(self, name='', raw_data='', date = None, dimensions=(1,3)):
        Data.__init__(self, name, raw_data, self.dType, date)
        self.dimensions = dimensions
        
    def set_dimensions(self,dimensions):
        if dimensions in [(1,3),(1,4)]:
            self.dimensions = dimensions
        else:
            raise errors.BadMatrix(dimensions, 'Bad dimensions')

############################################################################################

class FMem(Data):
    """Class for a casio F-Mem definition"""
    name_rule = r'^f[1-6]$'
    dType = 'function-memory'
    def __init__(self, name='', raw_data='', date=None):
        """ Makes an empty function memory"""
        Data.__init__(self, name, raw_data, self.dType, date)

    def get_text(self):
        """ Returns the content of the F-Mem in an human readable format"""
        return catdic.text_encode(self.raw_data)

    def set_text(self, text):
        """ Sets the content of the F-Mem from a newcat-like string"""
        self.raw_data = catdic.text_decode(text)
        
class GraphFunc(Data):
    """ Class for a casio Graph. Function. 
    
    Behaviour of this class is different according 
    to the type of the function : Y,X,Param,r """
    name_rule = r'^Y[1-20]|r[1-20]|f[1-20]|X[1-20]'
    dType = 'graph-function'
    func_types = ('Y','X','r','param')
    equal_types = ('=', '>', '<', '\\<=', '\\>=')
    colors = ('blue','orange','green')
    
    def __init__(self, name='', raw_data='', date=None, ftype='Y', equal_type='=', color='blue', selected=False ):
        """ Makes an empty Graph. Function """
        Data.__init__(self, name, raw_data, self.dType, date)
        self.set_type(ftype, equal_type)
        self.set_color(color)
        self.selected = selected
            
    def copy(self):
        """ Create a new GraphFunc object from this object """
        return GraphFunc(self.name, self.raw_data, self.date, self.ftype, self.equal_type, self.color, self.selected) 
    
    def set_color(self, color):
        if color in self.colors:
            self.color = color
            return True
        else:
            return False

    def set_type(self, ftype, equal_type):
        if ftype in self.func_types:
            self.ftype = ftype
        else:
            raise errors.BadFunction(ftype, 'Bad function type. Possible types are : '+str(func_types) )

        if equal_type != '=':
            if ftype == 'Y':
                if equal_type in self.equal_types:
                    self.equal_type = catdic.text_decode(equal_type, True)
                else:
                    raise errors.BadFunction(ftype, 'Bad equality/inequality. Possibilities are '+str(self.equal_types))
            else:
                raise errors.BadFunction(ftype, 'It is only possible to use inequalities with "Y" functions')
    
    def get_properties(self):
        """ Returns metadata about this function (type,equal_type,color, selected)"""
        return (self.ftype, catdic.text_encode(self.equal_type), self.color, self.selected)

    def get_text(self):
        """Returns the content of the fucntion in an human-readable format"""
        return catdic.text_encode(self.raw_data)
    
    def set_text(self, raw_data):
        """Sets the function content from a newcat-like string"""
        ### FIXME + WARNING : Maybe it would be good to add some tests !! e.g. : verify there are no instructions like \Prog etc... in the text / There are no X in X function types / there are one \xf6 chjaarcter in Param. functions
        self.raw_data = catdic.text_decode(raw_data, True) 
    
class Picture(Data):
    """Class for a casio picture"""
    name_rule = r'^Picture[1-6]$'
    dType = 'picture'
    def __init__(self, name = '', raw_data = '', date = None, pallet = None, \
            color_byte = None):
        """Make an empty picture"""
        Data.__init__(self, name, raw_data, self.dType, date)
        # self.pallet must be a list of characters as ['o','g','b','w']
        self.pallet = pallet
        self.color_byte = color_byte
    
    def copy(self):
        """Create a new Picture object from this object."""
        return Picture(self.name, self.raw_data, self.date, self.pallet, \
                       self.color_byte)
    
    def get_picture(self):
        """Return a list of pixels from the picture"""
        return pictures.raw_to_data(self.raw_data, \
                                    self.color_byte, \
                                    self.pallet)
    
    def set_picture(self, pixels_list):
        """Set the picture from a list of pixels"""
        self.raw_data = pictures.data_to_raw(pixels_list, \
                                             wanted_pallet = self.pallet)
    
    def change_pallet(self, new_pallet, headers = None):
        """Convert the picture from the current pallet to new_pallet.
        
        You can add a header using headers (like pictures.sheets_to_raw).
        """
        self.raw_data = pictures.sheets_to_raw(\
                            pictures.change_sheets_pallet(\
                                    pictures.raw_to_sheets( \
                                        self.raw_data, \
                                        self.color_byte, \
                                        len(self.pallet)), \
                                    self.pallet, new_pallet), \
                            headers)
        self.pallet = new_pallet
        if headers != None:
            # Change the color_byte
            self.color_byte = len(headers[0])

    def get_program(self):
        """Return a program in newcat-like format to draw the picture."""
        return pictures.img_to_basic(self.get_picture())

class ScreenCapture(Picture):
    """Class for a screen capture"""
    name_rule = r'^.'
    dType = 'screencapture'
    def __init__(self, name = '', raw_data = '', date = None, pallet = None, \
            color_byte = None):
        """Make an empty screen capture"""
        Picture.__init__(self, name, raw_data, date, pallet, color_byte)
    
    def copy(self):
        """Creat a new ScreenCapture object from this object."""
        return ScreenCapture(self.name, self.raw_data, self.date, self.pallet, \
                       self.color_byte)

############## Classes for various types of files ###################

class FileList(Data):
    """File List management class. It can only contains Lists"""
    name_rule = r'^File [1-6]$'
    dType = 'filelist'
    def __init__(self, number):
        """Create a filelist"""
        self.data = []
        self.number = number
        self.name = 'File ' + str(self.number)
    def __getitem__(self,index):
        """ Same as self.data[index] 
         
        This function is supposed to be used 
        to get a list from the filelist. (eg: filelist[5], 
        the returns the fifth list of the filelist"""
        return self.data[index-1]
    def __len__(self,index):
        """Returns the number of lists in the file. Same as len(self.data)"""
        return len(self.data)
    def add_list(self, lis):
        self.data.append(lis)

### Top-level file, able to contain any number of the above defined files

class FileData:
    """Data file manipulation class"""
    def __init__(self, filename = None, format = None):
        """Make a FileData.
        
        If filename is filled, the file given is opened.
        If format is None, the format is autodetected."""
        self.data = []
        self.export = []
        if filename != None:
            self.import_file(filename, format)

    def __repr__(self):
        """Descript this data"""
        out = "<File of %s casio data:\n" % str(len(self.data))
        for data_index in range(len(self.data)) :
            out += "#%s: %s\n" % (str(data_index), \
                                  self.data[data_index].__repr__())
        out += '>'
        return out

    def __getitem__(self, index):
        """The same of self.data[index]""" 
        return self.data[index]
    def __len__(self):
        """Returns the len of self.data, number of items contained in the file_data"""
        return len(self.data)
         
    def export_list(self):
        """Return the list of data to be exported."""
        out_list = []
        for index in self.export:
            out_list.append(self.data[index])
        return out_list

    def remove(self, data):
        """Remove the data record."""
        self.data.remove(data)
        # clear the export list because index list has changed
        self.export = []
    
    def new_record(self, data_class, arg_list = [], arg_dic = {}):
        """Make a new data from the data_class (Program, Backup, etc)
        
        The new data object is append to the file data, and the
        the data object is returned.
        arg_list and arg_dic can be used to give arguments in the class
        initialization.
        The name of the returned data should be checked.
        """
        new_data = data_class(*arg_list, **arg_dic)
        self.data.append(new_data)
        return new_data

    def save(self, filename, format=None, ignore_warnings=False, \
             export=False):
        """Save in (a) file(s) the data.

        If all data aren't managed by this format, raise DataNoManaged
        with the list of data type not managed (['all'] for all ;-) )
        Use ignore_warnings = True to save managed data in this format.
        """
        if format == None:
            from formats import choose_format
            format = choose_format(filename)
        if not format.write:
            raise errors.FormatCantSave(format)
        if not ignore_warnings :
            no_managed_data = self.check_no_managed_data(format, export)
            if no_managed_data != []:
                raise errors.DataNoManaged(no_managed_data)
        afile = format(filename)
        if export:
            afile.save(self.export_list())
        else:
            afile.save(self.data)
        
    def add_records(self, file_data):
        """Add records from another file_data.
        
        Return the list of new records.
        The name of returned data should be checked.
        """
        self.data += file_data.data
        return file_data.data
    
    def import_file(self, filename, format = None):
        """Import all records from filename.
        
        Return the list of new records.
        The name of returned data should be checked.
        """
        if format == None:
            from formats import choose_format
            format = choose_format(filename)
        if not format.read:
            raise errors.FormatCantOpen(format)
        imported_file = format(filename).open_file()
        return self.add_records(imported_file)

    def list_data_types(self, export=False):
        """Return the list of data types used"""
        ret = []
        if export:
            data_list = self.export_list()
        else :
            data_list = self.data
        for data in data_list:
            if not data.__class__ in ret:
                ret.append(data.__class__)
        return ret

    def check_name(self, checked_data):
        """Return 1 if the name of this data is correct
        
        Return 0 if the name syntax is bad
        Return -1 if the name is already used"""
        # check the syntax of the name
        if re.search(checked_data.name_rule, checked_data.name) == None:
            return 0
        # check if already employed
        for data in self.data:
            if data is not checked_data and \
                    data.__class__ == checked_data.__class__ and \
                    data.name == checked_data.name:
                return -1
        return 1

    def copy_record(self, data):
        """Copy data. Return the copy.
        
        The name of the returned data must be changed.
        """
        copy = data.copy()
        self.data.append(copy)
        return copy

    def split_by_data_type(self):
        """Split this file_data by data type.

        Return a dictionary of file data with data classes as keys.
        
        """
        ret = {}
        for data in self.data:
            if not data.__class__ in ret.keys():
                ret[data.__class__] = FileData()
            ret[data.__class__].data += data.copy()
        return ret

    def check_no_managed_data(self, format, export = False):
        """ Return the list of data classes no managed in format.

        Return [] if all data type are managed, ['all'] if nothing is managed
    
        """
        ret = []
        for data_class in self.list_data_types(export) :
            if not data_class in format.managed_data:
                ret.append(data_class)
        ret.sort()
        fd_list = self.list_data_types()
        fd_list.sort()
        if fd_list == ret :
            ret = ['all']
        return ret
    
    def send(self, tool, export = False):
        """Send data using tool."""
        if export:
            tool.send(self.export_list())
        else:
            tool.send(self.data)

    # Deprecated methods (here for compatibility reason):
    def del_data(self, delete_index):
        """Del the data with the index i
        
        Deprecated: please use remove instead
        """
        del(self.data[delete_index])
        # clear the export list because index list has changed
        self.export = []
    
    def add_data(self, file_data):
        """Add data from another file_data

        Return the list of new index
        (You should check the name of those datas)
        
        Deprecated: please use add_records instead
        """
        length = len(self.data)
        self.data += file_data.data
        return range(length, len(self.data))

#     def add(self, filename, format = 'unknown'):
#         """Open datas from filename and add them in the file .

#         Return the list of new index
#         (You should check the name of those datas)
#         
#         Deprecated: please use import instead
#         """
#         if format == 'unknown' :
#             format = formats.choose_format(filename)
#             if format == 'unknown':
#                 raise errors.FormatNotFound
#         return self.add_data(formats.openFunc[format](filename))

    def copy_data(self, data_index):
        """Copy data from dataIndex.

        Return the new index
        (You should check the name of the copy)
        
        Deprecated: use copy_record instead
        """
        new_index = self.new_data()
        self.data[new_index].name = self.data[data_index].name
        self.data[new_index].raw_data = self.data[data_index].raw_data
        self.data[new_index].dType = self.data[data_index].dType
        self.data[new_index].date = self.data[data_index].date
        self.data[new_index].password = self.data[data_index].password
        return new_index

    def new_data(self):
        """Make a new empty data, and return it index.
        
        Deprecated, use new_record instead
        """
        self.data.append(Data())
        return len(self.data) - 1

# Static data:
data_type_list = [Data, Program, Backup, Picture, ScreenCapture]
data_type_dic = {'unknown' : Data,
                 'program' : Program,
                 'backup' : Backup,
                 'picture' : Picture,
                 'screencapture' : ScreenCapture}
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2007 Florian Birée aka Thesa <florian.biree@xxxxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0dev
#
# changelog:
#
# # 0.3.0 version:
#
#   o First release
#     This code is hardly inspired by cafix softwares (cafix.sourceforge.com)
#     A lot of thanks for cafix coders.
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""Internal serial tansfer tool"""

__author__ = "Florian Birée"
__version__ = "0.3.0dev"
__copyright__ = "Copyright (c) 2007, Florian Birée"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"

import serial
import time
import sys
import cas
import errors
import data as datacls
### Opened files for log :
filereceived=open('/home/fabien_kubuntu/src/casetta/casetta-work/matrix/current-received.hex','w')
filesent=open('/home/fabien_kubuntu/src/casetta/casetta-work/matrix/current-sent.hex','w')
# Default functions
def default_status(data_name, current_size, total_size, is_header = False):
    """Print the status of a transfert on the standard output."""
    
    if total_size > 1: # Xion345 : I added this line to avoid a Zero-division error when receiving data of length 1
        percent = ((current_size * 100) / total_size)
    else:
        percent = 100

    if is_header:
        progress_line = 'Header   '
    else:
        progress_line = data_name + ' ' * (9 - len(data_name))
    progress_line += '[' + '=' * (percent / 2)
    progress_line += ' ' * (50 - (percent / 2)) + '] '
    progress_line += '(%i%%) %i/%i' % (percent, current_size, total_size)
    if current_size != 0:
        progress_line = '\r' + progress_line
    sys.stdout.write(progress_line)
    sys.stdout.flush()
    if current_size >= total_size:
        sys.stdout.write('\n')

def default_overwrite(data_name):
    """Ask the user if he wants to overwrite data_name on the calculator."""
    user_rep = raw_input('Overwrite %s (Y/N): ' % data_name)
    return user_rep.lower().startswith('y')


# Serial management classes
class Connexion:
    """Casetta serial transfer tool."""
    
    name = 'serial'
    managed_data = [datacls.Program, datacls.Backup, datacls.Picture, \
                    datacls.ScreenCapture]
    def __init__(self, serial_port = 0, status = default_status, \
                 overwrite = default_overwrite):
        """Initial configuration of the serial port
        
        status is a function which is runned as status(current_data_name, 
        current_size, total_size, is_header) each time the status change.
        overwrite is a function which is runned as overwrite(data_name) and 
        which must return True or False.
        """
        if type(serial_port) == str and serial_port.isdigit():
            serial_port = int(serial_port)
        self.serial = serial.Serial(
            port=serial_port,
            baudrate=9600,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            timeout=5, # or None
            xonxoff=0, #0
            rtscts=0) #(o:1)0
        self.status = status
        self.overwrite = overwrite
        self.cancel = False
    
    def check_cancel(self):
        """If the cancel flag is set, unset it and raise TransferAborted"""
        if self.cancel:
            self.cancel = False
            raise errors.TransferAborted()
    
    ################## Low level serial functions #######################
    def get_byte(self):
        """Read a single byte"""
        byte = self.serial.read()
        return byte

    def send_byte(self, byte):
        """Send a single byte"""
        self.serial.write(byte)
        
    def send_string(self, string, status = None, data_name = '', \
                    is_header = False):
        """Send a string of bytes"""
        for index in range(len(string)):
            self.check_cancel()
            self.send_byte(string[index])
            time.sleep(0.005)
            if status != None:
                status(data_name, index, len(string) - 1, is_header)
                
    ######################################################################

    ### Send system
    def send(self, data_list):
        """Send all data in data_list
        
        Warning: a backup must be the only data to send
        """
        while not self.calc_is_ready_to_receive():
            self.check_cancel()
        for data in data_list:
            if data.__class__ == datacls.FileList:
                for i in data:
                    self.send_data(i)
            else:
                self.send_data(data)
            ### Here to manage file list
        if data_list[-1].__class__ != datacls.Backup:
            # Send the END header
            self.send_header(cas.End())
    
    def calc_is_ready_to_receive(self):
        """Return if the calculator is ready to receive some data"""
        interactive = False
        if interactive:
            code = '\x06'
        else:
            code = '\x16'
        self.send_byte(code)
        byte_rep = self.get_byte()
        if interactive and byte_rep == '\x06':
            return True
        elif not interactive and byte_rep == '\x13':
            return True
        else:
            return False
    
    def send_data(self, data):
        """Send a data to the calc"""
        self.send_header(data)
        response = self.get_byte()
        if response == '\x06':
            # Calculator accept the header
            pass
        elif response == '\x24':
            raise errors.DataNoManaged([data.__class__])
        elif response == '\x2b':
            raise errors.ChecksumError()
        elif response == '\x21':
            #File already exist in the calculator.
            if self.overwrite(data.name) :
                self.send_byte('\x06')
                sec_resp = self.get_byte()
                if sec_resp != '\x06':
                    raise errors.CannotOverwrite(data.name)
                # Calculator accept to overwrite.
            else:
                # send abort code
                self.send_byte('\x15')
                sec_resp = self.get_byte()
                if sec_resp != '\x06':
                    raise errors.BadAnswerFromCalc(sec_resp)
                raise errors.TransferAborted()
        elif response == '\x51':
            raise errors.HeaderError()
        else:
            raise errors.BadAnswerFromCalc(response)
        # Make the data list to be transfered
        if data.__class__ == datacls.Picture:
            data.change_pallet(cas.PICTURE_PALLET, ['\x00\x01\x00'] * 4)
            raw_data_list = [data.raw_data[0:1028],
                             data.raw_data[1028:2056],
                             data.raw_data[2056:3084],
                             data.raw_data[3084:4112]]
        elif data.__class__ == datacls.Matrix or data.__class__ == datacls.List or data.__class__ == datacls.SimlEquationMatrix or data.__class__ == datacls.PolyEquationMatrix:
            raw_data_list = data.get_raw_data_list()
        else:
            raw_data_list = [data.raw_data]
        for raw_data in raw_data_list:
            # CRC calcul
            crc = 0
            for byte in raw_data:
                crc += ord(byte)
            #crc = abs(255 - (crc % 256)) + 1
            crc = (abs(255 - (crc % 256)) + 1) % 256
            #print "old crc=", crc
            #crc = 0
            #for byte in raw_data:
            #    crc += ord(byte)
            #    if crc > 255:
            #        crc -= 256
            #crc = 256 - crc
            #print "new crc=", crc
            # Now sending the data
            filesent.write('\x3a' + raw_data)
            self.send_string('\x3a' + raw_data, self.status, data.name, False)
            # All data sent, let the calc check the CRC
            filesent.write(chr(crc))
            self.send_byte(chr(crc))
            response = self.get_byte()
            if response != '\x06':
                raise errors.ChecksumError()
        return
    
    def send_header(self, data):
        """Send the header corresponding to data"""
        header = cas.build_header(data)
        if data.__class__ == cas.End:
            self.send_string('\x3a' + header, self.status, 'End', False)
        else:
            self.send_string('\x3a' + header, self.status, data.name, True)
        return




    ### Receive system
    def receive(self):
        """Return a file_data with all data receive from the calc."""
        file_data = datacls.FileData()
        while not self.calc_is_ready_to_send():
            self.check_cancel()
        # Debut de la réception
        while True:
            data = self.receive_data()
            
            if data.__class__ == cas.End:
                break
            if data.__class__ == datacls.List and data.belongs_to_file != None:
                #print "Liste appartenant à un fichier " + str(data.belongs_to_file)
                if data.name[-1] == '1':
                    #print "1ère liste détectée"
                    filelist = datacls.FileList(number = data.belongs_to_file)
                    #print filelist.__class__
                    file_data.data.append(filelist) ## FIXME : It would be better to use a method
                    #print file_data[0].__class__
                
                for i in range(len(file_data)): ## FIXME : It would be better to remember index of file_list
                    if file_data[i].__class__== datacls.FileList and file_data[i].name == 'File '+str(data.belongs_to_file):
                        print file_data[i]
                        file_data[i].add_list(data)
            else:
                file_data.data.append(data)
            
            if data.__class__ == datacls.ScreenCapture or \
                    data.__class__ == datacls.Backup:
                break
        return file_data
    
    def calc_is_ready_to_send(self):
        """Waiting for receiving data"""
        byte = self.get_byte()
        if byte == '\x15':
            # Received request for interactive handshake (0x15)
            # -- responding with 0x13
            self.send_byte("\x13")
            return True
        elif byte == '\x16':
            # Received request for noninteractive handshake (0x16)
            # -- responding with 0x13
            self.send_byte("\x13")
            return True
        else:
            return False

    def receive_data(self):
        """Receive a data"""
        # Get header
        self.status('unknown', 0, 49, True)
        header, data_type, sub_type = self.get_header()
        print [data_type, sub_type]
        self.status('unknown', 49, 49, True)
        if data_type == 'end':
            # End of the transfer : no data
            return cas.End()
        # Get data informations
        data_len = cas.get_data_len(header, data_type, sub_type)
        # Make the data
        if data_type == 'program':          # Xion345 : It is possible to 
            data = datacls.Program()        # replace all this code by something
        elif data_type == 'backup':         # shorter like : getattr('datacls.'\
            data = datacls.Backup()         # + data_type[0].upper() + data_type
        elif data_type == 'picture':        # [1:]+'()')
            data = datacls.Picture()
        elif data_type == 'screencapture':
            data = datacls.ScreenCapture()
        elif data_type == 'var':
            data = datacls.Variable()
        elif data_type == 'viewvar':
            data = datacls.ViewVariable()
        elif data_type == 'matrix':
            data = datacls.Matrix()
            #data.dimensions=(ord(header[7]),ord(header[9])) -> Moved to the function fill_metadata in cas.py
        elif data_type == 'list':
            data = datacls.List()
            #data.dimension=ord(header[7]) -> Moved to the function fill_metadata in cas.py
        elif data_type == 'simlequation-matrix':
            data = datacls.SimlEquationMatrix()
        elif data_type == 'polyequation-matrix':
            data = datacls.PolyEquationMatrix()
        elif data_type == 'function-memory':
            data = datacls.FMem()
        elif data_type == 'graph-func':
            data = datacls.GraphFunc()
        else:
            # unknown data, refuse header
            self.send_byte("\x00")
            raise errors.HeaderError()
        cas.fill_metadata(data, header)
        print data.__class__
        #if data_len == 0:
        #    #datalen is 0 -- sending 0x00
        #    self.send_byte('\x00')

        # we accepted the header -- so we send 0x06 to the calc
        self.send_byte("\x06")
        ### Transfer part
        ## The transfer part is executed only if data_len > 0. You can receive empty list
        ## when receiving a file list from the calculator. 
        ## In this case only a header is transmitted ! 
        ## So, the read byte is not the byte to initiate the transfer 
        ## but the byte to initiate the transfer of the next header but the first byte of the next
        ## header. Same thing happens when receiving all "Variables".
        if data_len > 0:
            if not self.get_byte() == '\x3a':
                raise errors.BadAnswerFromCalc()
            crc = 0
            raw_data = ''
            #print [data_len]
            for index in range(data_len):
                self.check_cancel()
                byte = self.get_byte()
                filereceived.write(byte)
                print [byte]
                crc = crc + ord(byte)
                
                ## List/Matrix management specific block
                if ( data_type == 'list' or data_type == 'matrix' or data_type == 'simlequation-matrix' or data_type == 'polyequation-matrix' ) and (index-13)%14==0 and index <= data_len-14:
                    # Checksum test
                    calc_crc=self.get_byte()
                    filereceived.write(calc_crc)
                    if abs(255 - (crc % 256)) + 1 == ord(calc_crc):
                        self.send_byte('\x06')
                        crc=0
                        resp=self.get_byte()
                        filereceived.write(resp)
                        if resp != '\x3a':
                            raise errors.BadAnswerFromCalc(resp)
                    else:
                        raise errors.ChecksumError()

                ## Screencapture management specific block
                if data_type == 'screencapture' and sub_type == 'color' \
                        and (index == 1024 or index == 2048):
                    resp2 = self.get_byte()
                    self.send_byte('\x06')
                    resp2 = self.get_byte()
                    crc = 0

                ## Picture management specific block
                if data_type == 'picture' and (index + 1) % 1028 == 0 \
                        and index != 0 and index + 1 < data_len:
                    # IMAGE / color screencapture
                    newcrc = ord(self.get_byte())
                    crc = abs(255 - (crc % 256)) + 1
                    if not newcrc == crc :
                        raise errors.ChecksumError()
                    crc = 0
                    self.send_byte('\x06')
                    resp2 = self.get_byte()
                    if not resp2 == '\x3a':
                        raise errors.BadAnswerFromCalc(resp2)
                #if data_type == 1 and bytesproc + 1 == 62684:
                #    print 'byte 62684 of a backup : send 0x06'
                #    send_byte('\x06')
                raw_data += byte
                #self.status(data.name, index, data_len - 1, False)
                self.status(data.name, index, data_len-1, False)
            ## Check CRC
            newcrc = ord(self.get_byte())
            crc = abs(255 - (crc % 256)) + 1
            #print 'new crc=', newcrc, 'crc=', crc
            if newcrc != crc and (data_type != 'screencapture' and \
                    sub_type != 'color') and data_len != 0 :
                # Warning: the crc check is not done for color screencapture
                #          because the crc of color screencapture is never
                #          valid.
    
                raise errors.ChecksumError()

            
            #if data_type == 8 and data_sub_type == 13:
            #    print "coincoin (cf.perl)" / Ah ça, c'est vraiment malin !
            if data_type == 'screencapture' and sub_type == 'color':
                data.raw_data = cas.color_screencapture_to_raw(raw_data)
            elif data_type == 'screencapture' and sub_type == 'mono':
                data.raw_data = cas.mono_screencapture_to_raw(raw_data)
            else:
                data.raw_data = raw_data
            # Everything is OK 
            self.send_byte('\x06')
        return data # Returns the data even if it is empty
    
    def get_header(self):
        """Return [header, header_type]"""
        byte = self.get_byte()
        if not byte == '\x3a':
            #while byte !='':
                #print [byte]
            #    byte=self.get_byte()
            raise errors.HeaderError()
        header = ''
        total_bytes = 100
        cur_byte = 0
        h_crc = 0
        while cur_byte < total_bytes:
            byte = self.get_byte()
            header += byte
            if cur_byte == 7:
                header_len, header_type, sub_type = \
                                cas.get_header_format(header)
                total_bytes = header_len
            cur_byte += 1
            if cur_byte < total_bytes:
                h_crc = (h_crc + ord(byte)) % 256
        h_crc = (0 - h_crc) % 256
        if h_crc != ord(byte) :
            raise errors.ChecksumError()
        print [header]
        return [header, header_type, sub_type]
# -*- coding: utf-8 -*-
#
############################################################################
#
# (c) 2006-2007 Florian Birée aka Thesa <florian.biree@xxxxxxxxxxx>
# (c) 2006 Achraf Cherti aka Asher256 <achrafcherti@xxxxxxxxx>
#
# Website: <URL:http://casetta.tuxfamily.org>
#
# Version 0.3.0dev
#
# changelog:
#
# # 0.3.0 version:
#
#   o First release (code from __init__.py)
#   o Add exceptions for the serial transfer tool
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
# MA 02110-1301, USA.
#
# http://www.gnu.org/copyleft/gpl.html
#
############################################################################
"""Exceptions used in casetta"""

__author__ = "Florian Birée, Achraf Cherti"
__version__ = "0.3.0dev"
__copyright__ = "Copyright (c) 2006-2007, Florian Birée, Achraf Cherti"
__license__ = "GPL"
__revision__ = "$Revision: $"
# $Source: $
__date__ = "$Date: $"

class FormatCantOpen(Exception):
    """Exception raised when a format cannot open a file (write only)"""

    def __init__(self, format):
        Exception.__init__(self)
        self.format = format

    def __str__(self):
        return repr(self.format.name)

class FormatCantSave(Exception):
    """Exception raised when a format canot save a file (read only)"""

    def __init__(self, format):
        Exception.__init__(self)
        self.format = format

    def __str__(self):
        return repr(self.format.name)


class ToolNotFound(Exception):
    """Exception raised when a tool cannot be used 
    
    (the open function is not found for this tool or the command is not 
    in the path)"""

    def __init__(self, tool):
        Exception.__init__(self)
        self.tool = tool

    def __str__(self):
        return repr(self.tool)

class FormatNotFound(Exception):
    """Exception raised when a function cannot select the right format"""
    pass

class DataNoManaged(Exception):
    """Exception raised when a format cannot manage some data type"""

    def __init__(self, data_type_list):
        Exception.__init__(self)
        self.data_type_list = data_type_list

    def __str__(self):
        return repr(self.data_type_list)

class NoCommand(Exception):
    """Exception raised when a command is not found in the PATH"""

    def __init__(self, command):
        Exception.__init__(self)
        self.command = command

    def __str__(self):
        return repr(self.command)

class ToolNotConfigured(Exception):
    """Exception raised when a mistake arrives using a transfer tool"""
    pass

class CalcOutOfMemory(Exception):
    """To be raised when the calculator is out of memory during a transfer"""
    pass
    
class ChecksumError(Exception):
    """To be raised when the calulator answer a CRC error"""
    pass

class CannotOverwrite(Exception):
    """To be raised when the calculator refuse to overwrite a data"""
    
    def __init__(self, data_name):
        Exception.__init__(self)
        self.data_name = data_name
        
    def __str__(self):
        return repr(self.data_name)

class BadAnswerFromCalc(Exception):
    """To be raised if an unexpected answer is sended by the calculator."""
    
    def __init__(self, byte):
        Exception.__init__(self)
        self.byte = byte
    
    def __str__(self):
        return repr(chr(ord(self.byte)))

class TransferAborted(Exception):
    """To be raised when a transfer is aborted."""
    pass

class HeaderError(Exception):
    """To be raised when the calculator answer an header error or send 
    a bad/ unknown header."""
    pass

class BadMatrix(Exception):
    """To be raised when giving a bad matrix to Matrix.set_val()
 
    (e.g.: a matrix with lines of different lengths, 
    a matrix containning strings
    or a matrix not given under the form of a list of lists)"""
    def __init__(self, value, message):
        Exception.__init__(self)
        self.value = value
        self.message = message
    def __str__(self):
        return self.message +' : '+ repr(self.value)

class BadList(Exception):
    """To be raised when giving a bad list to List.set_val() 

    (e.g.: a list containning strings
    or a list not given under the form of a list)"""
    def __init__(self, value, message):
        Exception.__init__(self)
        self.value = value
        self.message = message
    def __str__(self):
        return self.message +' : '+ repr(self.value)

class BadNumber(Exception):
    """ To be raised when the absolute value of the 
    number is too high/low, especially in data.set_simple_val()"""
    def __str__(self):
        return "The absolute value of the number is too high/too low and not supported by the calculator"
    
class BadFunction(Exception):
    def __init__(self, value, message):
        Exception.__init__(self)
        self.value = value
        self.message = message
    def __str__(self):
        return self.message +' : '+ repr(self.value)
    


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/