/// <summary> /// Creates a new file, with the correct naming scheme. /// </summary> /// <param name="fileInfo">The <see cref="ProlificDataFileInfo"/> instance representing the name of the file created.</param> /// <param name="useWriteThroughIfSupported"><c>true</c> to try to use <see cref="IFileSystemWriter.CreateWriteThroughBinary"/> instead of <see cref="IFileSystemWriter.CreateNewBinary"/>.</param> /// <returns>The <see cref="IBinaryWriter"/> representing the file created.</returns> public IBinaryWriter CreateFile( out ProlificDataFileInfo fileInfo, bool useWriteThroughIfSupported = false ) { Ensure.That(this).NotDisposed(); bool writeThroughSupported = false; if( useWriteThroughIfSupported ) writeThroughSupported = this.fileSystem.SupportsCreateWriteThroughBinary; // we may have to try multiple times, if we don't yet have an // app instance ID, that's verified to be unique while( true ) { string appID = appInstanceID; bool isNewAppID = false; if( appID.NullReference() ) { // NOTE: random does not necessarily mean unique. But since I find it unlikely to // have even a few dozen hashes at the same time, we should be in the clear: // 32^6 ~= 1+ billion; and we probably wouldn't see conflicts before we had // 40000-50000 hashes present at the same time. Based on an answer here: // http://stackoverflow.com/a/9543797 appID = GenerateRandomHash(DefaultAppIDLength); isNewAppID = true; } // try to create the file var file = ToFileInfo(this.fileManagerID, appID, this.fileExtension, this.fileSystem.EscapesNames); IBinaryWriter fileStream; try { if( useWriteThroughIfSupported && writeThroughSupported ) fileStream = this.fileSystem.CreateWriteThroughBinary(file.DataStoreName, overwriteIfExists: false); else fileStream = this.fileSystem.CreateNewBinary(file.DataStoreName, overwriteIfExists: false); } catch( Exception ex ) { //// the file already exists, or there was another problem bool fileAlreadyExists; try { fileAlreadyExists = FindAllFiles(this.fileSystem) .Where(f => DataStore.Comparer.Equals(f.DataStoreName, file.DataStoreName)) .FirstOrNullable() .HasValue; } catch( Exception exx ) { // there is definitely a problem with the file system: report both exceptions throw new AggregateException("Failed to create file!", ex, exx).StoreFileLine(); } if( fileAlreadyExists ) { //// NOTE: we are assuming here, that files created by the same source, //// in the same app instance, are at least a minute apart. // try again with a new app instance ID continue; } else { ex.StoreFileLine(); throw; // there was some kind of problem, trying to create the file... } } // alright, we successfully created the file, but that does not mean, that // the app instance ID was not already in use, sometime before that if( isNewAppID ) { bool appIDAlreadyInUse = FindAllFiles(this.fileSystem) .Where(f => !DataStore.Comparer.Equals(f.DataStoreName, file.DataStoreName)) .Where(f => AppInstanceIDComparer.Equals(f.AppInstanceID, appID)) .FirstOrNullable() .HasValue; if( appIDAlreadyInUse ) { // remove this file to avoid confusion, and try again this.fileSystem.DeleteFile(file.DataStoreName); continue; } else { // a new and unique app ID was found var prevAppID = Interlocked.CompareExchange(ref appInstanceID, appID, comparand: null); if( !prevAppID.NullReference() ) { // however, this method was called concurrently, and another unique app ID was already established // clean up, and try again with that ID this.fileSystem.DeleteFile(file.DataStoreName); continue; } } } // either the app ID was not generated just now, // or it was, and we successfully saved it fileInfo = file; return fileStream; } }
private static bool TryParseDataStoreName( string dataStoreName, bool escapesFileNames, out ProlificDataFileInfo fileInfo ) { if( dataStoreName.NullOrEmpty() ) { fileInfo = default(ProlificDataFileInfo); return false; } string fileName = escapesFileNames ? DataStore.UnescapeName(dataStoreName) : dataStoreName; string[] parts = fileName.Split(new char[] { '_' }, StringSplitOptions.None); if( parts.Length != 3 ) { fileInfo = default(ProlificDataFileInfo); return false; } string fileManagerID = parts[0]; string creationTimeString = parts[1]; string appInstanceID = parts[2]; string fileExtension = null; if( escapesFileNames && appInstanceID.Length > DefaultAppIDLength ) { fileExtension = appInstanceID.Substring(startIndex: DefaultAppIDLength + 1); appInstanceID = appInstanceID.Substring(startIndex: 0, length: DefaultAppIDLength); } DateTime creationTime; if( !DateTime.TryParseExact(creationTimeString, CreationTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out creationTime) ) { fileInfo = default(ProlificDataFileInfo); return false; } fileInfo = new ProlificDataFileInfo(fileManagerID, appInstanceID, creationTime, fileExtension, dataStoreName); return true; }