Compare commits

..

No commits in common. "master" and "v2.03" have entirely different histories.

1 changed files with 32 additions and 66 deletions

98
kitsune
View File

@ -12,19 +12,14 @@ class WatchedFile:
def __init__(self, path, seek_to_end): def __init__(self, path, seek_to_end):
self.path = path self.path = path
self.seek_to_end = seek_to_end self.seek_to_end = seek_to_end
stf = os.stat(self.path) self.reopen()
self.st_ctime_ns = stf.st_ctime_ns
self.st_size = stf.st_size
self.fileno = None
if stf.st_ctime_ns >= time.time_ns() - 1E9:
self.reopen()
def reopen(self): def reopen(self):
self.fd = open(self.path, errors='replace') self.fd = open(self.path, errors='replace')
self.fileno = self.fd.fileno() self.fileno = self.fd.fileno()
self.last_ts = 0 self.last_ts = 0
if self.seek_to_end: if self.seek_to_end:
self.fd.seek(self.st_size, 0) self.fd.seek(0, 2)
self.seek_to_end = False self.seek_to_end = False
def format_ts(ts_ns): def format_ts(ts_ns):
@ -54,63 +49,40 @@ def watch(args):
if st.st_mtime_ns > last_ts: if st.st_mtime_ns > last_ts:
last_ts = st.st_mtime_ns last_ts = st.st_mtime_ns
for de in os.scandir(d): for de in os.scandir(d):
try: if de.is_file():
if de.is_file(): if args.match_filename is None or fnmatch.fnmatch(de.name, args.match_filename):
if args.match_filename is None or fnmatch.fnmatch(de.name, args.match_filename): if de.path not in watched_files:
if de.path not in watched_files: watched_files[de.path] = WatchedFile(de.path, seek_to_end)
watched_files[de.path] = WatchedFile(de.path, seek_to_end) if len(de.path) > filename_length:
if len(de.path) > filename_length: filename_length = len(de.path)
filename_length = len(de.path)
except (PermissionError, OSError):
# ignore,
# or maybe remove from watched_files?
# or just mark as unavailable?
pass
# has any of the files changed # has any of the files changed
for f in watched_files.values(): for f in watched_files.values():
# XXX - we should also detect replaced files. # XXX - we should also detect replaced files.
if f.fileno: st = os.stat(f.fileno)
st = os.stat(f.fileno) try:
try: stf = os.stat(f.path)
stf = os.stat(f.path) if stf.st_ino != st.st_ino:
if stf.st_ino != st.st_ino: f.reopen()
f.reopen() st = os.stat(f.fileno)
st = os.stat(f.fileno) except FileNotFoundError:
except (FileNotFoundError, PermissionError): # ignore,
# ignore, # or maybe remove from watched_files?
# or maybe remove from watched_files? # or just mark as deleted?
# or just mark as deleted? pass
pass if st.st_mtime_ns > f.last_ts:
if st.st_mtime_ns > f.last_ts: if st.st_size < f.fd.tell():
if st.st_size < f.fd.tell(): # We are beyond the end of the file, so it has probably
# We are beyond the end of the file, so it has probably # been truncated and rewritten - read from beginning
# been truncated and rewritten - read from beginning f.fd.seek(0, 0)
f.fd.seek(0, 0) dead = "✝" if st.st_nlink == 0 else " "
dead = "✝" if st.st_nlink == 0 else " " f.last_ts = st.st_mtime_ns
f.last_ts = st.st_mtime_ns new_content = f.fd.read()
new_content = f.fd.read() lines = new_content.split("\n")
lines = new_content.split("\n") if lines[-1] == "":
if lines[-1] == "": lines.pop()
lines.pop() for ln in lines:
for ln in lines: print(f"{f.path:{filename_length}}", format_ts(f.last_ts), dead, ln)
s = ""
if args.print_filename:
s += f"{f.path:{filename_length}} "
if args.print_timestamp:
s += format_ts(f.last_ts) + " "
s += dead + " " + ln
print(s)
else:
try:
stf = os.stat(f.path)
if stf.st_ctime_ns != f.st_ctime_ns:
f.reopen()
except (FileNotFoundError, PermissionError):
# ignore,
# or maybe remove from watched_files?
# or just mark as deleted?
pass
time.sleep(0.1) time.sleep(0.1)
seek_to_end = False seek_to_end = False
@ -121,12 +93,6 @@ if __name__ == "__main__":
ap.add_argument("--match-filename", ap.add_argument("--match-filename",
help="follow only matching files in directories", help="follow only matching files in directories",
metavar="GLOB-PATTERN") metavar="GLOB-PATTERN")
ap.add_argument("--no-filename", "--no-print-filename", action="store_false",
dest="print_filename",
help="don't print filename in each line")
ap.add_argument("--no-timestamp", "--no-print-timestamp", action="store_false",
dest="print_timestamp",
help="don't print timestamp in each line")
ap.add_argument("files", nargs="*", default=["."], ap.add_argument("files", nargs="*", default=["."],
metavar="file") metavar="file")
args = ap.parse_args() args = ap.parse_args()