File: Core.py

package info (click to toggle)
mobyle 1.5.5%2Bdfsg-6
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 8,288 kB
  • sloc: python: 22,709; makefile: 35; sh: 33; ansic: 10; xml: 6
file content (864 lines) | stat: -rw-r--r-- 37,705 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
########################################################################################
#                                                                                      #
#   Author: Bertrand Neron,                                                            #
#   Organization:'Biological Software and Databases' Group, Institut Pasteur, Paris.   #  
#   Distributed under GPLv2 Licence. Please refer to the COPYING.LIB document.         #
#                                                                                      #
########################################################################################

"""
Content the basics Mobyle Parameter types 
"""

import os 
import re
from types import BooleanType , StringTypes
from logging import getLogger
c_log = getLogger(__name__)
b_log = getLogger('Mobyle.builder')

import shutil

from Mobyle.Utils import safeFileName
from Mobyle.Classes.DataType import DataType
from Mobyle.Service import MobyleType

from Mobyle.MobyleError import MobyleError , UserValueError , UnDefAttrError , UnSupportedFormatError
from Mobyle.ConfigManager import Config
_cfg = Config()




def safeMask( mask ):
    import string
    for car in mask :
        if car not in string.printable :
            mask = mask.replace( car , '_')
    #don't use re.UNICODE because safeFileName don't permit to use unicode char.
    #we must work whit the same char set    
    mask = re.sub( "(;|`|$\(\s)+" , '_' , mask )
    mask = re.sub( "^.*[\\\]", "" ,  mask  )
    mask = re.sub( "^/", "" ,  mask  )
    mask = re.sub( "\.(\.)+", "" ,  mask  )
    return mask



class DataTypeTemplate( DataType ):

    def convert( self , value , AcceptedMobyleType , detectedMobyleType = None , paramFile= False ):
        """
        cast the value to the right typeand compute the dataFormat conversion if needed (based on the 
        detectedMobyleType and the AcceptedMobyleType)
        @param value: the value provided by the User for this parameter
        @type value: String
        @param acceptedMobyleType: the MobyleType with the Dataformat accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType discribing the real dataformat of this data
        @type detectedMobyleType: L{MobyleType} instance
        @return: the casted value and the mobyleType describing this data (with the effective DataFormat)
                 for datatype which have not dataFormat (boolean , integer, ... )
                 the MobyleType returned is the same as the AcceptedMobyleType.
        @rtype: ( value any type , MobyleType instance )
        @raise UseValueError: if the cast failed
        """
        realMobyleType = MobyleType( self )
        return ( "DataTypeTemplate convert: " + str( value ) , realMobyleType )
 
    def detect( self , value ):
        mt = MobyleType( self )
        return mt    
 
    def validate( self, param ):
        """
        do type controls. if the value in the param pass the controls
        return True, False otherwise. 
        @rtype: boolean  
        """
        return "DataTypeTemplate validate"
        
        
        
class BooleanDataType( DataType ):
    """
    """
    def convert( self , value , acceptedMobyleType , detectedMobyleType = None , paramFile= False ):
        """
        cast the value to a Boolean.
        The values: "off", "false", "0" or '' (case insensitive) are false,
        all others values are True.  
        @param value: the value provided by the User for this parameter
        @type value: String
        @param acceptedMobyleType: the MobyleType  accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType discribing this data
        @type detectedMobyleType: L{MobyleType} instance
        @return: the value converted to Boolean, and a MobyleType discribing this value
        @rtype: tuple ( boolean , MobyleType instance )
        """
        if value is None:
            # in html form boolean appear as checkbox
            # if the checkbox is not selected
            # the parameter is not send in the request
            return ( False , acceptedMobyleType )
        if isinstance( value , BooleanType ):
            return ( value , acceptedMobyleType )
        elif isinstance( value , StringTypes ):
            value = value.lower()
            if value in [ "off", "false", "0", "", "''", '""' ]:
                return ( False , acceptedMobyleType )
            elif value in [ "on", "true", "1" ]:
                return ( True , acceptedMobyleType )
        else:
            msg = "Invalid value: " + str( value ) + " is not a boolean."
            raise UserValueError( msg = msg )

    def detect( self , value ):
        mt = MobyleType( self )
        return mt
    
    def validate( self, param ):
        """
        do type controls. if the value is True or False or None return True,
        otherwise return False..        
        """
        value = param.getValue()
        if value == True or value == False:
            return True
        else:
            return False


        
class IntegerDataType( DataType ):


    def convert( self , value , acceptedMobyleType , detectedMobyleType = None , paramFile= False ):
        """
        Try to cast the value to Integer. the allowed values are digits
        and strings.
        
        @param value: the value provided by the User for this parameter
        @type value: string
        @param acceptedMobyleType: the MobyleType  accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType discribing this data
        @type detectedMobyleType: L{MobyleType} instance
        @return: the value converted to Integer and the mobyleType discribing this value.
        @rtype: ( int , MobyleType instance )
        @raise UserValueError: if the cast fails.
        Unlike python, this method convert "8.0" to 8 and
        raise a UserValueError if you try to convert 8.2 .
        """
        if value is None:
            return ( None , acceptedMobyleType )
        #type controls
            # int("8.0") failed and a  ValueError is raised
            # int(8.1) return 8
        try:
            f= float( value )
            i = int( f )
            if ( (f - i) == 0):
                return ( i , acceptedMobyleType )
            else:
                msg = "\"%s\" : this parameter must be an integer" %value
                raise  UserValueError( msg = msg)
        except OverflowError:
            raise UserValueError( msg = "this value is too big" )
        except ( ValueError , TypeError ):
            msg = "\"%s\" : this parameter must be an integer" %value
            raise UserValueError( msg = msg)

    def detect( self , value ):
        mt = MobyleType( self )
        return mt  
         
    def validate( self, param  ):
        """
        @return True if the value is an integer, False othewise.
        """   
        value = param.getValue()

        if value is None:
            return True                       
        try:
            int( value )
        except ( TypeError , ValueError ):
            return False

class FloatDataType( DataType ):


    def convert( self , value , acceptedMobyleType , detectedMobyleType = None , paramFile= False ):
        """
        Try to cast the value to Float.
        @param value: the value provided by the User for this parameter
        @type value: 
        @param acceptedMobyleType: the MobyleType  accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType discribing this data
        @type detectedMobyleType: L{MobyleType} instance
        @return: the value converted to Float
        @rtype: float
        """
        if value is None:
            return ( None , acceptedMobyleType )
        try:
            return ( float( value ) , acceptedMobyleType )
        except ( ValueError, TypeError ):
            msg = str( value ) + " this parameter must be a Float"
            raise UserValueError( msg = msg )

    def detect( self , value ):
        mt = MobyleType( self )
        return mt
    
    def validate( self, param ):
        """
        @return: True if the value is a float, False othewise.
        @rtype: boolean
        """
        value = param.getValue()
        if value is None:
            return True     
        try:
            float( value )
        except ( ValueError , TypeError ):
            return False


class StringDataType( DataType ):

    def convert( self , value , acceptedMobyleType , detectedMobyleType = None , paramFile= False ):
        """
        Try to cast the value to String. 
        
        @param value: the value provided by the User for this parameter
        @type value: 
        @return: the value converted to String.
        @rtype: String
        @param acceptedMobyleType: the MobyleType  accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType discribing this data
        @type detectedMobyleType: L{MobyleType} instance
        @raise UserValueError: if the cast fails.
        """
        if value is None:
            return ( None , acceptedMobyleType )
        #type controls
        try:
            value = str( value ).encode('ascii')
        except ValueError :
            raise UserValueError( msg = "should be an (ascii) string" ) 
        #strings with space are allowed, but if the string will appear
        #as shell instruction it must be quoted
        if value.find(' ') != -1 and not paramFile:
            value = "'%s'" % value
        return ( value , acceptedMobyleType )                        
        
    def detect( self , value ):
        mt = MobyleType( self )
        return mt        
    
    def validate( self, param  ):
        """
        @return: True if the value is a string and doesnot contain dangerous characters.
        ( allowed characters: the words , space, ' , - , + , and dot if is not followed by another dot
        and eventually surrounded by commas)
        @rtype: boolean
        @raise UserValueError: if the value does not statisfy security regexp.
        """
        value = param.getValue()
        if value is None:
            return True
        
        #allowed characters:
        #the words , space, ' , - , + , and dot if is not followed by another dot 
        #and eventually surrounded by commas
        reg = "(\w|\ |-|\+|,|@|\.(?!\.))+"
        if re.search( "^(%s|'%s')$" % (reg, reg) ,  value ) :         
            return True
        else:
            msg = "this value: \"" + str( value ) + "\" , is not allowed"
            raise UserValueError( parameter = param , msg = msg )
        

class ChoiceDataType( StringDataType ):
    #the values of ChoiceDataType are literals thus they are String

    def convert( self , value , acceptedMobyleType , detectedMobyleType = None , paramFile= False ):
        """
        The values of ChoiceDataType are literals thus this method try to cast
        the value to string.
        @param value: the value provided by the User for this parameter
        @type value:
        @param acceptedMobyleType: the MobyleType  accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType discribing this data
        @type detectedMobyleType: L{MobyleType} instance 
        @return: the value converted in String
        @rtype: string
        """
        if value is None:
            return ( None , acceptedMobyleType )
        try:
            value  = str( value )
            if hasattr( acceptedMobyleType , '_undefValue' ) and value == acceptedMobyleType._undefValue :
                return ( None , acceptedMobyleType )
            else:
                return ( value, acceptedMobyleType )
        except ValueError:
            msg = " this parameter is a Choice its value should be a String" 
            raise UserValueError( msg = msg )
   
    def detect( self , value ):
        mt = MobyleType( self )
        return mt

    def validate( self, param ):
        """
        @return: True if the value is valid. that's mean the value
        should be a string among the list defined in the xml.
        otherwise a MobyleError is raised.
        @rtype: boolean
        @param value: a value from a Choice parameter
        @type value: Choice value
        @raise UserValueError: if the value is not a string or is not among the
        list defined in the xml vlist.
        """
        paramName = param.getName()
        value = param.getValue()
        if param.hasVlist() :
            authorizedValues = param.getVlistValues()
        elif param.hasFlist() :
            authorizedValues = param.getFlistValues()
        else:
            msg = "%s a Choice must have a flist or vlist" %( paramName )
            c_log.error( msg )
            raise MobyleError , msg     
        if value is None or value in authorizedValues:
            return True
        else:
            logMsg = "Unauthorized value for the parameter : %s : authorized values = %s : provided value = %s" %( paramName , 
                                                                                                                  authorizedValues ,
                                                                                                                  value 
                                                                                                                  )
            c_log.error( logMsg )
            msg = "Unauthorized value for the parameter : %s" %( paramName )
            raise UserValueError( parameter = param , msg = msg )
                
               

class MultipleChoiceDataType( StringDataType ):


    def convert( self , value , acceptedMobyleType , detectedMobyleType = None , paramFile= False, separator = None):
        """
        The MutipleChoiceDataType value are literals thus this method try to cast
        the value to a list of string.
        @param value: the values provided by the User for this parameter
        @type value: list
        @param acceptedMobyleType: the MobyleType  accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType describing this data
        @type detectedMobyleType: L{MobyleType} instance
        @return: a string based on each selected value and joined by the separator.
        @rtype: String .
        @raise UserValueError: if the value can't be converted to a string.
        """
        if value is None:
            return ( None , acceptedMobyleType )
        sep = separator if separator is not None else ''
        if hasattr( acceptedMobyleType , '_undefValue' ) and value == acceptedMobyleType._undefValue :
            return ( None , acceptedMobyleType )
        else:
            try:
                values = [ str( elem ) for elem in value ]
            except ValueError:
                msg  = "this parameter is a MultipleChoice its all values must be Strings" %value
                raise UserValueError( msg = msg )
            return ( sep.join(values) , acceptedMobyleType )

    def detect( self , value ):
        mt = MobyleType( self )
        return mt

    def validate( self, param ):
        #values are stored as string in evaluator
        #the string is computed by service.setValue()
        #the string is return by getValue and used as is in ComandBuilder 
        userValues = param.getValue() #it's a string
        if userValues is None:
            return True
        sep = param.getSeparator()
        #we must retransform the string in a list to check if each
        #element is in the list of authorized values

        if sep == '':
            userValues = [ i for i in userValues ]
        else:
            userValues = userValues.split( sep )

        authorizedValues =  param.getVlistValues()
        undef_value = param.getListUndefValue()
        for value in userValues:
            if value not in authorizedValues and value != param.getListUndefValue():
                msg = "the value %s is not allowed (allowed values: %s)" % (
                str( value ) ,
                str( param.getVlistValues() )
                )
                raise UserValueError( parameter = param , msg = msg )
        return True


    

class AbstractFileDataType(DataType):
    """
    AbstractFileDataType is an abstract class that centralizes the
    code which is common to text files (AbstractTextDataType) and
    binary files (BinaryDataType)
    """

    def isFile( self ):
        return True

    @classmethod
    def supportedFormat( cls ):
        """
        @return: the list supported by the format detector
        @rtype: list of string
        """
        uniq_formats = []
        for converter in _cfg.dataconverter( cls.__name__[:-8] ):
            for f in converter.detectedFormat():
                if f not in uniq_formats:
                    uniq_formats.append( f )
        return uniq_formats        
    
    @classmethod         
    def supportedConversion( cls ):
        """
        @return: the list of dataFormat conversion available.
        @rtype: list of tuple [ (string input format, string output formt) , ... ]
        """
        uniq_conversion = []
        for converter in _cfg.dataconverter( cls.__name__[:-8] ):
            for f in converter.convertedFormat():
                if f not in uniq_conversion:
                    uniq_conversion.append( f )
        return uniq_conversion

    def detect( self , value  ):
        """
        detects the format of the sequence(s) contained in fileName
        @param value: the src object and the filename in the src of the data to detect
        @type value: tuple ( session/Job/MobyleJob instance , string filename )
        @return: a tuple of the detection run information:
            - the detected format,
            - the detected items number,
            - program name used for the detection.
        """
        if value is None:
            return None
        if len( value ) == 2 :
            src , srcFileName = value
        else:
            raise MobyleError ,"value must be a tuple of 2 elements: ( Job/MobyleJob/JobState or Session instance , Job/MobyleJob/JobState or Session instance , srcFileName )"
        if src and not srcFileName :
            raise MobyleError , "if src is specified, srcFileName must be also specified"
        absFileName = os.path.join( src.getDir() , srcFileName )
        
        all_converters = _cfg.dataconverter( self.__class__.__name__[:-8] )
        for converter in all_converters:
            detected_format , seq_nb = converter.detect( absFileName )
            prg = converter.program_name
            if detected_format:
                detected_mt = MobyleType( self , dataFormat = detected_format , format_program = prg , item_nb = seq_nb) 
                return detected_mt 
        return MobyleType( self ) #no dataFormat have been detected
    
    
    def convert( self , value ,acceptedMobyleType , detectedMobyleType = None,  paramFile= False):
        """
        convert the sequence contain in the file fileName in the rigth format
        throws an UnsupportedFormatError if the output format is not supported
        or a MobyleError if something goes wrong during the conversion.

        @param value: is a tuple ( src , srcFileName) 
          - srcfilename is the name of the file to convert in the src
          - src must be a L{Job} instance the conversion are perform only by jobs (not session) .
        @type value: ( L{Job} instance dest, L{Job} instance, src)
        @return: the fileName ( basename ) of the  sequence file and the effective MobyleType associated to this 
        value
        @rtype: ( string fileName , MobyleType instance )
        @raise UnSupportedFormatError: if the data cannot be converted in any suitable format
        """
        if value is None:
            return None
        if len( value ) == 2 :
            src , srcFileName = value
        else:
            raise MobyleError ,"value must be a tuple of 2 elements: ( Job/MobyleJob/JobState or Session instance , Job/MobyleJob/JobState or Session instance , srcFileName )"
        if src and not srcFileName :
            raise MobyleError , "if src is specified, srcFileName must be also specified"
        absFileName = os.path.join( src.getDir() , srcFileName )
        all_converters = _cfg.dataconverter( self.__class__.__name__[:-8] )
        in_format = detectedMobyleType.getDataFormat()
       
        if not all_converters:
            fileName = safeFileName( absFileName )
            if detectedMobyleType :
                mt = detectedMobyleType
            else:
                mt = MobyleType( self )
            #filename is a basename
            return ( fileName , mt )
        else:
            def unknow_converter( in_format , converters ):
                result = None 
                for converter in converters:
                    try:
                        result = fixed_converter( in_format , converter )
                    except UnSupportedFormatError:
                        continue
                    if result is not None:
                        break
                #if I cannot find any suitable converter result is None    
                return result
                
            def fixed_converter( in_format ,  converter ):
                allowed_conversions = converter.convertedFormat()
                for out_format , force in acceptedMobyleType.getAcceptedFormats():
                    if ( in_format , out_format ) in allowed_conversions:
                        try:
                            outFileName = converter.convert( absFileName , out_format , inputFormat = in_format )
                            outFileName = os.path.basename( outFileName )
                        except UnSupportedFormatError, err:
                            raise UnSupportedFormatError( msg = "a problem occurred during the data conversion : "+str( err ) ) 
                        converted_mt = MobyleType( self  , dataFormat = out_format , format_program = converter.program_name )
                        return ( outFileName , converted_mt )
                #it's not a suitable converter
                return None
            if detectedMobyleType.format_program is None:
                result = unknow_converter( in_format , all_converters )
            else:
                for converter in all_converters:
                    if converter.program_name == detectedMobyleType.format_program :
                        detector_used = converter
                        break
                if detector_used is None:
                    raise MobyleError( "unable to find %s converter" % detectedMobyleType.format_program )
                result  = fixed_converter( in_format , detector_used )
                if result is None:# the detector_used is not suitable for the conversion
                    result= unknow_converter( in_format , [ c for c in all_converters if c != detector_used ] )
            if result is None:
                raise UnSupportedFormatError( "unable to convert %s in %s sequence format" %( in_format , 
                                                                                              [ f[0] for f in acceptedMobyleType.getAcceptedFormats() ] 
                                                                                               ) )
            else:
                return result #( outFileName , converted_mt )
            
 
    def validate( self , param ):
        """
        """
        value = param.getValue()
        if param.isout():
            if value is not None : #un parametre isout ne doit pas etre modifier par l'utilisateur 
                return False
            else:
                #####################################################
                #                                                   #
                #  check if the Parameter have a secure filenames   #
                #                                                   #
                #####################################################
                try:
                    debug = param.getDebug()
                    if debug > 1:
                        b_log.debug( "check if the Parameter have a secure filename" )
                    #getFilenames return a list of strings representing a unix file mask which is the result of a code evaluation
                    #getFilenames return None if there is no mask for a parameter.
                    filenames = param.getFilenames( ) 
                    for filename in filenames :
                        if filename is None:
                            continue
                        mask = safeMask( filename )
                        if debug > 1:
                            b_log.debug( "filename= %s    safeMask = %s"%(filename, mask))
                        if  not mask or mask != filename :
                            msg = "The Parameter:%s, have an unsecure filenames value: %s " %( param.getName() ,
                                                                                                filename )
                            c_log.error("Mobyle Internal Server Error")
                            if debug == 0:
                                c_log.critical( "%s : %s : %s" %( self._service.getName(),
                                                                       self._job.getKey() ,
                                                                       msg          
                                                                       )
                                                                       )
                            raise MobyleError , "Mobyle Internal Server Error" 
                        else:
                            if debug > 1:
                                b_log.debug( "filename = %s ...........OK" % filename )
                except UnDefAttrError :
                    b_log.debug("no filenames")
        else:
            if value is None:
                #un infile Text ne peut pas avoir de vdef mais peut il etre a None?
                #=> oui s'il n'est pas obligatoire
                return True
            else:
                return os.path.exists( param.getValue() )





class AbstractTextDataType( AbstractFileDataType ):

    def head( self , data ):
        return data[ 0 : 50 ]
    
    def cleanData( self , data ):
        """
        convert the data in right encoding and replace windows end of line by unix one.
        """
        # trying to guess the encoding, before converting the data to ascii
        try:
            # trying ascii
            data = unicode(data.decode('ascii','strict'))
        except:
            try:
                # utf8 codec with BOM support
                data = unicode(data,'utf_8_sig')
            except:
                try:
                    # utf16 (default Windows Unicode encoding)
                    data = unicode(data,'utf_16')
                except:
                    # latin1
                    data = unicode(data,'latin1')
        # converting the unicode data to ascii
        data = data.encode('ascii','replace')
        return  re.sub( "\r\n|\r|\n" , '\n' , data )


    def toFile( self , data  , dest , destFileName , src , srcFileName ):
        """
        Write file (of user data) in dest directory .
        @param fileName:
        @type fileName: string
        @param data: the content of the file
        @type data: string
        @param dest: the object in which the data will be copied
        @type dest: Job, Session object
        @param src: the object where the data can be found
        @type src: Job, Session object
        @param srcFileName: the file namae of the data in the src ( basename )
        @type srcFileName: string
        @return: the name of the created file (the basename)
        @rtype: string
        @raise: L{UserValueError} when filename is not allowed (for security reason)
        @raise: L{MobyleError} if an error occured during the file creation
        """
        try:
            destSafeFileName = safeFileName( destFileName )            
        except UserValueError, err:
            raise UserValueError( msg = "this value : %s is not allowed for a file name, please change it" % destFileName )
        abs_DestFileName= os.path.join( dest.getDir() , destSafeFileName )
        # if the user upload 2 files with the same basename safeFileName
        # return the same safeFileName
        # add an extension to avoid _toFile erase the existing file.
        ext = 1
        completeName = abs_DestFileName.split( '.' )
        base = completeName[0]
        suffixe = '.'.join( completeName[1:] )
        while os.path.exists( abs_DestFileName ):
            abs_DestFileName = base + '.' + str( ext ) + '.' + suffixe
            ext = ext + 1
        if src:
            if src.isLocal():
                try:
                    srcSafeFileName = safeFileName( srcFileName )
                except UserValueError, err:
                    raise UserValueError( msg = "this value : %s is not allowed for a file name, please change it" % srcFileName )
                #the realpath is because if the abs_SrcFileName is a soft link ( some results are ) the
                # hardlink point to softlink and it causse ane error : no such file 
                abs_SrcFileName = os.path.realpath( os.path.join( src.getDir() , srcSafeFileName ) )
                try:
                    os.link(  abs_SrcFileName , abs_DestFileName )
                except OSError :
                    #if the src and dest are not on the same device
                    #an OSError: [Errno 18] Invalid cross-device link , is raised
                    try:
                        shutil.copy( abs_SrcFileName , abs_DestFileName )
                    except IOError ,err:
                        # I don't know  - neither the service ( if it exists )
                        #               - nor the job or session ID 
                        # I keep the Job or the Session to log this error 
                        msg = "can't copy data from %s to %s : %s" %( abs_SrcFileName ,
                                                                      abs_DestFileName ,
                                                                      err )
                        c_log.error( msg )
                        raise MobyleError , "can't copy data : "+ str(err)
            else: #src is a job , jobState , MobyleJob instance ( Session is always Local )
                data = src.getOutputFile( srcFileName )
                try:
                    f = open( abs_DestFileName , 'w' )
                    f.write( data )
                    f.close()
                except IOError ,err:
                    msg = "unable to write data from %s to %s: %s"%(src , dest , err )
                    c_log.error( msg )
                    raise MobyleError , msg
        else:
            try:
                clean_content = self.cleanData(data)
            except Exception, err:
                msg = "error when cleaning file : " + abs_DestFileName + str( err )
                c_log.critical(msg, exc_info=True)
                raise MobyleError, msg
            try:
                fh = open(abs_DestFileName, "w")
                fh.write(clean_content)
                fh.close()
            except IOError, err:
                msg = "error when creating file : " + abs_DestFileName + str(err)
                raise MobyleError, msg
        size = os.path.getsize(abs_DestFileName)
        return os.path.basename(abs_DestFileName), size



class TextDataType( AbstractTextDataType ):
    # this trick is to avoid that SequenceDataType is a subclass of TextDataType
    pass


class ReportDataType( AbstractTextDataType ):
    pass


class BinaryDataType( AbstractFileDataType ):

    def head( self , data ):
        return 'Binary data'
            
    def cleanData( self, data ):
        """
        prepare data prior to write it on a disk
        @param data: 
        @type data:a buffer
        """
        return data

    def toFile( self , data , dest , destFileName , src , srcFileName ):
        """
        Write file (of user data) in the working directory .
        @param fileName:
        @type fileName: string
        @param content: the content of the file
        @type content: string
        @return: the name ( absolute path ) of the created file ( could be different from the arg fileName )
        @rtype: string
        @call: L{MobyleJob._fillEvaluator}
        """
        try:
            destSafeFileName = safeFileName( destFileName )
        except UserValueError, err:
            raise UserValueError( msg = "this value : %s is not allowed for a file name, please change it" % destFileName )
        abs_DestFileName = os.path.join( dest.getDir() , destSafeFileName )

        # if the user upload 2 files with the same basename safeFileName
        # return the same safeFileName
        # add an extension to avoid _toFile to erase the existing file.
        ext = 1
        completeName = abs_DestFileName.split( '.' )
        base = completeName[0]
        suffixe = '.'.join( completeName[1:] )
        while os.path.exists( abs_DestFileName ):
            abs_DestFileName = base + '.' + str( ext ) + '.' + suffixe
            ext = ext + 1
        if src:
            if src.isLocal():
                try:
                    srcSafeFileName = safeFileName( srcFileName )
                except UserValueError, err:
                    raise UserValueError( msg = "this value : %s is not allowed for a file name, please change it" % srcFileName  )
                
                #the realpath is because if the abs_SrcFileName is a soft link ( some results are ) the
                #hardlink point to softlink and it cause an error : no such file  
                abs_SrcFileName = os.path.realpath( os.path.join( src.getDir() , srcSafeFileName ) )
                try:
                    os.link(  abs_SrcFileName , abs_DestFileName )
                except OSError :
                    #if the src and dest are not on the same device
                    #an OSError: [Errno 18] Invalid cross-device link , is raised
                    try:
                        shutil.copy( abs_SrcFileName , abs_DestFileName )
                    except IOError ,err:
                        # je ne connais - ni le service (s'il existe)
                        #               - ni le l' ID du job ou de la session
                        # donc je laisse le soin au Job ou la session a logger l'erreur
    
                        msg = "can't copy data from %s to %s : %s" %( abs_SrcFileName ,
                                                                      abs_DestFileName ,
                                                                      err )
                        raise MobyleError , "can't copy data : "+ str(err)
            else: #src is a job , jobState , MobyleJOb instance ( session is Local )
                data = src.getOutputFile( srcFileName )
                try:
                    f = open( abs_DestFileName , 'wb' )
                    f.write( data )
                    f.close()
                except IOError ,err:
                    pass
        else:
            try:
                fh = open( abs_DestFileName , "wb" )
                fh.write( data )
                fh.close()
            except IOError , err:
                # je ne connais - ni le service (s'il existe)
                #               - ni le l' ID du job ou de la session
                # donc je laisse le soin au Job ou la session a logger l'erreur
                msg = "error when creating file %s: %s" %( os.path.basename( abs_DestFileName ) ,  err )
                raise MobyleError , msg
        size  = os.path.getsize( abs_DestFileName )
        return os.path.basename( abs_DestFileName ) , size
    
class FilenameDataType( DataType ):
 
    def convert( self , value , acceptedMobyleType , detectedMobyleType = None , paramFile= False ):  
        """
        @param acceptedMobyleType: the MobyleType  accepted by this service parameter 
        @type acceptedMobyleType: L{MobyleType} instance
        @param detectedMobyleType: the MobyleType describing this data
        @type detectedMobyleType: L{MobyleType} instance
        """  
        if value is None:
            return ( None , acceptedMobyleType )
            #raise UserValueError( parameter = param , msg= " this parameter must be a String" )
        #fileName = safeFileName( value )
        return ( value , acceptedMobyleType )
    
    def detect( self , value ):
        mt = MobyleType( self )
        return mt    
    
    def validate( self, param ):
        value = param.getValue()
        if value is None :
            return True
        safefileName = safeFileName( value )
        if safefileName != value:
            msg = "invalid value: %s : the followings characters \:/ ;` {} are not allowed" %( value )
            raise UserValueError( parameter = param , msg = msg )
        else:
            return True