File: Data\IFileHandle.cs
Web Access
Project: src\src\Microsoft.ML.Core\Microsoft.ML.Core.csproj (Microsoft.ML.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.ML.Internal.Utilities;
using Microsoft.ML.Runtime;
 
namespace Microsoft.ML.Data;
 
/// <summary>
/// A file handle.
/// </summary>
public interface IFileHandle : IDisposable
{
    /// <summary>
    /// Returns whether CreateWriteStream is expected to succeed. Typically, once
    /// CreateWriteStream has been called once, this will forever more return false.
    /// </summary>
    bool CanWrite { get; }
 
    /// <summary>
    /// Returns whether OpenReadStream is expected to succeed.
    /// </summary>
    bool CanRead { get; }
 
    /// <summary>
    /// Create a writable stream for this file handle.
    /// </summary>
    Stream CreateWriteStream();
 
    /// <summary>
    /// Open a readable stream for this file handle.
    /// </summary>
    Stream OpenReadStream();
}
 
/// <summary>
/// A simple disk-based file handle.
/// </summary>
public sealed class SimpleFileHandle : IFileHandle
{
    private readonly string _fullPath;
 
    // Exception context.
    private readonly IExceptionContext _ectx;
 
    private readonly object _lock;
 
    // Whether to delete the file when this is disposed.
    private readonly bool _autoDelete;
 
    // Whether this file has contents. This is false if the file needs CreateWriteStream to be
    // called (before OpenReadStream can be called).
    private bool _wrote;
    // If non-null, the active write stream. This should be disposed before the first OpenReadStream call.
    private Stream _streamWrite;
 
    // This contains the potentially active read streams. This is set to null once this file
    // handle has been disposed.
    private List<Stream> _streams;
 
    private bool IsDisposed => _streams == null;
 
    public SimpleFileHandle(IExceptionContext ectx, string path, bool needsWrite, bool autoDelete)
    {
        Contracts.CheckValue(ectx, nameof(ectx));
        ectx.CheckNonEmpty(path, nameof(path));
 
        _ectx = ectx;
        _fullPath = Path.GetFullPath(path);
 
        _autoDelete = autoDelete;
 
        // The file has already been written to iff needsWrite is false.
        _wrote = !needsWrite;
 
        // REVIEW: Should this do some basic validation? Eg, for output files, ensure that
        // the directory exists (and perhaps even create an empty file); for input files, ensure
        // that the file exists (and perhaps even attempt to open it).
 
        _lock = new object();
        _streams = new List<Stream>();
    }
 
    public bool CanWrite => !_wrote && !IsDisposed;
 
    public bool CanRead => _wrote && !IsDisposed;
 
    public void Dispose()
    {
        lock (_lock)
        {
            if (IsDisposed)
                return;
 
            Contracts.Assert(_streams != null);
 
            // REVIEW: Is it safe to dispose these streams? What if they are
            // being used on other threads? Does that matter?
            if (_streamWrite != null)
            {
                try
                {
                    _streamWrite.CloseEx();
                    _streamWrite.Dispose();
                }
                catch
                {
                    // REVIEW: What should we do here?
                    Contracts.Assert(false, "Closing a SimpleFileHandle write stream failed!");
                }
                _streamWrite = null;
            }
 
            foreach (var stream in _streams)
            {
                try
                {
                    stream.CloseEx();
                    stream.Dispose();
                }
                catch
                {
                    // REVIEW: What should we do here?
                    Contracts.Assert(false, "Closing a SimpleFileHandle read stream failed!");
                }
            }
 
            _streams = null;
            Contracts.Assert(IsDisposed);
 
            if (_autoDelete)
            {
                try
                {
                    // Finally, delete the file.
                    File.Delete(_fullPath);
                }
                catch
                {
                    // REVIEW: What should we do here?
                    Contracts.Assert(false, "Deleting a SimpleFileHandle physical file failed!");
                }
            }
        }
    }
 
    private void CheckNotDisposed()
    {
        if (IsDisposed)
            throw _ectx.Except("SimpleFileHandle has already been disposed");
    }
 
    public Stream CreateWriteStream()
    {
        lock (_lock)
        {
            CheckNotDisposed();
 
            if (_wrote)
                throw _ectx.Except("CreateWriteStream called multiple times on SimpleFileHandle");
 
            Contracts.Assert(_streamWrite == null);
            _streamWrite = new FileStream(_fullPath, FileMode.Create, FileAccess.Write);
            _wrote = true;
            return _streamWrite;
        }
    }
 
    public Stream OpenReadStream()
    {
        lock (_lock)
        {
            CheckNotDisposed();
 
            if (!_wrote)
                throw _ectx.Except("SimpleFileHandle hasn't been written yet");
 
            if (_streamWrite != null)
            {
                if (_streamWrite.CanWrite)
                    throw _ectx.Except("Write stream for SimpleFileHandle hasn't been disposed");
                _streamWrite = null;
            }
 
            // Drop read streams that have already been disposed.
            _streams.RemoveAll(s => !s.CanRead);
 
            var stream = new FileStream(_fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
            _streams.Add(stream);
            return stream;
        }
    }
}