mirror of
https://github.com/9001/copyparty.git
synced 2026-02-19 11:54:58 -05:00
tail/follow: add windows support; closes #1262
This commit is contained in:
@@ -2424,6 +2424,7 @@ buggy feature? rip it out by setting any of the following environment variables
|
||||
|
||||
| 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_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` |
|
||||
@@ -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_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_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_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) |
|
||||
|
||||
@@ -63,6 +63,7 @@ from .util import (
|
||||
Daemon,
|
||||
align_tab,
|
||||
b64enc,
|
||||
ctypes,
|
||||
dedent,
|
||||
has_resource,
|
||||
load_resource,
|
||||
@@ -70,6 +71,7 @@ from .util import (
|
||||
pybin,
|
||||
read_utf8,
|
||||
termsize,
|
||||
wk32,
|
||||
wrap,
|
||||
)
|
||||
|
||||
@@ -504,8 +506,10 @@ def sighandler(sig: Optional[int] = None, frame: Optional[FrameType] = None) ->
|
||||
|
||||
|
||||
def disable_quickedit() -> None:
|
||||
if not ctypes:
|
||||
raise Exception("no ctypes")
|
||||
|
||||
import atexit
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
|
||||
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
|
||||
return args
|
||||
|
||||
k32 = ctypes.WinDLL(str("kernel32"), use_last_error=True) # type: ignore
|
||||
if PY2:
|
||||
wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
|
||||
|
||||
k32 = wk32
|
||||
k32.GetStdHandle.errcheck = ecb # type: ignore
|
||||
k32.GetConsoleMode.errcheck = ecb # type: ignore
|
||||
k32.SetConsoleMode.errcheck = ecb # type: ignore
|
||||
|
||||
@@ -90,6 +90,7 @@ from .util import (
|
||||
loadpy,
|
||||
log_reloc,
|
||||
min_ex,
|
||||
open_nolock,
|
||||
pathmod,
|
||||
quotep,
|
||||
rand_name,
|
||||
@@ -4833,7 +4834,7 @@ class HttpCli(object):
|
||||
f = None
|
||||
try:
|
||||
st = os.stat(abspath)
|
||||
f = open(*open_args)
|
||||
f = open_nolock(*open_args)
|
||||
f.seek(0, os.SEEK_END)
|
||||
eof = f.tell()
|
||||
f.seek(0)
|
||||
@@ -4867,6 +4868,7 @@ class HttpCli(object):
|
||||
pass
|
||||
|
||||
gone = 0
|
||||
unsent = False
|
||||
t_fd = t_ka = time.time()
|
||||
while True:
|
||||
assert f # !rm
|
||||
@@ -4883,6 +4885,7 @@ class HttpCli(object):
|
||||
t_fd = t_ka = now
|
||||
self.s.sendall(buf)
|
||||
sent += len(buf)
|
||||
unsent = False
|
||||
dls[dl_id] = (time.time(), sent)
|
||||
continue
|
||||
|
||||
@@ -4893,14 +4896,16 @@ class HttpCli(object):
|
||||
if t_fd < now - sec_fd:
|
||||
try:
|
||||
st2 = os.stat(open_args[0])
|
||||
szd = st2.st_size - st.st_size
|
||||
if (
|
||||
st2.st_ino != st.st_ino
|
||||
or st2.st_size < sent
|
||||
or st2.st_size < st.st_size
|
||||
or szd < 0
|
||||
or unsent
|
||||
):
|
||||
assert f # !rm
|
||||
# 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 = f2
|
||||
f.seek(0, os.SEEK_END)
|
||||
@@ -4915,7 +4920,10 @@ class HttpCli(object):
|
||||
ofs = sent # just new fd? resume from same ofs
|
||||
f.seek(ofs)
|
||||
self.log("reopened at byte %d: %r" % (ofs, abspath), 6)
|
||||
unsent = False
|
||||
gone = 0
|
||||
elif szd:
|
||||
unsent = True
|
||||
st = st2
|
||||
except:
|
||||
gone += 1
|
||||
@@ -5029,7 +5037,7 @@ class HttpCli(object):
|
||||
self.log("moved to tier %d (%s)" % (tier, tiers[tier]))
|
||||
|
||||
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)
|
||||
page = f.read(min(winsz, data_end - lower, upper - lower))
|
||||
if not page:
|
||||
@@ -5062,7 +5070,7 @@ class HttpCli(object):
|
||||
break
|
||||
|
||||
if lower < upper and not broken:
|
||||
with open(req_path, "rb") as f:
|
||||
with open_nolock(req_path, "rb") as f:
|
||||
remains = sendfile_py(
|
||||
self.log,
|
||||
lower,
|
||||
|
||||
@@ -141,11 +141,23 @@ except:
|
||||
HAVE_FICLONE = False
|
||||
|
||||
try:
|
||||
import ctypes
|
||||
import termios
|
||||
except:
|
||||
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:
|
||||
if os.environ.get("PRTY_NO_IFADDR"):
|
||||
raise Exception()
|
||||
@@ -2887,7 +2899,7 @@ def get_df(abspath: str, prune: bool) -> tuple[int, int, str]:
|
||||
bfree = ctypes.c_ulonglong(0)
|
||||
btotal = 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.pointer(bavail),
|
||||
ctypes.pointer(btotal),
|
||||
@@ -4281,8 +4293,8 @@ def termsize() -> tuple[int, int]:
|
||||
def hidedir(dp) -> None:
|
||||
if ANYWIN:
|
||||
try:
|
||||
assert ctypes # type: ignore # !rm
|
||||
k32 = ctypes.WinDLL("kernel32")
|
||||
assert wk32 # type: ignore # !rm
|
||||
k32 = wk32
|
||||
attrs = k32.GetFileAttributesW(dp)
|
||||
if attrs >= 0:
|
||||
k32.SetFileAttributesW(dp, attrs | 2)
|
||||
@@ -4357,6 +4369,31 @@ else:
|
||||
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:
|
||||
if sys.version_info < (3, 10) or os.environ.get("PRTY_NO_IMPRESO"):
|
||||
# py3.8 doesn't have .files
|
||||
|
||||
Reference in New Issue
Block a user