WIP: Improved stat subcommand
authorMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Fri, 4 Jul 2014 14:12:31 +0000 (17:12 +0300)
committerMarkus Lehtonen <markus.lehtonen@linux.intel.com>
Wed, 24 Sep 2014 11:49:19 +0000 (14:49 +0300)
Change-Id: I93f6b076dd9abc56b09302fa825fa91602c8392b
Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
repocache_adm/common.py

index 197828b280e22a821bbac2ebbaaffe3c284b9939..0239c2d4d7716b5e9d12154377cbecb044e5da3b 100644 (file)
 # MA 02110-1301, USA.
 """Common functionality of the adm module"""
 
+import os
+import subprocess
+
+from collections import defaultdict
+from datetime import datetime
+
+
+class AdmError(Exception):
+    """Exception class for this module"""
+    pass
+
 
 def pprint_sz(size):
     """Pretty print file size in human readable format
@@ -42,6 +53,115 @@ def pprint_sz(size):
             break
     return "%.1f %s" % (float(size) / pow(1024, power - 1), unit)
 
+def run_at(cmd, path):
+    """Run command in cwd"""
+    popen = subprocess.Popen(cmd, cwd=path,
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, err = popen.communicate()
+    if popen.returncode:
+        raise AdmError("Failed to run '%s' in '%s': %s" %
+                        (' '.join(cmd), path, err))
+    return out
+
+def git_cmd(cmd, args, path):
+    """Run git command in repo"""
+    par_path = os.path.abspath(os.path.join(path, '..'))
+    cmd = ['git', cmd] + args
+    popen = subprocess.Popen(cmd, cwd=path,
+                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                             env={'GIT_CEILING_DIRECTORIES': par_path})
+    out, err = popen.communicate()
+    if popen.returncode:
+        raise AdmError('Git command (%s) in %s failed: %s' %
+                        (' '.join(cmd), path, err))
+    return out
+
+def repo_is_bare(path):
+    """Check if a path is a bare repository"""
+    try:
+        out = git_cmd('rev-parse', ['--is-bare-repository'], path)
+    except AdmError:
+        raise AdmError('Not a git repository at %s' % path)
+    return out.splitlines()[0].strip() == 'true'
+
+def _file_stat(path):
+    """Get repocache stat of file"""
+    if not os.path.isfile(path):
+        raise AdmError('%s is not a file' % path)
+
+    stat = {}
+    fstat = os.stat(path)
+
+    stat['path'] = os.path.abspath(path)
+    stat['du'] = fstat.st_blocks * fstat.st_blksize
+    #stat['mtime'] = datetime.fromtimestamp(fstat.st_mtime)
+    stat['mtime'] = str(datetime.fromtimestamp(fstat.st_mtime))
+
+    return stat
+
+def _repo_stat(path):
+    """Get repocache stat of a git repository"""
+
+    stat = {}
+    stat['bare'] = repo_is_bare(path)
+
+    remote = git_cmd('remote', ['-v'], path).splitlines()[0]
+    stat['remote_url'] = remote.split(None, 1)[1].rsplit(None, 1)[0]
+
+    return stat
+
+def _list_file_stat(path):
+    """Get stat of all non-directory files in a directory"""
+    stat = {}
+    for fname in os.listdir(path):
+        if not os.path.isdir(os.path.join(path, fname)):
+            stat[fname] = _file_stat(os.path.join(path, fname))
+    return stat
+
+def cache_stat(path):
+    """Get stat of a repo cache"""
+
+    if not os.path.isdir(path):
+        raise AdmError("Repocache basedir '%s' not found" % path)
+
+    stat = {'dirs': defaultdict(dict)}
+
+
+    # Get disk usage of directories, du output is something like:
+    #   1234  ./foo/bar
+    #   4567  ./foo
+    #   8901  .
+    out = run_at(['du', '-d2', '-B1', '-0'], path)
+    d_u = [(split[1], split[0]) for split in
+            [line.split() for line in out.split('\0') if line]]
+
+    for entry in d_u:
+        base, sub = os.path.split(entry[0])
+        if sub == '.':
+            dstat = stat
+        elif base == '.':
+            dstat = stat['dirs'][sub]
+        else:
+            basedir = os.path.split(base)[1]
+            if 'dirs' not in stat['dirs'][basedir]:
+                stat['dirs'][basedir]['dirs'] = {sub: {}}
+            else:
+                stat['dirs'][basedir]['dirs'][sub] = {}
+            dstat = stat['dirs'][basedir]['dirs'][sub]
+            try:
+                dstat['repostat'] = _repo_stat(os.path.join(path, base, sub))
+            except AdmError:
+                pass
+
+        dstat['path'] = os.path.abspath(os.path.normpath(os.path.join(path,
+                                                                    entry[0])))
+        dstat['du'] = entry[1]
+        # Don't list files inside repos
+        if not 'repostat' in dstat:
+            stat['files'] = _list_file_stat(dstat['path'])
+
+    return stat
+
 
 class SubcommandBase(object):
     """Base class / API for subcommand implementations"""