1
1
mirror of https://github.com/KenanZhu/AutoLibrary.git synced 2026-06-22 09:23:03 +08:00

fix: 修复 Git 文件名大小写与文件系统不一致的问题

Windows 下 git core.ignorecase=true 导致文件重命名时 Git 无法
检测到大小写变化,推送后服务器上仍为旧命名。通过两步 git mv
强制更新索引,统一所有文件名为规范大小写。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 14:05:20 +08:00
parent bb63ee6f03
commit b24f39456e
9 changed files with 0 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
"""
Copyright (c) 2026 KenanZhu.
All rights reserved.
This software is provided "as is", without any warranty of any kind.
You may use, modify, and distribute this file under the terms of the MIT License.
See the LICENSE file for details.
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
def timeStrToMins(
time_str: str,
) -> int:
hour, minute = map(int, time_str.split(":"))
return hour*60 + minute
def minsToTimeStr(
mins: int,
) -> str:
hour, minute = divmod(int(mins), 60)
return f"{hour:02d}:{minute:02d}"
@dataclass
class TimeOption:
value: int
element_text: str
@dataclass
class TimeSelectionResult:
selected_index: int = -1
selected_value: int = 0
display_text: str = ""
actual_diff: int = 0
free_times: list[str] = field(default_factory=list)
@dataclass
class TimeRangeResult:
begin_result: TimeSelectionResult = field(default_factory=TimeSelectionResult)
end_result: TimeSelectionResult = field(default_factory=TimeSelectionResult)
actual_begin_mins: int = -1
actual_end_mins: int = -1
expect_end_mins: int = 0
class TimeOptionReader(ABC):
@abstractmethod
def readOptions(
self,
elements: list
) -> list[TimeOption]:
...
def formatFreeTime(
self,
opt: TimeOption
) -> str:
return opt.element_text
class ReserveTimeReader(TimeOptionReader):
"""
Reads the ``time`` HTML attribute for the reserve flow.
Special value ``"now"`` is resolved to the current wall-clock minute.
"""
def readOptions(
self,
elements: list
) -> list[TimeOption]:
options: list[TimeOption] = []
for el in elements:
time_attr = el.get_attribute("time")
if time_attr == "now":
now = datetime.now()
value = now.hour * 60 + now.minute
elif time_attr and time_attr.isdigit():
value = int(time_attr)
else:
continue
options.append(TimeOption(value=value, element_text=el.text.strip()))
return options
def formatFreeTime(
self,
opt: TimeOption
) -> str:
return minsToTimeStr(opt.value)
class RenewTimeReader(TimeOptionReader):
"""
Reads the ``id`` HTML attribute for the renewal flow.
"""
def readOptions(
self,
elements: list
) -> list[TimeOption]:
options: list[TimeOption] = []
for el in elements:
time_attr = el.get_attribute("id")
if not (time_attr and time_attr.isdigit()):
continue
options.append(TimeOption(value=int(time_attr), element_text=el.text.strip()))
return options
class TimeDecisionMaker:
def __init__(
self,
reader: TimeOptionReader
) -> None:
self._reader = reader
def decide(
self,
elements: list,
target_time: int,
max_time_diff: int,
prefer_earlier: bool
) -> TimeSelectionResult:
options = self._reader.readOptions(elements)
free_times = [self._reader.formatFreeTime(o) for o in options]
best_diff = max_time_diff
best_actual_diff = None
best_index = -1
for i, opt in enumerate(options):
actual_diff = opt.value - target_time
abs_diff = abs(actual_diff)
if abs_diff < best_diff or (
abs_diff == best_diff
and (
(prefer_earlier and actual_diff <= 0)
or (not prefer_earlier and actual_diff >= 0)
)
):
best_diff = abs_diff
best_actual_diff = actual_diff
best_index = i
if best_index == -1:
return TimeSelectionResult(free_times=free_times)
chosen = options[best_index]
return TimeSelectionResult(
selected_index=best_index,
selected_value=chosen.value,
display_text=chosen.element_text,
actual_diff=best_actual_diff or 0,
free_times=free_times,
)
class TimeSelectMaker:
LIBRARY_CLOSE_MINS = 1350 # 22:30
MAX_DURATION_HOURS = 8
@staticmethod
def calcEndTime(
begin_mins: int,
duration: int,
library_close_mins: int = LIBRARY_CLOSE_MINS
) -> int:
expect_end_mins = int(begin_mins + duration*60)
if expect_end_mins > library_close_mins:
return library_close_mins
return expect_end_mins
@staticmethod
def calcRemainingDuration(
end_time_str: str,
target_mins: int,
library_close_mins: int = LIBRARY_CLOSE_MINS
) -> int:
return library_close_mins - timeStrToMins(end_time_str)
@staticmethod
def forReserve(
) -> TimeDecisionMaker:
return TimeDecisionMaker(ReserveTimeReader())
@staticmethod
def forRenew(
) -> TimeDecisionMaker:
return TimeDecisionMaker(RenewTimeReader())