#FEATURE by cg
authorClaus Gittinger <cg@exept.de>
Wed, 27 Apr 2016 18:56:09 +0200
changeset 19665 d815a7ce4e57
parent 19664 abce388b4b50
child 19666 755edabcbe67
#FEATURE by cg class: Filename added: #asUniqueFilename comment/format in: #newTemporaryIn: changed: #newTemporaryIn:nameTemplate:
Filename.st
--- a/Filename.st	Wed Apr 27 14:58:53 2016 +0200
+++ b/Filename.st	Wed Apr 27 18:56:09 2016 +0200
@@ -591,6 +591,7 @@
     "temp files in '/tmp':
 
      Filename newTemporary
+     Filename newTemporaryIn:(Filename tempDirectory)
     "
 
     "temp files somewhere
@@ -619,6 +620,10 @@
      The filenames returned are in aDirectoryOrNil and named after the given template,
      in which %1 and %2 are expanded to the unix process id, and a unique number, incremented
      with every call to this method respectively.
+     If the template does not contain %-meta characters, and the file already exists,
+     a sequence of _1, _2,... is appended to the name. This is dangerous, as it does not prevent race
+     conditions (if two such files are created at the same time).
+     
      Notice: only a unique filename object is created and returned - no physical
      file is created by this method (i.e. you have to send #writeStream or
      whatever to it in order to really create something).
@@ -626,38 +631,50 @@
 
      DO NOT USE THIS FOR PLAIN FILES - IT IS UNSECURE use FileStream>>#newTemporaryIn:nameTemplate:"
 
-    |nameString newTempFilename|
+    |newTempFilename oldNameString nextSeqNr|
 
     self isAbstract ifTrue:[
-	^ ConcreteClass newTemporaryIn:aDirectoryOrNil nameTemplate:template
+        ^ ConcreteClass newTemporaryIn:aDirectoryOrNil nameTemplate:template
     ].
 
     "although the above allows things to be redefined in concrete classes,
      the following should work on all systems ..."
 
     [
-	"Use random numbers in order to improve the security
-	 by making the generated names less predictable"
-	nameString := template bindWith:(OperatingSystem getProcessId) with:(RandomGenerator nextLettersOrDigits:4).
-
-	aDirectoryOrNil isNil ifTrue:[
-	    newTempFilename := self named:nameString
-	] ifFalse:[
-	    newTempFilename := aDirectoryOrNil asFilename construct:nameString
-	]
+        |nameString fn|
+        
+        "Use random numbers in order to improve the security
+         by making the generated names less predictable"
+        nameString := template bindWith:(OperatingSystem getProcessId) with:(RandomGenerator nextLettersOrDigits:4).
+        (oldNameString = nameString) ifTrue:[
+            "/ ouch - the given template seems to not generate unique file names.
+            "/ append a sequence number
+            nextSeqNr := (nextSeqNr ? 0) + 1.
+            fn := nameString asFilename.
+            nameString := fn withoutSuffix name,'_',nextSeqNr asString,'.' , fn suffix.
+        ].    
+
+        aDirectoryOrNil isNil ifTrue:[
+            newTempFilename := self named:nameString
+        ] ifFalse:[
+            newTempFilename := aDirectoryOrNil asFilename construct:nameString
+        ].
+        oldNameString := nameString.
     ] doWhile:[
-	"care for existing leftOver tempFiles
-	 from a previous boot of the OS
-	 i.e. my pid could be the same as when executed
-	 the last time before system reboot ...)"
-
-	newTempFilename exists
+        "care for existing leftOver tempFiles
+         from a previous boot of the OS
+         i.e. my pid could be the same as when executed
+         the last time before system reboot ...)"
+
+        newTempFilename exists
     ].
     ^ newTempFilename
 
     "temp files in '/tmp':
 
      Filename newTemporary
+     Filename newTemporaryIn:nil nameTemplate:'out_%1_%2.txt'
+     Filename newTemporaryIn:(Filename tempDirectory) nameTemplate:'out_%1_%2.txt'
     "
 
     "temp files somewhere
@@ -2039,6 +2056,31 @@
     ^ URL fromString:self pathName.
 !
 
+asUniqueFilename
+    "I a file by my name already exists, return a new filename with a unique string appended.
+     here, a somewhat naive strategy is performed, by trying _1, _2,... until a new name is generated."
+
+    |fn baseFn nextSeqNr|
+
+    fn := baseFn := self.
+    nextSeqNr := 0.
+    [ fn exists ] whileTrue:[
+        |newFn|
+        
+        nextSeqNr := (nextSeqNr ? 0) + 1.
+        fn := (baseFn withoutSuffix name,'_',nextSeqNr asString) asFilename withSuffix:baseFn suffix.
+    ].
+    ^ fn
+
+    "
+     'aaa.txt' asFilename contents:'bla'.
+     'aaa.txt' asFilename asUniqueFilename contents:'bla2'.
+     'aaa.txt' asFilename asUniqueFilename contents:'bla3'.
+     'aaa.txt' asFilename asUniqueFilename contents:'bla4'.
+     #('aaa.txt' 'aaa_1.txt' 'aaa_2.txt' 'aaa_3.txt') do:[:f | f asFilename delete].  
+    "
+!
+
 components
     "return the receiver's filename components - that is the name of each directory
      along the pathName (that DOES include the root directory)"