#FEATURE by cg
class: Filename
added: #asUniqueFilename
comment/format in: #newTemporaryIn:
changed: #newTemporaryIn:nameTemplate:
--- 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)"