mirror of
https://github.com/9001/copyparty.git
synced 2026-02-26 21:43:08 -07:00
tail/follow: add windows support; closes #1262
This commit is contained in:
parent
4cb4e820f6
commit
a368fc66b3
|
|
@ -2424,6 +2424,7 @@ buggy feature? rip it out by setting any of the following environment variables
|
||||||
|
|
||||||
| env-var | what it does |
|
| env-var | what it does |
|
||||||
| -------------------- | ------------ |
|
| -------------------- | ------------ |
|
||||||
|
| `PRTY_NO_CTYPES` | do not use features from external libraries such as kernel32 |
|
||||||
| `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
|
| `PRTY_NO_DB_LOCK` | do not lock session/shares-databases for exclusive access |
|
||||||
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
|
| `PRTY_NO_IFADDR` | disable ip/nic discovery by poking into your OS with ctypes |
|
||||||
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
|
| `PRTY_NO_IMPRESO` | do not try to load js/css files using `importlib.resources` |
|
||||||
|
|
@ -3088,6 +3089,7 @@ set any of the following environment variables to disable its associated optiona
|
||||||
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
|
| `PRTY_NO_PILF` | disable Pillow `ImageFont` text rendering, used for folder thumbnails |
|
||||||
| `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) |
|
| `PRTY_NO_PIL_AVIF` | disable Pillow avif support (internal and/or [plugin](https://pypi.org/project/pillow-avif-plugin/)) |
|
||||||
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pillow-heif/) |
|
| `PRTY_NO_PIL_HEIF` | disable 3rd-party Pillow plugin for [HEIF support](https://pypi.org/project/pillow-heif/) |
|
||||||
|
| `PRTY_NO_PIL_JXL` | disable 3rd-party Pillow plugin for [JXL support](https://pypi.org/project/pillow-jxl-plugin/) |
|
||||||
| `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
|
| `PRTY_NO_PIL_WEBP` | disable use of native webp support in Pillow |
|
||||||
| `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
|
| `PRTY_NO_PSUTIL` | do not use [psutil](https://pypi.org/project/psutil/) for reaping stuck hooks and plugins on Windows |
|
||||||
| `PRTY_NO_PYFTPD` | disable ftp(s) server ([pyftpdlib](https://pypi.org/project/pyftpdlib/)-based) |
|
| `PRTY_NO_PYFTPD` | disable ftp(s) server ([pyftpdlib](https://pypi.org/project/pyftpdlib/)-based) |
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ from .util import (
|
||||||
Daemon,
|
Daemon,
|
||||||
align_tab,
|
align_tab,
|
||||||
b64enc,
|
b64enc,
|
||||||
|
ctypes,
|
||||||
dedent,
|
dedent,
|
||||||
has_resource,
|
has_resource,
|
||||||
load_resource,
|
load_resource,
|
||||||
|
|
@ -70,6 +71,7 @@ from .util import (
|
||||||
pybin,
|
pybin,
|
||||||
read_utf8,
|
read_utf8,
|
||||||
termsize,
|
termsize,
|
||||||
|
wk32,
|
||||||
wrap,
|
wrap,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -504,8 +506,10 @@ def sighandler(sig: Optional[int] = None, frame: Optional[FrameType] = None) ->
|
||||||
|
|
||||||
|
|
||||||
def disable_quickedit() -> None:
|
def disable_quickedit() -> None:
|
||||||
|
if not ctypes:
|
||||||
|
raise Exception("no ctypes")
|
||||||
|
|
||||||
import atexit
|
import atexit
|
||||||
import ctypes
|
|
||||||
from ctypes import wintypes
|
from ctypes import wintypes
|
||||||
|
|
||||||
def ecb(ok: bool, fun: Any, args: list[Any]) -> list[Any]:
|
def ecb(ok: bool, fun: Any, args: list[Any]) -> list[Any]:
|
||||||
|
|
@ -515,10 +519,10 @@ def disable_quickedit() -> None:
|
||||||
raise ctypes.WinError(err) # type: ignore
|
raise ctypes.WinError(err) # type: ignore
|
||||||
return args
|
return args
|
||||||
|
|
||||||
k32 = ctypes.WinDLL(str("kernel32"), use_last_error=True) # type: ignore
|
|
||||||
if PY2:
|
if PY2:
|
||||||
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
|
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
|
||||||
|
|
||||||
|
k32 = wk32
|
||||||
k32.GetStdHandle.errcheck = ecb # type: ignore
|
k32.GetStdHandle.errcheck = ecb # type: ignore
|
||||||
k32.GetConsoleMode.errcheck = ecb # type: ignore
|
k32.GetConsoleMode.errcheck = ecb # type: ignore
|
||||||
k32.SetConsoleMode.errcheck = ecb # type: ignore
|
k32.SetConsoleMode.errcheck = ecb # type: ignore
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ from .util import (
|
||||||
loadpy,
|
loadpy,
|
||||||
log_reloc,
|
log_reloc,
|
||||||
min_ex,
|
min_ex,
|
||||||
|
open_nolock,
|
||||||
pathmod,
|
pathmod,
|
||||||
quotep,
|
quotep,
|
||||||
rand_name,
|
rand_name,
|
||||||
|
|
@ -4833,7 +4834,7 @@ class HttpCli(object):
|
||||||
f = None
|
f = None
|
||||||
try:
|
try:
|
||||||
st = os.stat(abspath)
|
st = os.stat(abspath)
|
||||||
f = open(*open_args)
|
f = open_nolock(*open_args)
|
||||||
f.seek(0, os.SEEK_END)
|
f.seek(0, os.SEEK_END)
|
||||||
eof = f.tell()
|
eof = f.tell()
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
|
|
@ -4867,6 +4868,7 @@ class HttpCli(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
gone = 0
|
gone = 0
|
||||||
|
unsent = False
|
||||||
t_fd = t_ka = time.time()
|
t_fd = t_ka = time.time()
|
||||||
while True:
|
while True:
|
||||||
assert f # !rm
|
assert f # !rm
|
||||||
|
|
@ -4883,6 +4885,7 @@ class HttpCli(object):
|
||||||
t_fd = t_ka = now
|
t_fd = t_ka = now
|
||||||
self.s.sendall(buf)
|
self.s.sendall(buf)
|
||||||
sent += len(buf)
|
sent += len(buf)
|
||||||
|
unsent = False
|
||||||
dls[dl_id] = (time.time(), sent)
|
dls[dl_id] = (time.time(), sent)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -4893,14 +4896,16 @@ class HttpCli(object):
|
||||||
if t_fd < now - sec_fd:
|
if t_fd < now - sec_fd:
|
||||||
try:
|
try:
|
||||||
st2 = os.stat(open_args[0])
|
st2 = os.stat(open_args[0])
|
||||||
|
szd = st2.st_size - st.st_size
|
||||||
if (
|
if (
|
||||||
st2.st_ino != st.st_ino
|
st2.st_ino != st.st_ino
|
||||||
or st2.st_size < sent
|
or st2.st_size < sent
|
||||||
or st2.st_size < st.st_size
|
or szd < 0
|
||||||
|
or unsent
|
||||||
):
|
):
|
||||||
assert f # !rm
|
assert f # !rm
|
||||||
# open new file before closing previous to avoid toctous (open may fail; cannot null f before)
|
# open new file before closing previous to avoid toctous (open may fail; cannot null f before)
|
||||||
f2 = open(*open_args)
|
f2 = open_nolock(*open_args)
|
||||||
f.close()
|
f.close()
|
||||||
f = f2
|
f = f2
|
||||||
f.seek(0, os.SEEK_END)
|
f.seek(0, os.SEEK_END)
|
||||||
|
|
@ -4915,7 +4920,10 @@ class HttpCli(object):
|
||||||
ofs = sent # just new fd? resume from same ofs
|
ofs = sent # just new fd? resume from same ofs
|
||||||
f.seek(ofs)
|
f.seek(ofs)
|
||||||
self.log("reopened at byte %d: %r" % (ofs, abspath), 6)
|
self.log("reopened at byte %d: %r" % (ofs, abspath), 6)
|
||||||
|
unsent = False
|
||||||
gone = 0
|
gone = 0
|
||||||
|
elif szd:
|
||||||
|
unsent = True
|
||||||
st = st2
|
st = st2
|
||||||
except:
|
except:
|
||||||
gone += 1
|
gone += 1
|
||||||
|
|
@ -5029,7 +5037,7 @@ class HttpCli(object):
|
||||||
self.log("moved to tier %d (%s)" % (tier, tiers[tier]))
|
self.log("moved to tier %d (%s)" % (tier, tiers[tier]))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(ap_data, "rb", self.args.iobuf) as f:
|
with open_nolock(ap_data, "rb", self.args.iobuf) as f:
|
||||||
f.seek(lower)
|
f.seek(lower)
|
||||||
page = f.read(min(winsz, data_end - lower, upper - lower))
|
page = f.read(min(winsz, data_end - lower, upper - lower))
|
||||||
if not page:
|
if not page:
|
||||||
|
|
@ -5062,7 +5070,7 @@ class HttpCli(object):
|
||||||
break
|
break
|
||||||
|
|
||||||
if lower < upper and not broken:
|
if lower < upper and not broken:
|
||||||
with open(req_path, "rb") as f:
|
with open_nolock(req_path, "rb") as f:
|
||||||
remains = sendfile_py(
|
remains = sendfile_py(
|
||||||
self.log,
|
self.log,
|
||||||
lower,
|
lower,
|
||||||
|
|
|
||||||
|
|
@ -141,11 +141,23 @@ except:
|
||||||
HAVE_FICLONE = False
|
HAVE_FICLONE = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ctypes
|
|
||||||
import termios
|
import termios
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.environ.get("PRTY_NO_CTYPES"):
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
except:
|
||||||
|
ctypes = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
wk32 = ctypes.WinDLL(str("kernel32"), use_last_error=True) # type: ignore
|
||||||
|
except:
|
||||||
|
wk32 = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if os.environ.get("PRTY_NO_IFADDR"):
|
if os.environ.get("PRTY_NO_IFADDR"):
|
||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
@ -2887,7 +2899,7 @@ def get_df(abspath: str, prune: bool) -> tuple[int, int, str]:
|
||||||
bfree = ctypes.c_ulonglong(0)
|
bfree = ctypes.c_ulonglong(0)
|
||||||
btotal = ctypes.c_ulonglong(0)
|
btotal = ctypes.c_ulonglong(0)
|
||||||
bavail = ctypes.c_ulonglong(0)
|
bavail = ctypes.c_ulonglong(0)
|
||||||
ctypes.windll.kernel32.GetDiskFreeSpaceExW( # type: ignore
|
wk32.GetDiskFreeSpaceExW( # type: ignore
|
||||||
ctypes.c_wchar_p(abspath),
|
ctypes.c_wchar_p(abspath),
|
||||||
ctypes.pointer(bavail),
|
ctypes.pointer(bavail),
|
||||||
ctypes.pointer(btotal),
|
ctypes.pointer(btotal),
|
||||||
|
|
@ -4281,8 +4293,8 @@ def termsize() -> tuple[int, int]:
|
||||||
def hidedir(dp) -> None:
|
def hidedir(dp) -> None:
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
try:
|
try:
|
||||||
assert ctypes # type: ignore # !rm
|
assert wk32 # type: ignore # !rm
|
||||||
k32 = ctypes.WinDLL("kernel32")
|
k32 = wk32
|
||||||
attrs = k32.GetFileAttributesW(dp)
|
attrs = k32.GetFileAttributesW(dp)
|
||||||
if attrs >= 0:
|
if attrs >= 0:
|
||||||
k32.SetFileAttributesW(dp, attrs | 2)
|
k32.SetFileAttributesW(dp, attrs | 2)
|
||||||
|
|
@ -4357,6 +4369,31 @@ else:
|
||||||
lock_file = _lock_file_noop
|
lock_file = _lock_file_noop
|
||||||
|
|
||||||
|
|
||||||
|
def _open_nolock_windows(bap: Union[str, bytes], *a, **ka) -> typing.BinaryIO:
|
||||||
|
assert ctypes # !rm
|
||||||
|
assert wk32 # !rm
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
try:
|
||||||
|
ap = bap.decode("utf-8", "replace") # type: ignore
|
||||||
|
except:
|
||||||
|
ap = bap
|
||||||
|
|
||||||
|
fh = wk32.CreateFileW(ap, 0x80000000, 7, None, 3, 0x80, None)
|
||||||
|
# `-ap, GENERIC_READ, FILE_SHARE_READ|WRITE|DELETE, None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, None
|
||||||
|
if fh == -1:
|
||||||
|
ec = ctypes.get_last_error() # type: ignore
|
||||||
|
raise ctypes.WinError(ec) # type: ignore
|
||||||
|
fd = msvcrt.open_osfhandle(fh, os.O_RDONLY) # type: ignore
|
||||||
|
return os.fdopen(fd, "rb")
|
||||||
|
|
||||||
|
|
||||||
|
if ANYWIN:
|
||||||
|
open_nolock = _open_nolock_windows
|
||||||
|
else:
|
||||||
|
open_nolock = open
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if sys.version_info < (3, 10) or os.environ.get("PRTY_NO_IMPRESO"):
|
if sys.version_info < (3, 10) or os.environ.get("PRTY_NO_IMPRESO"):
|
||||||
# py3.8 doesn't have .files
|
# py3.8 doesn't have .files
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue