blimp.nim 4.58 KB
import md5, os, osproc, parseopt2, strutils, parsecfg, streams

# blimp is a little utility program for handling large files
# in git repositories. Its inspired by git-fat and s3annex
# but doesn't rely on S3 for storage, is a single binary without
# need for Python, and has less features than git-fat. So far.
#
# Manual use:
#
# Use "blimp d mybigfile" to deflate it before commit.
# Use "blimp i mybigfile" to inflate it back to original size.
#
# When deflated the file only has an md5sum string inside it.
#
# The file is copied over into:
#  <homedir>/blimpStore/<originalfilename>-<md5sum>
#
# Configuration is in:
#   <gitroot>/.blimp.conf
#   ~/blimpstore/.blimp.conf

var blimpStore, remoteBlimpStore: string


# Load blimp.conf file, overkill for now but...
proc parseConfFile(filename: string) =
  echo filename
  var f = newFileStream(filename, fmRead)
  if f != nil:
    var p: CfgParser
    open(p, f, filename)
    while true:
      var e = next(p)
      case e.kind
      of cfgEof: 
        break
      of cfgSectionStart:
        continue # Ignore
      of cfgKeyValuePair:
        if e.key == "remote":
          remoteBlimpStore = e.value
        else:
          quit("Unknown configuration: " & e.key)
      of cfgOption:
        quit("Unknown configuration: " & e.key)
      of cfgError:
        quit("Parsing " & filename & ": " & e.msg)
    close(p)


# Upload a file to the remote master blimpStore
proc uploadFile(blimpFilename: string) =
  if remoteBlimpStore.isNil:
    echo("Remote blimpstore not set in configuration file, not uploading content:\n\t" & blimpFilename)
    return
  let errorCode = execCmd("rsync -a " & blimpStore / blimpFilename & " " & remoteBlimpStore)
  if errorCode != 0: quit("Something went wrong uploading content to " & remoteBlimpStore, 2)
  
# Download a file to the remote master blimpStore
proc downloadFile(blimpFilename: string) =
  if remoteBlimpStore.isNil:
    quit("Remote blimpstore not set in configuration file, can not download content:\n\t" & blimpFilename)
  let errorCode = execCmd("rsync -a " & remoteBlimpStore / blimpFilename & " " & blimpStore / "")
  if errorCode != 0: quit("Something went wrong downloading " & blimpFilename & " from " & remoteBlimpStore, 3)


# Copy content to blimpStore, no upload yet.
proc copyToBlimpStore(filename, blimpFilename: string) =
  if not existsFile(blimpStore / blimpFilename):
    copyFile(filename, blimpStore / blimpFilename)
    uploadFile(blimpFilename)

# Copy content from blimpStore, and downloading first if needed
proc copyFromblimpStore(blimpFilename, filename: string) =
  if not existsFile(blimpStore / blimpFilename):
    downloadFile(blimpFilename)
  copyFile(blimpStore / blimpFilename, filename)

    
# Copy original file to blimpStore and replace with hash stub in git.
proc deflate(filename: string) =
  let content = readFile(filename)
  if content[0..4] == "hash:":
    quit("File is already deflated, ignored.", 5)
  let hash = getMD5(content)
  let blimpFilename = filename & "-" & hash
  copyToBlimpStore(filename, blimpFilename)
  writeFile(filename, "hash:" & blimpFilename)

# Parse out hash from hash stub and copy back original content from blimpStore.
proc inflate(filename: string) =
  var hashfile: File
  if not open(hashfile, filename):
    quit("Could not open file: " & filename, 4)
  let hashline = split(string(readLine(hashfile)), {':'})
  if hashline[0] == "hash":
    let blimpfilename = hashline[1]
    #removeFile(filename)
    copyFromblimpStore(blimpfilename, filename)
  else:
    quit("File is not a blimp file.", 5)

# Find git root dir or fall back on current dir
proc gitRoot(): string =
  try:
    let tup = execCmdEx("git rev-parse --show-toplevel")
    if tup[1] == 0:
      result = tup[0]
    else:
      result = getCurrentDir()
  except:
    result = getCurrentDir()

################################ main #####################################

# Hardwired to "blimpstore" directory in home dir.
blimpStore = getHomeDir() / "blimpstore"

# Make sure we have the dir, or create it.
try:
  if not existsDir(blimpStore): createDir(blimpStore)
except:
  quit("Could not create " & blimpStore & " directory.", 1)


# Parse configuration files if they exist
parseConfFile(gitRoot() / ".blimp.conf")
parseConfFile(blimpStore / ".blimp.conf")

# Only a command and a path as argument
# TODO: Change to lapp
let argv = commandLineParams()
let command = argv[0]
let filename = argv[1]


# Do the deed
if command == "d" or command == "deflate":
  deflate(filename)
elif command == "i" or command == "inflate":
  inflate(filename)
else:
  quit("Unknown command, only (d)eflate or (i)inflate are valid.", 6)

# All good
quit(0)