1795 lines
56 KiB
C#
1795 lines
56 KiB
C#
//
|
|
// File.cs: Provides a basic framework for reading from and writing to
|
|
// a file, as well as accessing basic tagging and media properties.
|
|
//
|
|
// Author:
|
|
// Brian Nickel (brian.nickel@gmail.com)
|
|
// Aaron Bockover (abockover@novell.com)
|
|
//
|
|
// Original Source:
|
|
// tfile.cpp from TagLib
|
|
//
|
|
// Copyright (C) 2005, 2007 Brian Nickel
|
|
// Copyright (C) 2006 Novell, Inc.
|
|
// Copyright (C) 2002,2003 Scott Wheeler (Original Implementation)
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License version
|
|
// 2.1 as published by the Free Software Foundation.
|
|
//
|
|
// This library is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
// USA
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Runtime.Serialization;
|
|
|
|
namespace TagLib
|
|
{
|
|
|
|
/// <summary>
|
|
/// Specifies the options to use when reading the media.
|
|
/// </summary>
|
|
[Flags]
|
|
public enum ReadStyle
|
|
{
|
|
/// <summary>
|
|
/// The media properties will not be read.
|
|
/// </summary>
|
|
None = 0,
|
|
|
|
// Fast = 1,
|
|
|
|
/// <summary>
|
|
/// The media properties will be read with average accuracy.
|
|
/// </summary>
|
|
Average = 2,
|
|
|
|
/// <summary>
|
|
/// Use the <see cref="PictureLazy"/> class in the
|
|
/// the property <see cref="Tag.Pictures"/>.
|
|
/// This will avoid loading picture content when reading the Tag.
|
|
/// Picture will be read lazily, when the picture content is
|
|
/// accessed.
|
|
/// </summary>
|
|
PictureLazy = 4
|
|
}
|
|
|
|
/// <summary>
|
|
/// This abstract class provides a basic framework for reading from
|
|
/// and writing to a file, as well as accessing basic tagging and
|
|
/// media properties.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>This class is agnostic to all specific media types. Its
|
|
/// child classes, on the other hand, support the the intricacies of
|
|
/// different media and tagging formats. For example, <see
|
|
/// cref="Mpeg4.File" /> supports the MPEG-4 specificication and
|
|
/// Apple's tagging format.</para>
|
|
/// <para>Each file type can be created using its format specific
|
|
/// constructors, ie. <see cref="Mpeg4.File(string)" />, but the
|
|
/// preferred method is to use <see
|
|
/// cref="Create(string,string,ReadStyle)" /> or one of its
|
|
/// variants, as it automatically detects the appropriate class from
|
|
/// the file extension or provided mime-type.</para>
|
|
/// </remarks>
|
|
public abstract class File : IDisposable
|
|
{
|
|
#region Enums
|
|
|
|
/// <summary>
|
|
/// Specifies the type of file access operations currently
|
|
/// permitted on an instance of <see cref="File" />.
|
|
/// </summary>
|
|
public enum AccessMode
|
|
{
|
|
/// <summary>
|
|
/// Read operations can be performed.
|
|
/// </summary>
|
|
Read,
|
|
|
|
/// <summary>
|
|
/// Read and write operations can be performed.
|
|
/// </summary>
|
|
Write,
|
|
|
|
/// <summary>
|
|
/// The file is closed for both read and write
|
|
/// operations.
|
|
/// </summary>
|
|
Closed
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Delegates
|
|
|
|
/// <summary>
|
|
/// This delegate is used for intervening in <see
|
|
/// cref="Create(string)" /> by resolving the file type
|
|
/// before any standard resolution operations.
|
|
/// </summary>
|
|
/// <param name="abstraction">
|
|
/// A <see cref="IFileAbstraction" /> object representing the
|
|
/// file to be read.
|
|
/// </param>
|
|
/// <param name="mimetype">
|
|
/// A <see cref="string" /> object containing the mime-type
|
|
/// of the file.
|
|
/// </param>
|
|
/// <param name="style">
|
|
/// A <see cref="ReadStyle" /> value specifying how to read
|
|
/// media properties from the file.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A new instance of <see cref="File" /> or <see
|
|
/// langword="null" /> if the resolver could not match it.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <para>A <see cref="FileTypeResolver" /> is one way of
|
|
/// altering the behavior of <see cref="Create(string)" />
|
|
/// .</para>
|
|
/// <para>When <see cref="Create(string)" /> is called, the
|
|
/// registered resolvers are invoked in the reverse order in
|
|
/// which they were registered. The resolver may then perform
|
|
/// any operations necessary, including other type-finding
|
|
/// methods.</para>
|
|
/// <para>If the resolver returns a new <see cref="File" />,
|
|
/// it will instantly be returned, by <see
|
|
/// cref="Create(string)" />. If it returns <see
|
|
/// langword="null" />, <see cref="Create(string)" /> will
|
|
/// continue to process. If the resolver throws an exception
|
|
/// it will be uncaught.</para>
|
|
/// <para>To register a resolver, use <see
|
|
/// cref="AddFileTypeResolver" />.</para>
|
|
/// </remarks>
|
|
public delegate File FileTypeResolver (IFileAbstraction abstraction, string mimetype, ReadStyle style);
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Private Properties
|
|
|
|
/// <summary>
|
|
/// Contains the current stream used in reading/writing.
|
|
/// </summary>
|
|
Stream file_stream;
|
|
|
|
/// <summary>
|
|
/// Contains the internal file abstraction.
|
|
/// </summary>
|
|
protected IFileAbstraction file_abstraction;
|
|
|
|
/// <summary>
|
|
/// Contains buffer size to use when reading.
|
|
/// </summary>
|
|
static readonly int buffer_size = 1024;
|
|
|
|
/// <summary>
|
|
/// Contains the file type resolvers to use in <see
|
|
/// cref="Create(string)" />.
|
|
/// </summary>
|
|
static readonly List<FileTypeResolver> file_type_resolvers = new List<FileTypeResolver> ();
|
|
|
|
/// <summary>
|
|
/// The reasons (if any) why this file is marked as corrupt.
|
|
/// </summary>
|
|
List<string> corruption_reasons;
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Public Static Properties
|
|
|
|
/// <summary>
|
|
/// The buffer size to use when reading large blocks of data
|
|
/// in the <see cref="File" /> class.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="uint" /> containing the buffer size to use
|
|
/// when reading large blocks of data.
|
|
/// </value>
|
|
public static uint BufferSize => (uint)buffer_size;
|
|
|
|
#endregion
|
|
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Constructs and initializes a new instance of <see
|
|
/// cref="File" /> for a specified path in the local file
|
|
/// system.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// A <see cref="string" /> object containing the path of the
|
|
/// file to use in the new instance.
|
|
/// </param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="path" /> is <see langword="null" />.
|
|
/// </exception>
|
|
protected File (string path)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException (nameof (path));
|
|
|
|
file_abstraction = new LocalFileAbstraction (path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs and initializes a new instance of <see
|
|
/// cref="File" /> for a specified file abstraction.
|
|
/// </summary>
|
|
/// <param name="abstraction">
|
|
/// A <see cref="IFileAbstraction" /> object to use when
|
|
/// reading from and writing to the file.
|
|
/// </param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="abstraction" /> is <see langword="null"
|
|
/// />.
|
|
/// </exception>
|
|
protected File (IFileAbstraction abstraction)
|
|
{
|
|
if (abstraction == null)
|
|
throw new ArgumentNullException (nameof (abstraction));
|
|
|
|
file_abstraction = abstraction;
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Gets a abstract representation of all tags stored in the
|
|
/// current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="TagLib.Tag" /> object representing all tags
|
|
/// stored in the current instance.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// <para>This property provides generic and general access
|
|
/// to the most common tagging features of a file. To access
|
|
/// or add a specific type of tag in the file, use <see
|
|
/// cref="GetTag(TagLib.TagTypes,bool)" />.</para>
|
|
/// </remarks>
|
|
public abstract Tag Tag { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the media properties of the file represented by the
|
|
/// current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="TagLib.Properties" /> object containing the
|
|
/// media properties of the file represented by the current
|
|
/// instance.
|
|
/// </value>
|
|
public abstract Properties Properties { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the tag types contained in the physical file
|
|
/// represented by the current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A bitwise combined <see cref="TagLib.TagTypes" /> value
|
|
/// containing the tag types stored in the physical file as
|
|
/// it was read or last saved.
|
|
/// </value>
|
|
public TagTypes TagTypesOnDisk { get; protected set; } = TagTypes.None;
|
|
|
|
/// <summary>
|
|
/// Gets the tag types contained in the current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A bitwise combined <see cref="TagLib.TagTypes" /> value
|
|
/// containing the tag types stored in the current instance.
|
|
/// </value>
|
|
public TagTypes TagTypes => Tag?.TagTypes ?? TagTypes.None;
|
|
|
|
/// <summary>
|
|
/// Gets the name of the file as stored in its file
|
|
/// abstraction.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="string" /> object containing the name of the
|
|
/// file as stored in the <see cref="IFileAbstraction" />
|
|
/// object used to create it or the path if created with a
|
|
/// local path.
|
|
/// </value>
|
|
public string Name => file_abstraction.Name;
|
|
|
|
/// <summary>
|
|
/// Gets the mime-type of the file as determined by <see
|
|
/// cref="Create(IFileAbstraction,string,ReadStyle)" /> if
|
|
/// that method was used to create the current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="string" /> object containing the mime-type
|
|
/// used to create the file or <see langword="null" /> if <see
|
|
/// cref="Create(IFileAbstraction,string,ReadStyle)" /> was
|
|
/// not used to create the current instance.
|
|
/// </value>
|
|
public string MimeType { get; internal set; }
|
|
|
|
/// <summary>
|
|
/// Gets the seek position in the internal stream used by the
|
|
/// current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="long" /> value representing the seek
|
|
/// position, or 0 if the file is not open for reading.
|
|
/// </value>
|
|
public long Tell => (Mode == AccessMode.Closed) ? 0 : file_stream.Position;
|
|
|
|
/// <summary>
|
|
/// Gets the length of the file represented by the current
|
|
/// instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="long" /> value representing the size of the
|
|
/// file, or 0 if the file is not open for reading.
|
|
/// </value>
|
|
public long Length => (Mode == AccessMode.Closed) ? 0 : file_stream.Length;
|
|
|
|
/// <summary>
|
|
/// Gets the position at which the invariant portion of the
|
|
/// current instance begins.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="long" /> value representing the seek
|
|
/// position at which the file's invariant (media) data
|
|
/// section begins. If the value could not be determined,
|
|
/// <c>-1</c> is returned.
|
|
/// </value>
|
|
public long InvariantStartPosition { get; protected set; } = -1;
|
|
|
|
/// <summary>
|
|
/// Gets the position at which the invariant portion of the
|
|
/// current instance ends.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="long" /> value representing the seek
|
|
/// position at which the file's invariant (media) data
|
|
/// section ends. If the value could not be determined,
|
|
/// <c>-1</c> is returned.
|
|
/// </value>
|
|
public long InvariantEndPosition { get; protected set; } = -1;
|
|
|
|
/// <summary>
|
|
/// Gets and sets the file access mode in use by the current
|
|
/// instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="AccessMode" /> value describing the features
|
|
/// of stream currently in use by the current instance.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// Changing the value will cause the stream currently in use
|
|
/// to be closed, except when a change is made from <see
|
|
/// cref="AccessMode.Write" /> to <see cref="AccessMode.Read"
|
|
/// /> which has no effect.
|
|
/// </remarks>
|
|
public AccessMode Mode {
|
|
get {
|
|
if (file_stream == null)
|
|
return AccessMode.Closed;
|
|
|
|
if (file_stream.CanWrite)
|
|
return AccessMode.Write;
|
|
|
|
return AccessMode.Read;
|
|
}
|
|
set {
|
|
if (Mode == value || (Mode == AccessMode.Write && value == AccessMode.Read))
|
|
return;
|
|
|
|
if (file_stream != null)
|
|
file_abstraction.CloseStream (file_stream);
|
|
|
|
file_stream = null;
|
|
|
|
if (value == AccessMode.Read)
|
|
file_stream = file_abstraction.ReadStream;
|
|
else if (value == AccessMode.Write)
|
|
file_stream = file_abstraction.WriteStream;
|
|
|
|
Mode = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the <see cref="IFileAbstraction"/> representing the file.
|
|
/// </summary>
|
|
public IFileAbstraction FileAbstraction => file_abstraction;
|
|
|
|
/// <summary>
|
|
/// Indicates if tags can be written back to the current file or not
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="bool" /> which is true if tags can be written to the
|
|
/// current file, otherwise false.
|
|
/// </value>
|
|
public virtual bool Writeable => !PossiblyCorrupt;
|
|
|
|
/// <summary>
|
|
/// Indicates whether or not this file may be corrupt.
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if possibly corrupt; otherwise, <c>false</c>.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// Files with unknown corruptions should not be written.
|
|
/// </remarks>
|
|
public bool PossiblyCorrupt => corruption_reasons != null;
|
|
|
|
/// <summary>
|
|
/// The reasons for which this file is marked as corrupt.
|
|
/// </summary>
|
|
public IEnumerable<string> CorruptionReasons => corruption_reasons;
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Mark the file as corrupt.
|
|
/// </summary>
|
|
/// <param name="reason">
|
|
/// The reason why this file is considered to be corrupt.
|
|
/// </param>
|
|
internal void MarkAsCorrupt (string reason)
|
|
{
|
|
if (corruption_reasons == null)
|
|
corruption_reasons = new List<string> ();
|
|
corruption_reasons.Add (reason);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dispose the current file. Equivalent to setting the
|
|
/// mode to closed
|
|
/// </summary>
|
|
public void Dispose ()
|
|
{
|
|
Mode = AccessMode.Closed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the changes made in the current instance to the
|
|
/// file it represents.
|
|
/// </summary>
|
|
public abstract void Save ();
|
|
|
|
/// <summary>
|
|
/// Removes a set of tag types from the current instance.
|
|
/// </summary>
|
|
/// <param name="types">
|
|
/// A bitwise combined <see cref="TagLib.TagTypes" /> value
|
|
/// containing tag types to be removed from the file.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// In order to remove all tags from a file, pass <see
|
|
/// cref="TagTypes.AllTags" /> as <paramref name="types" />.
|
|
/// </remarks>
|
|
public abstract void RemoveTags (TagTypes types);
|
|
|
|
/// <summary>
|
|
/// Gets a tag of a specified type from the current instance,
|
|
/// optionally creating a new tag if possible.
|
|
/// </summary>
|
|
/// <param name="type">
|
|
/// A <see cref="TagLib.TagTypes" /> value indicating the
|
|
/// type of tag to read.
|
|
/// </param>
|
|
/// <param name="create">
|
|
/// A <see cref="bool" /> value specifying whether or not to
|
|
/// try and create the tag if one is not found.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="Tag" /> object containing the tag that was
|
|
/// found in or added to the current instance. If no
|
|
/// matching tag was found and none was created, <see
|
|
/// langword="null" /> is returned.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <para>Passing <see langword="true" /> to <paramref
|
|
/// name="create" /> does not guarantee the tag will be
|
|
/// created. For example, trying to create an ID3v2 tag on an
|
|
/// OGG Vorbis file will always fail.</para>
|
|
/// <para>It is safe to assume that if <see langword="null"
|
|
/// /> is not returned, the returned tag can be cast to the
|
|
/// appropriate type.</para>
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <para>The following example sets the mood of a file to
|
|
/// several tag types.</para>
|
|
/// <code lang="C#">string [] SetMoods (TagLib.File file, params string[] moods)
|
|
///{
|
|
/// TagLib.Id3v2.Tag id3 = file.GetTag (TagLib.TagTypes.Id3v2, true);
|
|
/// if (id3 != null)
|
|
/// id3.SetTextFrame ("TMOO", moods);
|
|
///
|
|
/// TagLib.Asf.Tag asf = file.GetTag (TagLib.TagTypes.Asf, true);
|
|
/// if (asf != null)
|
|
/// asf.SetDescriptorStrings (moods, "WM/Mood", "Mood");
|
|
///
|
|
/// TagLib.Ape.Tag ape = file.GetTag (TagLib.TagTypes.Ape);
|
|
/// if (ape != null)
|
|
/// ape.SetValue ("MOOD", moods);
|
|
///
|
|
/// // Whatever tag types you want...
|
|
///}</code>
|
|
/// </example>
|
|
public abstract Tag GetTag (TagTypes type, bool create);
|
|
|
|
/// <summary>
|
|
/// Gets a tag of a specified type from the current instance.
|
|
/// </summary>
|
|
/// <param name="type">
|
|
/// A <see cref="TagLib.TagTypes" /> value indicating the
|
|
/// type of tag to read.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="Tag" /> object containing the tag that was
|
|
/// found in the current instance. If no matching tag
|
|
/// was found, <see langword="null" /> is returned.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <para>This class merely accesses the tag if it exists.
|
|
/// <see cref="GetTag(TagTypes,bool)" /> provides the option
|
|
/// of adding the tag to the current instance if it does not
|
|
/// exist.</para>
|
|
/// <para>It is safe to assume that if <see langword="null"
|
|
/// /> is not returned, the returned tag can be cast to the
|
|
/// appropriate type.</para>
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <para>The following example reads the mood of a file from
|
|
/// several tag types.</para>
|
|
/// <code lang="C#">static string [] GetMoods (TagLib.File file)
|
|
///{
|
|
/// TagLib.Id3v2.Tag id3 = file.GetTag (TagLib.TagTypes.Id3v2);
|
|
/// if (id3 != null) {
|
|
/// TextIdentificationFrame f = TextIdentificationFrame.Get (this, "TMOO");
|
|
/// if (f != null)
|
|
/// return f.FieldList.ToArray ();
|
|
/// }
|
|
///
|
|
/// TagLib.Asf.Tag asf = file.GetTag (TagLib.TagTypes.Asf);
|
|
/// if (asf != null) {
|
|
/// string [] value = asf.GetDescriptorStrings ("WM/Mood", "Mood");
|
|
/// if (value.Length > 0)
|
|
/// return value;
|
|
/// }
|
|
///
|
|
/// TagLib.Ape.Tag ape = file.GetTag (TagLib.TagTypes.Ape);
|
|
/// if (ape != null) {
|
|
/// Item item = ape.GetItem ("MOOD");
|
|
/// if (item != null)
|
|
/// return item.ToStringArray ();
|
|
/// }
|
|
///
|
|
/// // Whatever tag types you want...
|
|
///
|
|
/// return new string [] {};
|
|
///}</code>
|
|
/// </example>
|
|
public Tag GetTag (TagTypes type)
|
|
{
|
|
return GetTag (type, false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a specified number of bytes at the current seek
|
|
/// position from the current instance.
|
|
/// </summary>
|
|
/// <param name="length">
|
|
/// A <see cref="int" /> value specifying the number of bytes
|
|
/// to read.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="ByteVector" /> object containing the data
|
|
/// read from the current instance.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// <para>This method reads the block of data at the current
|
|
/// seek position. To change the seek position, use <see
|
|
/// cref="Seek(long,SeekOrigin)" />.</para>
|
|
/// </remarks>
|
|
/// <exception cref="ArgumentException">
|
|
/// <paramref name="length" /> is less than zero.
|
|
/// </exception>
|
|
public ByteVector ReadBlock (int length)
|
|
{
|
|
if (length < 0)
|
|
throw new ArgumentException ("Length must be non-negative", nameof (length));
|
|
|
|
if (length == 0)
|
|
return new ByteVector ();
|
|
|
|
Mode = AccessMode.Read;
|
|
|
|
byte[] buffer = new byte[length];
|
|
|
|
int count = 0, read = 0, needed = length;
|
|
|
|
do {
|
|
count = file_stream.Read (buffer, read, needed);
|
|
|
|
read += count;
|
|
needed -= count;
|
|
} while (needed > 0 && count != 0);
|
|
|
|
return new ByteVector (buffer, read);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a block of data to the file represented by the
|
|
/// current instance at the current seek position.
|
|
/// </summary>
|
|
/// <param name="data">
|
|
/// A <see cref="ByteVector" /> object containing data to be
|
|
/// written to the current instance.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This will overwrite any existing data at the seek
|
|
/// position and append new data to the file if writing past
|
|
/// the current end.
|
|
/// </remarks>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="data" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public void WriteBlock (ByteVector data)
|
|
{
|
|
if (data == null)
|
|
throw new ArgumentNullException (nameof (data));
|
|
|
|
Mode = AccessMode.Write;
|
|
|
|
file_stream.Write (data.Data, 0, data.Count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches forwards through a file for a specified
|
|
/// pattern, starting at a specified offset.
|
|
/// </summary>
|
|
/// <param name="pattern">
|
|
/// A <see cref="ByteVector" /> object containing a pattern
|
|
/// to search for in the current instance.
|
|
/// </param>
|
|
/// <param name="startPosition">
|
|
/// A <see cref="int" /> value specifying at what
|
|
/// seek position to start searching.
|
|
/// </param>
|
|
/// <param name="before">
|
|
/// A <see cref="ByteVector" /> object specifying a pattern
|
|
/// that the searched for pattern must appear before. If this
|
|
/// pattern is found first, -1 is returned.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="long" /> value containing the index at which
|
|
/// the value was found. If not found, -1 is returned.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="pattern" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public long Find (ByteVector pattern, long startPosition, ByteVector before)
|
|
{
|
|
if (pattern == null)
|
|
throw new ArgumentNullException (nameof (pattern));
|
|
|
|
Mode = AccessMode.Read;
|
|
|
|
if (pattern.Count > buffer_size)
|
|
return -1;
|
|
|
|
// The position in the file that the current buffer
|
|
// starts at.
|
|
|
|
long buffer_offset = startPosition;
|
|
long original_position = file_stream.Position;
|
|
|
|
try {
|
|
// Start the search at the offset.
|
|
file_stream.Position = startPosition;
|
|
for (var buffer = ReadBlock (buffer_size); buffer.Count > 0; buffer = ReadBlock (buffer_size)) {
|
|
var location = buffer.Find (pattern);
|
|
if (before != null) {
|
|
var beforeLocation = buffer.Find (before);
|
|
if (beforeLocation < location)
|
|
return -1;
|
|
}
|
|
|
|
if (location >= 0)
|
|
return buffer_offset + location;
|
|
|
|
// Ensure that we always rewind the stream a little so we never have a partial
|
|
// match where our data exists between the end of read A and the start of read B.
|
|
buffer_offset += buffer_size - pattern.Count;
|
|
if (before != null && before.Count > pattern.Count)
|
|
buffer_offset -= before.Count - pattern.Count;
|
|
file_stream.Position = buffer_offset;
|
|
}
|
|
|
|
return -1;
|
|
} finally {
|
|
file_stream.Position = original_position;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches forwards through a file for a specified
|
|
/// pattern, starting at a specified offset.
|
|
/// </summary>
|
|
/// <param name="pattern">
|
|
/// A <see cref="ByteVector" /> object containing a pattern
|
|
/// to search for in the current instance.
|
|
/// </param>
|
|
/// <param name="startPosition">
|
|
/// A <see cref="int" /> value specifying at what
|
|
/// seek position to start searching.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="long" /> value containing the index at which
|
|
/// the value was found. If not found, -1 is returned.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="pattern" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public long Find (ByteVector pattern, long startPosition)
|
|
{
|
|
return Find (pattern, startPosition, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches forwards through a file for a specified
|
|
/// pattern, starting at the beginning of the file.
|
|
/// </summary>
|
|
/// <param name="pattern">
|
|
/// A <see cref="ByteVector" /> object containing a pattern
|
|
/// to search for in the current instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="long" /> value containing the index at which
|
|
/// the value was found. If not found, -1 is returned.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="pattern" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public long Find (ByteVector pattern)
|
|
{
|
|
return Find (pattern, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches backwards through a file for a specified
|
|
/// pattern, starting at a specified offset.
|
|
/// </summary>
|
|
/// <param name="pattern">
|
|
/// A <see cref="ByteVector" /> object containing a pattern
|
|
/// to search for in the current instance.
|
|
/// </param>
|
|
/// <param name="startPosition">
|
|
/// A <see cref="int" /> value specifying at what
|
|
/// seek position to start searching.
|
|
/// </param>
|
|
/// <param name="after">
|
|
/// A <see cref="ByteVector" /> object specifying a pattern
|
|
/// that the searched for pattern must appear after. If this
|
|
/// pattern is found first, -1 is returned.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="long" /> value containing the index at which
|
|
/// the value was found. If not found, -1 is returned.
|
|
/// </returns>
|
|
/// <remarks>
|
|
/// Searching for <paramref name="after" /> is not yet
|
|
/// implemented.
|
|
/// </remarks>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="pattern" /> is <see langword="null" />.
|
|
/// </exception>
|
|
long RFind (ByteVector pattern, long startPosition, ByteVector after)
|
|
{
|
|
if (pattern == null)
|
|
throw new ArgumentNullException (nameof (pattern));
|
|
|
|
Mode = AccessMode.Read;
|
|
|
|
if (pattern.Count > buffer_size)
|
|
return -1;
|
|
|
|
// The position in the file that the current buffer
|
|
// starts at.
|
|
|
|
ByteVector buffer;
|
|
|
|
// These variables are used to keep track of a partial
|
|
// match that happens at the end of a buffer.
|
|
|
|
/*
|
|
int previous_partial_match = -1;
|
|
int after_previous_partial_match = -1;
|
|
*/
|
|
|
|
// Save the location of the current read pointer. We
|
|
// will restore the position using Seek() before all
|
|
// returns.
|
|
|
|
long original_position = file_stream.Position;
|
|
|
|
// Start the search at the offset.
|
|
|
|
long buffer_offset = Length - startPosition;
|
|
int read_size = buffer_size;
|
|
|
|
read_size = (int)Math.Min (buffer_offset, buffer_size);
|
|
buffer_offset -= read_size;
|
|
file_stream.Position = buffer_offset;
|
|
|
|
// See the notes in find() for an explanation of this
|
|
// algorithm.
|
|
|
|
for (buffer = ReadBlock (read_size); buffer.Count > 0; buffer = ReadBlock (read_size)) {
|
|
|
|
// TODO: (1) previous partial match
|
|
|
|
// (2) pattern contained in current buffer
|
|
|
|
long location = buffer.RFind (pattern);
|
|
if (location >= 0) {
|
|
file_stream.Position = original_position;
|
|
return buffer_offset + location;
|
|
}
|
|
|
|
if (after != null && buffer.RFind (after) >= 0) {
|
|
file_stream.Position = original_position;
|
|
return -1;
|
|
}
|
|
|
|
read_size = (int)Math.Min (buffer_offset, buffer_size);
|
|
buffer_offset -= read_size;
|
|
if (read_size + pattern.Count > buffer_size)
|
|
buffer_offset += pattern.Count;
|
|
|
|
file_stream.Position = buffer_offset;
|
|
}
|
|
|
|
// Since we hit the end of the file, reset the status
|
|
// before continuing.
|
|
|
|
file_stream.Position = original_position;
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches backwards through a file for a specified
|
|
/// pattern, starting at a specified offset.
|
|
/// </summary>
|
|
/// <param name="pattern">
|
|
/// A <see cref="ByteVector" /> object containing a pattern
|
|
/// to search for in the current instance.
|
|
/// </param>
|
|
/// <param name="startPosition">
|
|
/// A <see cref="int" /> value specifying at what
|
|
/// seek position to start searching.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="long" /> value containing the index at which
|
|
/// the value was found. If not found, -1 is returned.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="pattern" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public long RFind (ByteVector pattern, long startPosition)
|
|
{
|
|
return RFind (pattern, startPosition, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Searches backwards through a file for a specified
|
|
/// pattern, starting at the end of the file.
|
|
/// </summary>
|
|
/// <param name="pattern">
|
|
/// A <see cref="ByteVector" /> object containing a pattern
|
|
/// to search for in the current instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A <see cref="long" /> value containing the index at which
|
|
/// the value was found. If not found, -1 is returned.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="pattern" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public long RFind (ByteVector pattern)
|
|
{
|
|
return RFind (pattern, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a specifed block of data into the file repesented
|
|
/// by the current instance at a specified location,
|
|
/// replacing a specified number of bytes.
|
|
/// </summary>
|
|
/// <param name="data">
|
|
/// A <see cref="ByteVector" /> object containing the data to
|
|
/// insert into the file.
|
|
/// </param>
|
|
/// <param name="start">
|
|
/// A <see cref="long" /> value specifying at which point to
|
|
/// insert the data.
|
|
/// </param>
|
|
/// <param name="replace">
|
|
/// A <see cref="long" /> value specifying the number of
|
|
/// bytes to replace. Typically this is the original size of
|
|
/// the data block so that a new block will replace the old
|
|
/// one.
|
|
/// </param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="data" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public void Insert (ByteVector data, long start, long replace)
|
|
{
|
|
if (data == null)
|
|
throw new ArgumentNullException (nameof (data));
|
|
|
|
Insert (data, data.Count, start, replace);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Inserts a specified block of data into the file repesented
|
|
/// by the current instance at a specified location.
|
|
/// </summary>
|
|
/// <param name="data">
|
|
/// A <see cref="ByteVector" /> object containing the data to
|
|
/// insert into the file.
|
|
/// </param>
|
|
/// <param name="start">
|
|
/// A <see cref="long" /> value specifying at which point to
|
|
/// insert the data.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method inserts a new block of data into the file. To
|
|
/// replace an existing block, ie. replacing an existing
|
|
/// tag with a new one of different size, use <see
|
|
/// cref="Insert(ByteVector,long,long)" />.
|
|
/// </remarks>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="data" /> is <see langword="null" />.
|
|
/// </exception>
|
|
public void Insert (ByteVector data, long start)
|
|
{
|
|
Insert (data, start, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a specified block-size into the file repesented
|
|
/// by the current instance at a specified location. Former
|
|
/// data at this location is not overwriten and may then
|
|
/// contain random content.
|
|
/// </summary>
|
|
/// <param name="size">
|
|
/// A <see cref="long" /> value specifying the size in bytes
|
|
/// of the block to be inserted (reserved).
|
|
/// </param>
|
|
/// <param name="start">
|
|
/// A <see cref="long" /> value specifying at which point to
|
|
/// insert the data.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method is usefull to reserve some space in the file.
|
|
/// To insert or replace defined data blocks, use <see
|
|
/// cref="Insert(ByteVector,long)" /> or
|
|
/// <see cref="Insert(ByteVector,long,long)"/>
|
|
/// </remarks>
|
|
public void Insert (long size, long start)
|
|
{
|
|
Insert (null, size, start, 0);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Removes a specified block of data from the file
|
|
/// represented by the current instance.
|
|
/// </summary>
|
|
/// <param name="start">
|
|
/// A <see cref="long" /> value specifying at which point to
|
|
/// remove data.
|
|
/// </param>
|
|
/// <param name="length">
|
|
/// A <see cref="long" /> value specifying the number of
|
|
/// bytes to remove.
|
|
/// </param>
|
|
public void RemoveBlock (long start, long length)
|
|
{
|
|
if (length <= 0)
|
|
return;
|
|
|
|
Mode = AccessMode.Write;
|
|
|
|
int buffer_length = buffer_size;
|
|
|
|
long read_position = start + length;
|
|
long write_position = start;
|
|
|
|
ByteVector buffer = (byte)1;
|
|
|
|
while (buffer.Count != 0) {
|
|
file_stream.Position = read_position;
|
|
buffer = ReadBlock (buffer_length);
|
|
read_position += buffer.Count;
|
|
|
|
file_stream.Position = write_position;
|
|
WriteBlock (buffer);
|
|
write_position += buffer.Count;
|
|
}
|
|
|
|
Truncate (write_position);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seeks the read/write pointer to a specified offset in the
|
|
/// current instance, relative to a specified origin.
|
|
/// </summary>
|
|
/// <param name="offset">
|
|
/// A <see cref="long" /> value indicating the byte offset to
|
|
/// seek to.
|
|
/// </param>
|
|
/// <param name="origin">
|
|
/// A <see cref="SeekOrigin" /> value specifying an
|
|
/// origin to seek from.
|
|
/// </param>
|
|
public void Seek (long offset, SeekOrigin origin)
|
|
{
|
|
if (Mode != AccessMode.Closed)
|
|
file_stream.Seek (offset, origin);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seeks the read/write pointer to a specified offset in the
|
|
/// current instance, relative to the beginning of the file.
|
|
/// </summary>
|
|
/// <param name="offset">
|
|
/// A <see cref="long" /> value indicating the byte offset to
|
|
/// seek to.
|
|
/// </param>
|
|
public void Seek (long offset)
|
|
{
|
|
Seek (offset, SeekOrigin.Begin);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Public Static Methods
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a <see cref="File" /> subclass
|
|
/// for a specified path, guessing the mime-type from the
|
|
/// file's extension and using the average read style.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// A <see cref="string" /> object specifying the file to
|
|
/// read from and write to.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A new instance of <see cref="File" /> as read from the
|
|
/// specified path.
|
|
/// </returns>
|
|
/// <exception cref="CorruptFileException">
|
|
/// The file could not be read due to corruption.
|
|
/// </exception>
|
|
/// <exception cref="UnsupportedFormatException">
|
|
/// The file could not be read because the mime-type could
|
|
/// not be resolved or the library does not support an
|
|
/// internal feature of the file crucial to its reading.
|
|
/// </exception>
|
|
public static File Create (string path)
|
|
{
|
|
return Create (path, null, ReadStyle.Average);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a <see cref="File" /> subclass
|
|
/// for a specified file abstraction, guessing the mime-type
|
|
/// from the file's extension and using the average read
|
|
/// style.
|
|
/// </summary>
|
|
/// <param name="abstraction">
|
|
/// A <see cref="IFileAbstraction" /> object to use when
|
|
/// reading to and writing from the current instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A new instance of <see cref="File" /> as read from the
|
|
/// specified abstraction.
|
|
/// </returns>
|
|
/// <exception cref="CorruptFileException">
|
|
/// The file could not be read due to corruption.
|
|
/// </exception>
|
|
/// <exception cref="UnsupportedFormatException">
|
|
/// The file could not be read because the mime-type could
|
|
/// not be resolved or the library does not support an
|
|
/// internal feature of the file crucial to its reading.
|
|
/// </exception>
|
|
public static File Create (IFileAbstraction abstraction)
|
|
{
|
|
return Create (abstraction, null, ReadStyle.Average);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a <see cref="File" /> subclass
|
|
/// for a specified path and read style, guessing the
|
|
/// mime-type from the file's extension.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// A <see cref="string" /> object specifying the file to
|
|
/// read from and write to.
|
|
/// </param>
|
|
/// <param name="propertiesStyle">
|
|
/// A <see cref="ReadStyle" /> value specifying the level of
|
|
/// detail to use when reading the media information from the
|
|
/// new instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A new instance of <see cref="File" /> as read from the
|
|
/// specified path.
|
|
/// </returns>
|
|
/// <exception cref="CorruptFileException">
|
|
/// The file could not be read due to corruption.
|
|
/// </exception>
|
|
/// <exception cref="UnsupportedFormatException">
|
|
/// The file could not be read because the mime-type could
|
|
/// not be resolved or the library does not support an
|
|
/// internal feature of the file crucial to its reading.
|
|
/// </exception>
|
|
public static File Create (string path, ReadStyle propertiesStyle)
|
|
{
|
|
return Create (path, null, propertiesStyle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a <see cref="File" /> subclass
|
|
/// for a specified file abstraction and read style, guessing
|
|
/// the mime-type from the file's extension.
|
|
/// </summary>
|
|
/// <param name="abstraction">
|
|
/// A <see cref="IFileAbstraction" /> object to use when
|
|
/// reading to and writing from the current instance.
|
|
/// </param>
|
|
/// <param name="propertiesStyle">
|
|
/// A <see cref="ReadStyle" /> value specifying the level of
|
|
/// detail to use when reading the media information from the
|
|
/// new instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A new instance of <see cref="File" /> as read from the
|
|
/// specified abstraction.
|
|
/// </returns>
|
|
/// <exception cref="CorruptFileException">
|
|
/// The file could not be read due to corruption.
|
|
/// </exception>
|
|
/// <exception cref="UnsupportedFormatException">
|
|
/// The file could not be read because the mime-type could
|
|
/// not be resolved or the library does not support an
|
|
/// internal feature of the file crucial to its reading.
|
|
/// </exception>
|
|
public static File Create (IFileAbstraction abstraction, ReadStyle propertiesStyle)
|
|
{
|
|
return Create (abstraction, null, propertiesStyle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a <see cref="File" /> subclass
|
|
/// for a specified path, mime-type, and read style.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// A <see cref="string" /> object specifying the file to
|
|
/// read from and write to.
|
|
/// </param>
|
|
/// <param name="mimetype">
|
|
/// A <see cref="string" /> object containing the mime-type
|
|
/// to use when selecting the appropriate class to use, or
|
|
/// <see langword="null" /> if the extension in <paramref
|
|
/// name="path" /> is to be used.
|
|
/// </param>
|
|
/// <param name="propertiesStyle">
|
|
/// A <see cref="ReadStyle" /> value specifying the level of
|
|
/// detail to use when reading the media information from the
|
|
/// new instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A new instance of <see cref="File" /> as read from the
|
|
/// specified path.
|
|
/// </returns>
|
|
/// <exception cref="CorruptFileException">
|
|
/// The file could not be read due to corruption.
|
|
/// </exception>
|
|
/// <exception cref="UnsupportedFormatException">
|
|
/// The file could not be read because the mime-type could
|
|
/// not be resolved or the library does not support an
|
|
/// internal feature of the file crucial to its reading.
|
|
/// </exception>
|
|
public static File Create (string path, string mimetype, ReadStyle propertiesStyle)
|
|
{
|
|
return Create (new LocalFileAbstraction (path), mimetype, propertiesStyle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new instance of a <see cref="File" /> subclass
|
|
/// for a specified file abstraction, mime-type, and read
|
|
/// style.
|
|
/// </summary>
|
|
/// <param name="abstraction">
|
|
/// A <see cref="IFileAbstraction" /> object to use when
|
|
/// reading to and writing from the current instance.
|
|
/// </param>
|
|
/// <param name="mimetype">
|
|
/// A <see cref="string" /> object containing the mime-type
|
|
/// to use when selecting the appropriate class to use, or
|
|
/// <see langword="null" /> if the extension in <paramref
|
|
/// name="abstraction" /> is to be used.
|
|
/// </param>
|
|
/// <param name="propertiesStyle">
|
|
/// A <see cref="ReadStyle" /> value specifying the level of
|
|
/// detail to use when reading the media information from the
|
|
/// new instance.
|
|
/// </param>
|
|
/// <returns>
|
|
/// A new instance of <see cref="File" /> as read from the
|
|
/// specified abstraction.
|
|
/// </returns>
|
|
/// <exception cref="CorruptFileException">
|
|
/// The file could not be read due to corruption.
|
|
/// </exception>
|
|
/// <exception cref="UnsupportedFormatException">
|
|
/// The file could not be read because the mime-type could
|
|
/// not be resolved or the library does not support an
|
|
/// internal feature of the file crucial to its reading.
|
|
/// </exception>
|
|
public static File Create (IFileAbstraction abstraction, string mimetype, ReadStyle propertiesStyle)
|
|
{
|
|
if (mimetype == null) {
|
|
string ext = string.Empty;
|
|
|
|
int index = abstraction.Name.LastIndexOf (".") + 1;
|
|
|
|
if (index >= 1 && index < abstraction.Name.Length)
|
|
ext = abstraction.Name.Substring (index, abstraction.Name.Length - index);
|
|
|
|
mimetype = "taglib/" + ext.ToLower (CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
foreach (var resolver in file_type_resolvers) {
|
|
var file = resolver (abstraction, mimetype, propertiesStyle);
|
|
|
|
if (file != null)
|
|
return file;
|
|
}
|
|
|
|
if (!FileTypes.AvailableTypes.ContainsKey (mimetype))
|
|
throw new UnsupportedFormatException (
|
|
string.Format (CultureInfo.InvariantCulture, "{0} ({1})", abstraction.Name, mimetype));
|
|
|
|
var file_type = FileTypes.AvailableTypes[mimetype];
|
|
|
|
try {
|
|
var file = (File)Activator.CreateInstance (file_type, new object[] { abstraction, propertiesStyle });
|
|
|
|
file.MimeType = mimetype;
|
|
return file;
|
|
} catch (System.Reflection.TargetInvocationException e) {
|
|
PrepareExceptionForRethrow (e.InnerException);
|
|
throw e.InnerException;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a <see cref="FileTypeResolver" /> to the <see
|
|
/// cref="File" /> class. The one added last gets run first.
|
|
/// </summary>
|
|
/// <param name="resolver">
|
|
/// A <see cref="FileTypeResolver" /> delegate to add to the
|
|
/// file type recognition stack.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// A <see cref="FileTypeResolver" /> adds support for
|
|
/// recognizing a file type outside of the standard mime-type
|
|
/// methods.
|
|
/// </remarks>
|
|
public static void AddFileTypeResolver (FileTypeResolver resolver)
|
|
{
|
|
if (resolver != null)
|
|
file_type_resolvers.Insert (0, resolver);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Private/Protected Methods
|
|
|
|
/// <summary>
|
|
/// Prepare to Save the file. Thismust be called at the begining
|
|
/// of every File.Save() method.
|
|
/// </summary>
|
|
protected void PreSave ()
|
|
{
|
|
// Check validity
|
|
|
|
if (!Writeable)
|
|
throw new InvalidOperationException ("File not writeable.");
|
|
|
|
if (PossiblyCorrupt)
|
|
throw new CorruptFileException ("Corrupted file cannot be saved.");
|
|
|
|
// All the Lazy objects must be loaded before opening the file
|
|
// in Write mode
|
|
if (Tag?.Pictures != null) {
|
|
foreach (var pic in Tag.Pictures) {
|
|
if (pic is ILazy lazy) {
|
|
lazy.Load ();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a specified block into the file repesented
|
|
/// by the current instance at a specified location.
|
|
/// </summary>
|
|
/// <param name="data">
|
|
/// A <see cref="ByteVector" /> object containing the data to
|
|
/// insert into the file. if null, no data is writen to the
|
|
/// file and the block is just inserted without overwriting the
|
|
/// former data at the given location.
|
|
/// </param>
|
|
/// <param name="size">
|
|
/// A <see cref="long" /> value specifying the size of the block
|
|
/// to be inserted.
|
|
/// </param>
|
|
/// <param name="start">
|
|
/// A <see cref="long" /> value specifying at which point to
|
|
/// insert the data.
|
|
/// </param>
|
|
/// <param name="replace">
|
|
/// A <see cref="long" /> value specifying the number of
|
|
/// bytes to replace. Typically this is the original size of
|
|
/// the data block so that a new block will replace the old
|
|
/// one.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// This method inserts a new block of data into the file. To
|
|
/// replace an existing block, ie. replacing an existing
|
|
/// tag with a new one of different size, use <see
|
|
/// cref="Insert(ByteVector,long,long)" />.
|
|
/// </remarks>
|
|
void Insert (ByteVector data, long size, long start, long replace)
|
|
{
|
|
Mode = AccessMode.Write;
|
|
|
|
if (size == replace) {
|
|
if (data != null) {
|
|
file_stream.Position = start;
|
|
WriteBlock (data);
|
|
}
|
|
return;
|
|
} else if (size < replace) {
|
|
if (data != null) {
|
|
file_stream.Position = start;
|
|
WriteBlock (data);
|
|
}
|
|
RemoveBlock (start + size, replace - size);
|
|
return;
|
|
}
|
|
|
|
// Woohoo! Faster (about 20%) than id3lib at last. I
|
|
// had to get hardcore and avoid TagLib's high level API
|
|
// for rendering just copying parts of the file that
|
|
// don't contain tag data.
|
|
//
|
|
// Now I'll explain the steps in this ugliness:
|
|
|
|
// First, make sure that we're working with a buffer
|
|
// that is longer or equal than the *difference* in the tag sizes,
|
|
// and that is a multiple of buffer_size.
|
|
// We want to avoid overwriting parts that aren't yet in
|
|
// memory, so this is necessary.
|
|
|
|
int buffer_length = (int)(size - replace);
|
|
int modulo = buffer_length % buffer_size;
|
|
if (modulo != 0) buffer_length += buffer_size - modulo;
|
|
|
|
|
|
// Set where to start the reading and writing.
|
|
|
|
long read_position = start + replace;
|
|
long write_position = start;
|
|
|
|
byte[] buffer;
|
|
byte[] about_to_overwrite;
|
|
|
|
// This is basically a special case of the loop below.
|
|
// Here we're just doing the same steps as below, but
|
|
// since we aren't using the same buffer size -- instead
|
|
// we're using the tag size -- this has to be handled as
|
|
// a special case. We're also using File::writeBlock()
|
|
// just for the tag. That's a bit slower than using char
|
|
// *'s so, we're only doing it here.
|
|
|
|
file_stream.Position = read_position;
|
|
about_to_overwrite = ReadBlock (buffer_length).Data;
|
|
read_position += buffer_length;
|
|
|
|
if (data != null) {
|
|
file_stream.Position = write_position;
|
|
WriteBlock (data);
|
|
} else if (start + size > Length) {
|
|
file_stream.SetLength (start + size);
|
|
}
|
|
write_position += size;
|
|
|
|
buffer = new byte[about_to_overwrite.Length];
|
|
Array.Copy (about_to_overwrite, 0, buffer, 0,
|
|
about_to_overwrite.Length);
|
|
|
|
// Ok, here's the main loop. We want to loop until the
|
|
// read fails, which means that we hit the end of the
|
|
// file.
|
|
|
|
while (buffer_length != 0) {
|
|
// Seek to the current read position and read
|
|
// the data that we're about to overwrite.
|
|
// Appropriately increment the readPosition.
|
|
|
|
file_stream.Position = read_position;
|
|
int bytes_read = file_stream.Read (
|
|
about_to_overwrite, 0, buffer_length < about_to_overwrite.Length ?
|
|
buffer_length : about_to_overwrite.Length);
|
|
read_position += buffer_length;
|
|
|
|
// Seek to the write position and write our
|
|
// buffer. Increment the writePosition.
|
|
|
|
file_stream.Position = write_position;
|
|
file_stream.Write (buffer, 0, buffer_length < buffer.Length ? buffer_length : buffer.Length);
|
|
write_position += buffer_length;
|
|
|
|
// Make the current buffer the data that we read
|
|
// in the beginning.
|
|
|
|
Array.Copy (about_to_overwrite, 0, buffer, 0, bytes_read);
|
|
|
|
// Again, we need this for the last write. We
|
|
// don't want to write garbage at the end of our
|
|
// file, so we need to set the buffer size to
|
|
// the amount that we actually read.
|
|
|
|
buffer_length = bytes_read;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resized the current instance to a specified number of
|
|
/// bytes.
|
|
/// </summary>
|
|
/// <param name="length">
|
|
/// A <see cref="long" /> value specifying the number of
|
|
/// bytes to resize the file to.
|
|
/// </param>
|
|
protected void Truncate (long length)
|
|
{
|
|
var old_mode = Mode;
|
|
Mode = AccessMode.Write;
|
|
file_stream.SetLength (length);
|
|
Mode = old_mode;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Causes the original strack trace of the exception to be preserved when it is rethrown
|
|
/// </summary>
|
|
/// <param name="ex"></param>
|
|
static void PrepareExceptionForRethrow (Exception ex)
|
|
{
|
|
var ctx = new StreamingContext (StreamingContextStates.CrossAppDomain);
|
|
var mgr = new ObjectManager (null, ctx);
|
|
var si = new SerializationInfo (ex.GetType (), new FormatterConverter ());
|
|
|
|
ex.GetObjectData (si, ctx);
|
|
mgr.RegisterObject (ex, 1, si); // prepare for SetObjectData
|
|
mgr.DoFixups (); // ObjectManager calls SetObjectData
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Classes
|
|
|
|
/// <summary>
|
|
/// This class implements <see cref="IFileAbstraction" />
|
|
/// to provide support for accessing the local/standard file
|
|
/// system.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This class is used as the standard file abstraction
|
|
/// throughout the library.
|
|
/// </remarks>
|
|
public class LocalFileAbstraction : IFileAbstraction
|
|
{
|
|
/// <summary>
|
|
/// Contains the name used to open the file.
|
|
/// </summary>
|
|
readonly string name;
|
|
|
|
/// <summary>
|
|
/// Constructs and initializes a new instance of
|
|
/// <see cref="LocalFileAbstraction" /> for a
|
|
/// specified path in the local file system.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// A <see cref="string" /> object containing the
|
|
/// path of the file to use in the new instance.
|
|
/// </param>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// <paramref name="path" /> is <see langword="null"
|
|
/// />.
|
|
/// </exception>
|
|
public LocalFileAbstraction (string path)
|
|
{
|
|
if (path == null)
|
|
throw new ArgumentNullException (nameof (path));
|
|
|
|
name = path;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the path of the file represented by the
|
|
/// current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="string" /> object containing the
|
|
/// path of the file represented by the current
|
|
/// instance.
|
|
/// </value>
|
|
public string Name => name;
|
|
|
|
/// <summary>
|
|
/// Gets a new readable, seekable stream from the
|
|
/// file represented by the current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A new <see cref="Stream" /> to be used
|
|
/// when reading the file represented by the current
|
|
/// instance.
|
|
/// </value>
|
|
public Stream ReadStream => System.IO.File.Open (Name,
|
|
FileMode.Open,
|
|
FileAccess.Read,
|
|
FileShare.Read);
|
|
|
|
/// <summary>
|
|
/// Gets a new writable, seekable stream from the
|
|
/// file represented by the current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A new <see cref="Stream" /> to be used
|
|
/// when writing to the file represented by the
|
|
/// current instance.
|
|
/// </value>
|
|
public Stream WriteStream => System.IO.File.Open (Name,
|
|
FileMode.Open,
|
|
FileAccess.ReadWrite);
|
|
|
|
/// <summary>
|
|
/// Closes a stream created by the current instance.
|
|
/// </summary>
|
|
/// <param name="stream">
|
|
/// A <see cref="Stream" /> object
|
|
/// created by the current instance.
|
|
/// </param>
|
|
public void CloseStream (Stream stream)
|
|
{
|
|
if (stream == null)
|
|
throw new ArgumentNullException (nameof (stream));
|
|
|
|
stream.Close ();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
#region Interfaces
|
|
|
|
/// <summary>
|
|
/// This interface provides abstracted access to a file. It
|
|
/// premits access to non-standard file systems and data
|
|
/// retrieval methods.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>To use a custom abstraction, use <see
|
|
/// cref="Create(IFileAbstraction)" /> instead of <see
|
|
/// cref="Create(string)" /> when creating files.</para>
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <para>The following example uses Gnome VFS to open a file
|
|
/// and read its title.</para>
|
|
/// <code lang="C#">using TagLib;
|
|
///using Gnome.Vfs;
|
|
///
|
|
///public class ReadTitle
|
|
///{
|
|
/// public static void Main (string [] args)
|
|
/// {
|
|
/// if (args.Length != 1)
|
|
/// return;
|
|
///
|
|
/// Gnome.Vfs.Vfs.Initialize ();
|
|
///
|
|
/// try {
|
|
/// TagLib.File file = TagLib.File.Create (
|
|
/// new VfsFileAbstraction (args [0]));
|
|
/// System.Console.WriteLine (file.Tag.Title);
|
|
/// } finally {
|
|
/// Vfs.Shutdown()
|
|
/// }
|
|
/// }
|
|
///}
|
|
///
|
|
///public class VfsFileAbstraction : TagLib.File.IFileAbstraction
|
|
///{
|
|
/// private string name;
|
|
///
|
|
/// public VfsFileAbstraction (string file)
|
|
/// {
|
|
/// name = file;
|
|
/// }
|
|
///
|
|
/// public string Name {
|
|
/// get { return name; }
|
|
/// }
|
|
///
|
|
/// public System.IO.Stream ReadStream {
|
|
/// get { return new VfsStream(Name, System.IO.FileMode.Open); }
|
|
/// }
|
|
///
|
|
/// public System.IO.Stream WriteStream {
|
|
/// get { return new VfsStream(Name, System.IO.FileMode.Open); }
|
|
/// }
|
|
///
|
|
/// public void CloseStream (System.IO.Stream stream)
|
|
/// {
|
|
/// stream.Close ();
|
|
/// }
|
|
///}</code>
|
|
/// <code lang="Boo">import TagLib from "taglib-sharp.dll"
|
|
///import Gnome.Vfs from "gnome-vfs-sharp"
|
|
///
|
|
///class VfsFileAbstraction (TagLib.File.IFileAbstraction):
|
|
///
|
|
/// _name as string
|
|
///
|
|
/// def constructor(file as string):
|
|
/// _name = file
|
|
///
|
|
/// Name:
|
|
/// get:
|
|
/// return _name
|
|
///
|
|
/// ReadStream:
|
|
/// get:
|
|
/// return VfsStream(_name, FileMode.Open)
|
|
///
|
|
/// WriteStream:
|
|
/// get:
|
|
/// return VfsStream(_name, FileMode.Open)
|
|
///
|
|
///if len(argv) == 1:
|
|
/// Vfs.Initialize()
|
|
///
|
|
/// try:
|
|
/// file as TagLib.File = TagLib.File.Create (VfsFileAbstraction (argv[0]))
|
|
/// print file.Tag.Title
|
|
/// ensure:
|
|
/// Vfs.Shutdown()</code>
|
|
/// </example>
|
|
public interface IFileAbstraction
|
|
{
|
|
/// <summary>
|
|
/// Gets the name or identifier used by the
|
|
/// implementation.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="string" /> object containing the
|
|
/// name or identifier used by the implementation.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// This value would typically represent a path or
|
|
/// URL to be used when identifying the file in the
|
|
/// file system, but it could be any value
|
|
/// as appropriate for the implementation.
|
|
/// </remarks>
|
|
string Name { get; }
|
|
|
|
/// <summary>
|
|
/// Gets a readable, seekable stream for the file
|
|
/// referenced by the current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="System.IO.Stream" /> object to be
|
|
/// used when reading a file.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// This property is typically used when creating
|
|
/// constructing an instance of <see cref="File" />.
|
|
/// Upon completion of the constructor, <see
|
|
/// cref="CloseStream" /> will be called to close
|
|
/// the stream. If the stream is to be reused after
|
|
/// this point, <see cref="CloseStream" /> should be
|
|
/// implemented in a way to keep it open.
|
|
/// </remarks>
|
|
Stream ReadStream { get; }
|
|
|
|
/// <summary>
|
|
/// Gets a writable, seekable stream for the file
|
|
/// referenced by the current instance.
|
|
/// </summary>
|
|
/// <value>
|
|
/// A <see cref="Stream" /> object to be
|
|
/// used when writing to a file.
|
|
/// </value>
|
|
/// <remarks>
|
|
/// This property is typically used when saving a
|
|
/// file with <see cref="Save" />. Upon completion of
|
|
/// the method, <see cref="CloseStream" /> will be
|
|
/// called to close the stream. If the stream is to
|
|
/// be reused after this point, <see
|
|
/// cref="CloseStream" /> should be implemented in a
|
|
/// way to keep it open.
|
|
/// </remarks>
|
|
Stream WriteStream { get; }
|
|
|
|
/// <summary>
|
|
/// Closes a stream originating from the current
|
|
/// instance.
|
|
/// </summary>
|
|
/// <param name="stream">
|
|
/// A <see cref="Stream" /> object
|
|
/// originating from the current instance.
|
|
/// </param>
|
|
/// <remarks>
|
|
/// If the stream is to be used outside of the scope,
|
|
/// of TagLib#, this method should perform no action.
|
|
/// For example, a stream that was created outside of
|
|
/// the current instance, or a stream that will
|
|
/// subsequently be used to play the file.
|
|
/// </remarks>
|
|
void CloseStream (Stream stream);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|