' *
' * The project site is at: http://sourceforge.jp/projects/fishbornas/
' *
' * First author tiritomato 2012.
' *
' * Distributed under the FishbornArchiveShelf License (See
' *  file "Licenses/License.txt" contained in a project, or the following link.
' *  http://sourceforge.jp/projects/fishbornas/scm/svn/blobs/head/trunk/Licenses/License.txt)
' *
' * 2012.06.07 Initial Revision (tiritomato)
' *

Partial Public Class AppBase
    Partial Public Class Archive

        Public Class RarProcess
            Inherits ArchiveProcess.Impl(Of RarOptionConfig)

            Private Enum EXITCODE
                SUCCESS = 0
                WARNING = 1
                FATAL_ERROR = 2
                CRC_ERROR = 3
                LOCKED_ARCHIVE = 4
                WRITE_ERROR = 5
                OPEN_ERROR = 6
                USER_ERROR = 7
                MEMORY_ERROR = 8
                CREATE_ERROR = 9
                NO_FILES_ERROR = 10
                USER_BREAK = 255
            End Enum

            Public Sub New(ByVal src As RarOptionConfig, ByVal AppBase As AppBase, Optional ByVal IsStopReq As Logic.Threading.SignalHandler = Nothing)
                MyBase.New(src, AppBase, IsStopReq)
            End Sub

            Public Overrides Function CrcCheck(ByVal Path As Logic.FileSystem.Path) As RESULT

                If Path.FileExists = False Then Return RESULT.NO_FILE
                If IO.File.Exists(RarOptionConfig.RarPath) = False Then Return RESULT.ARCHIVE_LIB_NOT_FOUND
                If BaseConfig Is Nothing Then Return RESULT.NO_OBJECT

                CrcCheck = RESULT.OK
                Dim ps As New Process
                Try
                    ps.StartInfo.FileName = RarOptionConfig.RarPath
                    ps.StartInfo.Arguments = "t -p- """ & Path.ToString & """"
                    ps.StartInfo.CreateNoWindow = True
                    ps.StartInfo.UseShellExecute = False

                    ps.StartInfo.RedirectStandardOutput = True
                    AddHandler ps.OutputDataReceived, AddressOf DefaultConsoleEcho

                    ps.Start()
                    ps.BeginOutputReadLine()
                    ps.WaitForExit()

                    Select Case ps.ExitCode
                        Case EXITCODE.SUCCESS
                            ' do nothing
                        Case EXITCODE.NO_FILES_ERROR, EXITCODE.OPEN_ERROR
                            CrcCheck = RESULT.NO_FILE
                        Case EXITCODE.CRC_ERROR
                            If GetEntryListImplement.IsNeedPass(Path.ToString, BaseConfig) = RESULT.ARCHIVE_PASSWORD_MISSMATCH Then
                                CrcCheck = RESULT.ARCHIVE_PASSWORD_MISSMATCH
                            Else
                                CrcCheck = RESULT.ARCHIVE_CRC_ERR
                            End If
                        Case EXITCODE.USER_BREAK
                            CrcCheck = RESULT.USER_CANCEL
                    End Select
                Catch ex As Exception
                    Throw
                Finally
                    If Not ps Is Nothing Then
                        ps.Close()
                        ps.Dispose()
                        ps = Nothing
                    End If
                End Try

            End Function

            Public Overrides Function Extract(ByVal DestDir As Logic.FileSystem.Path, ByVal SrcArcPath As Logic.FileSystem.Path) As RESULT

                If SrcArcPath.FileExists = False Then Return RESULT.NO_FILE
                If IO.File.Exists(RarOptionConfig.RarPath) = False Then Return RESULT.ARCHIVE_LIB_NOT_FOUND
                If BaseConfig Is Nothing Then Return RESULT.NO_OBJECT

                Try
                    If DestDir.DirectoryExists = False Then IO.Directory.CreateDirectory(DestDir.ToString)
                Catch ex As Exception
#If DEBUG Then
                    Throw
#End If
                    Return RESULT.ARCHIVE_EXTRACT_ERR
                End Try

                Extract = RESULT.OK
                Dim ps As New Process
                Try
                    ps.StartInfo.FileName = RarOptionConfig.RarPath
                    ps.StartInfo.Arguments = String.Format("x -p- -mt{0} ""{1}""", BaseConfig.nThread, SrcArcPath)
                    ps.StartInfo.CreateNoWindow = True
                    ps.StartInfo.UseShellExecute = False
                    ps.StartInfo.WorkingDirectory = DestDir.ToString
                    ps.StartInfo.RedirectStandardOutput = True
                    AddHandler ps.OutputDataReceived, AddressOf ExtractProgressEcho
                    ps.Start()
                    ps.BeginOutputReadLine()
                    ps.WaitForExit()

                    Select Case ps.ExitCode
                        Case EXITCODE.SUCCESS
                            ' do nothing
                        Case EXITCODE.NO_FILES_ERROR, EXITCODE.OPEN_ERROR
                            Extract = RESULT.NO_FILE
                        Case EXITCODE.CRC_ERROR
                            If GetEntryListImplement.IsNeedPass(SrcArcPath.ToString, BaseConfig) = RESULT.ARCHIVE_PASSWORD_MISSMATCH Then
                                Extract = RESULT.ARCHIVE_PASSWORD_MISSMATCH
                            Else
                                Extract = RESULT.ARCHIVE_CRC_ERR
                            End If
                        Case EXITCODE.USER_BREAK
                            Extract = RESULT.USER_CANCEL
                    End Select
                Catch ex As Exception
#If DEBUG Then
                    Throw
#End If
                    Return RESULT.ARCHIVE_EXTRACT_ERR
                Finally
                    If Not ps Is Nothing Then
                        ps.Close()
                        ps.Dispose()
                        ps = Nothing
                    End If
                End Try

            End Function

            Public Overrides Function Extract(ByRef DestBuf As IO.MemoryStream, ByVal SrcEntryPath As String, ByVal SrcArcPath As Logic.FileSystem.Path) As RESULT

                DestBuf = Nothing

                If SrcArcPath.FileExists = False Then Return RESULT.NO_FILE
                If IO.File.Exists(RarOptionConfig.RarPath) = False Then Return RESULT.ARCHIVE_LIB_NOT_FOUND
                If BaseConfig Is Nothing Then Return RESULT.NO_OBJECT

                Extract = RESULT.OK

                DestBuf = New IO.MemoryStream
                Dim ps = New Process
                ps.StartInfo.FileName = RarOptionConfig.RarPath
                ps.StartInfo.Arguments = String.Format("p -inul -p- -mt{0} ""{1}"" ""{2}""", BaseConfig.nThread, SrcArcPath, SrcEntryPath)
                ps.StartInfo.CreateNoWindow = True
                ps.StartInfo.UseShellExecute = False
                ps.StartInfo.RedirectStandardOutput = True
                Try
                    ps.Start()
                    ps.StandardOutput.BaseStream.CopyTo(DestBuf)
                    ps.WaitForExit()
                    Select Case ps.ExitCode
                        Case EXITCODE.NO_FILES_ERROR, EXITCODE.OPEN_ERROR
                            Extract = RESULT.NO_FILE
                        Case EXITCODE.CRC_ERROR
                            If GetEntryListImplement.IsNeedPass(SrcArcPath.ToString, BaseConfig) = RESULT.ARCHIVE_PASSWORD_MISSMATCH Then
                                Extract = RESULT.ARCHIVE_PASSWORD_MISSMATCH
                            Else
                                Extract = RESULT.ARCHIVE_CRC_ERR
                            End If
                        Case EXITCODE.USER_BREAK
                            Extract = RESULT.USER_CANCEL
                    End Select
                Catch ex As Exception
                    If Extract = RESULT.OK Then Extract = RESULT.ARCHIVE_EXTRACT_ERR
                Finally
                    If Extract <> RESULT.OK AndAlso (Not DestBuf Is Nothing) Then
                        DestBuf.Close()
                        DestBuf = Nothing
                    End If
                    ps.Close()
                    ps.Dispose()
                    ps = Nothing
                End Try

            End Function

            Public Overrides Function CompressDir(ByVal DestArcPath As Logic.FileSystem.Path, ByVal SrcDir As Logic.FileSystem.Path) As RESULT

                If BaseConfig Is Nothing Then Return RESULT.NO_OBJECT
                Dim Path As New CompressPathInfo(DestArcPath.ToString, SrcDir.ToString, BaseConfig.Extention)
                If Path.Exists = False Then Return RESULT.NO_DIRECTORY
                If IO.File.Exists(RarOptionConfig.RarPath) = False Then Return RESULT.ARCHIVE_LIB_NOT_FOUND

                Dim SrcTargetSelector As String = Logic.FileSystem.JoinPathElements(Path.SrcDir.Name.ToString, "*")
                Dim SrcWorkingDir As String = Path.SrcDir.Parent.ToString

                Dim strOption As String = "a -ep1 -r -m" & BaseConfig.nLv & " -mt" & BaseConfig.nThread
                If 0 < BaseConfig.nRecoveryRecord Then strOption &= (" -rr" & BaseConfig.nRecoveryRecord & "p")

                Dim strIgnoreExt As String = BaseConfig.IgnoreExtSwitch
                If Not String.IsNullOrWhiteSpace(strIgnoreExt) Then strOption = strOption & " " & strIgnoreExt

                Dim strStoreExt As String = BaseConfig.StoreExtSwitch
                If Not String.IsNullOrWhiteSpace(strStoreExt) Then strOption = strOption & " " & strStoreExt

                CompressDir = RESULT.OK

                Dim psCompress As New Process
                Try
                    psCompress.StartInfo.FileName = RarOptionConfig.RarPath
                    psCompress.StartInfo.Arguments = strOption & " """ & Path.DestArcPath.ToString & """ """ & SrcTargetSelector & """"
                    psCompress.StartInfo.WorkingDirectory = SrcWorkingDir
                    psCompress.StartInfo.CreateNoWindow = True
                    psCompress.StartInfo.UseShellExecute = False
                    psCompress.StartInfo.RedirectStandardOutput = True
                    AddHandler psCompress.OutputDataReceived, AddressOf DefaultConsoleEcho

                    psCompress.Start()
                    psCompress.BeginOutputReadLine()
                    psCompress.WaitForExit()

                    If psCompress.ExitCode <> 0 Then
                        CompressDir = RESULT.ARCHIVE_COMPRESS_ERR
                        Throw New ApplicationException("rar compress err >> option is " & psCompress.StartInfo.Arguments)
                    End If

                Catch ex As Exception
                    If CompressDir = RESULT.OK Then CompressDir = RESULT.ARCHIVE_COMPRESS_ERR
                    If AppBase IsNot Nothing Then AppBase.Echoing.Log.Echo(ResultMessage(CompressDir) & ":" & ex.Message)
#If DEBUG Then
                    Throw New ApplicationException(ResultMessage(CompressDir) & ":" & ex.Message)
#End If
                Finally
                    psCompress.Close()
                    psCompress.Dispose()
                    psCompress = Nothing
                End Try

            End Function

            Public Overrides Function GetEntryList(ByRef Dest() As String, ByVal SrcArcPath As Logic.FileSystem.Path) As RESULT
                Dest = Nothing
                Dim GetEntryListImpl As New GetEntryListImplement
                GetEntryList = GetEntryListImpl.Execute(SrcArcPath, BaseConfig)
                If GetEntryList = RESULT.OK Then Dest = GetEntryListImpl.ExitPathArray
            End Function

            Public Overrides Function GetEntryList(ByRef Dest() As EntryInfo, ByVal SrcArcPath As Logic.FileSystem.Path) As RESULT
                Dest = Nothing
                Dim GetEntryListImpl As New GetEntryListImplement
                GetEntryList = GetEntryListImpl.Execute(SrcArcPath, BaseConfig)
                If GetEntryList = RESULT.OK Then Dest = GetEntryListImpl.ExitArray
            End Function

            Private Class GetEntryListImplement
                Private isExecute As Boolean
                Private isPassSign As Boolean ' is password protecting sign
                Private isReadStart As Boolean
                Private isReadEnd As Boolean
                Private isPathLine As Boolean
                Private ret As Collections.Generic.List(Of EntryInfo)

                Private Sub ConsoleReader(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
                    If Not String.IsNullOrWhiteSpace(outLine.Data) Then
                        SyncLock Me
                            If isReadStart = False Then

                                If String.IsNullOrWhiteSpace(outLine.Data.Replace("-", "")) Then
                                    isReadStart = True
                                    isPathLine = True
                                End If

                            ElseIf isReadEnd = False Then

                                If isPathLine = False Then ' => information line
                                    Dim AddItem As EntryInfo = ret(ret.Count - 1)
                                    'ex. 625907   621601  99% 14-12-11 00:47  .....A.   0DEAF445 m5e 2.9
                                    Dim Pattern As String = "\s*(\d+)\s+(\d+)\s+(\d+)%\s+(\d+-\d+-\d+\s+\d+:\d+)\s*[^0-9 \t\r\n\v\f]+\s*([0-9a-f]+)"
                                    For Each Matches As System.Text.RegularExpressions.Match In
                                        System.Text.RegularExpressions.Regex.Matches(outLine.Data, Pattern,
                                                                                     System.Text.RegularExpressions.RegexOptions.IgnoreCase)
                                        AddItem.ExtractSize = CInt(Matches.Groups(1).ToString)
                                        AddItem.CompressedSize = CInt(Matches.Groups(2).ToString)
                                        AddItem.CompressionRatio = CDbl(Matches.Groups(3).ToString) / 100.0
                                        AddItem.ModifiedTime = Nothing
                                        Dim trydate As DateTime
                                        Dim strtime As String = Matches.Groups(4).ToString
                                        If DateTime.TryParseExact(Matches.Groups(4).ToString, "d-M-y H:m", Nothing, Globalization.DateTimeStyles.None, trydate) Then
                                            AddItem.ModifiedTime = trydate
                                        End If
                                        Dim trycrc As UInt32
                                        If UInt32.TryParse(Matches.Groups(5).ToString, Globalization.NumberStyles.HexNumber, Nothing, trycrc) Then AddItem.Crc32 = trycrc
                                        ret(ret.Count - 1) = AddItem
                                        Exit For
                                    Next
                                    isPathLine = True
                                Else
                                    isPassSign = isPassSign OrElse (outLine.Data.Substring(0, 1) = "*")
                                    If Not ret Is Nothing Then
                                        Dim AddItem As New EntryInfo
                                        AddItem.EntryPath = outLine.Data.Substring(1).Trim
                                        If Not String.IsNullOrWhiteSpace(AddItem.EntryPath.Replace("-", "")) Then
                                            ret.Add(AddItem)
                                        Else
                                            isReadEnd = True
                                        End If
                                    End If
                                    isPathLine = False
                                End If
                            End If
                        End SyncLock
                    End If
                End Sub

                Public Sub New(Optional ByVal isCreateList As Boolean = True)
                    isPassSign = False
                    isReadStart = False
                    isReadEnd = False
                    ret = Nothing
                    If isCreateList Then ret = New Collections.Generic.List(Of EntryInfo)
                End Sub

                Public Function Execute(ByVal SrcArcPath As Logic.FileSystem.Path, ByVal BaseConfig As RarOptionConfig) As RESULT

                    If SrcArcPath.FileExists = False Then Return RESULT.NO_FILE
                    If IO.File.Exists(RarOptionConfig.RarPath) = False Then Return RESULT.ARCHIVE_LIB_NOT_FOUND
                    If BaseConfig Is Nothing Then Return RESULT.NO_OBJECT

                    If isExecute Then Return RESULT.ARCHIVE_EXTRACT_INVALID_PARAMS
                    isExecute = True

                    Execute = RESULT.OK
                    Dim ps As New Process
                    Try
                        AddHandler ps.OutputDataReceived, AddressOf ConsoleReader
                        With ps.StartInfo
                            .FileName = RarOptionConfig.RarPath
                            .Arguments = "v """ & SrcArcPath.ToString & """ *.*"
                            .CreateNoWindow = True
                            .UseShellExecute = False
                            .RedirectStandardOutput = True
                        End With
                        ps.Start()
                        ps.BeginOutputReadLine()
                        ps.WaitForExit()
                        Select Case ps.ExitCode
                            Case EXITCODE.SUCCESS
                                ' do nothing
                            Case EXITCODE.NO_FILES_ERROR, EXITCODE.OPEN_ERROR
                                Execute = RESULT.NO_FILE
                            Case EXITCODE.CRC_ERROR
                                Execute = RESULT.ARCHIVE_CRC_ERR
                            Case EXITCODE.USER_BREAK
                                Execute = RESULT.USER_CANCEL
                        End Select
                    Catch ex As Exception
                        Execute = RESULT.ARCHIVE_EXTRACT_ERR
                    Finally
                        ps.Close()
                        ps.Dispose()
                        ps = Nothing
                    End Try

                    If Execute <> RESULT.OK And isPassSign Then Execute = RESULT.ARCHIVE_PASSWORD_MISSMATCH
                    If Execute <> RESULT.OK Or ret Is Nothing Then Exit Function

                End Function

                Public Function ExitPathArray() As String()
                    Dim ary() As String = Nothing
                    If Not ret Is Nothing AndAlso 0 < ret.Count Then
                        ReDim ary(ret.Count - 1)
                        Logic.CopyTo(ary, ret)
                    End If
                    Return ary
                End Function

                Public Function ExitArray() As EntryInfo()
                    Dim ary() As EntryInfo = Nothing
                    If Not ret Is Nothing AndAlso 0 < ret.Count Then
                        ReDim ary(ret.Count - 1)
                        ret.CopyTo(ary)
                    End If
                    Return ary
                End Function

                Public Function IsNeedPass() As Boolean
                    Return isPassSign
                End Function

                Public Shared Function IsNeedPass(ByVal Path As String, ByVal BaseConfig As RarOptionConfig) As RESULT
                    Dim GetEntryListImpl As New GetEntryListImplement(False)
                    IsNeedPass = GetEntryListImpl.Execute(Path, BaseConfig)
                    If IsNeedPass <> RESULT.OK Then Exit Function
                    If GetEntryListImpl.IsNeedPass Then Return RESULT.ARCHIVE_PASSWORD_MISSMATCH
                End Function

            End Class

            Private Sub DefaultConsoleEcho(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
                If Not String.IsNullOrEmpty(outLine.Data) And AppBase IsNot Nothing Then AppBase.Echoing.Log.Echo(outLine.Data, AppendLogArgs.LogType.ConsoleOut)
            End Sub

            Private Sub ExtractProgressEcho(ByVal sendingProcess As Object, ByVal outLine As DataReceivedEventArgs)
                If Not String.IsNullOrEmpty(outLine.Data) Then
                    Dim Pattern As String = ".+\s+([0-9]+)%"
                    For Each Matches As System.Text.RegularExpressions.Match In
                        System.Text.RegularExpressions.Regex.Matches(outLine.Data, Pattern,
                                                                     System.Text.RegularExpressions.RegexOptions.IgnoreCase)
                        Try
                            Dim nVal As Integer = CInt(Matches.Groups(1).ToString)
                            If 0 <= nVal And nVal <= 100 And AppBase IsNot Nothing Then AppBase.Echoing.Progress.Echo(nVal, 100)
                        Catch
                        End Try
                    Next
                    If AppBase IsNot Nothing Then AppBase.Echoing.Log.Echo(outLine.Data, AppendLogArgs.LogType.ConsoleOut)
                End If
            End Sub

        End Class

    End Class
End Class


