Skip to content

Commit

Permalink
异步读文件,修复很多bug,优化下载
Browse files Browse the repository at this point in the history
  • Loading branch information
Wulian233 committed Aug 4, 2024
1 parent 1f04225 commit bf30874
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 110 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
* text eol=lf
*.png binary
*.ico binary
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,27 @@ WIP

## Develop and Build
### Requirements
- **Git**
- **Python Version**: 3.8+
- **[Git](https://git-scm.com/downloads)**
- **[Python](https://www.python.org/downloads/)**: 3.8+
- **Supported Operating Systems**: Windows 10 or later, macOS, Linux

### Running from Source

1. **Install Dependencies**:
- Open a terminal and run the following command to install required packages:
- Open a terminal and run:
```bash
git clone https://github.com/Wulian233/FeedTheForge.git
cd FeedTheForge
pip install -r requirements.txt
```

2. **Run the Application**:
- **Windows**: Use the command `python __main__.py`
- **macOS and Linux**: Use the command `python3 __main__.py`
2. **Run**:
- **Windows**: `python __main__.py`
- **macOS and Linux**: `python3 __main__.py`

### Building Executable
### Package as Executable

1. **Package as Executable**:
1. **Package**:
```bash
pip install pyinstaller
pyinstaller PyBuild/main.spec
Expand Down
34 changes: 27 additions & 7 deletions feedtheforge/async_downloader.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import aiohttp
import aiofiles
import asyncio

class AsyncDownloader:
def __init__(self):
def __init__(self, retries=2, retry_delay=2):
self.session = None
self.retries = retries
self.retry_delay = retry_delay

async def __aenter__(self):
self.session = await aiohttp.ClientSession().__aenter__()
timeout = aiohttp.ClientTimeout(total=10) # 设置总超时
self.session = await aiohttp.ClientSession(
connector=aiohttp.TCPConnector(ssl=False),
timeout=timeout
).__aenter__()
return self

async def __aexit__(self, exc_type, exc, tb):
await self.session.__aexit__(exc_type, exc, tb)

async def download_file(self, url, output_path):
async with self.session.get(url) as response:
with open(output_path, "wb") as f:
while chunk := await response.content.read(1024):
f.write(chunk)
attempts = 0
while attempts < self.retries:
try:
async with self.session.get(url) as response:
async with aiofiles.open(output_path, "wb") as f:
async for chunk in response.content.iter_chunked(1024*64):
await f.write(chunk)
print(f"文件下载成功: {output_path}")
break # 下载成功,退出循环
except Exception:
attempts += 1
if attempts >= self.retries:
print(f"文件下载失败,跳过: {output_path}")
else:
print(f"下载失败,尝试重新下载 ({attempts}/{self.retries})...")
await asyncio.sleep(self.retry_delay)

async def fetch_json(self, url):
async with self.session.get(url) as response:
return await response.json()
return await response.json()
2 changes: 1 addition & 1 deletion feedtheforge/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def __init__(self, lang: str):
self.path = Path(sys._MEIPASS) / f"feedtheforge/lang/{lang}.json"
else:
# If running in a normal Python environment
self.path = Path(f"./feedtheforge/lang/{lang}.py")
self.path = Path(f"./feedtheforge/lang/{lang}.json")
self.data = {}
self.load()

Expand Down
134 changes: 68 additions & 66 deletions feedtheforge/utils.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,68 @@
import os
import shutil
from zipfile import ZIP_DEFLATED, ZipFile

from feedtheforge.const import *


async def create_directory(path):
"""
创建目录,如果目录不存在则创建
"""
os.makedirs(path, exist_ok=True)

async def is_recent_file(filepath, days=7):
"""
检查文件最后修改时间是否在指定天数内
:param filepath: 文件路径
:param days: 距离当前的天数间隔,默认值为7天
:return: 如果文件存在且最后修改时间在指定天数内,返回 True 否则返回 False
"""
from datetime import datetime, timedelta

if not os.path.exists(filepath):
return False
modification_date = datetime.fromtimestamp(os.path.getmtime(filepath)).date()
current_date = datetime.now().date()
if current_date - modification_date < timedelta(days=days):
return True
return False

def zip_modpack(modpack_name):
"""
压缩整合包文件夹为一个zip文件
:param modpack_name: 整合包的名称
"""
print(lang.t("feedtheforge.main.zipping_modpack"))

with ZipFile(f"{modpack_name}.zip", "w", ZIP_DEFLATED) as zf:
for dirname, _, files in os.walk(modpack_path):
for filename in files:
file_path = os.path.join(dirname, filename)
zf.write(file_path, os.path.relpath(file_path, modpack_path))

print(lang.t("feedtheforge.main.modpack_created", modpack_name=f"{modpack_name}.zip"))
shutil.rmtree(modpack_path, ignore_errors=True)

def clean_temp():
"""
清理缓存目录中的临时文件
"""
size = 0
for root, _, files in os.walk(cache_dir):
size += sum([os.path.getsize(os.path.join(root, name)) for name in files])
shutil.rmtree(cache_dir, ignore_errors=True)
print(lang.t("feedtheforge.main.clean_temp", size=int(size / 1024)))

def pause():
"""
退出程序
"""
if os.name == 'nt':
os.system('pause')
else:
input(lang.t("feedtheforge.main.pause"))
import os
import shutil

from feedtheforge.const import *


async def create_directory(path):
"""
创建目录,如果目录不存在则创建
"""
os.makedirs(path, exist_ok=True)

async def is_recent_file(filepath, days=7):
"""
检查文件最后修改时间是否在指定天数内
:param filepath: 文件路径
:param days: 距离当前的天数间隔,默认值为7天
:return: 如果文件存在且最后修改时间在指定天数内,返回 True 否则返回 False
"""
from datetime import datetime, timedelta

if not os.path.exists(filepath):
return False
modification_date = datetime.fromtimestamp(os.path.getmtime(filepath)).date()
current_date = datetime.now().date()
if current_date - modification_date < timedelta(days=days):
return True
return False

def zip_modpack(modpack_name):
"""
压缩整合包文件夹为一个zip文件
:param modpack_name: 整合包的名称
"""
from zipfile import ZIP_DEFLATED, ZipFile

print(lang.t("feedtheforge.main.zipping_modpack"))

with ZipFile(f"{modpack_name}.zip", "w", ZIP_DEFLATED) as zf:
for dirname, _, files in os.walk(modpack_path):
for filename in files:
file_path = os.path.join(dirname, filename)
zf.write(file_path, os.path.relpath(file_path, modpack_path))

print(lang.t("feedtheforge.main.modpack_created", modpack_name=f"{modpack_name}.zip"))
shutil.rmtree(modpack_path, ignore_errors=True)

def clean_temp():
"""
清理缓存目录中的临时文件
"""
size = 0
for root, _, files in os.walk(cache_dir):
size += sum([os.path.getsize(os.path.join(root, name)) for name in files])
shutil.rmtree(cache_dir, ignore_errors=True)
print(lang.t("feedtheforge.main.clean_temp", size=int(size / 1024)))

def pause():
"""
退出程序
"""
if os.name == 'nt':
os.system('pause')
else:
input(lang.t("feedtheforge.main.pause"))
exit(0)
57 changes: 29 additions & 28 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import os
import shutil
import aiofiles
from pick import pick, Option

from feedtheforge import utils
Expand Down Expand Up @@ -34,16 +35,16 @@ async def load_modpack_data(modpack_id: str) -> dict:
async with AsyncDownloader() as dl:
data = await dl.fetch_json(url)

with open(modpack_id_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
async with aiofiles.open(modpack_id_path, "w", encoding="utf-8") as f:
await f.write(json.dumps(data, indent=4))

return data

async def display_modpack_list(load_json):
"""读取json并制作对应的选择菜单"""
options = []
with open(load_json, "r", encoding="utf-8") as f:
data = json.load(f)
async with aiofiles.open(load_json, "r", encoding="utf-8") as f:
data = json.loads(await f.read())
for modpack in data["packs"]:
# 处理字典形式的数据,即搜索整合包
if isinstance(modpack, dict): modpack_id = modpack['id']
Expand Down Expand Up @@ -100,7 +101,7 @@ async def apply_chinese_patch(lanzou_url: str) -> None:
from zipfile import ZipFile
# 获取返回的json中downUrl的值为下载链接
data = json.loads(LanzouDownloader().get_direct_link(lanzou_url))
down_url = data.get("downUrl")
down_url = data.get("downUrl")
async with AsyncDownloader() as dl:
await dl.download_file(down_url, patch)

Expand All @@ -119,10 +120,11 @@ async def apply_chinese_patch(lanzou_url: str) -> None:


async def download_modpack(modpack_id: str) -> None:
print(lang.t("feedtheforge.main.modpack_name", modpack_name=modpack_name))

modpack_data = await load_modpack_data(modpack_id)
modpack_name = modpack_data["name"]

print(lang.t("feedtheforge.main.modpack_name", modpack_name=modpack_name))

modpack_author = modpack_data["authors"][0]["name"]
versions = modpack_data["versions"]
version_list = [version["id"] for version in versions]
Expand All @@ -138,11 +140,10 @@ async def download_modpack(modpack_id: str) -> None:
print(lang.t("feedtheforge.main.invalid_modpack_version"))
utils.pause()


async with AsyncDownloader() as dl:
download_url = f"https://api.modpacks.ch/public/modpack/{modpack_id}/{selected_version}"
await dl.download_file(download_url, os.path.join(cache_dir, "download.json"))
await prepare_modpack_files(modpack_name, modpack_author, selected_version)
await prepare_modpack_files(modpack_name, modpack_author)

if current_language == "zh_CN":
async with AsyncDownloader() as dl:
Expand All @@ -157,15 +158,12 @@ async def download_modpack(modpack_id: str) -> None:
utils.zip_modpack(modpack_name)


async def prepare_modpack_files(modpack_name, modpack_author, modpack_version):
async def prepare_modpack_files(modpack_name, modpack_author):
os.makedirs(modpack_path, exist_ok=True)
with open(os.path.join(cache_dir, "download.json"), "r", encoding="utf-8") as f:
data = json.load(f)
# 下面均为CurseForge整合包识别的固定格式
mc_version = data["targets"][1]["version"]
modloader_name = data["targets"][0]["name"]
modloader_version = data["targets"][0]["version"]

async with aiofiles.open(os.path.join(cache_dir, "download.json"), "r", encoding="utf-8") as f:
data = json.loads(await f.read())

# 下面均为CurseForge整合包识别的固定格式
curse_files, non_curse_files = [], []
for file_info in data["files"]:
if "curseforge" in file_info:
Expand All @@ -177,7 +175,11 @@ async def prepare_modpack_files(modpack_name, modpack_author, modpack_version):
else:
non_curse_files.append(file_info)

mc_version = data["targets"][1]["version"]
modloader_name = data["targets"][0]["name"]
modloader_version = data["targets"][0]["version"]
modloader_id = f"{modloader_name}-{modloader_version}"
modpack_version = data["name"]
if modloader_name == "neoforge" and mc_version == "1.20.1":
modloader_id = f"{modloader_name}-{mc_version}-{modloader_version}"

Expand All @@ -195,17 +197,17 @@ async def prepare_modpack_files(modpack_name, modpack_author, modpack_version):
"version": modpack_version
}

with open(os.path.join(modpack_path, "manifest.json"), "w", encoding="utf-8") as f:
json.dump(manifest_data, f, indent=4)
async with aiofiles.open(os.path.join(modpack_path, "manifest.json"), "w", encoding="utf-8") as f:
await f.write(json.dumps(manifest_data, indent=4))

# FIXME 生成 modlist.html 文件
modlist_file = os.path.join(modpack_path, "modlist.html")
with open(modlist_file, "w", encoding="utf-8") as f:
f.write("<ul>\n")
async with aiofiles.open(modlist_file, "w", encoding="utf-8") as f:
await f.write("<ul>\n")
# for file_info in curse_files:
# mod_page_url = file_info.get("url", "#")
# f.write(f'<li><a href="{mod_page_url}">{file_info["fileID"]}</a></li>\n')
f.write("</ul>\n")
# await f.write(f'<li><a href="{mod_page_url}">{file_info["fileID"]}</a></li>\n')
await f.write("</ul>\n")

os.makedirs(os.path.join(modpack_path, "overrides"), exist_ok=True)
await download_files(non_curse_files)
Expand All @@ -216,15 +218,14 @@ async def fetch_modpack_list():
try:
async with AsyncDownloader() as dl:
modpacks_data = await dl.fetch_json(API_LIST)
with open(packlist_path, "w", encoding="utf-8") as f:
json.dump(modpacks_data, f, indent=4)
async with aiofiles.open(packlist_path, "w", encoding="utf-8") as f:
await f.write(json.dumps(modpacks_data, indent=4))
except OSError:
print(lang.t("feedtheforge.main.getting_error"))
utils.pause()

# 从本地文件读取数据
with open(packlist_path, "r", encoding="utf-8") as f:
modpacks_data = json.load(f)
async with aiofiles.open(packlist_path, "r", encoding="utf-8") as f:
modpacks_data = json.loads(await f.read())

global all_pack_ids
all_pack_ids = [str(pack_id) for pack_id in modpacks_data.get("packs", [])]
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
aiohttp
aiofiles
pick

0 comments on commit bf30874

Please sign in to comment.