plugin-jm-server 0.1.24__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/PKG-INFO +56 -18
- plugin_jm_server-0.2.1/README.md +151 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/__init__.py +3 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/app.py +109 -7
- plugin_jm_server-0.2.1/src/plugin_jm_server/files.py +170 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/css/index.css +729 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/css/index_spa.css +637 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/css/style_photo.css +1 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/img/bg.jpg +0 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/js/common.js +78 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/js/index.js +317 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/js/index_spa.js +893 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/js/jquery.lazyload-2.0.js +444 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/static/js/jquery.photo-0.3.js +427 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/templates/index.html +191 -0
- plugin_jm_server-0.2.1/src/plugin_jm_server/templates/index_spa.html +121 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/jm_view.html +10 -3
- plugin_jm_server-0.2.1/src/plugin_jm_server/templates/m_index.html +187 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server.egg-info/PKG-INFO +56 -18
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server.egg-info/SOURCES.txt +5 -0
- plugin_jm_server-0.1.24/README.md +0 -113
- plugin_jm_server-0.1.24/src/plugin_jm_server/__init__.py +0 -3
- plugin_jm_server-0.1.24/src/plugin_jm_server/files.py +0 -119
- plugin_jm_server-0.1.24/src/plugin_jm_server/static/css/index.css +0 -200
- plugin_jm_server-0.1.24/src/plugin_jm_server/static/css/style_photo.css +0 -1
- plugin_jm_server-0.1.24/src/plugin_jm_server/static/img/bg.jpg +0 -0
- plugin_jm_server-0.1.24/src/plugin_jm_server/static/js/common.js +0 -49
- plugin_jm_server-0.1.24/src/plugin_jm_server/static/js/index.js +0 -95
- plugin_jm_server-0.1.24/src/plugin_jm_server/templates/index.html +0 -100
- plugin_jm_server-0.1.24/src/plugin_jm_server/templates/m_index.html +0 -92
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/setup.cfg +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/setup.py +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/driver.py +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/all.min.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/bootstrap.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/download_error.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/h5/m_download_error.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/h5/m_index.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/h5/m_login.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/h5/m_upload.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/h5/normalize.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/login.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/css/upload.css +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/img/m_bg.jpeg +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/js/h5/m_index.js +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/js/h5/m_upload.js +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/js/jquery-3.7.1.min.js +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/js/jquery.album-0.2.js +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/js/upload.js +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/webfonts/fa-regular-400.woff2 +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/static/webfonts/fa-solid-900.woff2 +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/download_error.html +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/login.html +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/m_download_error.html +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/m_jm_view.html +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/m_login.html +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/m_upload.html +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server/templates/upload.html +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server.egg-info/dependency_links.txt +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server.egg-info/requires.txt +0 -0
- {plugin_jm_server-0.1.24 → plugin_jm_server-0.2.1}/src/plugin_jm_server.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plugin_jm_server
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: plugin_jm_server, a plugin for jmcomic that can be used to view comics in a web browser.
|
|
5
5
|
Home-page: https://github.com/hect0x7/plugin-jm-server
|
|
6
6
|
Author: hect0x7
|
|
@@ -44,20 +44,33 @@ Dynamic: summary
|
|
|
44
44
|
|
|
45
45
|
* 双页阅读插件:https://sleazyfork.org/zh-CN/scripts/374903-comicread
|
|
46
46
|
|
|
47
|
-
#
|
|
47
|
+
# 效果图(文件浏览、整章看图)
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
## 1. 电脑浏览器访问
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
### 登录页
|
|
52
|
+

|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
### 文件夹列表页
|
|
55
|
+

|
|
56
|
+

|
|
54
57
|
|
|
58
|
+
### 看本模式
|
|
59
|
+

|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
### 文件夹&看本 二合一模式
|
|
62
|
+

|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
## 2. 手机浏览器访问
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
### 登录页
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
### 文件夹列表页
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
### 看本模式
|
|
73
|
+

|
|
61
74
|
|
|
62
75
|
|
|
63
76
|
# 使用方式
|
|
@@ -70,7 +83,6 @@ pip install plugin_jm_server
|
|
|
70
83
|
```
|
|
71
84
|
|
|
72
85
|
|
|
73
|
-
|
|
74
86
|
## 2. 运行代码
|
|
75
87
|
|
|
76
88
|
* **HTTP版**
|
|
@@ -135,15 +147,41 @@ op.wait_all_plugins_finish()
|
|
|
135
147
|
```
|
|
136
148
|
|
|
137
149
|
|
|
138
|
-
|
|
150
|
+
## 前端页面与功能介绍
|
|
151
|
+
|
|
152
|
+
本项目提供了一套完整的 Web 前端界面,用于管理和浏览本地图片资源。主要包含以下三个核心页面及其功能:
|
|
153
|
+
|
|
154
|
+
### 1. 登录页
|
|
155
|
+
为了保护个人隐私,系统提供了简单的访问控制。
|
|
156
|
+
* **密码验证**:启动时可配置密码,访问时需校验通过。
|
|
157
|
+
* **设备自适应**:根据访问设备(手机/电脑)自动适配最佳的登录布局。
|
|
158
|
+
|
|
159
|
+
### 2. 文件夹浏览页(主页)
|
|
160
|
+
这是资源管理的核心入口,支持高效的文件查找与导航。目前最新版本采用了 SPA(单页应用)架构进行全面增强:
|
|
161
|
+
* **智能路径导航**:
|
|
162
|
+
* **路径补全**:输入路径时自动联想下级文件夹,支持键盘上下键选择及回车跳转。
|
|
163
|
+
* **层级跳转**:输入框支持“..”快速返回上一级,并提供“确认”按钮进行精准跳转。
|
|
164
|
+
* **可视化展示**:
|
|
165
|
+
* **文件夹预览**:鼠标悬停在文件夹上时,会自动展示该文件夹内的首张图片作为封面预览。
|
|
166
|
+
* **类型区分**:通过文件夹角标(图标)清晰区分普通文件夹与特殊资源文件夹。
|
|
167
|
+
* **辅助功能**:
|
|
168
|
+
* **书签管理**:支持将常看目录加入左侧书签栏,方便下次快速访问。
|
|
169
|
+
* **本地交互**:支持通过网页菜单直接打开服务器端对应的本地文件夹窗口(Explorer)。
|
|
170
|
+
|
|
171
|
+
### 3. 图片阅读页(看本模式)
|
|
172
|
+
专为漫画阅读优化的沉浸式浏览页面。
|
|
173
|
+
* **加载优化**:采用图片懒加载机制,流畅浏览含有数百张图片的大型章节,节省系统资源。
|
|
174
|
+
* **多端适配**:
|
|
175
|
+
* **PC端**:适合大屏浏览,支持浮动工具栏进行页面跳转、全屏切换等。
|
|
176
|
+
* **移动端**:适配触摸操作,提供更好的手机阅读体验。
|
|
177
|
+
* **阅读辅助**:
|
|
178
|
+
* **浮动菜单**:提供“回到顶部”、“加载全部图片”及“在资源管理器打开”等便捷操作。
|
|
179
|
+
* **键盘支持**:支持常用快捷键进行页面滚动和翻页。
|
|
139
180
|
|
|
140
|
-
|
|
141
|
-

|
|
142
|
-

|
|
143
|
-

|
|
181
|
+
# 想法起源
|
|
144
182
|
|
|
145
|
-
|
|
146
|
-

|
|
147
|
-

|
|
148
|
-

|
|
183
|
+
想法起源:https://github.com/hect0x7/JMComic-Crawler-Python/issues/192
|
|
149
184
|
|
|
185
|
+
基于原项目:https://github.com/AiCorein/Flask-Files-Server
|
|
186
|
+
|
|
187
|
+
为了方便修改,将原项目中的文件复制到本项目中,然后进行修改
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# plugin-jm-server
|
|
2
|
+
|
|
3
|
+
“离线版”禁漫天堂
|
|
4
|
+
|
|
5
|
+
该项目会在本地起一个`文件服务器`,支持用浏览器查看文件夹内的图片,界面模仿禁漫的章节观看页面,并适配手机端。
|
|
6
|
+
|
|
7
|
+
用浏览器的好处就是支持很多浏览器插件和脚本,例如:
|
|
8
|
+
|
|
9
|
+
* 双页阅读插件:https://sleazyfork.org/zh-CN/scripts/374903-comicread
|
|
10
|
+
|
|
11
|
+
# 效果图(文件浏览、整章看图)
|
|
12
|
+
|
|
13
|
+
## 1. 电脑浏览器访问
|
|
14
|
+
|
|
15
|
+
### 登录页
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
### 文件夹列表页
|
|
19
|
+

|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
### 看本模式
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
### 文件夹&看本 二合一模式
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
## 2. 手机浏览器访问
|
|
29
|
+
|
|
30
|
+
### 登录页
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
### 文件夹列表页
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
### 看本模式
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# 使用方式
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## 1. pip安装
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
pip install plugin_jm_server
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## 2. 运行代码
|
|
51
|
+
|
|
52
|
+
* **HTTP版**
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from plugin_jm_server import *
|
|
56
|
+
|
|
57
|
+
# http
|
|
58
|
+
server = JmServer(
|
|
59
|
+
'D:/',
|
|
60
|
+
'password',
|
|
61
|
+
)
|
|
62
|
+
server.run(
|
|
63
|
+
host='0.0.0.0',
|
|
64
|
+
port=80,
|
|
65
|
+
)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
* **HTTPS版**
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from plugin_jm_server import *
|
|
72
|
+
|
|
73
|
+
# https
|
|
74
|
+
server = JmServer(
|
|
75
|
+
'D:/',
|
|
76
|
+
'password',
|
|
77
|
+
)
|
|
78
|
+
server.run(
|
|
79
|
+
host='0.0.0.0',
|
|
80
|
+
port=443,
|
|
81
|
+
ssl_context='adhoc',
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
* 作为jmcomic的插件
|
|
86
|
+
|
|
87
|
+
```yml
|
|
88
|
+
# 配置文件
|
|
89
|
+
plugins:
|
|
90
|
+
after_init:
|
|
91
|
+
# after_album:
|
|
92
|
+
- plugin: jm_server
|
|
93
|
+
kwargs:
|
|
94
|
+
password: ''
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# 代码
|
|
100
|
+
from jmcomic import *
|
|
101
|
+
|
|
102
|
+
op = create_option('op.yml')
|
|
103
|
+
op.download_album(123)
|
|
104
|
+
# 运行到这里,虽然主线程代码执行完毕,但程序不会退出,因为服务器线程还没有结束。
|
|
105
|
+
# 控制台会输出提示信息如下(atexit时输出的):
|
|
106
|
+
# “主线程执行完毕,但插件jm_server的服务器线程仍运行中,可按下ctrl+c结束程序”
|
|
107
|
+
# 此时需要你主动按下ctrl+c终止程序。
|
|
108
|
+
|
|
109
|
+
# python 3.12 特别注意:需要插入下面这行代码,服务器才能继续处理请求
|
|
110
|
+
op.wait_all_plugins_finish()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## 前端页面与功能介绍
|
|
115
|
+
|
|
116
|
+
本项目提供了一套完整的 Web 前端界面,用于管理和浏览本地图片资源。主要包含以下三个核心页面及其功能:
|
|
117
|
+
|
|
118
|
+
### 1. 登录页
|
|
119
|
+
为了保护个人隐私,系统提供了简单的访问控制。
|
|
120
|
+
* **密码验证**:启动时可配置密码,访问时需校验通过。
|
|
121
|
+
* **设备自适应**:根据访问设备(手机/电脑)自动适配最佳的登录布局。
|
|
122
|
+
|
|
123
|
+
### 2. 文件夹浏览页(主页)
|
|
124
|
+
这是资源管理的核心入口,支持高效的文件查找与导航。目前最新版本采用了 SPA(单页应用)架构进行全面增强:
|
|
125
|
+
* **智能路径导航**:
|
|
126
|
+
* **路径补全**:输入路径时自动联想下级文件夹,支持键盘上下键选择及回车跳转。
|
|
127
|
+
* **层级跳转**:输入框支持“..”快速返回上一级,并提供“确认”按钮进行精准跳转。
|
|
128
|
+
* **可视化展示**:
|
|
129
|
+
* **文件夹预览**:鼠标悬停在文件夹上时,会自动展示该文件夹内的首张图片作为封面预览。
|
|
130
|
+
* **类型区分**:通过文件夹角标(图标)清晰区分普通文件夹与特殊资源文件夹。
|
|
131
|
+
* **辅助功能**:
|
|
132
|
+
* **书签管理**:支持将常看目录加入左侧书签栏,方便下次快速访问。
|
|
133
|
+
* **本地交互**:支持通过网页菜单直接打开服务器端对应的本地文件夹窗口(Explorer)。
|
|
134
|
+
|
|
135
|
+
### 3. 图片阅读页(看本模式)
|
|
136
|
+
专为漫画阅读优化的沉浸式浏览页面。
|
|
137
|
+
* **加载优化**:采用图片懒加载机制,流畅浏览含有数百张图片的大型章节,节省系统资源。
|
|
138
|
+
* **多端适配**:
|
|
139
|
+
* **PC端**:适合大屏浏览,支持浮动工具栏进行页面跳转、全屏切换等。
|
|
140
|
+
* **移动端**:适配触摸操作,提供更好的手机阅读体验。
|
|
141
|
+
* **阅读辅助**:
|
|
142
|
+
* **浮动菜单**:提供“回到顶部”、“加载全部图片”及“在资源管理器打开”等便捷操作。
|
|
143
|
+
* **键盘支持**:支持常用快捷键进行页面滚动和翻页。
|
|
144
|
+
|
|
145
|
+
# 想法起源
|
|
146
|
+
|
|
147
|
+
想法起源:https://github.com/hect0x7/JMComic-Crawler-Python/issues/192
|
|
148
|
+
|
|
149
|
+
基于原项目:https://github.com/AiCorein/Flask-Files-Server
|
|
150
|
+
|
|
151
|
+
为了方便修改,将原项目中的文件复制到本项目中,然后进行修改
|
|
@@ -8,7 +8,7 @@ from typing import Optional
|
|
|
8
8
|
import common
|
|
9
9
|
from flask import Flask, abort, Response, stream_with_context
|
|
10
10
|
from flask import render_template, send_from_directory
|
|
11
|
-
from flask import request, session, redirect, flash
|
|
11
|
+
from flask import request, session, redirect, flash, jsonify
|
|
12
12
|
|
|
13
13
|
from .files import FileManager
|
|
14
14
|
|
|
@@ -22,10 +22,12 @@ class JmServer:
|
|
|
22
22
|
def __init__(self,
|
|
23
23
|
default_path,
|
|
24
24
|
password,
|
|
25
|
+
*,
|
|
25
26
|
jm_option=None,
|
|
26
27
|
ip_whitelist=None,
|
|
27
28
|
current_path=None,
|
|
28
29
|
img_overwrite: Optional[dict] = None,
|
|
30
|
+
env=None,
|
|
29
31
|
**extra,
|
|
30
32
|
):
|
|
31
33
|
"""
|
|
@@ -59,6 +61,9 @@ class JmServer:
|
|
|
59
61
|
import queue
|
|
60
62
|
self.jm_log_msg_queue = queue.Queue()
|
|
61
63
|
self.__hook_jm_logging()
|
|
64
|
+
if env:
|
|
65
|
+
for k, v in env.items():
|
|
66
|
+
os.environ[k] = v
|
|
62
67
|
|
|
63
68
|
def __hook_jm_logging(self):
|
|
64
69
|
import jmcomic
|
|
@@ -77,7 +82,7 @@ class JmServer:
|
|
|
77
82
|
"""
|
|
78
83
|
验证登录状态
|
|
79
84
|
"""
|
|
80
|
-
if session.get('password', None) == self.password:
|
|
85
|
+
if (self.password == '') or session.get('password', None) == self.password:
|
|
81
86
|
return True
|
|
82
87
|
else:
|
|
83
88
|
return False
|
|
@@ -112,9 +117,88 @@ class JmServer:
|
|
|
112
117
|
"""
|
|
113
118
|
url添加一个随机参数,防止浏览器缓存
|
|
114
119
|
"""
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
from random import randint
|
|
121
|
+
return randint(100000, 1000000)
|
|
122
|
+
|
|
123
|
+
def spa_view(self):
|
|
124
|
+
"""
|
|
125
|
+
[New] V2 SPA Interface (PC Only)
|
|
126
|
+
"""
|
|
127
|
+
if not self.verify():
|
|
128
|
+
return redirect('/login')
|
|
129
|
+
|
|
130
|
+
# 获取路径参数,如果为空则使用默认路径
|
|
131
|
+
path = request.args.get('path', None)
|
|
132
|
+
if path is None:
|
|
133
|
+
path = self.file_manager.default_path
|
|
134
|
+
|
|
135
|
+
path = os.path.abspath(path)
|
|
136
|
+
path = common.fix_filepath(path)
|
|
137
|
+
|
|
138
|
+
if common.file_not_exists(path):
|
|
139
|
+
return abort(404)
|
|
140
|
+
|
|
141
|
+
return render_template('index_spa.html',
|
|
142
|
+
data={
|
|
143
|
+
'currentPath': path,
|
|
144
|
+
'defaultPath': self.file_manager.default_path,
|
|
145
|
+
'drivers': self.file_manager.DRIVERS_LIST
|
|
146
|
+
},
|
|
147
|
+
randomArg=self.url_random_arg())
|
|
148
|
+
|
|
149
|
+
def api_list_files(self):
|
|
150
|
+
"""
|
|
151
|
+
[New] API: List files in directory
|
|
152
|
+
"""
|
|
153
|
+
if not self.verify():
|
|
154
|
+
return abort(403)
|
|
155
|
+
|
|
156
|
+
path = request.args.get('path', self.file_manager.default_path)
|
|
157
|
+
path = os.path.abspath(path)
|
|
158
|
+
path = common.fix_filepath(path)
|
|
159
|
+
|
|
160
|
+
if common.file_not_exists(path):
|
|
161
|
+
return jsonify({'error': 'Path not found'}), 404
|
|
162
|
+
|
|
163
|
+
files_data = self.file_manager.get_files_data(path)
|
|
164
|
+
return jsonify({
|
|
165
|
+
'currentPath': path,
|
|
166
|
+
'files': files_data
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
def api_album_images(self):
|
|
170
|
+
"""
|
|
171
|
+
[New] API: Get images for an album
|
|
172
|
+
"""
|
|
173
|
+
if not self.verify():
|
|
174
|
+
return abort(403)
|
|
175
|
+
|
|
176
|
+
path = request.args.get('path', None)
|
|
177
|
+
if not path:
|
|
178
|
+
return jsonify({'error': 'Path required'}), 400
|
|
179
|
+
|
|
180
|
+
path = os.path.abspath(path)
|
|
181
|
+
if common.file_not_exists(path):
|
|
182
|
+
return jsonify({'error': 'Path not found'}), 404
|
|
183
|
+
|
|
184
|
+
images = self.file_manager.get_jm_view_images(path)
|
|
185
|
+
return jsonify({
|
|
186
|
+
'title': common.of_file_name(path),
|
|
187
|
+
'images': images
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
def api_open_file(self):
|
|
191
|
+
"""
|
|
192
|
+
[New] API: Open file/folder in Explorer
|
|
193
|
+
"""
|
|
194
|
+
if not self.verify():
|
|
195
|
+
return abort(403)
|
|
196
|
+
|
|
197
|
+
path = request.args.get('path', None)
|
|
198
|
+
if not path:
|
|
199
|
+
return jsonify({'error': 'Path required'}), 400
|
|
200
|
+
|
|
201
|
+
return self.open_directory(path) or jsonify({'status': 'ok'})
|
|
118
202
|
|
|
119
203
|
def jm_view(self):
|
|
120
204
|
"""
|
|
@@ -189,6 +273,7 @@ class JmServer:
|
|
|
189
273
|
"files": self.file_manager.get_files_data(path),
|
|
190
274
|
"drivers": self.file_manager.DRIVERS_LIST,
|
|
191
275
|
"currentPath": path,
|
|
276
|
+
"defaultPath": self.file_manager.default_path
|
|
192
277
|
},
|
|
193
278
|
randomArg=self.url_random_arg())
|
|
194
279
|
|
|
@@ -314,8 +399,14 @@ class JmServer:
|
|
|
314
399
|
finally:
|
|
315
400
|
self.jm_log_msg_queue.put(end)
|
|
316
401
|
|
|
317
|
-
def
|
|
318
|
-
|
|
402
|
+
def open_directory(self, directory):
|
|
403
|
+
path = os.path.abspath(directory)
|
|
404
|
+
# os.startfile(path) # Windows特有,打开文件夹
|
|
405
|
+
import subprocess
|
|
406
|
+
subprocess.Popen(f'explorer /select,"{path}"')
|
|
407
|
+
return ''
|
|
408
|
+
|
|
409
|
+
def register_routes(self):
|
|
319
410
|
# 添加路由
|
|
320
411
|
self.app.add_url_rule('/jm_view', 'jm_view', self.jm_view, methods=['GET'])
|
|
321
412
|
self.app.add_url_rule("/view_file/", 'view_file', self.view_file, methods=['GET'], strict_slashes=False)
|
|
@@ -323,8 +414,19 @@ class JmServer:
|
|
|
323
414
|
self.app.add_url_rule('/login', 'login', self.login, methods=['GET', 'POST'])
|
|
324
415
|
self.app.add_url_rule('/logout', 'logout', self.logout, methods=['GET', 'POST'])
|
|
325
416
|
self.app.add_url_rule("/download_file/<filename>", 'file_content', self.file_content)
|
|
417
|
+
self.app.add_url_rule('/open/<path:directory>', 'open_directory', self.open_directory)
|
|
326
418
|
self.app.add_url_rule("/upload_file", 'upload', self.upload, methods=['GET', 'POST'])
|
|
327
419
|
self.app.add_url_rule("/stream", 'stream', self.stream, methods=['GET', 'POST'])
|
|
420
|
+
|
|
421
|
+
# [New] SPA Routes
|
|
422
|
+
self.app.add_url_rule("/spa", 'spa_view', self.spa_view, methods=['GET'])
|
|
423
|
+
self.app.add_url_rule("/api/list_files", 'api_list_files', self.api_list_files, methods=['GET'])
|
|
424
|
+
self.app.add_url_rule("/api/album_images", 'api_album_images', self.api_album_images, methods=['GET'])
|
|
425
|
+
self.app.add_url_rule("/api/open_file", 'api_open_file', self.api_open_file, methods=['GET'])
|
|
426
|
+
|
|
427
|
+
def run(self, **kwargs):
|
|
428
|
+
kwargs.setdefault('port', self.DEFAULT_PORT)
|
|
429
|
+
self.register_routes()
|
|
328
430
|
# 监听在所有 IP 地址上
|
|
329
431
|
self.app.run(**kwargs)
|
|
330
432
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from urllib.parse import quote
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
import common
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FileManager:
|
|
10
|
+
def __init__(self, default_path, current_path):
|
|
11
|
+
self.default_path = default_path
|
|
12
|
+
self.current_path = current_path
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def DRIVERS_LIST(self):
|
|
16
|
+
# 判断如果是windows系统
|
|
17
|
+
if os.name == 'nt':
|
|
18
|
+
from .driver import get_winDriver
|
|
19
|
+
return get_winDriver()
|
|
20
|
+
else:
|
|
21
|
+
return ['/']
|
|
22
|
+
|
|
23
|
+
def get_jm_view_images(self, path):
|
|
24
|
+
images_data = []
|
|
25
|
+
|
|
26
|
+
give_up_sort = False
|
|
27
|
+
|
|
28
|
+
for f in self.files_of_dir_safe(path):
|
|
29
|
+
if not self.is_image_file(f):
|
|
30
|
+
continue
|
|
31
|
+
f = quote(f)
|
|
32
|
+
name = common.of_file_name(f)
|
|
33
|
+
try:
|
|
34
|
+
index = int(name[:name.index('.')])
|
|
35
|
+
except ValueError:
|
|
36
|
+
give_up_sort = True
|
|
37
|
+
index = None
|
|
38
|
+
|
|
39
|
+
images_data.append({
|
|
40
|
+
'filename': name,
|
|
41
|
+
'data_original': f'/view_file?path={f}',
|
|
42
|
+
'index': index,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
if give_up_sort is False:
|
|
46
|
+
images_data.sort(key=lambda item: item['index'])
|
|
47
|
+
|
|
48
|
+
return images_data
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def is_image_file(filename):
|
|
52
|
+
file_extension = filename.lower().split('.')[-1]
|
|
53
|
+
image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp']
|
|
54
|
+
return file_extension in image_extensions
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def files_of_dir_safe(dir_path):
|
|
58
|
+
try:
|
|
59
|
+
return common.files_of_dir(dir_path)
|
|
60
|
+
except OSError:
|
|
61
|
+
return []
|
|
62
|
+
|
|
63
|
+
def check_dir_can_open_jm_view(self, dirpath):
|
|
64
|
+
return any(f for f in self.files_of_dir_safe(dirpath) if self.is_image_file(f))
|
|
65
|
+
|
|
66
|
+
def build_one_path_info(self, file_path, dtype=''):
|
|
67
|
+
jm_view, the_type = self.get_file_type(file_path)
|
|
68
|
+
|
|
69
|
+
name = common.of_file_name(file_path)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
size = os.path.getsize(file_path)
|
|
73
|
+
except OSError:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
size = dtype if dtype != '' else self.file_size_format(size, the_type)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
getctime = os.path.getctime(file_path)
|
|
80
|
+
ctime = time.localtime(getctime)
|
|
81
|
+
time_str = time.strftime("%Y-%m-%d %H:%M:%S", ctime)
|
|
82
|
+
except OSError:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
first_img_url = ''
|
|
86
|
+
if jm_view and the_type == 'dir':
|
|
87
|
+
imgs = self.get_jm_view_images(file_path)
|
|
88
|
+
if imgs:
|
|
89
|
+
first_img_url = imgs[0]
|
|
90
|
+
|
|
91
|
+
if name.endswith('.lnk'):
|
|
92
|
+
target_path = self.get_target_path(file_path)
|
|
93
|
+
return self.build_one_path_info(target_path, 'Link')
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"name": name,
|
|
97
|
+
'path': file_path,
|
|
98
|
+
'href': f'/?path={file_path}' if the_type == 'dir' else f'./download_file/{name}',
|
|
99
|
+
"size": size,
|
|
100
|
+
"ctime": time_str,
|
|
101
|
+
"type": the_type,
|
|
102
|
+
'jm_view': jm_view,
|
|
103
|
+
'first_img_url': first_img_url
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
def get_files_data(self, path):
|
|
107
|
+
files = list(filter(bool, map(self.build_one_path_info, self.files_of_dir_safe(path))))
|
|
108
|
+
self.current_path = path
|
|
109
|
+
files = self.sort_files(files)
|
|
110
|
+
return files
|
|
111
|
+
|
|
112
|
+
def sort_files(self, files: list):
|
|
113
|
+
def natural_key(item):
|
|
114
|
+
return [int(text) if text.isdigit() else text.lower()
|
|
115
|
+
for text in re.split(r'(\d+)', item['name'])]
|
|
116
|
+
|
|
117
|
+
# Split into groups
|
|
118
|
+
jm_albums = []
|
|
119
|
+
other_dirs = []
|
|
120
|
+
other_files = []
|
|
121
|
+
|
|
122
|
+
for f in files:
|
|
123
|
+
if f['jm_view']:
|
|
124
|
+
jm_albums.append(f)
|
|
125
|
+
elif f['type'] == 'dir':
|
|
126
|
+
other_dirs.append(f)
|
|
127
|
+
else:
|
|
128
|
+
other_files.append(f)
|
|
129
|
+
|
|
130
|
+
# Sort each group
|
|
131
|
+
jm_albums.sort(key=natural_key)
|
|
132
|
+
other_dirs.sort(key=natural_key)
|
|
133
|
+
other_files.sort(key=natural_key)
|
|
134
|
+
|
|
135
|
+
return jm_albums + other_dirs + other_files
|
|
136
|
+
|
|
137
|
+
def get_file_type(self, file_path):
|
|
138
|
+
if os.path.isfile(file_path):
|
|
139
|
+
the_type = 'file'
|
|
140
|
+
jm_view = self.check_dir_can_open_jm_view(common.of_dir_path(file_path))
|
|
141
|
+
else:
|
|
142
|
+
the_type = 'dir'
|
|
143
|
+
jm_view = self.check_dir_can_open_jm_view(file_path)
|
|
144
|
+
return jm_view, the_type
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def get_target_path(lnk_path: str) -> str:
|
|
148
|
+
try:
|
|
149
|
+
import pylnk3
|
|
150
|
+
lnk = pylnk3.parse(lnk_path)
|
|
151
|
+
return lnk.path.replace("\\", '/').replace("//", '/') # 获取目标文件/文件夹路径
|
|
152
|
+
except Exception as e:
|
|
153
|
+
return lnk_path
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def file_size_format(size, the_type):
|
|
157
|
+
if the_type == 'dir':
|
|
158
|
+
return '<DIR>'
|
|
159
|
+
else:
|
|
160
|
+
if size < 1024:
|
|
161
|
+
return '%i' % size + ' B'
|
|
162
|
+
elif 1024 < size <= 1048576:
|
|
163
|
+
return '%.1f' % float(size / 1024) + ' KB'
|
|
164
|
+
elif 1048576 < size <= 1073741824:
|
|
165
|
+
return '%.1f' % float(size / 1048576) + ' MB'
|
|
166
|
+
elif 1073741824 < size <= 1099511627776:
|
|
167
|
+
return '%.1f' % float(size / 1073741824) + ' GB'
|
|
168
|
+
|
|
169
|
+
def get_current_path(self):
|
|
170
|
+
return self.current_path
|