blob: c460ce34c16681e72754f6389f7236155f9fc7cf [file] [log] [blame]
using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using System.IO;
namespace Lucene.Net.Store
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using IOUtils = Lucene.Net.Util.IOUtils;
/// <summary>
/// A <see cref="Directory"/> is a flat list of files. Files may be written once, when they
/// are created. Once a file is created it may only be opened for read, or
/// deleted. Random access is permitted both when reading and writing.
/// <para/>
/// .NET's i/o APIs not used directly, but rather all i/o is
/// through this API. This permits things such as:
/// <list type="bullet">
/// <item><description> implementation of RAM-based indices;</description></item>
/// <item><description> implementation indices stored in a database;</description></item>
/// <item><description> implementation of an index as a single file;</description></item>
/// </list>
/// <para/>
/// Directory locking is implemented by an instance of
/// <see cref="Store.LockFactory"/>, and can be changed for each <see cref="Directory"/>
/// instance using <see cref="SetLockFactory"/>.
/// </summary>
public abstract class Directory : IDisposable // LUCENENET TODO: Subclass System.IO.FileSystemInfo ?
{
/// <summary>
/// Returns an array of strings, one for each file in the directory.
/// </summary>
/// <exception cref="DirectoryNotFoundException"> if the directory is not prepared for any
/// write operations (such as <see cref="CreateOutput(string, IOContext)"/>). </exception>
/// <exception cref="IOException"> in case of other IO errors </exception>
public abstract string[] ListAll();
/// <summary>
/// Returns <c>true</c> iff a file with the given name exists.
/// </summary>
[Obsolete("this method will be removed in 5.0")]
public abstract bool FileExists(string name);
/// <summary>
/// Removes an existing file in the directory. </summary>
public abstract void DeleteFile(string name);
/// <summary>
/// Returns the length of a file in the directory. this method follows the
/// following contract:
/// <list>
/// <item><description>Throws <see cref="FileNotFoundException"/>
/// if the file does not exist.</description></item>
/// <item><description>Returns a value &gt;=0 if the file exists, which specifies its length.</description></item>
/// </list>
/// </summary>
/// <param name="name"> the name of the file for which to return the length. </param>
/// <exception cref="IOException"> if there was an IO error while retrieving the file's
/// length. </exception>
public abstract long FileLength(string name);
/// <summary>
/// Creates a new, empty file in the directory with the given name.
/// Returns a stream writing this file.
/// </summary>
public abstract IndexOutput CreateOutput(string name, IOContext context);
/// <summary>
/// Ensure that any writes to these files are moved to
/// stable storage. Lucene uses this to properly commit
/// changes to the index, to prevent a machine/OS crash
/// from corrupting the index.<br/>
/// <br/>
/// NOTE: Clients may call this method for same files over
/// and over again, so some impls might optimize for that.
/// For other impls the operation can be a noop, for various
/// reasons.
/// </summary>
public abstract void Sync(ICollection<string> names);
/// <summary>
/// Returns a stream reading an existing file, with the
/// specified read buffer size. The particular <see cref="Directory"/>
/// implementation may ignore the buffer size. Currently
/// the only <see cref="Directory"/> implementations that respect this
/// parameter are <see cref="FSDirectory"/> and
/// <see cref="CompoundFileDirectory"/>.
/// <para/>Throws <see cref="FileNotFoundException"/>
/// if the file does not exist.
/// </summary>
public abstract IndexInput OpenInput(string name, IOContext context);
/// <summary>
/// Returns a stream reading an existing file, computing checksum as it reads </summary>
public virtual ChecksumIndexInput OpenChecksumInput(string name, IOContext context)
{
return new BufferedChecksumIndexInput(OpenInput(name, context));
}
/// <summary>
/// Construct a <see cref="Lock"/>. </summary>
/// <param name="name"> the name of the lock file </param>
public abstract Lock MakeLock(string name);
/// <summary>
/// Attempt to clear (forcefully unlock and remove) the
/// specified lock. Only call this at a time when you are
/// certain this lock is no longer in use. </summary>
/// <param name="name"> name of the lock to be cleared. </param>
public abstract void ClearLock(string name);
/// <summary>
/// Disposes the store. </summary>
// LUCENENET specific - implementing proper dispose pattern
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the store. </summary>
protected abstract void Dispose(bool disposing);
/// <summary>
/// Set the <see cref="Store.LockFactory"/> that this <see cref="Directory"/> instance should
/// use for its locking implementation. Each * instance of
/// <see cref="Store.LockFactory"/> should only be used for one directory (ie,
/// do not share a single instance across multiple
/// Directories).
/// </summary>
/// <param name="lockFactory"> instance of <see cref="Store.LockFactory"/>. </param>
public abstract void SetLockFactory(LockFactory lockFactory);
/// <summary>
/// Get the <see cref="Store.LockFactory"/> that this <see cref="Directory"/> instance is
/// using for its locking implementation. Note that this
/// may be null for <see cref="Directory"/> implementations that provide
/// their own locking implementation.
/// </summary>
public abstract LockFactory LockFactory { get; }
/// <summary>
/// Return a string identifier that uniquely differentiates
/// this <see cref="Directory"/> instance from other <see cref="Directory"/> instances.
/// This ID should be the same if two <see cref="Directory"/> instances
/// (even in different AppDomains and/or on different machines)
/// are considered "the same index". This is how locking
/// "scopes" to the right index.
/// </summary>
public virtual string GetLockID()
{
return this.ToString();
}
public override string ToString()
{
return this.GetType().Name + '@' + GetHashCode().ToString("x") + " lockFactory=" + LockFactory;
}
/// <summary>
/// Copies the file <paramref name="src"/> to <seealso cref="Directory"/> <paramref name="to"/> under the new
/// file name <paramref name="dest"/>.
/// <para/>
/// If you want to copy the entire source directory to the destination one, you
/// can do so like this:
///
/// <code>
/// Directory to; // the directory to copy to
/// foreach (string file in dir.ListAll()) {
/// dir.Copy(to, file, newFile, IOContext.DEFAULT); // newFile can be either file, or a new name
/// }
/// </code>
/// <para/>
/// <b>NOTE:</b> this method does not check whether <paramref name="dest"/> exist and will
/// overwrite it if it does.
/// </summary>
public virtual void Copy(Directory to, string src, string dest, IOContext context)
{
IndexOutput os = null;
IndexInput @is = null;
IOException priorException = null;
try
{
os = to.CreateOutput(dest, context);
@is = OpenInput(src, context);
os.CopyBytes(@is, @is.Length);
}
catch (IOException ioe)
{
priorException = ioe;
}
finally
{
bool success = false;
try
{
IOUtils.DisposeWhileHandlingException(priorException, os, @is);
success = true;
}
finally
{
if (!success)
{
try
{
to.DeleteFile(dest);
}
catch (Exception)
{
}
}
}
}
}
/// <summary>
/// Creates an <see cref="IndexInputSlicer"/> for the given file name.
/// <see cref="IndexInputSlicer"/> allows other <see cref="Directory"/> implementations to
/// efficiently open one or more sliced <see cref="IndexInput"/> instances from a
/// single file handle. The underlying file handle is kept open until the
/// <see cref="IndexInputSlicer"/> is closed.
/// <para/>Throws <see cref="FileNotFoundException"/>
/// if the file does not exist.
/// <para/>
/// @lucene.internal
/// @lucene.experimental
/// </summary>
/// <exception cref="IOException">
/// if an <seealso cref="IOException"/> occurs</exception>
public virtual IndexInputSlicer CreateSlicer(string name, IOContext context)
{
EnsureOpen();
return new IndexInputSlicerAnonymousClass(OpenInput(name, context));
}
private class IndexInputSlicerAnonymousClass : IndexInputSlicer
{
private readonly IndexInput @base;
public IndexInputSlicerAnonymousClass(IndexInput @base)
{
this.@base = @base;
}
public override IndexInput OpenSlice(string sliceDescription, long offset, long length)
{
return new SlicedIndexInput("SlicedIndexInput(" + sliceDescription + " in " + @base + ")", @base, offset, length);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
@base.Dispose();
}
}
[Obsolete("Only for reading CFS files from 3.x indexes.")]
public override IndexInput OpenFullSlice()
{
return (IndexInput)@base.Clone();
}
}
/// <exception cref="ObjectDisposedException"> if this Directory is closed </exception>
protected internal virtual void EnsureOpen()
{
}
/// <summary>
/// Allows to create one or more sliced <see cref="IndexInput"/> instances from a single
/// file handle. Some <see cref="Directory"/> implementations may be able to efficiently map slices of a file
/// into memory when only certain parts of a file are required.
/// <para/>
/// @lucene.internal
/// @lucene.experimental
/// </summary>
public abstract class IndexInputSlicer : IDisposable
{
/// <summary>
/// Returns an <see cref="IndexInput"/> slice starting at the given offset with the given length.
/// </summary>
public abstract IndexInput OpenSlice(string sliceDescription, long offset, long length);
/// <summary>
/// Returns an <see cref="IndexInput"/> slice starting at offset <c>0</c> with a
/// length equal to the length of the underlying file </summary>
[Obsolete("Only for reading CFS files from 3.x indexes.")]
public abstract IndexInput OpenFullSlice(); // can we remove this somehow?
protected abstract void Dispose(bool disposing);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Implementation of an <see cref="IndexInput"/> that reads from a portion of
/// a file.
/// </summary>
private sealed class SlicedIndexInput : BufferedIndexInput
{
private IndexInput @base;
private long fileOffset;
private long length;
internal SlicedIndexInput(string sliceDescription, IndexInput @base, long fileOffset, long length)
: this(sliceDescription, @base, fileOffset, length, BufferedIndexInput.BUFFER_SIZE)
{
}
internal SlicedIndexInput(string sliceDescription, IndexInput @base, long fileOffset, long length, int readBufferSize)
: base("SlicedIndexInput(" + sliceDescription + " in " + @base + " slice=" + fileOffset + ":" + (fileOffset + length) + ")", readBufferSize)
{
this.@base = (IndexInput)@base.Clone();
this.fileOffset = fileOffset;
this.length = length;
}
public override object Clone()
{
SlicedIndexInput clone = (SlicedIndexInput)base.Clone();
clone.@base = (IndexInput)@base.Clone();
clone.fileOffset = fileOffset;
clone.length = length;
return clone;
}
/// <summary>
/// Expert: implements buffer refill. Reads bytes from the current
/// position in the input. </summary>
/// <param name="b"> the array to read bytes into </param>
/// <param name="offset"> the offset in the array to start storing bytes </param>
/// <param name="len"> the number of bytes to read </param>
protected override void ReadInternal(byte[] b, int offset, int len)
{
long start = GetFilePointer();
if (start + len > length)
{
throw new Exception("read past EOF: " + this);
}
@base.Seek(fileOffset + start);
@base.ReadBytes(b, offset, len, false);
}
/// <summary>
/// Expert: implements seek. Sets current position in this file, where
/// the next <see cref="ReadInternal(byte[], int, int)"/> will occur.
/// </summary>
/// <seealso cref="ReadInternal(byte[], int, int)"/>
protected override void SeekInternal(long pos)
{
}
/// <summary>
/// Closes the stream to further operations.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
@base.Dispose();
}
}
public override long Length => length;
}
// LUCENENET specific - formatter to defer building the string of directory contents in string.Format().
// This struct is meant to wrap a directory parameter when passed as a string.Format() argument.
internal struct ListAllFormatter // For assert/test/debug
{
#pragma warning disable IDE0044 // Add readonly modifier
private Directory directory;
#pragma warning restore IDE0044 // Add readonly modifier
public ListAllFormatter(Directory directory)
{
this.directory = directory ?? throw new ArgumentNullException(nameof(directory));
}
public override string ToString()
{
return Arrays.ToString(directory.ListAll());
}
}
}
}