Add command arbtt-recover
authorJoachim Breitner <mail@joachim-breitner.de>
Wed, 28 Oct 2009 18:01:28 +0000 (18:01 +0000)
committerJoachim Breitner <mail@joachim-breitner.de>
Wed, 28 Oct 2009 18:01:28 +0000 (18:01 +0000)
arbtt.cabal
doc/arbtt.xml
src/TimeLog.hs
src/recover-main.hs [new file with mode: 0644]

index ab63ac5..3823b27 100644 (file)
@@ -61,6 +61,16 @@ executable arbtt-dump
         TimeLog
         System.Locale.SetLocale
 
+executable arbtt-recover
+    main-is:            recover-main.hs
+    hs-source-dirs:     src
+    build-depends:
+        base == 4.*, parsec == 2.*, containers
+    other-modules:
+        Data
+        TimeLog
+        System.Locale.SetLocale
+
 source-repository head
     type:     darcs
     location: http://darcs.nomeata.de/arbtt
index 64015d5..8038453 100644 (file)
@@ -389,6 +389,11 @@ $ runhaskell Setup.hs install</screen>
     <command>arbtt-dump</command> can bee used to dump the data in
     textual form. Its command line reference is given in <xref
     linkend="arbtt-dump"/>.</para>
+
+    <para>If <command>arbtt-capture</command> crashes it might be that the log
+    file is not readable any more. In some cases, this can be fixed using the
+    (relatively raw) <command>arbtt-recover</command> tool. Its command line
+    reference is given in <xref linkend="arbtt-recover"/>.</para>
   <!--
   </sect2>
   -->
@@ -694,6 +699,78 @@ arbtt-stats -f '$sampleage &lt; 1:00'</screen>
       newer versions of arbtt.</para>
     </refsect1>
   </refentry>
+
+  <refentry id="arbtt-recover">
+    <refmeta>
+      <refentrytitle>arbtt-recover</refentrytitle>
+      <manvolnum>1</manvolnum>
+      <refmiscinfo class="source">arbtt manual</refmiscinfo>
+    </refmeta>
+
+    <refnamediv>
+      <refname>arbtt-recover</refname>
+      <refpurpose>tries to recover a broken arbtt data log</refpurpose>
+    </refnamediv>
+  
+    <refsynopsisdiv>
+      <cmdsynopsis>
+        <command>arbtt-recover</command>
+        <arg rep="repeat">OPTION</arg>
+      </cmdsynopsis>
+    </refsynopsisdiv>
+
+    <refsect1><title>Description</title>
+      <para>
+      <command>arbtt-recover</command> tries to readsthe data samples recorded
+      by <xref linkend="arbtt-capture"/>, skipping over possible broken entries. A fixed log file is written to <file>~/.arbtt/capture.log.recovered</file>.
+      </para>
+    </refsect1>
+
+    <refsect1><title>Options</title>
+      <variablelist>
+        <varlistentry>
+          <term><option>-h</option></term>
+          <term><option>-?</option></term>
+          <term><option>--help</option></term>
+          <listitem><simpara>shows a short summary of the available
+          options, and exists.</simpara></listitem>
+        </varlistentry>
+        <varlistentry>
+        <term><option>-V</option></term>
+        <term><option>--version</option></term>
+        <listitem><simpara>shows the version number, and exists.</simpara></listitem>
+       </varlistentry>
+        <varlistentry>
+        <term><option>-i</option></term>
+        <term><option>--infile</option></term>
+        <listitem><simpara>logfile to use instead of <filename>~/.arbtt/capture.log</filename></simpara></listitem>
+        </varlistentry>
+        <varlistentry>
+        <term><option>-o</option></term>
+        <term><option>--outfile</option></term>
+        <listitem><simpara>where to save the recovered file, instead of <filename>~/.arbtt/capture.log</filename></simpara></listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect1>
+    <refsect1><title>Files</title>
+      <variablelist>
+        <varlistentry>
+          <term><filename>~/.arbtt/capture.log</filename></term>
+          <listitem><para>binary file, storing the arbtt data samples</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><filename>~/.arbtt/capture.log.recovered</filename></term>
+          <listitem><para>binary file, storing the fixed arbtt data samples</para></listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect1>
+
+    <refsect1><title>See also</title>
+      <para>See the arbtt manual for more information and the <ulink
+      url="http://www.hackage.org/package/arbtt">arbtt hackage page</ulink> for
+      newer versions of arbtt.</para>
+    </refsect1>
+  </refentry>
 </sect1>
 
 <sect1 id="copyright">
@@ -744,6 +821,10 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
         <para>Implement option <option>--logfile</option> to
         <command>arbtt-dump</command></para>.
       </listitem>
+      <listitem>
+       <para>New command <command>arbtt-recover</command> to rescue data from
+       a proken data log file.</para>.
+      </listitem>
     </itemizedlist>
   </sect2>
 
index a26c71c..e954e62 100644 (file)
@@ -12,8 +12,11 @@ import Data.Binary.Get
 import Data.Function
 import Data.Char
 import System.Directory
+import Control.Exception
+import Prelude hiding (catch)
 
 import qualified Data.ByteString.Lazy as BS
+import Data.Maybe
 
 magic = BS.pack $ map (fromIntegral.ord) "arbtt-timelog-v1\n"
 
@@ -39,6 +42,36 @@ writeTimeLog :: Binary a => FilePath -> TimeLog a -> IO ()
 writeTimeLog filename tl = do createTimeLog True filename
                              mapM_ (appendTimeLog filename) tl
 
+-- | This might be very bad style, and it hogs memory, but it might help in some situations...
+recoverTimeLog :: Binary a => FilePath -> IO (TimeLog a)
+recoverTimeLog filename = do
+       content <- BS.readFile filename
+        start content
+  where start content = do
+               let (startString, rest, _) = runGetState (getLazyByteString (BS.length magic)) content 0
+               if startString /= magic
+                 then do putStrLn $ "WARNING: Timelog starts with unknown marker " ++
+                               show (map (chr.fromIntegral) (BS.unpack startString))
+                 else do putStrLn $ "Found header, continuing... (" ++ show (BS.length rest) ++ " bytes to go)"
+               go rest
+        go input = do mb <- tryGet input
+                     flip (maybe (return [])) mb $
+                        \(v,rest,_) -> if BS.null rest then return [v]
+                                                       else (v :) <$> go rest
+       tryGet input = catch (
+                       do -- putStrLn $ "Trying value at offset " ++ show off
+                          let (v,rest,off) = runGetState get input 0
+                          evaluate rest
+                          return (Just (v,rest,off))
+                       ) (
+                       \e -> do
+                          putStrLn $ "Failed: " ++ show (e :: SomeException)
+                          if BS.length input <= 1
+                            then return Nothing
+                            else do putStrLn $ "Failed to read value, retrying with rest"-- ++ show (off+1)
+                                    tryGet (BS.tail input)
+                       )
+
 readTimeLog :: Binary a => FilePath -> IO (TimeLog a)
 readTimeLog filename = do
        content <- BS.readFile filename
diff --git a/src/recover-main.hs b/src/recover-main.hs
new file mode 100644 (file)
index 0000000..531d122
--- /dev/null
@@ -0,0 +1,67 @@
+module Main where
+import System.Directory
+import System.FilePath
+import System.Console.GetOpt
+import System.Environment
+import System.Exit
+import System.IO
+import qualified Data.Map as M
+import Data.Version (showVersion)
+import Data.Maybe
+import Control.Monad
+
+import TimeLog
+import Data
+
+import Paths_arbtt (version)
+
+data Flag = Help | Version | InFile String | OutFile String
+        deriving Eq
+
+versionStr = "arbtt-recover " ++ showVersion version
+header = "Usage: arbtt-recover [OPTIONS...]"
+
+options :: [OptDescr Flag]
+options =
+     [ Option "h?"     ["help"]
+              (NoArg Help)
+             "show this help"
+     , Option "V"      ["version"]
+              (NoArg Version)
+             "show the version number"
+     , Option "i"      ["infile"]
+              (ReqArg InFile "FILE")
+              "read from this file instead of ~/.arbtt/capture.log"
+     , Option "o"      ["outfile"]
+              (ReqArg OutFile "FILE")
+              "write to this file instead of ~/.arbtt/capture.log.recovered"
+     ]
+
+
+main = do
+  args <- getArgs
+  flags <- case getOpt Permute options args of
+          (o,[],[]) | Help `notElem` o  && Version `notElem` o -> return o
+          (o,_,_) | Version `elem` o -> do
+                hPutStrLn stderr versionStr
+                exitSuccess
+          (o,_,_) | Help `elem` o -> do
+                hPutStr stderr (usageInfo header options)
+                exitSuccess
+          (_,_,errs) -> do
+                hPutStr stderr (concat errs ++ usageInfo header options)
+                exitFailure
+
+  dir <- getAppUserDataDirectory "arbtt"
+
+  let captureFilename =
+       fromMaybe (dir </> "capture.log") $ listToMaybe $
+       mapMaybe (\f -> case f of { InFile f -> Just f; _ -> Nothing}) $
+       flags
+  let saveFilename =
+       fromMaybe (dir </> "capture.log.recovered") $ listToMaybe $
+       mapMaybe (\f -> case f of { OutFile f -> Just f; _ -> Nothing}) $
+       flags
+
+  captures <- recoverTimeLog captureFilename :: IO (TimeLog CaptureData)
+  writeTimeLog saveFilename captures