summaryrefslogtreecommitdiff
path: root/avfs.py
blob: 5ab7beaaa0b68f67398a47ba50003ca337319417 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#!/usr/bin/env python3

"""
Traversing directories and archives with AVFS/fuse
"""

import os
import os.path
import subprocess
import stat

avfs_sys_mount = os.fsencode(os.environ['HOME'] + '/.avfs')
avfs_started = False

def sys_name(fpath):
	global avfs_started
	if not avfs_started:
		subprocess.check_call(['mountavfs'])
		avfs_started = True
	return avfs_sys_mount + os.path.abspath(fpath)

def exists(fpath):
	return os.path.exists(sys_name(fpath))

def isdir(fpath):
	return os.path.isdir(sys_name(fpath))

orig_open = open
def open(fpath, *pargs, **kwargs):
	return orig_open(sys_name(fpath), *pargs, **kwargs)

# AVFS has its own automatic view selection using file extensions, but it
# includes plugins (like #patch) that will lead us into an infinite loop
# if we try to do a directory traversal.  Also, there are a few
# extensions we want to add.

avfscmds = {
	(b'.gz', b'#ugz'),
	(b'.tgz', b'#ugz#utar'),
	(b'.tar.bz2', b'#ubz2#utar'),
	(b'.bz2', b'#ubz2'),
	(b'.bz', b'#ubz2'),
	(b'.tbz2', b'#ubz2#utar'),
	(b'.tbz', b'#ubz2#utar'),
	(b'.Z', b'#uz'),
	(b'.tpz', b'#uz#utar'),
	(b'.tz', b'#uz#utar'),
	(b'.taz', b'#uz#utar'),
	(b'.a', b'#uar'),
	(b'.deb', b'#uar'),
	(b'.tar', b'#utar'),
	(b'.gem', b'#utar'),    # Add upstream
	(b'.rar', b'#urar'),
	(b'.sfx', b'#urar'),
	(b'.zip', b'#uzip'),
	(b'.jar', b'#uzip'),
	(b'.ear', b'#uzip'),
	(b'.war', b'#uzip'),
	(b'.nupkg', b'#uzip'),  # Add upstream
	(b'.whl', b'#uzip'),    # Add upstream
	(b'.7z', b'#u7z'),
	(b'.zoo', b'#uzoo'),
	(b'.lha', b'#ulha'),
	(b'.lhz', b'#ulha'),
	(b'.arj', b'#uarj'),
	(b'.cpio', b'#ucpio'),
	(b'.rpm', b'#rpm'),
	(b'.tar.xz', b'#uxze#utar'),
	(b'.txz', b'#uxze#utar'),
	(b'.xz', b'#uxze'),
	(b'.lzma', b'#uxze'),
}

def guesscmd(filename):
	for ext, cmd in avfscmds:
		if filename.endswith(ext):
			return cmd + guesscmd(filename[:-len(ext)])
	return b''

def get_lstat_mode(filename):
	"""
	Get the st_mode stat value of the given file, or None if the stat fails
	(doesn't exist, I/O error)
	"""
	try:
		return os.lstat(filename).st_mode
	except:
		return None

def find(path, excludes):
	"""
	Recursively list all files under path, including files in archives
	supported by AVFS.
	"""
	sys_path = sys_name(path)
	name = os.path.basename(path)
	mode = get_lstat_mode(sys_path)

	if mode is None:
		return
	if name in excludes:
		return

	# If AVFS provides a cooked view of the file, substitute the view
	# for the original file.
	if stat.S_ISREG(mode):
		cmd = guesscmd(name)
		if cmd:
			cmd_mode = get_lstat_mode(sys_path + cmd)
			if cmd_mode is not None:
				path = path + cmd
				sys_path = sys_path + cmd
				name = name + cmd
				mode = cmd_mode

	if stat.S_ISDIR(mode):
		for entry in os.listdir(sys_path):
			yield from find(path + b'/' + entry, excludes)
	elif stat.S_ISREG(mode):
		yield path

if __name__ == "__main__":
	import sys
	for f in find(os.fsencode(sys.argv[1]), {b'.git'}):
		print(f.decode('utf-8', 'replace'))