forked from mit-athena/delete
-
Notifications
You must be signed in to change notification settings - Fork 0
/
undelete
executable file
·161 lines (148 loc) · 5.58 KB
/
undelete
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/usr/bin/python
import logging
import optparse
import os
import shutil
import stat
import sys
import libdelete
logger = logging.getLogger('undelete')
whoami = os.path.basename(sys.argv[0])
def debug_callback(option, opt_str, value, parser):
"""
An OptionParser callback that enables debugging.
"""
all_loggers = [logger.name, 'libdelete']
loggers = [x.strip() for x in value.split(',')]
if value.lower() == 'all':
loggers = all_loggers
else:
if not set(loggers) == set(all_loggers):
parser.error('Valid debug targets: {0}'.format(
", ".join(all_loggers)))
for l in loggers:
logging.getLogger(l).setLevel(logging.DEBUG)
def ask(question, *args, **kwargs):
"""
Ask a question, possibly prepended with the name of the program
and determine whether the user answered in the affirmative
"""
yes = ('y', 'yes')
prepend = '' if kwargs.get('nowhoami', False) else "{0}: ".format(whoami)
try:
return raw_input("%s%s " % (prepend,
question % args)).strip().lower() in yes
except KeyboardInterrupt:
sys.exit(0)
def perror(message, **kwargs):
"""
Format an error message, log it in the debug log
and maybe also print it to stderr.
"""
should_print = kwargs.pop('_maybe', False)
msg = "{0}: {1}".format(whoami, message.format(**kwargs))
logger.debug("Error: %s", msg)
if should_print:
print >>sys.stderr, msg
def actually_undelete(filename, options):
undeleted_name = libdelete.undeleted_name(filename)
logger.debug("actually_undelete(%s)", filename)
logger.debug("undeleted name: %s", undeleted_name)
if options.interactive and not ask('Undelete %s%s?', 'directory' if os.path.isdir(filename) else '', filename):
return False
if os.path.exists(undeleted_name):
if not options.force:
if not ask('Undeleted %s exists. Remove it?', undeleted_name):
return False
if os.path.isdir(undeleted_name) and \
not os.path.islink(undeleted_name) and \
not libdelete.is_mountpoint(undeleted_name):
shutil.rmtree(undleted_name)
else:
os.unlink(undeleted_name)
if options.noop:
print >>sys.stderr, "{0}: {1} would be undeleted".format(whoami, filename)
return True
os.rename(filename, undeleted_name)
print >>sys.stderr, "{0}: {1} undeleted".format(whoami, filename)
return True
def undelete(filename, options):
r_undel = None
if options.dirsonly:
r_undel = False
r_del = None
if options.recursive:
r_del = True
if options.dirsonly:
r_del = False
deleted_files = libdelete.find_deleted_files(
filename,
recurse_undeleted_subdirs=r_undel,
recurse_deleted_subdirs=r_del,
follow_links=True, follow_mounts=True)
if len(deleted_files) == 0:
perror("{0}: No such file or directory".format(filename), _maybe=options.report_errors)
return False
deleted_files.sort(reverse=True, key=lambda x: x.count(os.path.sep))
for f in deleted_files:
actually_undelete(f, options)
return True
def get_filenames_from_stdin(options):
errors = 0
if options.verbose:
print "Enter the files to be undeleted, one file per line."
print "Hit <RETURN> on a line by itself to exit.\n"
while True:
try:
filename = raw_input("{0}: ".format(whoami))
except (EOFError, KeyboardInterrupt):
sys.exit(errors)
if len(filename) == 0:
sys.exit(errors)
try:
undelete(filename, options)
except libdelete.DeleteError as e:
perror(e.message, _maybe=options.report_errors)
errors = 1
# Control should never get here, but just for good measure
sys.exit(errors)
def main():
rv = 0
parser = optparse.OptionParser(usage="%prog [options] filename ...")
parser.add_option(
"-r", dest="recursive", action="store_true", default=False,
help="Recursively delete non-empty directories")
parser.add_option(
"-f", dest="force", action="store_true", default=False,
help="Do not ask questions or report errors about nonexistent files")
parser.add_option(
"-i", dest="interactive", action="store_true", default=False,
help="Prompt for confirmation before deleting each file/directory")
parser.add_option(
"-n", dest="noop", action="store_true", default=False,
help="Don't actually delete, just print what would be deleted")
parser.add_option(
"-v", dest="verbose", action="store_true", default=False,
help="Print each filename as it is deleted")
parser.add_option(
"-R", dest="dirsonly", action="store_true", default=False,
help="directories only (no recursion)")
parser.add_option(
"--debug", action="callback", type='string', help="Enable debugging (logger target or 'all')",
callback=debug_callback, metavar='target')
(options, args) = parser.parse_args()
if options.recursive and options.dirsonly:
parser.error("-r and -R are mutually exclusive")
options.report_errors = not options.force
if len(args) < 1:
get_filenames_from_stdin(options)
for filename in args:
try:
undelete(filename, options)
except libdelete.DeleteError as e:
perror(e.message, _maybe=options.report_errors)
rv = 1
return rv
if __name__ == "__main__":
logging.basicConfig(level=logging.WARNING)
sys.exit(main())