configurationengine/source/plugins/symbian/ConeCRMLPlugin/CRMLPlugin/crml_writer.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configurationengine/source/plugins/symbian/ConeCRMLPlugin/CRMLPlugin/crml_writer.py	Thu Mar 11 17:04:37 2010 +0200
@@ -0,0 +1,574 @@
+#
+# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description:
+#
+import re
+from cone.public import exceptions
+import crml_reader
+from crml_model import *
+
+class CenRepEntry(object):
+    """
+    Class representing an entry in a CenRep text file.
+    """
+    def __init__(self, **kwargs):
+        self.int            = kwargs.get('int')
+        self.crml_type      = kwargs.get('crml_type')
+        self.confml_type    = kwargs.get('confml_type')
+        self.value          = kwargs.get('value')
+        self.orig_value     = kwargs.get('orig_value')
+        self.access         = kwargs.get('access')
+        self.backup         = kwargs.get('backup', False)
+    
+    @property
+    def metadata(self):
+        return _get_metadata(self.backup)
+    
+    def __lt__(self, other):
+        return crml_reader.convert_num(self.int) < crml_reader.convert_num(other.int)
+
+class CenRepRfsRecord(object):
+    """
+    Class representing an entry in the CenRep RFS text file.
+    """
+    def __init__(self, repo_uid, key_uids=None):
+        self.repo_uid = repo_uid
+        self.key_uids = key_uids or []
+    
+    def __eq__(self, other):
+        if type(self) == type(other):   return self.repo_uid == other.repo_uid
+        else:                           return False
+    
+    def __ne__(self, other):
+        return not (self == other)
+    
+    def __lt__(self, other):
+        return  self.repo_uid < other.repo_uid
+    
+    def __repr__(self):
+        return "CenRepRfsRecord(repo_uid=%s, key_uids=%r)" % (self.repo_uid, self.key_uids)
+        
+
+class CrmlTxtWriter(object):
+    """
+    Writer class for generating CenRep .txt files based on a CRML model.
+    """
+    
+    def __init__(self, configuration, log):
+        self.configuration = configuration
+        self.log = log
+    
+    def get_cenrep_txt_data(self, repository):
+        """
+        Return the text data for the CenRep txt generated based on the given
+        CRML repository model.
+        @return: Text data for the CenRep text file.
+        """
+        data = []
+        
+        # Generate header lines 
+        data.extend(self.get_header_lines(repository))
+        
+        self._check_repository_attrs(repository)
+        
+        # Generate CenRep entries for all keys
+        cenrep_entries = []
+        for key in repository.keys:
+            cenrep_entries.extend(self.get_cenrep_entries(key))
+        
+        # Generate entry lines based on the entries
+        cenrep_entries.sort()
+        for entry in cenrep_entries:
+            data.append(self.get_cenrep_entry_line(entry))
+        
+        data.append('')
+        
+        # Remove Nones from the line list
+        data = filter(lambda val: val is not None, data)
+        
+        return '\r\n'.join(data)
+    
+    def get_cenrep_rfs_txt_data(self, rfs_records):
+        """
+        Return the text data for the CenRep RFS txt generated based on the given
+        CenRep RFS record list.
+        """
+        data = []
+        
+        # Make a distinct and sorted array of the records
+        records = []
+        for r in rfs_records:
+            if r not in records: records.append(r)
+        records.sort()
+        
+        for record in records:
+            repo_uid = record.repo_uid
+            
+            # Add padding zeros to the UID
+            if len(repo_uid) < 8:
+                repo_uid = (8 - (len(repo_uid) % 8)) * '0' + repo_uid
+            temp = "CR %s" % repo_uid
+            if record.key_uids:
+                temp += " %s" % ' '.join(record.key_uids)
+            data.append(temp)
+        
+        return '\r\n'.join(data)
+    
+    def get_cenrep_rfs_record(self, repository):
+        """
+        Return the RFS record for the given CRML repository.
+        
+        @return: A CenRepRfsRecord object if the repository should be listed
+            in cenrep_rfs.txt, None if not.
+        """
+        # Get the UID as a hex value without the leading 0x
+        repo_uid = _translate_key_uid(repository.uid_value)[2:]
+        
+        # Check if the whole repository has RFS=true
+        if repository.rfs:
+            return CenRepRfsRecord(repo_uid)
+        
+        # Collect the UIDs of the keys that should be listed
+        rfs_key_uids = []
+        for key in repository.keys:
+            if self._key_is_rfs(key) and key.int:
+                # Get the UID as a hex value without the leading 0x
+                uid = _translate_key_uid(key.int)[2:]
+                rfs_key_uids.append(uid)
+                    
+        if rfs_key_uids:
+            return CenRepRfsRecord(repo_uid, rfs_key_uids)
+        else:
+            return None
+    
+    def _key_is_rfs(self, key):
+        """
+        @return: True if the key UID should be listed in cenrep_rfs.txt
+        """
+        if isinstance(key, CrmlSimpleKey):
+            return bool(self._get_rfs_value(key.ref))
+        elif isinstance(key, CrmlBitmaskKey):
+            for bit in key.bits:
+                if self._get_rfs_value(bit.ref):
+                    return True
+        else:
+            return False
+    
+    def _get_rfs_value(self, ref):
+        """
+        @return: The RFS value for the given setting, or None if not available.
+        """
+        if ref is None: return
+        
+        try:
+            feature = self._get_feature(ref)
+        except exceptions.NotFound:
+            # Feature not found in the configuration
+            return None
+        
+        return feature.get_value(attr='rfs')
+    
+    def get_header_lines(self, repository):
+        """
+        Return a list of lines to be written in the header section of the CenRep text file.
+        """
+        data = ['cenrep',
+                'version %s' % repository.version]
+        
+        if repository.owner:
+            data.append('[owner]')
+            data.append(repository.owner)
+        
+        data.append('[defaultmeta]')
+        data.append(' %d' % _get_metadata(repository.backup))
+        for key in repository.keys:
+            data.append(self.get_defaultmeta_line(key))
+        
+        data.append('[platsec]')
+        acc_text = self.get_access_line(repository.access)
+        if acc_text: acc_text = ' ' + acc_text
+        data.append(acc_text)
+        for key in repository.keys:
+                data.append(self.get_platsec_line(key, repository))
+        
+        
+        data.append('[Main]')
+        return data
+    
+    def get_cenrep_entries(self, key):
+        """
+        Generate CenRep entries based on the given CRML key object.
+        @return: A list of CenRepEntry objects.
+        """
+        if isinstance(key, CrmlSimpleKey):
+            feature = self._get_feature(key.ref)
+            entry = CenRepEntry(int          = key.int,
+                                crml_type    = key.type,
+                                confml_type  = feature.get_type(),
+                                value        = feature.get_value(),
+                                orig_value   = feature.get_original_value(),
+                                backup       = key.backup,
+                                access       = key.access)
+            return [entry]
+        elif isinstance(key, CrmlBitmaskKey):
+            return self.get_bitmask_key_cenrep_entries(key)
+        elif isinstance(key, CrmlKeyRange):
+            return self.get_key_range_cenrep_entries(key)
+        else:
+            raise TypeError("Unsupported CRML key object type %s" % type(key))
+    
+    def get_key_range_cenrep_entries(self, key_range):
+        """
+        Generate CenRep entries based on the given CrmlKeyRange object.
+        @return: A list of CenRepEntry objects.
+        """
+        entries = []
+        count = 0
+        
+        # Generate the countInt entry if necessary
+        if key_range.count_int is not None and key_range.ref is not None:
+            try:
+                feature = self._get_feature(key_range.ref)
+                
+                # For CT2 output compatibility
+                if feature.get_type() != 'sequence':
+                    return []
+                
+                values = feature.get_value()
+            except exceptions.NotFound:
+                values = []
+            
+            count = len(values)
+                
+            entry = CenRepEntry(int         = key_range.count_int,
+                                crml_type   = 'int',
+                                confml_type = 'int',
+                                value       = count,
+                                backup      = key_range.backup,
+                                access      = key_range.access)
+            entries.append(entry)
+        
+        # Generate entries based on the sequence values
+        for subkey in key_range.subkeys:
+            full_ref = "%s.%s"% (key_range.ref, subkey.ref)
+            
+            try:
+                feature = self._get_feature(full_ref)
+                values = feature.get_value()
+                confml_type = feature.get_type()
+                backup = key_range.backup
+            except exceptions.NotFound:
+                # For CT2 output compatibility
+                values = ['null' for i in xrange(count)]
+                confml_type = None
+                backup = False
+                
+            for i, value in enumerate(values):
+                # Calculate the index of the entry
+                index = self.get_index(crml_reader.convert_num(key_range.first_int),
+                                       crml_reader.convert_num(key_range.first_index),
+                                       crml_reader.convert_num(key_range.index_bits),
+                                       i,
+                                       crml_reader.convert_num(subkey.int))
+                
+                entry = CenRepEntry(int         = "0x%x" % index,
+                                    crml_type   = subkey.type,
+                                    confml_type = confml_type,
+                                    value       = value,
+                                    orig_value  = value,
+                                    backup      = backup,
+                                    access      = key_range.access)
+                entries.append(entry)
+        
+        return entries
+    
+    def get_bitmask_key_cenrep_entries(self, key):
+        """
+        Generate CenRep entries based on the given CrmlBitmaskKey object.
+        @return: A list of CenRepEntry objects.
+        """
+        # Calculate the value based on the bit values
+        # -------------------------------------------
+        value = 0
+        for bit in key.bits:
+            feature = self._get_feature(bit.ref)
+            bit_value = feature.get_value()
+            if bit.invert:  bit_value = not bit_value
+            if bit_value:   value |= 1 << (bit.index - 1)
+        
+        # Generate the textual representation of the bitmask value.
+        # This is done at this point because in get_cenrep_entry_line()
+        # we don't know anymore if the key was a bitmask key or a
+        # simple key.
+        # -------------------------------------------------------------
+        if key.type == 'binary':
+            orig_value = "%X" % value
+            # Add padding zeroes so that the number of digits
+            # is divisible by 8 (done manually since the length
+            # of a binary bitmask is unbounded).
+            padding_zeroes = (8 - len(orig_value) % 8) * '0' 
+            # 4 is a special case for CT2 output compatibility
+            if len(orig_value) != 4:
+                orig_value = padding_zeroes + orig_value
+        else:
+            orig_value = str(value)
+        
+        entry = CenRepEntry(int         = key.int,
+                            crml_type   = key.type,
+                            confml_type = 'int',
+                            value       = value,
+                            orig_value  = orig_value,
+                            backup      = key.backup,
+                            access      = key.access)
+        return [entry]
+    
+    def get_defaultmeta_line(self, key):
+        """
+        Return the defaultmeta section line for the given CRML key object.
+        """
+        if not isinstance(key, CrmlKeyRange): return None
+        
+        return "%s %s %d" % (key.first_int,
+                             key.last_int,
+                             _get_metadata(key.backup))
+    
+    def get_platsec_line(self, key, repository):
+        """
+        Return the platsec section line for the given CRML key object.
+        """
+        if not isinstance(key, CrmlKeyRange): return None
+        
+        # In a key range platsec entry something must be present, so if
+        # the access object is empty, use cap_rd and cap_wr from the repository's
+        # global access definition
+        access = key.access.copy()
+        is_empty = True
+        for attrname in ('sid_rd', 'cap_rd', 'sid_wr', 'cap_wr'):
+            if getattr(access, attrname) not in ('', None):
+                is_empty = False
+        if is_empty:
+            access.cap_rd = repository.access.cap_rd
+            access.cap_wr = repository.access.cap_wr
+        
+        acc_text = self.get_access_line(access)
+        if acc_text: acc_text = ' ' + acc_text
+        
+        return "%s %s%s" % (key.first_int,
+                             key.last_int,
+                             acc_text)
+        
+    
+    def get_cenrep_entry_line(self, entry):
+        """
+        Return the text line for a CenRepEntry object.
+        """
+        value = None
+        if entry.crml_type in ('string', 'string8'):
+            if entry.confml_type is None:
+                pass
+            else:
+                if entry.orig_value is None:
+                    value = '""'
+                else:
+                    value = '"%s"' % entry.orig_value
+        elif entry.crml_type == 'int':
+            if entry.confml_type == 'boolean':
+                if entry.value: value = '1'
+                else:           value = '0'
+            else:
+                value = entry.orig_value
+        elif entry.crml_type == 'real':
+            value = entry.orig_value or ''
+        elif entry.crml_type == 'binary':
+            # Empty binary values are denoted by a single dash
+            value = entry.orig_value or '-'
+            
+            if value != '-':
+                # Make sure that the number of digits is divisible by two
+                if len(value) % 2 != 0:
+                    value = '0' + value
+        
+        if value is None:
+            value = unicode(entry.value)
+        
+        self._check_value(entry, value)
+        
+        acc_text = self.get_access_line(entry.access)
+        if acc_text: acc_text = ' ' + acc_text
+        
+        return '%s %s %s %d%s' % (_translate_key_uid(entry.int),
+                                  entry.crml_type,
+                                  value,
+                                  entry.metadata,
+                                  acc_text)
+    def _check_value(self, entry, value):
+        """
+        Check that the given value is valid for the given CenRep entry,
+        and log a warning if it is not.
+        """
+        if entry.crml_type == 'int':
+            # Check if the value is a string, since it may already
+            # be an integer
+            if not isinstance(value, basestring):
+                return
+            
+            try:
+                value = value.strip()
+                if value.lower().startswith('0x'):
+                    long(value, 16)
+                else:
+                    long(value)
+            except ValueError:
+                self.log.warn("Key %s: Invalid integer value '%s'" % (entry.int, value))
+        elif entry.crml_type == 'real':
+            try:
+                float(value)
+            except ValueError:
+                self.log.warn("Key %s: Invalid real value '%s'" % (entry.int, value))
+        elif entry.crml_type == 'binary':
+            if value != '-' and re.match(r'^(0[xX])?[0-9a-fA-F]+$', value) is None:
+                self.log.warn("Key %s: Invalid binary value '%s'" % (entry.int, value))
+    
+    def _check_repository_attrs(self, repository):
+        """
+        Check that the attributes of the given repository are valid and
+        log warnings if not.
+        """
+        if repository.owner is not None:
+            owner = repository.owner.strip()
+            # An empty owner UID is OK, it doesn't generate anything
+            # invalid into the output
+            if owner != '':
+                try:
+                    if owner.lower().startswith('0x'):
+                        long(owner, 16)
+                    else:
+                        long(owner)
+                except ValueError:
+                    self.log.warn("Invalid owner UID '%s'" % owner)
+    
+    def get_access_line(self, access):
+        """
+        Generate a line containing access information based on a CrmlAccess object.
+        """
+        # Write the access information in a specific order, because it
+        # won't work otherwise
+        var_order = ['sid_rd', 'cap_rd', 'sid_wr', 'cap_wr']
+        data = []
+        for varname in var_order:
+            val = getattr(access, varname)
+            if val not in ('', None):
+                # Using _translate_capability_string() on all, since a SID should
+                # not contain anything that could be messed up by the function
+                data.append('%s=%s' % (varname, _translate_capability_string(val)))
+        
+        return ' '.join(data)
+    
+    def _get_feature(self, ref):
+        return self.configuration.get_default_view().get_feature(ref)
+    
+    @classmethod
+    def get_index(cls,firstInt,firstIndex,indexBits,seqIndex, subIndex):
+        """
+        @param firstIndex: The first value available in the keyrange 
+        @param lastInt: The last value available in the keyrange 
+        @param indexBits: The index bits or mask for sequence index
+        @param seqIndex: The sequence index
+        @param subIndex: The sequence sub element index
+        @return: an numeric value for the encoded index
+        """
+        rangeshift = cls.get_range_shift(indexBits)
+        return (((seqIndex+firstIndex) << rangeshift) + firstInt) + subIndex
+
+    @classmethod
+    def get_seqid(cls,firstInt,firstIndex,indexBits,cenrepkey):
+        """
+        @param firstIndex: The first value available in the keyrange 
+        @param lastInt: The last value available in the keyrange 
+        @param indexBits: The index bits or mask for sequence index
+        @param cenrepkey: Crml key id
+        @return: an numeric value for the encoded index
+        """
+        rangeshift = cls.get_range_shift(indexBits)
+        return (((cenrepkey & indexBits) -firstInt) >> rangeshift)-firstIndex
+
+    @classmethod
+    def get_subseqid(cls,firstInt,firstIndex,indexBits,cenrepkey):
+        """
+        @param firstIndex: The first value available in the keyrange 
+        @param lastInt: The last value available in the keyrange 
+        @param indexBits: The index bits or mask for sequence index
+        @param cenrepkey: Crml key id
+        @return: an numeric value for the encoded index
+        """
+        range = cls.get_range(indexBits)
+        return (cenrepkey - firstInt) & range
+
+    @classmethod
+    def get_range_shift(cls,indexBits):
+        """ Get the bit left to the """
+        seqrange = cls.get_range(indexBits)
+        shiftamount = 0
+        for i in range(0,32):
+            if (seqrange >> i)  == 0:
+                shiftamount = i
+                break
+        return shiftamount
+
+    @classmethod
+    def get_range(cls,indexBits):
+        """ Get the bit left to the """
+        indexshift = 0 
+        for i in range(0,32):
+            if (indexBits >> i)  == 0:
+                indexshift = i
+                break
+        return ((0x1 << indexshift) - indexBits)-1
+
+
+# =============================================================================
+# Utility functions
+# =============================================================================
+
+def _get_metadata(backup):
+    metadata = 0
+    if backup: metadata |= 0x1000000
+    return metadata
+        
+def _translate_key_uid(uid):
+    """Translate a key UID given in CRML so that it matches the output of CT2."""
+    if uid.lower().startswith("0x"):
+        prefix = uid[:2]
+        temp = uid[2:]
+        if int(temp, 16) == 0:  return prefix + "0"
+        else:                   return prefix + uid[2:].lstrip('0')
+    else:
+        if int(uid) == 0:       return "0"
+        else:                   return uid.lstrip('0')
+
+def _translate_capability_string(cap_str):
+    """
+    Translate a capability string so that it is
+    suitable for writing to a CenRep txt file.
+    """
+    cap_str = cap_str.replace('AlwaysPass', 'alwayspass').replace('AlwaysFail', 'alwaysfail')
+    
+    # The capability string can be a list separated either by
+    # whitespace or commas
+    if ',' in cap_str:  caps = cap_str.split(',')
+    else:               caps = cap_str.split()
+    
+    # The output must always be comma-separated
+    return ','.join(caps)
+