virtual file system preliminary implementation.

This commit is contained in:
morkt 2015-08-31 10:48:27 +04:00
parent 47b3d6adf0
commit 9d1d320cd2
19 changed files with 352 additions and 350 deletions

View File

@ -49,16 +49,6 @@ namespace GARbro.GUI
/// </summary> /// </summary>
public string InitPath { get; private set; } public string InitPath { get; private set; }
/// <summary>
/// Path to the currently open archive, or empty string if none.
/// </summary>
public string CurrentPath { get; private set; }
/// <summary>
/// Archive file being browsed, or null.
/// </summary>
public ArcFile CurrentArchive { get; private set; }
void ApplicationStartup (object sender, StartupEventArgs e) void ApplicationStartup (object sender, StartupEventArgs e)
{ {
#if DEBUG #if DEBUG
@ -90,106 +80,11 @@ namespace GARbro.GUI
if (string.IsNullOrEmpty (InitPath)) if (string.IsNullOrEmpty (InitPath))
InitPath = Directory.GetCurrentDirectory(); InitPath = Directory.GetCurrentDirectory();
CurrentPath = "";
} }
void ApplicationExit (object sender, ExitEventArgs e) void ApplicationExit (object sender, ExitEventArgs e)
{ {
Settings.Default.Save(); Settings.Default.Save();
} }
public ICollection<Entry> GetDirectoryList (string path)
{
var info = new DirectoryInfo (path);
var list = new List<Entry>();
foreach (var subdir in info.EnumerateDirectories())
{
if (0 != (subdir.Attributes & (FileAttributes.Hidden | FileAttributes.System)))
continue;
list.Add (new SubDirEntry (subdir.Name));
}
foreach (var file in info.EnumerateFiles())
{
if (0 != (file.Attributes & (FileAttributes.System)))
continue;
var entry = FormatCatalog.Instance.Create<Entry> (file.Name);
entry.Size = (uint)Math.Min (file.Length, uint.MaxValue);
list.Add (entry);
}
return list;
}
public ArcFile GetArchive (string path)
{
if (path.Equals (CurrentPath, StringIgnoreCase))
return CurrentArchive;
FormatCatalog.Instance.LastError = null;
var arc = ArcFile.TryOpen (path);
if (null == arc)
{
if (null != FormatCatalog.Instance.LastError)
throw FormatCatalog.Instance.LastError;
throw new UnknownFormatException();
}
if (null != CurrentArchive)
CurrentArchive.Dispose();
CurrentPath = path;
CurrentArchive = arc;
return CurrentArchive;
}
public void ResetCache ()
{
if (null != CurrentArchive)
CurrentArchive.Dispose();
CurrentArchive = null;
CurrentPath = "";
}
// Update UI on demand.
private static DispatcherOperationCallback exitFrameCallback =
new DispatcherOperationCallback(ExitFrame);
/// <summary>
/// Processes all UI messages currently in the message queue.
/// </summary>
public static void DoEvents()
{
// Create new nested message pump.
DispatcherFrame nestedFrame = new DispatcherFrame();
// Dispatch a callback to the current message queue, when getting called,
// this callback will end the nested message loop.
// note that the priority of this callback should be lower than the that of UI event messages.
DispatcherOperation exitOperation = Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Background, exitFrameCallback, nestedFrame);
// pump the nested message loop, the nested message loop will
// immediately process the messages left inside the message queue.
Dispatcher.PushFrame(nestedFrame);
// If the "exitFrame" callback doesn't get finished, Abort it.
if (exitOperation.Status != DispatcherOperationStatus.Completed)
exitOperation.Abort();
}
static Object ExitFrame(Object state)
{
DispatcherFrame frame = state as DispatcherFrame;
// Exit the nested message loop.
frame.Continue = false;
return null;
}
}
public class UnknownFormatException : Exception
{
public UnknownFormatException () : base (guiStrings.MsgUnknownFormat) { }
public UnknownFormatException (string path)
: base (string.Format ("{1}: {0}", guiStrings.MsgUnknownFormat, path))
{ }
} }
} }

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.1.10.422")] [assembly: AssemblyVersion ("1.1.12.423")]
[assembly: AssemblyFileVersion ("1.1.10.422")] [assembly: AssemblyFileVersion ("1.1.12.423")]

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013 # Visual Studio 2013
VisualStudioVersion = 12.0.21005.1 VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GARbro.Console", "Console\GARbro.Console.csproj", "{B966F292-431A-4D8A-A1D3-1EB45048A1D2}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GARbro.Console", "Console\GARbro.Console.csproj", "{B966F292-431A-4D8A-A1D3-1EB45048A1D2}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
@ -17,9 +17,6 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameRes", "GameRes\GameRes.csproj", "{453C087F-E416-4AE9-8C03-D8760DA0574B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameRes", "GameRes\GameRes.csproj", "{453C087F-E416-4AE9-8C03-D8760DA0574B}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GARbro.GUI", "GARbro.GUI.csproj", "{2935BE57-C4E0-43E7-86DE-C1848C820B19}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GARbro.GUI", "GARbro.GUI.csproj", "{2935BE57-C4E0-43E7-86DE-C1848C820B19}"
ProjectSection(ProjectDependencies) = postProject
{A8865685-27CC-427B-AC38-E48D2AD05DF4} = {A8865685-27CC-427B-AC38-E48D2AD05DF4}
EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Image.Convert", "Image.Convert\Image.Convert.csproj", "{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Image.Convert", "Image.Convert\Image.Convert.csproj", "{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}"
ProjectSection(ProjectDependencies) = postProject ProjectSection(ProjectDependencies) = postProject
@ -32,26 +29,22 @@ Global
Release|Any CPU = Release|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B966F292-431A-4D8A-A1D3-1EB45048A1D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B966F292-431A-4D8A-A1D3-1EB45048A1D2}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{B966F292-431A-4D8A-A1D3-1EB45048A1D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B966F292-431A-4D8A-A1D3-1EB45048A1D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {B966F292-431A-4D8A-A1D3-1EB45048A1D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B966F292-431A-4D8A-A1D3-1EB45048A1D2}.Release|Any CPU.Build.0 = Release|Any CPU {A8865685-27CC-427B-AC38-E48D2AD05DF4}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{A8865685-27CC-427B-AC38-E48D2AD05DF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A8865685-27CC-427B-AC38-E48D2AD05DF4}.Debug|Any CPU.Build.0 = Release|Any CPU
{A8865685-27CC-427B-AC38-E48D2AD05DF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8865685-27CC-427B-AC38-E48D2AD05DF4}.Release|Any CPU.ActiveCfg = Release|Any CPU {A8865685-27CC-427B-AC38-E48D2AD05DF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8865685-27CC-427B-AC38-E48D2AD05DF4}.Release|Any CPU.Build.0 = Release|Any CPU {A8865685-27CC-427B-AC38-E48D2AD05DF4}.Release|Any CPU.Build.0 = Release|Any CPU
{453C087F-E416-4AE9-8C03-D8760DA0574B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {453C087F-E416-4AE9-8C03-D8760DA0574B}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{453C087F-E416-4AE9-8C03-D8760DA0574B}.Debug|Any CPU.Build.0 = Debug|Any CPU {453C087F-E416-4AE9-8C03-D8760DA0574B}.Debug|Any CPU.Build.0 = Release|Any CPU
{453C087F-E416-4AE9-8C03-D8760DA0574B}.Release|Any CPU.ActiveCfg = Release|Any CPU {453C087F-E416-4AE9-8C03-D8760DA0574B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{453C087F-E416-4AE9-8C03-D8760DA0574B}.Release|Any CPU.Build.0 = Release|Any CPU {453C087F-E416-4AE9-8C03-D8760DA0574B}.Release|Any CPU.Build.0 = Release|Any CPU
{2935BE57-C4E0-43E7-86DE-C1848C820B19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2935BE57-C4E0-43E7-86DE-C1848C820B19}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{2935BE57-C4E0-43E7-86DE-C1848C820B19}.Debug|Any CPU.Build.0 = Debug|Any CPU {2935BE57-C4E0-43E7-86DE-C1848C820B19}.Debug|Any CPU.Build.0 = Release|Any CPU
{2935BE57-C4E0-43E7-86DE-C1848C820B19}.Release|Any CPU.ActiveCfg = Release|Any CPU {2935BE57-C4E0-43E7-86DE-C1848C820B19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2935BE57-C4E0-43E7-86DE-C1848C820B19}.Release|Any CPU.Build.0 = Release|Any CPU {2935BE57-C4E0-43E7-86DE-C1848C820B19}.Release|Any CPU.Build.0 = Release|Any CPU
{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU {757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{757EB8B1-F62C-4690-AC3D-DAE4A5576B3E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -49,8 +49,6 @@ namespace GameRes
/// <summary>Archive contents.</summary> /// <summary>Archive contents.</summary>
public ICollection<Entry> Dir { get { return m_dir; } } public ICollection<Entry> Dir { get { return m_dir; } }
public event EventHandler<OverwriteEventArgs> OverwriteNotify;
public ArcFile (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir) public ArcFile (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir)
{ {
m_arc = arc; m_arc = arc;
@ -66,11 +64,11 @@ namespace GameRes
/// </returns> /// </returns>
public static ArcFile TryOpen (string filename) public static ArcFile TryOpen (string filename)
{ {
var info = new FileInfo (filename); var entry = VFS.FindFile (filename);
if (info.Length < 4) if (entry.Size < 4)
return null; return null;
var ext = new Lazy<string> (() => Path.GetExtension (filename).TrimStart ('.').ToLowerInvariant()); var ext = new Lazy<string> (() => Path.GetExtension (filename).TrimStart ('.').ToLowerInvariant());
var file = new ArcView (filename); var file = VFS.OpenView (entry);
try try
{ {
uint signature = file.View.ReadUInt32 (0); uint signature = file.View.ReadUInt32 (0);
@ -191,15 +189,6 @@ namespace GameRes
} }
} }
/// <summary>
/// Create file corresponding to <paramref name="entry"/> within current directory and open
/// it for writing.
/// </summary>
public Stream CreateFile (Entry entry)
{
return ArchiveFormat.CreateFile (entry.Name);
}
public IFileSystem CreateFileSystem () public IFileSystem CreateFileSystem ()
{ {
if (m_interface.IsHierarchic) if (m_interface.IsHierarchic)
@ -229,98 +218,4 @@ namespace GameRes
} }
#endregion #endregion
} }
public class OverwriteEventArgs : EventArgs
{
public string Filename { get; set; }
public bool Overwrite { get; set; }
}
public class AppendStream : System.IO.Stream
{
private Stream m_base;
private long m_start_pos;
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return true; } }
public override bool CanWrite { get { return true; } }
public override long Length { get { return m_base.Length - m_start_pos; } }
public override long Position
{
get { return m_base.Position - m_start_pos; }
set { m_base.Position = Math.Max (m_start_pos+value, m_start_pos); }
}
public AppendStream (System.IO.Stream file)
{
m_base = file;
m_start_pos = m_base.Seek (0, SeekOrigin.End);
}
public AppendStream (System.IO.Stream file, long offset)
{
m_base = file;
m_start_pos = m_base.Seek (offset, SeekOrigin.Begin);
}
public Stream BaseStream { get { return m_base; } }
public override void Flush()
{
m_base.Flush();
}
public override long Seek (long offset, SeekOrigin origin)
{
if (SeekOrigin.Begin == origin)
{
offset = Math.Max (offset + m_start_pos, m_start_pos);
}
long position = m_base.Seek (offset, origin);
if (position < m_start_pos)
{
m_base.Seek (m_start_pos, SeekOrigin.Begin);
position = m_start_pos;
}
return position - m_start_pos;
}
public override void SetLength (long length)
{
if (length < 0)
length = 0;
m_base.SetLength (length + m_start_pos);
}
public override int Read (byte[] buffer, int offset, int count)
{
return m_base.Read (buffer, offset, count);
}
public override int ReadByte ()
{
return m_base.ReadByte();
}
public override void Write (byte[] buffer, int offset, int count)
{
m_base.Write (buffer, offset, count);
}
public override void WriteByte (byte value)
{
m_base.WriteByte (value);
}
bool disposed = false;
protected override void Dispose (bool disposing)
{
if (!disposed)
{
m_base = null;
disposed = true;
base.Dispose (disposing);
}
}
}
} }

View File

@ -28,16 +28,28 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using GameRes.Strings;
namespace GameRes namespace GameRes
{ {
public interface IFileSystem : IDisposable public interface IFileSystem : IDisposable
{ {
/// <summary> /// <summary>
/// Open file for reading. /// Returns entry corresponding to the given filename within filesystem.
/// </summary>
/// <exception cref="FileNotFoundException">File is not found.</exception>
Entry FindFile (string filename);
/// <summary>
/// Open file for reading as stream.
/// </summary> /// </summary>
Stream OpenStream (Entry entry); Stream OpenStream (Entry entry);
Stream OpenSeekableStream (Entry entry);
/// <summary>
/// Open file for reading as memory-mapped view.
/// </summary>
ArcView OpenView (Entry entry); ArcView OpenView (Entry entry);
/// <summary> /// <summary>
@ -47,7 +59,7 @@ namespace GameRes
/// <summary> /// <summary>
/// Recursively enumerates files in the current directory and its subdirectories. /// Recursively enumerates files in the current directory and its subdirectories.
/// Subdirectory entries are omitted. /// Subdirectory entries are omitted from resulting set.
/// </summary> /// </summary>
IEnumerable<Entry> GetFilesRecursive (); IEnumerable<Entry> GetFilesRecursive ();
@ -73,6 +85,15 @@ namespace GameRes
set { Directory.SetCurrentDirectory (value); } set { Directory.SetCurrentDirectory (value); }
} }
public Entry FindFile (string filename)
{
var attr = File.GetAttributes (filename);
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
return new SubDirEntry (filename);
else
return EntryFromFileInfo (new FileInfo (filename));
}
public IEnumerable<Entry> GetFiles () public IEnumerable<Entry> GetFiles ()
{ {
var info = new DirectoryInfo (CurrentDirectory); var info = new DirectoryInfo (CurrentDirectory);
@ -84,7 +105,7 @@ namespace GameRes
} }
foreach (var file in info.EnumerateFiles()) foreach (var file in info.EnumerateFiles())
{ {
if (0 != (file.Attributes & (FileAttributes.Hidden | FileAttributes.System))) if (0 != (file.Attributes & FileAttributes.System))
continue; continue;
yield return EntryFromFileInfo (file); yield return EntryFromFileInfo (file);
} }
@ -113,6 +134,11 @@ namespace GameRes
return File.OpenRead (entry.Name); return File.OpenRead (entry.Name);
} }
public Stream OpenSeekableStream (Entry entry)
{
return OpenStream (entry);
}
public ArcView OpenView (Entry entry) public ArcView OpenView (Entry entry)
{ {
return new ArcView (entry.Name); return new ArcView (entry.Name);
@ -126,7 +152,10 @@ namespace GameRes
public class FlatArchiveFileSystem : IFileSystem public class FlatArchiveFileSystem : IFileSystem
{ {
protected ArcFile m_arc; protected readonly ArcFile m_arc;
protected readonly Dictionary<string, Entry> m_dir;
public ArcFile Source { get { return m_arc; } }
public virtual string CurrentDirectory public virtual string CurrentDirectory
{ {
@ -146,6 +175,11 @@ namespace GameRes
public FlatArchiveFileSystem (ArcFile arc) public FlatArchiveFileSystem (ArcFile arc)
{ {
m_arc = arc; m_arc = arc;
m_dir = new Dictionary<string, Entry> (arc.Dir.Count, StringComparer.InvariantCultureIgnoreCase);
foreach (var entry in arc.Dir)
{
m_dir.Add (entry.Name, entry);
}
} }
public Stream OpenStream (Entry entry) public Stream OpenStream (Entry entry)
@ -153,11 +187,24 @@ namespace GameRes
return m_arc.OpenEntry (entry); return m_arc.OpenEntry (entry);
} }
public Stream OpenSeekableStream (Entry entry)
{
return m_arc.OpenSeekableEntry (entry);
}
public ArcView OpenView (Entry entry) public ArcView OpenView (Entry entry)
{ {
return m_arc.OpenView (entry); return m_arc.OpenView (entry);
} }
public virtual Entry FindFile (string filename)
{
Entry entry = null;
if (!m_dir.TryGetValue (filename, out entry))
throw new FileNotFoundException();
return entry;
}
public virtual IEnumerable<Entry> GetFiles () public virtual IEnumerable<Entry> GetFiles ()
{ {
return m_arc.Dir; return m_arc.Dir;
@ -211,6 +258,16 @@ namespace GameRes
set { ChDir (value); } set { ChDir (value); }
} }
public override Entry FindFile (string filename)
{
Entry entry = null;
if (m_dir.TryGetValue (filename, out entry))
return entry;
if (m_dir.Keys.Any (n => n.StartsWith (filename + PathDelimiter)))
return new SubDirEntry (filename);
throw new FileNotFoundException();
}
static readonly Regex path_re = new Regex (@"\G[/\\]?([^/\\]+)([/\\])"); static readonly Regex path_re = new Regex (@"\G[/\\]?([^/\\]+)([/\\])");
public override IEnumerable<Entry> GetFiles () public override IEnumerable<Entry> GetFiles ()
@ -300,4 +357,188 @@ namespace GameRes
m_cwd = new_path; m_cwd = new_path;
} }
} }
public sealed class FileSystemStack : IDisposable
{
Stack<IFileSystem> m_fs_stack = new Stack<IFileSystem>();
Stack<string> m_arc_name_stack = new Stack<string>();
public IEnumerable<IFileSystem> All { get { return m_fs_stack; } }
public IFileSystem Top { get { return m_fs_stack.Peek(); } }
public int Count { get { return m_fs_stack.Count; } }
public IEnumerable<string> ArcStack { get { return m_arc_name_stack; } }
public ArcFile CurrentArchive { get; private set; }
private IFileSystem LastVisitedArc { get; set; }
private string LastVisitedPath { get; set; }
public FileSystemStack ()
{
m_fs_stack.Push (new PhysicalFileSystem());
}
public void ChDir (Entry entry)
{
if (entry is SubDirEntry)
{
if (1 == m_fs_stack.Count)
{
Top.CurrentDirectory = entry.Name;
return;
}
if (".." == entry.Name && string.IsNullOrEmpty (Top.CurrentDirectory))
{
Pop();
return;
}
}
if (entry.Name == LastVisitedPath && null != LastVisitedArc)
{
Push (LastVisitedPath, LastVisitedArc);
return;
}
Flush();
var arc = ArcFile.TryOpen (entry.Name);
if (null == arc)
throw new UnknownFormatException();
try
{
Push (entry.Name, arc.CreateFileSystem());
CurrentArchive = arc;
}
catch
{
arc.Dispose();
throw;
}
}
private void Push (string path, IFileSystem fs)
{
m_fs_stack.Push (fs);
m_arc_name_stack.Push (path);
}
private void Pop ()
{
if (m_fs_stack.Count > 1)
{
Flush();
LastVisitedArc = m_fs_stack.Pop();
LastVisitedPath = m_arc_name_stack.Pop();
if (m_fs_stack.Count > 1 && m_fs_stack.Peek() is FlatArchiveFileSystem)
CurrentArchive = (m_fs_stack.Peek() as FlatArchiveFileSystem).Source;
else
CurrentArchive = null;
}
}
public void Flush ()
{
if (LastVisitedArc != null)
{
LastVisitedArc.Dispose();
LastVisitedArc = null;
LastVisitedPath = null;
}
}
private bool _disposed = false;
public void Dispose ()
{
if (!_disposed)
{
Flush();
foreach (var fs in m_fs_stack.Reverse())
fs.Dispose();
_disposed = true;
}
GC.SuppressFinalize (this);
}
}
public static class VFS
{
private static FileSystemStack m_vfs = new FileSystemStack();
public static IFileSystem Top { get { return m_vfs.Top; } }
public static bool IsVirtual { get { return m_vfs.Count > 1; } }
public static int Count { get { return m_vfs.Count; } }
public static ArcFile CurrentArchive { get { return m_vfs.CurrentArchive; } }
private static string[] m_top_path = new string[1];
public static IEnumerable<string> FullPath
{
get
{
m_top_path[0] = Top.CurrentDirectory;
if (1 == Count)
return m_top_path;
else
return m_vfs.ArcStack.Concat (m_top_path);
}
set
{
if (!value.Any())
return;
var new_vfs = new FileSystemStack();
var desired = value.ToArray();
for (int i = 0; i < desired.Length-1; ++i)
new_vfs.ChDir (new_vfs.Top.FindFile (desired[i]));
new_vfs.Top.CurrentDirectory = desired.Last();
m_vfs.Dispose();
m_vfs = new_vfs;
}
}
public static Entry FindFile (string filename)
{
if (".." == filename)
return new SubDirEntry ("..");
return m_vfs.Top.FindFile (filename);
}
public static Stream OpenStream (Entry entry)
{
return m_vfs.Top.OpenStream (entry);
}
public static Stream OpenSeekableStream (Entry entry)
{
return m_vfs.Top.OpenSeekableStream (entry);
}
public static ArcView OpenView (Entry entry)
{
return m_vfs.Top.OpenView (entry);
}
public static void ChDir (Entry entry)
{
m_vfs.ChDir (entry);
}
public static void ChDir (string path)
{
m_vfs.ChDir (FindFile (path));
}
public static void Flush ()
{
m_vfs.Flush();
}
public static IEnumerable<Entry> GetFiles ()
{
return m_vfs.Top.GetFiles();
}
}
public class UnknownFormatException : FileFormatException
{
public UnknownFormatException () : base (garStrings.MsgUnknownFormat) { }
}
} }

View File

@ -261,6 +261,12 @@ namespace GameRes
public ResourceOptions Options { get; set; } public ResourceOptions Options { get; set; }
} }
public class OverwriteEventArgs : EventArgs
{
public string Filename { get; set; }
public bool Overwrite { get; set; }
}
public sealed class FormatCatalog public sealed class FormatCatalog
{ {
private static readonly FormatCatalog m_instance = new FormatCatalog(); private static readonly FormatCatalog m_instance = new FormatCatalog();

View File

@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.1.7.90")] [assembly: AssemblyVersion ("1.1.9.91")]
[assembly: AssemblyFileVersion ("1.1.7.90")] [assembly: AssemblyFileVersion ("1.1.9.91")]

View File

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.18444 // Runtime Version:4.0.30319.34209
// //
// Changes to this file may cause incorrect behavior and will be lost if // Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated. // the code is regenerated.
@ -104,5 +104,14 @@ namespace GameRes.Strings {
return ResourceManager.GetString("MsgUnknownEncryption", resourceCulture); return ResourceManager.GetString("MsgUnknownEncryption", resourceCulture);
} }
} }
/// <summary>
/// Looks up a localized string similar to file could not be opened as resource archive.
/// </summary>
public static string MsgUnknownFormat {
get {
return ResourceManager.GetString("MsgUnknownFormat", resourceCulture);
}
}
} }
} }

View File

@ -132,4 +132,7 @@
<data name="MsgUnknownEncryption" xml:space="preserve"> <data name="MsgUnknownEncryption" xml:space="preserve">
<value>Unknown encryption scheme</value> <value>Unknown encryption scheme</value>
</data> </data>
<data name="MsgUnknownFormat" xml:space="preserve">
<value>file could not be opened as resource archive</value>
</data>
</root> </root>

View File

@ -132,4 +132,7 @@
<data name="MsgUnknownEncryption" xml:space="preserve"> <data name="MsgUnknownEncryption" xml:space="preserve">
<value>Неизвестный метод шифрования</value> <value>Неизвестный метод шифрования</value>
</data> </data>
<data name="MsgUnknownFormat" xml:space="preserve">
<value>файл не может быть открыт как архив ресурсов</value>
</data>
</root> </root>

View File

@ -68,7 +68,7 @@ namespace GARbro.GUI
} }
try try
{ {
Directory.SetCurrentDirectory (ViewModel.Path); Directory.SetCurrentDirectory (ViewModel.Path.First());
var converter = new GarConvertMedia (this); var converter = new GarConvertMedia (this);
converter.IgnoreErrors = convert_dialog.IgnoreErrors.IsChecked ?? false; converter.IgnoreErrors = convert_dialog.IgnoreErrors.IsChecked ?? false;
converter.Convert (source, format); converter.Convert (source, format);

View File

@ -34,6 +34,7 @@ using System.Windows.Input;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using Ookii.Dialogs.Wpf; using Ookii.Dialogs.Wpf;
using GameRes; using GameRes;
using GameRes.Strings;
using GARbro.GUI.Strings; using GARbro.GUI.Strings;
using GARbro.GUI.Properties; using GARbro.GUI.Properties;
@ -58,11 +59,12 @@ namespace GARbro.GUI
string destination = Settings.Default.appLastDestination; string destination = Settings.Default.appLastDestination;
if (!Directory.Exists (destination)) if (!Directory.Exists (destination))
destination = ""; destination = "";
if (!ViewModel.IsArchive) var vm = ViewModel;
if (!vm.IsArchive)
{ {
if (!entry.IsDirectory) if (!entry.IsDirectory)
{ {
var arc_dir = CurrentPath; var arc_dir = vm.Path.First();
var source = Path.Combine (arc_dir, entry.Name); var source = Path.Combine (arc_dir, entry.Name);
if (string.IsNullOrEmpty (destination)) if (string.IsNullOrEmpty (destination))
destination = arc_dir; destination = arc_dir;
@ -73,13 +75,13 @@ namespace GARbro.GUI
extractor.ExtractAll (destination); extractor.ExtractAll (destination);
} }
} }
else if (null != m_app.CurrentArchive) else if (vm.Path.Skip (1).Any())
{ {
var vm = ViewModel as ArchiveViewModel;
if (string.IsNullOrEmpty (destination)) if (string.IsNullOrEmpty (destination))
destination = Path.GetDirectoryName (vm.Path); destination = Path.GetDirectoryName (vm.Path.First());
extractor = new GarExtract (this, vm.Path, m_app.CurrentArchive); var archive_name = vm.Path.Reverse().Skip (1).First();
if (null == entry || (entry.Name == ".." && vm.SubDir == "")) // root entry extractor = new GarExtract (this, archive_name, VFS.CurrentArchive);
if (null == entry || (entry.Name == ".." && string.IsNullOrEmpty (vm.Path.Last()))) // root entry
extractor.ExtractAll (destination); extractor.ExtractAll (destination);
else else
extractor.Extract (entry, destination); extractor.Extract (entry, destination);
@ -132,7 +134,7 @@ namespace GARbro.GUI
if (FormatCatalog.Instance.LastError != null) if (FormatCatalog.Instance.LastError != null)
error_message = FormatCatalog.Instance.LastError.Message; error_message = FormatCatalog.Instance.LastError.Message;
else else
error_message = guiStrings.MsgUnknownFormat; error_message = garStrings.MsgUnknownFormat;
throw new OperationCanceledException (string.Format ("{1}: {0}", error_message, m_arc_name)); throw new OperationCanceledException (string.Format ("{1}: {0}", error_message, m_arc_name));
} }
m_should_dispose = true; m_should_dispose = true;

View File

@ -75,14 +75,13 @@ namespace GARbro.GUI
class PreviewFile class PreviewFile
{ {
public string Path { get; set; } public IEnumerable<string> Path { get; set; }
public string Name { get; set; } public string Name { get; set; }
public Entry Entry { get; set; } public Entry Entry { get; set; }
public ArcFile Archive { get; set; }
public bool IsEqual (string path, string name) public bool IsEqual (IEnumerable<string> path, string name)
{ {
return path.Equals (Path) && name.Equals (Name); return Path != null && path.SequenceEqual (Path) && name.Equals (Name);
} }
} }
@ -167,8 +166,6 @@ namespace GARbro.GUI
ActiveViewer = ImageView; ActiveViewer = ImageView;
return; return;
} }
if (vm.IsArchive)
m_current_preview.Archive = m_app.CurrentArchive;
if ("image" != entry.Type) if ("image" != entry.Type)
LoadPreviewText (m_current_preview); LoadPreviewText (m_current_preview);
else if (!m_preview_worker.IsBusy) else if (!m_preview_worker.IsBusy)
@ -193,15 +190,7 @@ namespace GARbro.GUI
Stream OpenPreviewStream (PreviewFile preview) Stream OpenPreviewStream (PreviewFile preview)
{ {
if (null == preview.Archive) return VFS.OpenSeekableStream (preview.Entry);
{
string filename = Path.Combine (preview.Path, preview.Name);
return File.OpenRead (filename);
}
else
{
return preview.Archive.OpenSeekableEntry (preview.Entry);
}
} }
void LoadPreviewText (PreviewFile preview) void LoadPreviewText (PreviewFile preview)

View File

@ -223,16 +223,15 @@ namespace GARbro.GUI
StopWatchDirectoryChanges(); StopWatchDirectoryChanges();
var cvs = this.Resources["ListViewSource"] as CollectionViewSource; var cvs = this.Resources["ListViewSource"] as CollectionViewSource;
cvs.Source = value; cvs.Source = value;
pathLine.Text = value.Path; pathLine.Text = value.Path.Last();
if (value.IsArchive) if (value.IsArchive && !value.Path.Skip (2).Any())
PushRecentFile (value.Path); PushRecentFile (value.Path.First());
lv_Sort (SortMode, m_lvSortDirection); lv_Sort (SortMode, m_lvSortDirection);
if (!value.IsArchive && !string.IsNullOrEmpty (value.Path)) if (!value.IsArchive && !string.IsNullOrEmpty (value.Path.First()))
{ {
Directory.SetCurrentDirectory (value.Path); WatchDirectoryChanges (value.Path.First());
WatchDirectoryChanges (value.Path);
} }
CurrentDirectory.UpdateLayout(); CurrentDirectory.UpdateLayout();
} }
@ -240,16 +239,13 @@ namespace GARbro.GUI
DirectoryViewModel GetNewViewModel (string path) DirectoryViewModel GetNewViewModel (string path)
{ {
if (!VFS.IsVirtual)
path = Path.GetFullPath (path); path = Path.GetFullPath (path);
if (Directory.Exists (path)) var entry = VFS.FindFile (path);
{ if (!(entry is SubDirEntry))
return new DirectoryViewModel (path, m_app.GetDirectoryList (path));
}
else
{
SetBusyState(); SetBusyState();
return new ArchiveViewModel (path, m_app.GetArchive (path)); VFS.ChDir (entry);
} return new DirectoryViewModel (VFS.FullPath, VFS.GetFiles(), VFS.IsVirtual);
} }
private bool m_busy_state = false; private bool m_busy_state = false;
@ -293,7 +289,7 @@ namespace GARbro.GUI
catch (Exception X) catch (Exception X)
{ {
PopupError (X.Message, guiStrings.MsgErrorOpening); PopupError (X.Message, guiStrings.MsgErrorOpening);
return new DirectoryViewModel ("", new Entry[0]); return new DirectoryViewModel (new string[] { "" }, new Entry[0], false);
} }
} }
@ -323,13 +319,14 @@ namespace GARbro.GUI
public void ResumeWatchDirectoryChanges () public void ResumeWatchDirectoryChanges ()
{ {
m_watcher.EnableRaisingEvents = true; m_watcher.EnableRaisingEvents = !ViewModel.IsArchive;
} }
private void InvokeRefreshView (object source, FileSystemEventArgs e) private void InvokeRefreshView (object source, FileSystemEventArgs e)
{ {
var watcher = source as FileSystemWatcher; var watcher = source as FileSystemWatcher;
if (watcher.Path == ViewModel.Path) var vm = ViewModel;
if (!vm.IsArchive && vm.Path.First() == watcher.Path)
{ {
watcher.EnableRaisingEvents = false; watcher.EnableRaisingEvents = false;
Dispatcher.Invoke (RefreshView); Dispatcher.Invoke (RefreshView);
@ -632,7 +629,7 @@ namespace GARbro.GUI
#region Navigation history implementation #region Navigation history implementation
internal string CurrentPath { get { return ViewModel.Path; } } internal string CurrentPath { get { return ViewModel.Path.First(); } }
HistoryStack<DirectoryPosition> m_history = new HistoryStack<DirectoryPosition>(); HistoryStack<DirectoryPosition> m_history = new HistoryStack<DirectoryPosition>();
@ -644,12 +641,12 @@ namespace GARbro.GUI
public bool SetCurrentPosition (DirectoryPosition pos) public bool SetCurrentPosition (DirectoryPosition pos)
{ {
var vm = TryCreateViewModel (pos.Path);
if (null == vm)
return false;
try try
{ {
vm.SetPosition (pos); VFS.FullPath = pos.Path;
var vm = TryCreateViewModel (pos.Path.Last());
if (null == vm)
return false;
ViewModel = vm; ViewModel = vm;
if (null != pos.Item) if (null != pos.Item)
lv_SelectItem (pos.Item); lv_SelectItem (pos.Item);
@ -670,7 +667,7 @@ namespace GARbro.GUI
public void ChangePosition (DirectoryPosition new_pos) public void ChangePosition (DirectoryPosition new_pos)
{ {
var current = GetCurrentPosition(); var current = GetCurrentPosition();
if (current.Path != new_pos.Path || current.ArchivePath != new_pos.ArchivePath) if (!current.Path.SequenceEqual (new_pos.Path))
SaveCurrentPosition(); SaveCurrentPosition();
SetCurrentPosition (new_pos); SetCurrentPosition (new_pos);
} }
@ -722,8 +719,8 @@ namespace GARbro.GUI
var vm = GetNewViewModel (filename); var vm = GetNewViewModel (filename);
SaveCurrentPosition(); SaveCurrentPosition();
ViewModel = vm; ViewModel = vm;
if (null != m_app.CurrentArchive) if (null != VFS.CurrentArchive)
SetStatusText (m_app.CurrentArchive.Description); SetStatusText (VFS.CurrentArchive.Description);
lv_SelectItem (0); lv_SelectItem (0);
} }
catch (OperationCanceledException X) catch (OperationCanceledException X)
@ -766,48 +763,37 @@ namespace GARbro.GUI
PlayFile (entry.Source); PlayFile (entry.Source);
return; return;
} }
if (vm.IsArchive) // tried to open file inside archive
{
var arc_vm = vm as ArchiveViewModel;
if (!("" == arc_vm.SubDir && ".." == entry.Name))
{
OpenArchiveEntry (arc_vm, entry);
return;
}
}
OpenDirectoryEntry (vm, entry); OpenDirectoryEntry (vm, entry);
} }
private void OpenDirectoryEntry (DirectoryViewModel vm, EntryViewModel entry) private void OpenDirectoryEntry (DirectoryViewModel vm, EntryViewModel entry)
{ {
string old_dir = vm.Path; string old_dir = vm.Path.Last();
string new_dir = Path.Combine (old_dir, entry.Name); string new_dir = entry.Source.Name;
if (!vm.IsArchive && ".." == new_dir)
new_dir = Path.Combine (old_dir, entry.Name);
Trace.WriteLine (new_dir, "OpenDirectoryEntry"); Trace.WriteLine (new_dir, "OpenDirectoryEntry");
int old_fs_count = VFS.Count;
vm = TryCreateViewModel (new_dir); vm = TryCreateViewModel (new_dir);
if (null == vm) if (null == vm)
{ {
// if (entry.Type != "archive")
// SystemOpen (new_dir);
return; return;
} }
SaveCurrentPosition(); SaveCurrentPosition();
ViewModel = vm; ViewModel = vm;
if (vm.IsArchive && null != m_app.CurrentArchive) if (VFS.Count > old_fs_count && null != VFS.CurrentArchive)
SetStatusText (string.Format ("{0}: {1}", m_app.CurrentArchive.Description, SetStatusText (string.Format ("{0}: {1}", VFS.CurrentArchive.Description,
Localization.Format ("MsgFiles", m_app.CurrentArchive.Dir.Count()))); Localization.Format ("MsgFiles", VFS.CurrentArchive.Dir.Count())));
else else
SetStatusText (""); SetStatusText ("");
var old_parent = Directory.GetParent (old_dir);
if (null != old_parent && vm.Path == old_parent.FullName) if (".." == entry.Name)
{
lv_SelectItem (Path.GetFileName (old_dir)); lv_SelectItem (Path.GetFileName (old_dir));
}
else else
{
lv_SelectItem (0); lv_SelectItem (0);
} }
}
/*
private void OpenArchiveEntry (ArchiveViewModel vm, EntryViewModel entry) private void OpenArchiveEntry (ArchiveViewModel vm, EntryViewModel entry)
{ {
if (entry.IsDirectory) if (entry.IsDirectory)
@ -829,14 +815,11 @@ namespace GARbro.GUI
} }
} }
} }
*/
Stream OpenEntry (Entry entry) Stream OpenEntry (Entry entry)
{ {
var vm = ViewModel; return VFS.OpenStream (entry);
if (vm.IsArchive)
return m_app.CurrentArchive.OpenEntry (entry);
else
return File.OpenRead (Path.Combine (vm.Path, entry.Name));
} }
WaveOutEvent m_audio_device; WaveOutEvent m_audio_device;
@ -935,7 +918,7 @@ namespace GARbro.GUI
public void RefreshView () public void RefreshView ()
{ {
m_app.ResetCache(); VFS.Flush();
var pos = GetCurrentPosition(); var pos = GetCurrentPosition();
SetCurrentPosition (pos); SetCurrentPosition (pos);
} }
@ -974,7 +957,7 @@ namespace GARbro.GUI
this.IsEnabled = false; this.IsEnabled = false;
try try
{ {
m_app.ResetCache(); VFS.Flush();
ResetPreviewPane(); ResetPreviewPane();
if (!items.Skip (1).Any()) // items.Count() == 1 if (!items.Skip (1).Any()) // items.Count() == 1
{ {

View File

@ -51,5 +51,5 @@ using System.Windows;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion ("1.1.6.659")] [assembly: AssemblyVersion ("1.1.8.660")]
[assembly: AssemblyFileVersion ("1.1.6.659")] [assembly: AssemblyFileVersion ("1.1.8.660")]

View File

@ -729,15 +729,6 @@ namespace GARbro.GUI.Strings {
} }
} }
/// <summary>
/// Looks up a localized string similar to file could not be opened as resource archive.
/// </summary>
public static string MsgUnknownFormat {
get {
return ResourceManager.GetString("MsgUnknownFormat", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Version {0}. /// Looks up a localized string similar to Version {0}.
/// </summary> /// </summary>

View File

@ -243,9 +243,6 @@
<data name="MsgUnableInterpretImage" xml:space="preserve"> <data name="MsgUnableInterpretImage" xml:space="preserve">
<value>unable to interpret image format</value> <value>unable to interpret image format</value>
</data> </data>
<data name="MsgUnknownFormat" xml:space="preserve">
<value>file could not be opened as resource archive</value>
</data>
<data name="MsgVersion" xml:space="preserve"> <data name="MsgVersion" xml:space="preserve">
<value>Version {0}</value> <value>Version {0}</value>
</data> </data>

View File

@ -237,9 +237,6 @@
<data name="MsgUnableInterpretImage" xml:space="preserve"> <data name="MsgUnableInterpretImage" xml:space="preserve">
<value>не удалось интерпретировать формат изображения</value> <value>не удалось интерпретировать формат изображения</value>
</data> </data>
<data name="MsgUnknownFormat" xml:space="preserve">
<value>файл не может быть открыт как архив ресурсов</value>
</data>
<data name="MsgVersion" xml:space="preserve"> <data name="MsgVersion" xml:space="preserve">
<value>Версия {0}</value> <value>Версия {0}</value>
</data> </data>

View File

@ -41,20 +41,22 @@ namespace GARbro.GUI
{ {
public class DirectoryViewModel : ObservableCollection<EntryViewModel> public class DirectoryViewModel : ObservableCollection<EntryViewModel>
{ {
public string Path { get; private set; } public IEnumerable<string> Path { get; private set; }
public ICollection<Entry> Source { get; private set; } public IEnumerable<Entry> Source { get; private set; }
public virtual bool IsArchive { get { return false; } } public bool IsArchive { get; private set; }
public DirectoryViewModel (string path, ICollection<Entry> filelist) public DirectoryViewModel (IEnumerable<string> path, IEnumerable<Entry> filelist, bool is_archive)
{ {
Path = path; Path = path;
Source = filelist; Source = filelist;
IsArchive = is_archive;
ImportFromSource(); ImportFromSource();
} }
protected virtual void ImportFromSource () protected void ImportFromSource ()
{ {
if (!string.IsNullOrEmpty (Path) && null != Directory.GetParent (Path)) var last_dir = Path.Last();
if (IsArchive || !string.IsNullOrEmpty (last_dir) && null != Directory.GetParent (last_dir))
{ {
Add (new EntryViewModel (new SubDirEntry (".."), -2)); Add (new EntryViewModel (new SubDirEntry (".."), -2));
} }
@ -80,6 +82,7 @@ namespace GARbro.GUI
} }
} }
/*
public class ArchiveViewModel : DirectoryViewModel public class ArchiveViewModel : DirectoryViewModel
{ {
public override bool IsArchive { get { return true; } } public override bool IsArchive { get { return true; } }
@ -212,6 +215,7 @@ namespace GARbro.GUI
base.OnCollectionChanged(e); base.OnCollectionChanged(e);
} }
} }
*/
public class EntryViewModel : INotifyPropertyChanged public class EntryViewModel : INotifyPropertyChanged
{ {
@ -332,24 +336,18 @@ namespace GARbro.GUI
/// </summary> /// </summary>
public class DirectoryPosition public class DirectoryPosition
{ {
public string Path { get; set; } public IEnumerable<string> Path { get; set; }
public string ArchivePath { get; set; }
public string Item { get; set; } public string Item { get; set; }
public DirectoryPosition (DirectoryViewModel vm, EntryViewModel item) public DirectoryPosition (DirectoryViewModel vm, EntryViewModel item)
{ {
Path = vm.Path; Path = vm.Path;
Item = null != item ? item.Name : null; Item = null != item ? item.Name : null;
if (vm.IsArchive)
ArchivePath = (vm as ArchiveViewModel).SubDir;
else
ArchivePath = "";
} }
public DirectoryPosition (string filename) public DirectoryPosition (string filename)
{ {
Path = System.IO.Path.GetDirectoryName (filename); Path = new string[] { System.IO.Path.GetDirectoryName (filename) };
ArchivePath = "";
Item = System.IO.Path.GetFileName (filename); Item = System.IO.Path.GetFileName (filename);
} }
} }