Python简单的小说阅读器的设计

题目 :简单的小说阅读器的设计,服务器端保存小说文本(txt 格式的即可),客户可以打开对应的文本,翻页,翻章,跳页,书签,下载,关闭等,有图形界面,因为是 txt 格式,所谓的“页”可以通过规定每次内容包含的字节来规定。

#sever
import socket
import re
import chardet
import math
import os
import json


class NovelServer:
    def __init__(self, novel_path, page_size=1024):
        # 创建一个新的 socket 对象
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 将 socket 绑定到本地的 8080 端口
        self.server.bind(("localhost", 8080))
        # 开始监听连接请求,最大连接数为 1
        self.server.listen(1)
        # 设置每页的大小,即每次发送给客户端的小说内容的长度
        self.page_size = page_size
        # 加载小说,获取小说的内容、章节数、每个章节的开始和结束位置
        (
            self.novel_content,
            self.total_chapters,
            self.chapter_starts,
            self.chapter_ends,
        ) = self.load_novel(novel_path)
        # 计算总页数
        self.total_pages = math.ceil(len(self.novel_content) / self.page_size)
        # 设置当前页码为 0
        self.current_page = 0
        # 设置当前章节为 1
        self.current_chapter = 1
        # 设置书签文件的名称
        self.bookmarks_file = "bookmarks.json"
        # 加载书签
        self.load_bookmarks()
        # 设置请求处理器,每个处理器都是一个正则表达式和一个方法的对应关系
        # 当收到客户端的请求时,会根据请求的内容匹配相应的正则表达式,然后调用对应的方法处理请求
        self.request_handlers = {
            re.compile(r"^NEXT_PAGE$"): self.handle_next_page,
            re.compile(r"^PREVIOUS_PAGE$"): self.handle_previous_page,
            re.compile(r"^SHOW_CHAPTERS$"): self.handle_show_chapters,
            re.compile(r"^NEXT_CHAPTER (\d+)$"): self.handle_next_chapter,
            re.compile(r"^JUMP_TO_PAGE (\d+)$"): self.handle_jump_to_page,
            re.compile(r"^ADD_BOOKMARK$"): self.handle_add_bookmark,
            re.compile(r"^SHOW_BOOKMARKS$"): self.handle_show_bookmarks,
            re.compile(r"^DOWNLOAD$"): self.handle_download,
            re.compile(r"^REMOVE_BOOKMARK (\d+)$"): self.handle_remove_bookmark,
            re.compile(r"^CLOSE$"): self.handle_close,
        }

    def start(self):
        # 打印服务器启动的消息
        print("Server is starting...")
        while True:
            # 接受客户端的连接请求
            client, address = self.server.accept()
            # 打印客户端的地址
            print(f"Connected by {address}")
            # 处理客户端的请求
            self.handle_client(client)

    def detect_encoding(self, novel_path):
        # 检测小说文件的编码
        with open(novel_path, "rb") as f:
            result = chardet.detect(f.read())
            return result["encoding"]

    def load_novel(self, novel_path):
        # 打开并读取小说文件,使用检测到的编码
        with open(novel_path, "r", encoding=self.detect_encoding(novel_path)) as f:
            content = f.read()
        # 查找所有的章节
        chapters = self.find_chapters(content)
        # 查找每个章节的开始和结束位置
        chapter_starts, chapter_ends = self.find_chapter_starts_and_ends(content)
        # 替换章节标记
        content = self.replace_chapter_markers(content)
        # 返回小说的内容、章节数、每个章节的开始和结束位置
        return content, len(chapters), chapter_starts, chapter_ends

    def find_chapters(self, content):
        # 使用正则表达式查找所有的章节
        return re.findall(r"(第\d+章|第\d+节|第\d+回)", content)

    def find_chapter_starts_and_ends(self, content):
        # 使用正则表达式查找每个章节的开始和结束位置
        matches = list(re.finditer(r"(第\d+章|第\d+节|第\d+回)", content))
        starts = [match.start() for match in matches]
        ends = [match.end() for match in matches[:-1]] + [len(content)]
        return starts, ends

    def replace_chapter_markers(self, content):
        # 替换章节标记,每个章节标记前面添加一个换行符
        return re.sub(r"(第\d+章)", lambda m: "\n" + m.group(1), content)

    def load_bookmarks(self):
        # 如果书签文件存在,就加载书签
        if os.path.exists(self.bookmarks_file):
            with open(self.bookmarks_file, "r") as f:
                self.bookmarks = json.load(f)
        else:
            # 否则,初始化一个空的书签列表
            self.bookmarks = []

    def save_bookmarks(self):
        # 保存书签到文件
        with open(self.bookmarks_file, "w") as f:
            json.dump(self.bookmarks, f)

    def handle_client(self, client):
        # 循环处理客户端的请求
        while True:
            # 接收客户端的请求
            request = client.recv(512).decode("utf-32")
            # 遍历所有的请求处理器
            for pattern, handler in self.request_handlers.items():
                # 如果找到了匹配的处理器,就调用它处理请求
                match = pattern.match(request)
                if match:
                    handler(client, match)
                    break
            else:
                # 如果没有找到匹配的处理器,就发送一个错误消息
                client.send("Invalid request".encode("utf-8"))

    def handle_next_page(self, client, match):
        # 如果当前页码小于总页数,就将当前页码加 1
        if self.current_page < self.total_pages:
            self.current_page += 1
        # 发送当前页的内容给客户端
        self.send_page(client)

    def handle_previous_page(self, client, match):
        # 如果当前页码大于 1,就将当前页码减 1
        if self.current_page > 1:
            self.current_page -= 1
        # 发送当前页的内容给客户端
        self.send_page(client)

    def handle_show_chapters(self, client, match):
        # 生成章节列表的字符串
        chapters = "\n".join(f"Chapter {i+1}" for i in range(self.total_chapters))
        # 将章节列表发送给客户端
        client.send(chapters.encode("utf-32"))

    def handle_next_chapter(self, client, match):
        # 获取客户端请求的章节号
        next_chapter = int(match.group(1))
        # 如果章节号在有效范围内,就跳转到该章节
        if 1 <= next_chapter <= self.total_chapters:
            self.current_chapter = next_chapter
            self.current_page = (
                self.chapter_starts[next_chapter - 1] // self.page_size + 1
            )
        # 发送当前页的内容给客户端
        self.send_page(client)

    def handle_jump_to_page(self, client, match):
        # 获取客户端请求的页码
        page = int(match.group(1))
        # 如果页码在有效范围内,就跳转到该页
        if 1 <= page <= self.total_pages:
            self.current_page = page
        # 发送当前页的内容给客户端
        self.send_page(client)

    def handle_add_bookmark(self, client, match):
        # 将当前页码添加到书签列表
        self.bookmarks.append(self.current_page)
        # 保存书签列表到文件
        self.save_bookmarks()
        # 向客户端发送添加书签成功的消息
        client.send("Bookmark added".encode("utf-32"))

    def handle_show_bookmarks(self, client, match):
        # 如果书签列表为空,就发送一个消息告诉客户端没有书签
        if not self.bookmarks:
            client.send("没有书签".encode("utf-32"))
        else:
            # 否则,生成书签列表的字符串,然后发送给客户端
            bookmarks = "\n".join(
                f"书签{index+1}: 第{page}页" for index, page in enumerate(self.bookmarks)
            )
            client.send(bookmarks.encode("utf-32"))

    def handle_download(self, client, match):
        # 将小说的内容写入到文件
        with open("Download_novel.txt", "w", encoding="utf-32") as f:
            f.write(self.novel_content)
        # 向客户端发送下载成功的消息
        client.send("小说已成功下载并保存到 'Download_novel.txt'。".encode("utf-32"))
        # 打印一条消息,表示已经发送了下载成功的消息
        print("已发送下载成功的消息。")

    def handle_remove_bookmark(self, client, match):
        # 获取客户端请求删除的书签的索引
        index = int(match.group(1))
        # 如果索引在有效范围内,就删除该书签
        if 1 <= index <= len(self.bookmarks):
            del self.bookmarks[index - 1]
            # 保存书签列表到文件
            self.save_bookmarks()
        # 向客户端发送删除书签成功的消息
        client.send("Bookmark removed".encode("utf-32"))

    def handle_close(self, client, match):
        # 关闭客户端的连接
        client.close()

    def get_chapter_start_page(self, chapter_number):
        # 生成章节标题的字符串
        chapter_title = f"第{chapter_number}章"
        # 在小说的内容中查找章节标题
        match = re.search(chapter_title, self.novel_content)
        # 如果找到了章节标题,就返回该章节的开始页码
        if match:
            return match.start() // self.page_size + 1
        else:
            # 如果没有找到章节标题,就打印一条消息,并返回当前页码
            print(f"Chapter {chapter_number} not found.")
            return self.current_page

    def send_page(self, client):
        # 计算当前页的开始和结束位置
        start = self.page_size * (self.current_page - 1)
        end = start + self.page_size
        # 获取当前页的内容
        page_content = self.novel_content[start:end]
        # 查找最后一个句号、问号或感叹号的位置
        end_mark = max(page_content.rfind(mark) for mark in "。?!")
        # 如果找到了句号、问号或感叹号,并且它的位置在当前页的后半部分,就将结束位置设置为它的位置
        if end_mark != -1 and end_mark > start + self.page_size / 2:
            end = start + end_mark + 1
        # 重新获取当前页的内容
        page_content = self.novel_content[start:end]
        # 查找当前页所在的章节
        current_chapter = next(
            (
                i
                for i, (chapter_start, chapter_end) in enumerate(
                    zip(self.chapter_starts, self.chapter_ends)
                )
                if chapter_start > start
            ),
            self.total_chapters,
        )#比较每个章节的开始位置和当前页的开始位置,如果章节的开始位置大于当前页的开始位置,就说明当前页在该章节中,然后返回该章节的索引
        # 生成当前页的信息
        page_info = f"Page {self.current_page}/{self.total_pages}, Chapter {current_chapter}/{self.total_chapters}\n"
        # 将当前页的信息和内容发送给客户端
        client.send((page_info + page_content).encode("utf-32"))


# 创建一个 NovelServer 对象,指定小说文件的路径和每页的大小
server = NovelServer(
    novel_path="YourPath", page_size=512
)
# 启动服务器
server.start()

将yourpath更换为你的文件路径

#client

import socket
import tkinter as tk
from tkinter import scrolledtext
from tkinter import simpledialog
import threading
import tkinter.messagebox as messagebox

class NovelClient:
    def __init__(self, root):
        # 创建一个socket对象
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 连接到服务器
        self.client.connect(("localhost", 8080))
        # 保存Tkinter的root对象
        self.root = root
        # 创建界面上的组件
        self.create_widgets()

    def _send_request(self, request):
        # 向服务器发送请求,并接收响应
        try:
            self.client.send(request.encode("utf-32"))
            response = self.client.recv(4096).decode("utf-32")
            self.text.delete("1.0", tk.END)  # 清空文本框
            self.text.insert(tk.END, response)  # 将响应显示在文本框中
        except Exception as e:
            print(f"Error sending request: {e}")

    def handle_download(self):
        # 处理下载小说的请求
        try:
            print("开始下载小说...")
            self.client.send("DOWNLOAD".encode("utf-32"))
            response = self.client.recv(4096).decode("utf-32")
            messagebox.showinfo("下载小说", response)
        except Exception as e:
            print(f"下载小说时出错: {e}")

    def create_button(self, text, command, row, column):
        # 创建一个按钮
        button = tk.Button(self.root, text=text, command=command)
        button.grid(row=row, column=column, padx=10, pady=10)  # 添加间距

    def create_widgets(self):
        # 创建界面上的组件
        self.text = scrolledtext.ScrolledText(self.root, width=80, height=25)
        self.text.grid(row=0, column=0, columnspan=4)  # 将文本框放在顶部

        # 创建一系列的按钮,每个按钮都有一个对应的命令
        buttons = {
            "Next Page": lambda: self.send_request("NEXT_PAGE"),
            "Previous Page": lambda: self.send_request("PREVIOUS_PAGE"),
            "Jump to Page": self.jump_to_page,
            "Next Chapter": self.next_chapter,
            "Add Bookmark": lambda: self.send_request("ADD_BOOKMARK"),
            "Show Bookmarks": lambda: self.send_request("SHOW_BOOKMARKS"),
            "Download Novel": self.handle_download,
            "Show Chapters": lambda: self.send_request("SHOW_CHAPTERS"),
            "Remove Bookmark": self.remove_bookmark,
        }

        # 遍历按钮字典,获取每个按钮的文本和命令
        for i, (text, command) in enumerate(buttons.items()):
            # 按钮的行号是1加上索引除以3的商,列号是索引除以3的余数这样可以将按钮平均分布在网格上
            self.create_button(text, command, 1 + i // 3, i % 3)

    def send_request(self, request):
        # 创建并启动一个新的线程来发送请求避免阻塞主线程
        threading.Thread(target=self._send_request, args=(request,)).start()

    def close(self):
        # 关闭客户端连接
        self.client.close()
        self.root.quit()

    def jump_to_page(self):
        # 请求用户输入要跳转的页码
        page_number = self.ask_integer("Jump to Page", "Enter page number:")
        # 如果用户输入了页码,就发送请求跳转到该页
        if page_number is not None:
            self.send_request(f"JUMP_TO_PAGE {page_number}")

    def next_chapter(self):
        # 请求用户输入要跳转的章节号
        chapter_number = self.ask_integer("Next Chapter", "Enter chapter number:")
        # 如果用户输入了章节号,就发送请求跳转到该章节
        if chapter_number is not None:
            self.send_request(f"NEXT_CHAPTER {chapter_number}")

    def remove_bookmark(self):
        # 请求用户输入要删除的书签索引
        bookmark_index = self.ask_integer("Remove Bookmark", "Enter bookmark index:")
        # 如果用户输入了书签索引,就发送请求删除该书签
        if bookmark_index is not None:
            self.send_request(f"REMOVE_BOOKMARK {bookmark_index}")

    def ask_integer(self, title, prompt):
        # 弹出一个对话框,请求用户输入一个整数
        # title是对话框的标题,prompt是提示信息
        return simpledialog.askinteger(title, prompt)


# 创建一个Tkinter应用程序的根窗口
root = tk.Tk()
# 设置根窗口的大小为600x600像素
root.geometry("600x600")  
# 创建一个NovelClient对象用于处理小说客户端的逻辑
client = NovelClient(root)
# 启动Tkinter应用程序的主事件循环
root.mainloop()

你的小说内容应为以下形式(例)

第1章

内容

第2章

内容

..........

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值