diff --git a/DiscImageChef.Core/DiscImageChef.Core.csproj b/DiscImageChef.Core/DiscImageChef.Core.csproj
index 084803f1..7c65ef90 100644
--- a/DiscImageChef.Core/DiscImageChef.Core.csproj
+++ b/DiscImageChef.Core/DiscImageChef.Core.csproj
@@ -86,6 +86,7 @@
+
@@ -163,7 +164,7 @@
-
+
diff --git a/DiscImageChef.Core/Sidecar/BlockTape.cs b/DiscImageChef.Core/Sidecar/BlockTape.cs
new file mode 100644
index 00000000..27d8f2d5
--- /dev/null
+++ b/DiscImageChef.Core/Sidecar/BlockTape.cs
@@ -0,0 +1,200 @@
+// /***************************************************************************
+// The Disc Image Chef
+// ----------------------------------------------------------------------------
+//
+// Filename : BlockTape.cs
+// Version : 1.0
+// Author(s) : Natalia Portillo
+//
+// Component : Component
+//
+// Revision : $Revision$
+// Last change by : $Author$
+// Date : $Date$
+//
+// --[ Description ] ----------------------------------------------------------
+//
+// Description
+//
+// --[ License ] --------------------------------------------------------------
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+// ----------------------------------------------------------------------------
+// Copyright (C) 2011-2015 Claunia.com
+// ****************************************************************************/
+// //$Id$
+
+using System.Collections.Generic;
+using Schemas;
+using System.IO;
+
+namespace DiscImageChef.Core
+{
+ public static partial class Sidecar
+ {
+ public static CICMMetadataType Create(string folderName, List files, int blockSize)
+ {
+ CICMMetadataType sidecar = new CICMMetadataType
+ {
+ BlockMedia = new[]
+ {
+ new BlockMediaType
+ {
+ Image = new ImageType
+ {
+ format = "Directory",
+ offsetSpecified = false,
+ Value = folderName
+ },
+ Sequence = new SequenceType
+ {
+ MediaTitle = folderName,
+ MediaSequence = 1,
+ TotalMedia = 1
+ },
+ PhysicalBlockSize = (int)blockSize,
+ LogicalBlockSize = (int)blockSize,
+ TapeInformation = new []
+ {
+ new TapePartitionType
+ {
+ Image = new ImageType
+ {
+ format = "Directory",
+ offsetSpecified = false,
+ Value = folderName
+ }
+ }
+ }
+ }
+ }
+ };
+
+ long currentBlock = 0;
+ long totalSize = 0;
+ Checksum tapeWorker = new Checksum();
+ List tapeFiles = new List();
+
+ for(int i = 0; i < files.Count; i++)
+ {
+ FileStream fs = new FileStream(files[i], FileMode.Open, FileAccess.Read);
+ Checksum fileWorker = new Checksum();
+ TapeFileType tapeFile = new TapeFileType
+ {
+ Image = new ImageType
+ {
+ format = "Raw disk image (sector by sector copy)",
+ offset = 0,
+ Value = Path.GetFileName(files[i])
+ },
+ Size = fs.Length,
+ BlockSize = blockSize,
+ StartBlock = currentBlock,
+ Sequence = i
+ };
+
+ uint sectorsToRead = 512;
+ long sectors = fs.Length / blockSize;
+ long doneSectors = 0;
+
+ InitProgress2();
+ while(doneSectors < sectors)
+ {
+ byte[] sector;
+
+ if((sectors - doneSectors) >= sectorsToRead)
+ {
+ sector = new byte[sectorsToRead * blockSize];
+ fs.Read(sector, 0, sector.Length);
+ UpdateProgress2(string.Format("Hashing block {0} of {1} on file {2} of {3}", doneSectors, sectors, i + 1, files.Count), doneSectors, sectors);
+ doneSectors += sectorsToRead;
+ }
+ else
+ {
+ sector = new byte[(uint)(sectors - doneSectors) * blockSize];
+ fs.Read(sector, 0, sector.Length);
+ UpdateProgress2(string.Format("Hashing block {0} of {1} on file {2} of {3}", doneSectors, sectors, i + 1, files.Count), doneSectors, sectors);
+ doneSectors += (sectors - doneSectors);
+ }
+
+ fileWorker.Update(sector);
+ tapeWorker.Update(sector);
+ }
+
+ tapeFile.EndBlock = (tapeFile.StartBlock + sectors) - 1;
+ currentBlock += sectors;
+ totalSize += fs.Length;
+ tapeFile.Checksums = fileWorker.End().ToArray();
+ tapeFiles.Add(tapeFile);
+
+ EndProgress2();
+ }
+
+ sidecar.BlockMedia[0].Checksums = tapeWorker.End().ToArray();
+ sidecar.BlockMedia[0].ContentChecksums = sidecar.BlockMedia[0].Checksums;
+ sidecar.BlockMedia[0].Size = totalSize;
+ sidecar.BlockMedia[0].LogicalBlocks = currentBlock;
+ sidecar.BlockMedia[0].TapeInformation[0].EndBlock = currentBlock - 1;
+ sidecar.BlockMedia[0].TapeInformation[0].Size = totalSize;
+ sidecar.BlockMedia[0].TapeInformation[0].Checksums = sidecar.BlockMedia[0].Checksums;
+ sidecar.BlockMedia[0].TapeInformation[0].File = tapeFiles.ToArray();
+
+ // This is purely for convenience, as typically these kind of data represents QIC tapes
+ if(blockSize == 512)
+ {
+ sidecar.BlockMedia[0].DiskType = "Quarter-inch cartridge";
+
+ if(totalSize <= 20 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-11";
+ else if(totalSize <= 40 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-40";
+ else if(totalSize <= 60 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-24";
+ else if(totalSize <= 80 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-80";
+ else if(totalSize <= 120 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-120";
+ else if(totalSize <= 150 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-150";
+ else if(totalSize <= 320 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-320";
+ else if(totalSize <= 340 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-3010";
+ else if(totalSize <= 525 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-525";
+ else if(totalSize <= 670 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-3020";
+ else if(totalSize <= 1200 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-3080";
+ else if(totalSize <= 1350 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-1350";
+ else if(totalSize <= (long)4000 * 1048576)
+ sidecar.BlockMedia[0].DiskSubType = "QIC-3095";
+ else
+ {
+ sidecar.BlockMedia[0].DiskType = "Unknown tape";
+ sidecar.BlockMedia[0].DiskSubType = "Unknown tape";
+ }
+ }
+ else
+ {
+ sidecar.BlockMedia[0].DiskType = "Unknown tape";
+ sidecar.BlockMedia[0].DiskSubType = "Unknown tape";
+ }
+
+ return sidecar;
+ }
+ }
+}
diff --git a/DiscImageChef/ChangeLog b/DiscImageChef/ChangeLog
index 7d4d6a19..50201b11 100644
--- a/DiscImageChef/ChangeLog
+++ b/DiscImageChef/ChangeLog
@@ -1,3 +1,8 @@
+* Options.cs:
+* Commands/CreateSidecar.cs:
+ Added support for creating a sidecar from a folder containing the
+ files of a tape dump. e.g.: tape1/file01, tape2/file02, etc.
+
* Commands/Ls.cs:
* Commands/Analyze.cs:
* Commands/ExtractFiles.cs:
diff --git a/DiscImageChef/Commands/CreateSidecar.cs b/DiscImageChef/Commands/CreateSidecar.cs
index 525bfc50..63b3cf4f 100644
--- a/DiscImageChef/Commands/CreateSidecar.cs
+++ b/DiscImageChef/Commands/CreateSidecar.cs
@@ -31,6 +31,7 @@
// ****************************************************************************/
using System;
+using System.Collections.Generic;
using System.IO;
using DiscImageChef.Console;
using DiscImageChef.Core;
@@ -44,70 +45,118 @@ namespace DiscImageChef.Commands
{
public static void doSidecar(CreateSidecarOptions options)
{
- ImagePlugin _imageFormat;
+ Sidecar.InitProgressEvent += Progress.InitProgress;
+ Sidecar.UpdateProgressEvent += Progress.UpdateProgress;
+ Sidecar.EndProgressEvent += Progress.EndProgress;
+ Sidecar.InitProgressEvent2 += Progress.InitProgress2;
+ Sidecar.UpdateProgressEvent2 += Progress.UpdateProgress2;
+ Sidecar.EndProgressEvent2 += Progress.EndProgress2;
+ Sidecar.UpdateStatusEvent += Progress.UpdateStatus;
- FiltersList filtersList = new FiltersList();
- Filter inputFilter = filtersList.GetFilter(options.InputFile);
-
- if(inputFilter == null)
+ if(File.Exists(options.InputFile))
{
- DicConsole.ErrorWriteLine("Cannot open specified file.");
- return;
- }
-
- try
- {
- _imageFormat = ImageFormat.Detect(inputFilter);
-
- if(_imageFormat == null)
+ if(options.Tape)
{
- DicConsole.WriteLine("Image format not identified, not proceeding with analysis.");
+ DicConsole.ErrorWriteLine("You cannot use --tape option when input is a file.");
return;
}
- else
+
+ ImagePlugin _imageFormat;
+
+ FiltersList filtersList = new FiltersList();
+ Filter inputFilter = filtersList.GetFilter(options.InputFile);
+
+ if(inputFilter == null)
{
- if(options.Verbose)
- DicConsole.VerboseWriteLine("Image format identified by {0} ({1}).", _imageFormat.Name, _imageFormat.PluginUUID);
- else
- DicConsole.WriteLine("Image format identified by {0}.", _imageFormat.Name);
+ DicConsole.ErrorWriteLine("Cannot open specified file.");
+ return;
}
try
{
- if(!_imageFormat.OpenImage(inputFilter))
+ _imageFormat = ImageFormat.Detect(inputFilter);
+
+ if(_imageFormat == null)
{
- DicConsole.WriteLine("Unable to open image format");
- DicConsole.WriteLine("No error given");
+ DicConsole.WriteLine("Image format not identified, not proceeding with analysis.");
+ return;
+ }
+ else
+ {
+ if(options.Verbose)
+ DicConsole.VerboseWriteLine("Image format identified by {0} ({1}).", _imageFormat.Name, _imageFormat.PluginUUID);
+ else
+ DicConsole.WriteLine("Image format identified by {0}.", _imageFormat.Name);
+ }
+
+ try
+ {
+ if(!_imageFormat.OpenImage(inputFilter))
+ {
+ DicConsole.WriteLine("Unable to open image format");
+ DicConsole.WriteLine("No error given");
+ return;
+ }
+
+ DicConsole.DebugWriteLine("Analyze command", "Correctly opened image file.");
+ }
+ catch(Exception ex)
+ {
+ DicConsole.ErrorWriteLine("Unable to open image format");
+ DicConsole.ErrorWriteLine("Error: {0}", ex.Message);
return;
}
- DicConsole.DebugWriteLine("Analyze command", "Correctly opened image file.");
+ Core.Statistics.AddMediaFormat(_imageFormat.GetImageFormat());
+ Core.Statistics.AddFilter(inputFilter.Name);
+
+ CICMMetadataType sidecar = Sidecar.Create(_imageFormat, options.InputFile, inputFilter.UUID);
+
+ DicConsole.WriteLine("Writing metadata sidecar");
+
+ FileStream xmlFs = new FileStream(Path.GetDirectoryName(options.InputFile) +
+ //Path.PathSeparator +
+ Path.GetFileNameWithoutExtension(options.InputFile) + ".cicm.xml",
+ FileMode.CreateNew);
+
+ System.Xml.Serialization.XmlSerializer xmlSer = new System.Xml.Serialization.XmlSerializer(typeof(CICMMetadataType));
+ xmlSer.Serialize(xmlFs, sidecar);
+ xmlFs.Close();
+
+ Core.Statistics.AddCommand("create-sidecar");
}
catch(Exception ex)
{
- DicConsole.ErrorWriteLine("Unable to open image format");
- DicConsole.ErrorWriteLine("Error: {0}", ex.Message);
+ DicConsole.ErrorWriteLine(string.Format("Error reading file: {0}", ex.Message));
+ DicConsole.DebugWriteLine("Analyze command", ex.StackTrace);
+ }
+ }
+ else if(Directory.Exists(options.InputFile))
+ {
+ if(!options.Tape)
+ {
+ DicConsole.ErrorWriteLine("Cannot create a sidecar from a directory.");
return;
}
- Core.Statistics.AddMediaFormat(_imageFormat.GetImageFormat());
- Core.Statistics.AddFilter(inputFilter.Name);
+ string[] contents = Directory.GetFiles(options.InputFile, "*", SearchOption.TopDirectoryOnly);
+ List files = new List();
- Sidecar.InitProgressEvent += Progress.InitProgress;
- Sidecar.UpdateProgressEvent += Progress.UpdateProgress;
- Sidecar.EndProgressEvent += Progress.EndProgress;
- Sidecar.InitProgressEvent2 += Progress.InitProgress2;
- Sidecar.UpdateProgressEvent2 += Progress.UpdateProgress2;
- Sidecar.EndProgressEvent2 += Progress.EndProgress2;
- Sidecar.UpdateStatusEvent += Progress.UpdateStatus;
+ foreach(string file in contents)
+ {
+ if(new FileInfo(file).Length % options.BlockSize == 0)
+ files.Add(file);
+ }
- CICMMetadataType sidecar = Sidecar.Create(_imageFormat, options.InputFile, inputFilter.UUID);
+ files.Sort(StringComparer.CurrentCultureIgnoreCase);
+
+ CICMMetadataType sidecar = Sidecar.Create(Path.GetFileName(options.InputFile), files, options.BlockSize);
DicConsole.WriteLine("Writing metadata sidecar");
FileStream xmlFs = new FileStream(Path.GetDirectoryName(options.InputFile) +
//Path.PathSeparator +
- Path.GetFileNameWithoutExtension(options.InputFile) + ".cicm.xml",
+ Path.GetFileName(options.InputFile) + ".cicm.xml",
FileMode.CreateNew);
System.Xml.Serialization.XmlSerializer xmlSer = new System.Xml.Serialization.XmlSerializer(typeof(CICMMetadataType));
@@ -116,14 +165,12 @@ namespace DiscImageChef.Commands
Core.Statistics.AddCommand("create-sidecar");
}
- catch(Exception ex)
+ else
{
- DicConsole.ErrorWriteLine(string.Format("Error reading file: {0}", ex.Message));
- DicConsole.DebugWriteLine("Analyze command", ex.StackTrace);
+ DicConsole.ErrorWriteLine("The specified input file cannot be found.");
+ return;
}
-
}
-
}
}
diff --git a/DiscImageChef/Options.cs b/DiscImageChef/Options.cs
index 96141508..98377e17 100644
--- a/DiscImageChef/Options.cs
+++ b/DiscImageChef/Options.cs
@@ -268,6 +268,10 @@ namespace DiscImageChef
{
[Option('i', "input", Required = true, HelpText = "Disc image.")]
public string InputFile { get; set; }
+ [Option('t', "tape", Required = false, Default = false, HelpText = "When used indicates that input is a folder containing alphabetically sorted files extracted from a linear block-based tape with fixed block size (e.g. a SCSI tape device).")]
+ public bool Tape { get; set; }
+ [Option('b', "block-size", Required = false, Default = 512, HelpText = "Only used for tapes, indicates block size. Files in the folder whose size is not a multiple of this value will simply be ignored.")]
+ public int BlockSize { get; set; }
}
[Verb("dump-media", HelpText = "Dumps the media inserted on a device to a media image.")]