Refactoring xfer logic from twainsession out.

This commit is contained in:
soukoku
2014-04-20 18:42:51 -04:00
parent 1743b8379b
commit 4b08d3bc29
55 changed files with 717 additions and 688 deletions

View File

@@ -1,6 +1,8 @@
using NTwain.Data;
using NTwain.Properties;
using System;
using System.Globalization;
using System.IO;
namespace NTwain.Internals
{
@@ -30,14 +32,70 @@ namespace NTwain.Internals
/// <param name="dataArgumentType">The triplet data argument type.</param>
/// <param name="message">The triplet message.</param>
/// <exception cref="TwainStateException"></exception>
public static void VerifyState(this ITwainStateInternal session, int allowedMinimum, int allowedMaximum, DataGroups group, DataArgumentType dataArgumentType, Message message)
public static void VerifyState(this ITwainSessionInternal session, int allowedMinimum, int allowedMaximum, DataGroups group, DataArgumentType dataArgumentType, Message message)
{
if (session.EnforceState && (session.State < allowedMinimum || session.State > allowedMaximum))
{
throw new TwainStateException(session.State, allowedMinimum, allowedMaximum, group, dataArgumentType, message,
string.Format("TWAIN state {0} does not match required range {1}-{2} for operation {3}-{4}-{5}.",
string.Format(CultureInfo.InvariantCulture, "TWAIN state {0} does not match required range {1}-{2} for operation {3}-{4}-{5}.",
session.State, allowedMinimum, allowedMaximum, group, dataArgumentType, message));
}
}
public static string ChangeExtensionByFormat(this TWSetupFileXfer fileInfo, string currentFilePath)
{
string finalFile = null;
switch (fileInfo.Format)
{
case FileFormat.Bmp:
finalFile = Path.ChangeExtension(currentFilePath, ".bmp");
break;
case FileFormat.Dejavu:
finalFile = Path.ChangeExtension(currentFilePath, ".dejavu");
break;
case FileFormat.Exif:
finalFile = Path.ChangeExtension(currentFilePath, ".exit");
break;
case FileFormat.Fpx:
finalFile = Path.ChangeExtension(currentFilePath, ".fpx");
break;
case FileFormat.Jfif:
finalFile = Path.ChangeExtension(currentFilePath, ".jpg");
break;
case FileFormat.Jp2:
finalFile = Path.ChangeExtension(currentFilePath, ".jp2");
break;
case FileFormat.Jpx:
finalFile = Path.ChangeExtension(currentFilePath, ".jpx");
break;
case FileFormat.Pdf:
case FileFormat.PdfA:
case FileFormat.PdfA2:
finalFile = Path.ChangeExtension(currentFilePath, ".pdf");
break;
case FileFormat.Pict:
finalFile = Path.ChangeExtension(currentFilePath, ".pict");
break;
case FileFormat.Png:
finalFile = Path.ChangeExtension(currentFilePath, ".png");
break;
case FileFormat.Spiff:
finalFile = Path.ChangeExtension(currentFilePath, ".spiff");
break;
case FileFormat.Tiff:
case FileFormat.TiffMulti:
finalFile = Path.ChangeExtension(currentFilePath, ".tif");
break;
case FileFormat.Xbm:
finalFile = Path.ChangeExtension(currentFilePath, ".xbm");
break;
default:
finalFile = Path.ChangeExtension(currentFilePath, ".unknown");
break;
}
return finalFile;
}
}
}

View File

@@ -1,11 +1,12 @@
using NTwain.Data;
using System.Collections.Generic;
namespace NTwain.Internals
{
/// <summary>
/// Internal interface for state management.
/// </summary>
interface ITwainStateInternal : ITwainState
interface ITwainSessionInternal : ITwainSession
{
/// <summary>
/// Gets the app id used for the session.
@@ -37,5 +38,11 @@ namespace NTwain.Internals
ICommittable GetPendingStateChanger(int newState);
void ChangeSourceId(TWIdentity sourceId);
ReturnCode DisableSource();
void SafeSyncableRaiseEvent(DataTransferredEventArgs e);
void SafeSyncableRaiseEvent(TransferErrorEventArgs e);
void SafeSyncableRaiseEvent(TransferReadyEventArgs e);
}
}

View File

@@ -10,17 +10,17 @@ namespace NTwain.Internals
#region mem stuff for twain 1.x
[DllImport("kernel32", SetLastError = true, EntryPoint = "GlobalAlloc")]
internal static extern IntPtr WinGlobalAlloc(uint uFlags, UIntPtr dwBytes);
public static extern IntPtr WinGlobalAlloc(uint uFlags, UIntPtr dwBytes);
[DllImport("kernel32", SetLastError = true, EntryPoint = "GlobalFree")]
internal static extern IntPtr WinGlobalFree(IntPtr hMem);
public static extern IntPtr WinGlobalFree(IntPtr hMem);
[DllImport("kernel32", SetLastError = true, EntryPoint = "GlobalLock")]
internal static extern IntPtr WinGlobalLock(IntPtr handle);
public static extern IntPtr WinGlobalLock(IntPtr handle);
[DllImport("kernel32", SetLastError = true, EntryPoint = "GlobalUnlock")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool WinGlobalUnlock(IntPtr handle);
public static extern bool WinGlobalUnlock(IntPtr handle);
#endregion
}

View File

@@ -3,10 +3,10 @@
class TentativeStateCommitable : ICommittable
{
bool _commit;
ITwainStateInternal _session;
ITwainSessionInternal _session;
int _origState;
int _newState;
public TentativeStateCommitable(ITwainStateInternal session, int newState)
public TentativeStateCommitable(ITwainSessionInternal session, int newState)
{
_session = session;
_origState = session.State;

View File

@@ -0,0 +1,436 @@
using NTwain.Data;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace NTwain.Internals
{
/// <summary>
/// Contains the actual data transfer logic since TwainSession is getting too large.
/// </summary>
static class TransferLogic
{
/// <summary>
/// Performs the TWAIN transfer routine at state 6.
/// </summary>
public static void DoTransferRoutine(ITwainSessionInternal session)
{
var pending = new TWPendingXfers();
var rc = ReturnCode.Success;
do
{
#region build and raise xfer ready
TWAudioInfo audInfo;
if (session.DGAudio.AudioInfo.Get(out audInfo) != ReturnCode.Success)
{
audInfo = null;
}
TWImageInfo imgInfo;
if (session.DGImage.ImageInfo.Get(out imgInfo) != ReturnCode.Success)
{
imgInfo = null;
}
// ask consumer for xfer details
var preXferArgs = new TransferReadyEventArgs
{
AudioInfo = audInfo,
PendingImageInfo = imgInfo,
PendingTransferCount = pending.Count,
EndOfJob = pending.EndOfJob == 0
};
session.SafeSyncableRaiseEvent(preXferArgs);
#endregion
#region actually handle xfer
if (preXferArgs.CancelAll)
{
rc = session.DGControl.PendingXfers.Reset(pending);
}
else if (!preXferArgs.CancelCurrent)
{
DataGroups xferGroup = DataGroups.None;
if (session.DGControl.XferGroup.Get(ref xferGroup) != ReturnCode.Success)
{
xferGroup = DataGroups.None;
}
if ((xferGroup & DataGroups.Image) == DataGroups.Image)
{
var mech = session.GetCurrentCap(CapabilityId.ICapXferMech).ConvertToEnum<XferMech>();
switch (mech)
{
case XferMech.Native:
DoImageNativeXfer(session);
break;
case XferMech.Memory:
DoImageMemoryXfer(session);
break;
case XferMech.File:
DoImageFileXfer(session);
break;
case XferMech.MemFile:
DoImageMemoryFileXfer(session);
break;
}
}
if ((xferGroup & DataGroups.Audio) == DataGroups.Audio)
{
var mech = session.GetCurrentCap(CapabilityId.ACapXferMech).ConvertToEnum<XferMech>();
switch (mech)
{
case XferMech.Native:
DoAudioNativeXfer(session);
break;
case XferMech.File:
DoAudioFileXfer(session);
break;
}
}
}
rc = session.DGControl.PendingXfers.EndXfer(pending);
#endregion
} while (rc == ReturnCode.Success && pending.Count != 0);
session.ChangeState(5, true);
session.DisableSource();
}
#region audio xfers
static void DoAudioNativeXfer(ITwainSessionInternal session)
{
IntPtr dataPtr = IntPtr.Zero;
IntPtr lockedPtr = IntPtr.Zero;
try
{
var xrc = session.DGAudio.AudioNativeXfer.Get(ref dataPtr);
if (xrc == ReturnCode.XferDone)
{
session.ChangeState(7, true);
if (dataPtr != IntPtr.Zero)
{
lockedPtr = Platform.MemoryManager.Lock(dataPtr);
}
session.SafeSyncableRaiseEvent(new DataTransferredEventArgs { NativeData = lockedPtr });
}
else
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = session.GetSourceStatus() });
}
}
catch (Exception ex)
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { Exception = ex });
}
finally
{
session.ChangeState(6, true);
// data here is allocated by source so needs to use shared mem calls
if (lockedPtr != IntPtr.Zero)
{
Platform.MemoryManager.Unlock(lockedPtr);
lockedPtr = IntPtr.Zero;
}
if (dataPtr != IntPtr.Zero)
{
Platform.MemoryManager.Free(dataPtr);
dataPtr = IntPtr.Zero;
}
}
}
static void DoAudioFileXfer(ITwainSessionInternal session)
{
string filePath = null;
TWSetupFileXfer setupInfo;
if (session.DGControl.SetupFileXfer.Get(out setupInfo) == ReturnCode.Success)
{
filePath = setupInfo.FileName;
}
var xrc = session.DGAudio.AudioFileXfer.Get();
if (xrc == ReturnCode.XferDone)
{
session.SafeSyncableRaiseEvent(new DataTransferredEventArgs { FileDataPath = filePath });
}
else
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = session.GetSourceStatus() });
}
}
#endregion
#region image xfers
static void DoImageNativeXfer(ITwainSessionInternal session)
{
IntPtr dataPtr = IntPtr.Zero;
IntPtr lockedPtr = IntPtr.Zero;
try
{
var xrc = session.DGImage.ImageNativeXfer.Get(ref dataPtr);
if (xrc == ReturnCode.XferDone)
{
session.ChangeState(7, true);
if (dataPtr != IntPtr.Zero)
{
lockedPtr = Platform.MemoryManager.Lock(dataPtr);
}
DoImageXferredEventRoutine(session, lockedPtr, null, null);
}
else
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = session.GetSourceStatus() });
}
}
catch (Exception ex)
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { Exception = ex });
}
finally
{
session.ChangeState(6, true);
// data here is allocated by source so needs to use shared mem calls
if (lockedPtr != IntPtr.Zero)
{
Platform.MemoryManager.Unlock(lockedPtr);
lockedPtr = IntPtr.Zero;
}
if (dataPtr != IntPtr.Zero)
{
Platform.MemoryManager.Free(dataPtr);
dataPtr = IntPtr.Zero;
}
}
}
static void DoImageFileXfer(ITwainSessionInternal session)
{
string filePath = null;
TWSetupFileXfer setupInfo;
if (session.DGControl.SetupFileXfer.Get(out setupInfo) == ReturnCode.Success)
{
filePath = setupInfo.FileName;
}
var xrc = session.DGImage.ImageFileXfer.Get();
if (xrc == ReturnCode.XferDone)
{
DoImageXferredEventRoutine(session, IntPtr.Zero, null, filePath);
}
else
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = session.GetSourceStatus() });
}
}
static void DoImageMemoryXfer(ITwainSessionInternal session)
{
TWSetupMemXfer memInfo;
if (session.DGControl.SetupMemXfer.Get(out memInfo) == ReturnCode.Success)
{
TWImageMemXfer xferInfo = new TWImageMemXfer();
try
{
// how to tell if going to xfer in strip vs tile?
// if tile don't allocate memory in app?
xferInfo.Memory = new TWMemory
{
Flags = MemoryFlags.AppOwns | MemoryFlags.Pointer,
Length = memInfo.Preferred,
TheMem = Platform.MemoryManager.Allocate(memInfo.Preferred)
};
// do the unthinkable and keep all xferred batches in memory,
// possibly defeating the purpose of mem xfer
// unless compression is used.
// todo: use array instead of memory stream?
using (MemoryStream xferredData = new MemoryStream())
{
var xrc = ReturnCode.Success;
do
{
xrc = session.DGImage.ImageMemFileXfer.Get(xferInfo);
if (xrc == ReturnCode.Success ||
xrc == ReturnCode.XferDone)
{
session.ChangeState(7, true);
// optimize and allocate buffer only once instead of inside the loop?
byte[] buffer = new byte[(int)xferInfo.BytesWritten];
IntPtr lockPtr = IntPtr.Zero;
try
{
lockPtr = Platform.MemoryManager.Lock(xferInfo.Memory.TheMem);
Marshal.Copy(lockPtr, buffer, 0, buffer.Length);
xferredData.Write(buffer, 0, buffer.Length);
}
finally
{
if (lockPtr != IntPtr.Zero)
{
Platform.MemoryManager.Unlock(lockPtr);
}
}
}
} while (xrc == ReturnCode.Success);
if (xrc == ReturnCode.XferDone)
{
DoImageXferredEventRoutine(session, IntPtr.Zero, xferredData.ToArray(), null);
}
else
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = session.GetSourceStatus() });
}
}
}
catch (Exception ex)
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { Exception = ex });
}
finally
{
session.ChangeState(6, true);
if (xferInfo.Memory.TheMem != IntPtr.Zero)
{
Platform.MemoryManager.Free(xferInfo.Memory.TheMem);
}
}
}
}
static void DoImageMemoryFileXfer(ITwainSessionInternal session)
{
// since it's memory-file xfer need info from both (maybe)
TWSetupMemXfer memInfo;
TWSetupFileXfer fileInfo;
if (session.DGControl.SetupMemXfer.Get(out memInfo) == ReturnCode.Success &&
session.DGControl.SetupFileXfer.Get(out fileInfo) == ReturnCode.Success)
{
TWImageMemXfer xferInfo = new TWImageMemXfer();
var tempFile = Path.GetTempFileName();
string finalFile = null;
try
{
// no strip or tile here, just chunks
xferInfo.Memory = new TWMemory
{
Flags = MemoryFlags.AppOwns | MemoryFlags.Pointer,
Length = memInfo.Preferred,
TheMem = Platform.MemoryManager.Allocate(memInfo.Preferred)
};
var xrc = ReturnCode.Success;
using (var outStream = File.OpenWrite(tempFile))
{
do
{
xrc = session.DGImage.ImageMemFileXfer.Get(xferInfo);
if (xrc == ReturnCode.Success ||
xrc == ReturnCode.XferDone)
{
session.ChangeState(7, true);
byte[] buffer = new byte[(int)xferInfo.BytesWritten];
IntPtr lockPtr = IntPtr.Zero;
try
{
lockPtr = Platform.MemoryManager.Lock(xferInfo.Memory.TheMem);
Marshal.Copy(lockPtr, buffer, 0, buffer.Length);
}
finally
{
if (lockPtr != IntPtr.Zero)
{
Platform.MemoryManager.Unlock(lockPtr);
}
}
outStream.Write(buffer, 0, buffer.Length);
}
} while (xrc == ReturnCode.Success);
}
if (xrc == ReturnCode.XferDone)
{
finalFile = fileInfo.ChangeExtensionByFormat(tempFile);
File.Move(tempFile, finalFile);
}
else
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { ReturnCode = xrc, SourceStatus = session.GetSourceStatus() });
}
}
catch (Exception ex)
{
session.SafeSyncableRaiseEvent(new TransferErrorEventArgs { Exception = ex });
}
finally
{
session.ChangeState(6, true);
if (xferInfo.Memory.TheMem != IntPtr.Zero)
{
Platform.MemoryManager.Free(xferInfo.Memory.TheMem);
}
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}
if (File.Exists(finalFile))
{
DoImageXferredEventRoutine(session, IntPtr.Zero, null, finalFile);
}
}
}
static void DoImageXferredEventRoutine(ITwainSessionInternal session, IntPtr dataPtr, byte[] dataArray, string filePath)
{
TWImageInfo imgInfo;
TWExtImageInfo extInfo = null;
if (session.SupportedCaps.Contains(CapabilityId.ICapExtImageInfo))
{
if (session.DGImage.ExtImageInfo.Get(out extInfo) != ReturnCode.Success)
{
extInfo = null;
}
}
if (session.DGImage.ImageInfo.Get(out imgInfo) != ReturnCode.Success)
{
imgInfo = null;
}
session.SafeSyncableRaiseEvent(new DataTransferredEventArgs
{
NativeData = dataPtr,
MemData = dataArray,
FileDataPath = filePath,
ImageInfo = imgInfo,
ExImageInfo = extInfo
});
if (extInfo != null) { extInfo.Dispose(); }
}
#endregion
}
}

View File

@@ -1,6 +1,7 @@
using NTwain.Data;
using NTwain.Internals;
using System;
using System.ComponentModel;
namespace NTwain
{
@@ -16,7 +17,7 @@ namespace NTwain
if (retVal == IntPtr.Zero)
{
throw new OutOfMemoryException();
throw new Win32Exception();
}
return retVal;
}