pyloid 0.24.8__py3-none-any.whl → 0.24.9__py3-none-any.whl
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.
- pyloid/browser_window.py +34 -34
- pyloid/serve.py +190 -14
- pyloid/store.py +7 -2
- {pyloid-0.24.8.dist-info → pyloid-0.24.9.dist-info}/LICENSE +1 -1
- {pyloid-0.24.8.dist-info → pyloid-0.24.9.dist-info}/METADATA +4 -3
- {pyloid-0.24.8.dist-info → pyloid-0.24.9.dist-info}/RECORD +7 -7
- {pyloid-0.24.8.dist-info → pyloid-0.24.9.dist-info}/WHEEL +0 -0
pyloid/browser_window.py
CHANGED
|
@@ -40,7 +40,7 @@ from PySide6.QtWidgets import QSplashScreen, QLabel
|
|
|
40
40
|
from typing import TYPE_CHECKING, Any
|
|
41
41
|
from PySide6.QtWebEngineCore import (
|
|
42
42
|
QWebEngineSettings,
|
|
43
|
-
QWebEngineDesktopMediaRequest,
|
|
43
|
+
# QWebEngineDesktopMediaRequest, # 6.8.3 부터
|
|
44
44
|
)
|
|
45
45
|
import threading
|
|
46
46
|
|
|
@@ -55,7 +55,7 @@ class CustomWebPage(QWebEnginePage):
|
|
|
55
55
|
def __init__(self, profile=None):
|
|
56
56
|
super().__init__(profile)
|
|
57
57
|
self.featurePermissionRequested.connect(self._handlePermissionRequest)
|
|
58
|
-
self.desktopMediaRequested.connect(self._handleDesktopMediaRequest)
|
|
58
|
+
# self.desktopMediaRequested.connect(self._handleDesktopMediaRequest)
|
|
59
59
|
self._permission_handlers = {}
|
|
60
60
|
self._desktop_media_handler = None
|
|
61
61
|
self._url_handlers = {} # URL 핸들러 저장을 위한 딕셔너리 추가
|
|
@@ -78,31 +78,31 @@ class CustomWebPage(QWebEnginePage):
|
|
|
78
78
|
"""Register a handler for a specific permission"""
|
|
79
79
|
self._permission_handlers[feature] = handler
|
|
80
80
|
|
|
81
|
-
def _handleDesktopMediaRequest(self, request: QWebEngineDesktopMediaRequest):
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
# def _handleDesktopMediaRequest(self, request: QWebEngineDesktopMediaRequest):
|
|
82
|
+
# return
|
|
83
|
+
# print("Desktop media request received:", request)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
# # check the available screen list
|
|
86
|
+
# screens_model = request.screensModel()
|
|
87
|
+
# print("\n=== Available Screens ===")
|
|
88
|
+
# for i in range(screens_model.rowCount()):
|
|
89
|
+
# screen_index = screens_model.index(i)
|
|
90
|
+
# screen_name = screens_model.data(screen_index)
|
|
91
|
+
# print(f"Screen {i}: {screen_name}")
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
# # check the available window list
|
|
94
|
+
# windows_model = request.windowsModel()
|
|
95
|
+
# print("\n=== Available Windows ===")
|
|
96
|
+
# for i in range(windows_model.rowCount()):
|
|
97
|
+
# window_index = windows_model.index(i)
|
|
98
|
+
# window_name = windows_model.data(window_index)
|
|
99
|
+
# print(f"Window {i}: {window_name}")
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
# request.selectWindow(windows_model.index(3))
|
|
102
102
|
|
|
103
103
|
# # interceptor ( navigation request )
|
|
104
104
|
# def acceptNavigationRequest(self, url, navigation_type, is_main_frame):
|
|
105
|
-
# """
|
|
105
|
+
# """method to handle navigation requests"""
|
|
106
106
|
# print(f"Navigation Request - URL: {url.toString()}")
|
|
107
107
|
# print(f"Navigation Type: {navigation_type}")
|
|
108
108
|
# print(f"Is Main Frame: {is_main_frame}")
|
|
@@ -228,10 +228,10 @@ class CustomWebEngineView(QWebEngineView):
|
|
|
228
228
|
|
|
229
229
|
def eventFilter(self, source, event):
|
|
230
230
|
if self.focusProxy() is source:
|
|
231
|
-
#
|
|
231
|
+
# when in the resize area, all click events are intercepted
|
|
232
232
|
if self.is_in_resize_area and event.type() == QEvent.MouseButtonPress:
|
|
233
233
|
self.mouse_press_event(event)
|
|
234
|
-
return True #
|
|
234
|
+
return True # consume the event so it is not passed to the web view
|
|
235
235
|
|
|
236
236
|
if event.type() == QEvent.MouseButtonPress:
|
|
237
237
|
self.mouse_press_event(event)
|
|
@@ -358,18 +358,18 @@ class _BrowserWindow:
|
|
|
358
358
|
self.close_on_load = True
|
|
359
359
|
self.splash_screen = None
|
|
360
360
|
###########################################################################################
|
|
361
|
-
# RPC
|
|
361
|
+
# if the RPC server is not present, do not add it
|
|
362
362
|
if not self.rpc:
|
|
363
363
|
return
|
|
364
364
|
|
|
365
365
|
self.rpc.pyloid = self.app.pyloid_wrapper
|
|
366
366
|
# self.rpc.window = self.window_wrapper
|
|
367
367
|
|
|
368
|
-
#
|
|
368
|
+
# prevent duplicate RPC servers
|
|
369
369
|
if self.rpc in self.app.rpc_servers:
|
|
370
370
|
return
|
|
371
371
|
|
|
372
|
-
# RPC
|
|
372
|
+
# add the RPC server
|
|
373
373
|
self.app.rpc_servers.add(self.rpc)
|
|
374
374
|
|
|
375
375
|
# Start unique RPC servers
|
|
@@ -405,7 +405,7 @@ class _BrowserWindow:
|
|
|
405
405
|
central_widget.setLayout(layout)
|
|
406
406
|
self._window.setCentralWidget(central_widget)
|
|
407
407
|
|
|
408
|
-
#
|
|
408
|
+
# add properties for window movement
|
|
409
409
|
self._window.moving = False
|
|
410
410
|
self._window.offset = QPoint()
|
|
411
411
|
else:
|
|
@@ -543,10 +543,10 @@ class _BrowserWindow:
|
|
|
543
543
|
# Set F12 shortcut
|
|
544
544
|
self.set_dev_tools(self.dev_tools)
|
|
545
545
|
|
|
546
|
-
#
|
|
546
|
+
# get the profile and set the interceptor
|
|
547
547
|
profile = self.web_view.page().profile()
|
|
548
548
|
|
|
549
|
-
# #
|
|
549
|
+
# # if the existing interceptor is present, remove it
|
|
550
550
|
# if self.interceptor:
|
|
551
551
|
# profile.setUrlRequestInterceptor(None)
|
|
552
552
|
|
|
@@ -565,15 +565,15 @@ class _BrowserWindow:
|
|
|
565
565
|
new QWebChannel(qt.webChannelTransport, function (channel) {
|
|
566
566
|
window.pyloid = {
|
|
567
567
|
EventAPI: {
|
|
568
|
-
_listeners: {}, //
|
|
568
|
+
_listeners: {}, // object to store the callback functions
|
|
569
569
|
|
|
570
570
|
listen: function(eventName, callback) {
|
|
571
|
-
//
|
|
571
|
+
// if the callback array for the event is not present, create it
|
|
572
572
|
if (!this._listeners[eventName]) {
|
|
573
573
|
this._listeners[eventName] = [];
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
-
//
|
|
576
|
+
// save the callback function
|
|
577
577
|
this._listeners[eventName].push(callback);
|
|
578
578
|
|
|
579
579
|
document.addEventListener(eventName, function(event) {
|
|
@@ -588,12 +588,12 @@ class _BrowserWindow:
|
|
|
588
588
|
},
|
|
589
589
|
|
|
590
590
|
unlisten: function(eventName) {
|
|
591
|
-
//
|
|
591
|
+
// remove all listeners for the event
|
|
592
592
|
if (this._listeners[eventName]) {
|
|
593
593
|
this._listeners[eventName].forEach(callback => {
|
|
594
594
|
document.removeEventListener(eventName, callback);
|
|
595
595
|
});
|
|
596
|
-
//
|
|
596
|
+
// remove the saved callback
|
|
597
597
|
delete this._listeners[eventName];
|
|
598
598
|
}
|
|
599
599
|
}
|
pyloid/serve.py
CHANGED
|
@@ -1,21 +1,180 @@
|
|
|
1
1
|
import threading
|
|
2
|
-
|
|
2
|
+
import asyncio
|
|
3
|
+
import mimetypes
|
|
4
|
+
import aiofiles
|
|
5
|
+
from aiohttp import web
|
|
6
|
+
from aiohttp.web_fileresponse import FileResponse
|
|
3
7
|
from typing import Optional
|
|
4
|
-
from
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from .utils import get_free_port, is_production
|
|
10
|
+
import logging
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
# 로깅 설정
|
|
13
|
+
logging.getLogger('aiohttp').setLevel(logging.WARNING)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ZeroCopyFileResponse(FileResponse):
|
|
17
|
+
"""zero-copy optimized file response class"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, path, chunk_size=65536, headers=None):
|
|
20
|
+
"""
|
|
21
|
+
Args:
|
|
22
|
+
path: file path
|
|
23
|
+
chunk_size: streaming chunk size (64KB default)
|
|
24
|
+
headers: additional HTTP headers
|
|
25
|
+
"""
|
|
26
|
+
super().__init__(path, chunk_size=chunk_size, headers=headers)
|
|
27
|
+
self._enable_sendfile = True # sendfile() system call activation
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ZeroCopyStaticHandler:
|
|
31
|
+
"""zero-copy optimized static file handler"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, directory: str):
|
|
34
|
+
self.directory = Path(directory).resolve()
|
|
35
|
+
self.chunk_size = 65536 # 64KB chunk
|
|
36
|
+
|
|
37
|
+
async def handle_request(self, request):
|
|
38
|
+
"""HTTP request processing"""
|
|
39
|
+
try:
|
|
40
|
+
# URL path parsing
|
|
41
|
+
path = request.path_qs.split('?')[0] # 쿼리 파라미터 제거
|
|
42
|
+
if path.endswith('/'):
|
|
43
|
+
path += 'index.html'
|
|
44
|
+
|
|
45
|
+
# Security: prevent directory traversal attacks
|
|
46
|
+
file_path = (self.directory / path.lstrip('/')).resolve()
|
|
47
|
+
if not str(file_path).startswith(str(self.directory)):
|
|
48
|
+
return web.Response(status=403, text="Forbidden")
|
|
49
|
+
|
|
50
|
+
# Check if the file exists
|
|
51
|
+
if not file_path.exists() or not file_path.is_file():
|
|
52
|
+
return web.Response(status=404, text="File not found")
|
|
53
|
+
|
|
54
|
+
# Check the file size and type
|
|
55
|
+
stat = file_path.stat()
|
|
56
|
+
file_size = stat.st_size
|
|
57
|
+
content_type, _ = mimetypes.guess_type(str(file_path))
|
|
58
|
+
|
|
59
|
+
# Range request processing (partial download supported)
|
|
60
|
+
range_header = request.headers.get('Range')
|
|
61
|
+
if range_header:
|
|
62
|
+
return await self._handle_range_request(file_path, range_header, file_size, content_type, request)
|
|
63
|
+
|
|
64
|
+
if is_production():
|
|
65
|
+
cache_control = 'public, max-age=3600' # 1 hour caching
|
|
66
|
+
else:
|
|
67
|
+
cache_control = 'no-cache'
|
|
68
|
+
|
|
69
|
+
# zero-copy file response creation
|
|
70
|
+
headers = {
|
|
71
|
+
'Content-Type': content_type or 'application/octet-stream',
|
|
72
|
+
'Content-Length': str(file_size),
|
|
73
|
+
'Accept-Ranges': 'bytes',
|
|
74
|
+
'Cache-Control': cache_control,
|
|
75
|
+
'ETag': f'"{stat.st_mtime}-{file_size}"'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ETag based cache check
|
|
79
|
+
if_none_match = request.headers.get('If-None-Match')
|
|
80
|
+
if if_none_match == headers['ETag']:
|
|
81
|
+
return web.Response(status=304, headers=headers)
|
|
82
|
+
|
|
83
|
+
# zero-copy response return (sendfile used)
|
|
84
|
+
return ZeroCopyFileResponse(
|
|
85
|
+
path=file_path,
|
|
86
|
+
chunk_size=self.chunk_size,
|
|
87
|
+
headers=headers
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
return web.Response(status=500, text=f"Internal Server Error: {str(e)}")
|
|
92
|
+
|
|
93
|
+
async def _handle_range_request(self, file_path, range_header, file_size, content_type, request):
|
|
94
|
+
"""Range request processing (partial download)"""
|
|
95
|
+
try:
|
|
96
|
+
# Range header parsing: bytes=start-end
|
|
97
|
+
range_match = range_header.replace('bytes=', '').split('-')
|
|
98
|
+
start = int(range_match[0]) if range_match[0] else 0
|
|
99
|
+
end = int(range_match[1]) if range_match[1] else file_size - 1
|
|
100
|
+
|
|
101
|
+
# Range validation
|
|
102
|
+
if start >= file_size or end >= file_size or start > end:
|
|
103
|
+
return web.Response(
|
|
104
|
+
status=416, # Range Not Satisfiable
|
|
105
|
+
headers={'Content-Range': f'bytes */{file_size}'}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
content_length = end - start + 1
|
|
109
|
+
|
|
110
|
+
headers = {
|
|
111
|
+
'Content-Type': content_type or 'application/octet-stream',
|
|
112
|
+
'Content-Length': str(content_length),
|
|
113
|
+
'Content-Range': f'bytes {start}-{end}/{file_size}',
|
|
114
|
+
'Accept-Ranges': 'bytes'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# zero-copy partial response
|
|
118
|
+
response = web.StreamResponse(status=206, headers=headers)
|
|
119
|
+
await response.prepare(request)
|
|
120
|
+
|
|
121
|
+
# aiofiles to async zero-copy transfer
|
|
122
|
+
async with aiofiles.open(file_path, 'rb') as f:
|
|
123
|
+
await f.seek(start)
|
|
124
|
+
remaining = content_length
|
|
125
|
+
|
|
126
|
+
while remaining > 0:
|
|
127
|
+
chunk_size = min(self.chunk_size, remaining)
|
|
128
|
+
chunk = await f.read(chunk_size)
|
|
129
|
+
if not chunk:
|
|
130
|
+
break
|
|
131
|
+
await response.write(chunk)
|
|
132
|
+
remaining -= len(chunk)
|
|
133
|
+
|
|
134
|
+
await response.write_eof()
|
|
135
|
+
return response
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
return web.Response(status=500, text=f"Range Request Error: {str(e)}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def start_zero_copy_server(directory: str, port: int):
|
|
142
|
+
"""zero-copy optimized server starts"""
|
|
143
|
+
handler = ZeroCopyStaticHandler(directory)
|
|
9
144
|
|
|
10
|
-
|
|
11
|
-
|
|
145
|
+
# aiohttp app creation
|
|
146
|
+
app = web.Application()
|
|
147
|
+
app.router.add_route('GET', '/{path:.*}', handler.handle_request)
|
|
148
|
+
|
|
149
|
+
# server configuration optimization
|
|
150
|
+
runner = web.AppRunner(
|
|
151
|
+
app,
|
|
152
|
+
access_log=None, # access log disabled (performance improvement)
|
|
153
|
+
keepalive_timeout=75, # Keep-Alive timeout
|
|
154
|
+
client_timeout=600, # client timeout
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
await runner.setup()
|
|
158
|
+
|
|
159
|
+
site = web.TCPSite(
|
|
160
|
+
runner,
|
|
161
|
+
'127.0.0.1',
|
|
162
|
+
port,
|
|
163
|
+
backlog=1024, # connection backlog size
|
|
164
|
+
reuse_address=True # port reuse only
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
await site.start()
|
|
168
|
+
|
|
169
|
+
return runner, site
|
|
170
|
+
|
|
12
171
|
|
|
13
172
|
def pyloid_serve(
|
|
14
173
|
directory: str,
|
|
15
174
|
port: Optional[int] = None,
|
|
16
175
|
) -> str:
|
|
17
176
|
"""
|
|
18
|
-
|
|
177
|
+
zero-copy optimized static file server starts.
|
|
19
178
|
|
|
20
179
|
Args
|
|
21
180
|
----
|
|
@@ -44,13 +203,30 @@ def pyloid_serve(
|
|
|
44
203
|
if port is None:
|
|
45
204
|
port = get_free_port()
|
|
46
205
|
|
|
47
|
-
|
|
48
|
-
|
|
206
|
+
def run_zero_copy_server():
|
|
207
|
+
"""run async server in a separate thread"""
|
|
208
|
+
loop = asyncio.new_event_loop()
|
|
209
|
+
asyncio.set_event_loop(loop)
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
runner, site = loop.run_until_complete(
|
|
213
|
+
start_zero_copy_server(directory, port)
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
print(f"🚀 Zero-copy server started on http://127.0.0.1:{port}")
|
|
217
|
+
print(f"📁 Serving directory: {directory}")
|
|
218
|
+
print(f"⚡ Features: sendfile, Range requests, ETag caching")
|
|
219
|
+
|
|
220
|
+
# wait until the server starts
|
|
221
|
+
loop.run_forever()
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print(f"Zero-copy server error: {e}")
|
|
225
|
+
finally:
|
|
226
|
+
loop.close()
|
|
49
227
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
daemon=True
|
|
53
|
-
)
|
|
228
|
+
# start server in a daemon thread
|
|
229
|
+
thread = threading.Thread(target=run_zero_copy_server, daemon=True)
|
|
54
230
|
thread.start()
|
|
55
231
|
|
|
56
232
|
return f"http://127.0.0.1:{port}"
|
pyloid/store.py
CHANGED
|
@@ -20,7 +20,7 @@ class Store:
|
|
|
20
20
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
21
21
|
self.db = PickleDB(path)
|
|
22
22
|
|
|
23
|
-
def get(self, key: str) -> Any:
|
|
23
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
24
24
|
"""
|
|
25
25
|
Retrieve the value associated with the specified key.
|
|
26
26
|
|
|
@@ -28,6 +28,8 @@ class Store:
|
|
|
28
28
|
----------
|
|
29
29
|
key: str
|
|
30
30
|
The key to look up in the database
|
|
31
|
+
default: Any
|
|
32
|
+
The value to return if the value does not exist in the database
|
|
31
33
|
|
|
32
34
|
Returns
|
|
33
35
|
-------
|
|
@@ -44,8 +46,11 @@ class Store:
|
|
|
44
46
|
{'name': 'John Doe', 'age': 30}
|
|
45
47
|
>>> print(store.get("non_existent_key"))
|
|
46
48
|
None
|
|
49
|
+
>>> print(store.get("non_existent_key", "default_value"))
|
|
50
|
+
'default_value'
|
|
47
51
|
"""
|
|
48
|
-
|
|
52
|
+
stored_value = self.db.get(key)
|
|
53
|
+
return stored_value if stored_value is not None else default
|
|
49
54
|
|
|
50
55
|
def set(self, key: str, value: Any) -> bool:
|
|
51
56
|
"""
|
|
@@ -198,4 +198,4 @@ Apache License
|
|
|
198
198
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
199
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
200
|
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
|
201
|
+
limitations under the License.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pyloid
|
|
3
|
-
Version: 0.24.
|
|
3
|
+
Version: 0.24.9
|
|
4
4
|
Summary:
|
|
5
5
|
Author: aesthetics-of-record
|
|
6
6
|
Author-email: 111675679+aesthetics-of-record@users.noreply.github.com
|
|
@@ -11,10 +11,11 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
|
|
14
15
|
Requires-Dist: aiohttp-cors (>=0.8.1,<0.9.0)
|
|
15
16
|
Requires-Dist: pickledb (>=1.3.2,<2.0.0)
|
|
16
17
|
Requires-Dist: platformdirs (>=4.3.7,<5.0.0)
|
|
17
|
-
Requires-Dist: pyside6 (==6.9.
|
|
18
|
+
Requires-Dist: pyside6 (==6.9.2)
|
|
18
19
|
Description-Content-Type: text/markdown
|
|
19
20
|
|
|
20
21
|
<h1 style="text-align: center; font-size: 200px; font-weight: 500;">
|
|
@@ -23,7 +24,7 @@ Description-Content-Type: text/markdown
|
|
|
23
24
|
|
|
24
25
|

|
|
25
26
|
|
|
26
|
-
<h2 align="center" style="font-size: 28px;"><b>Pyloid:
|
|
27
|
+
<h2 align="center" style="font-size: 28px;"><b>Pyloid: Electron for Python Developer • Web-based desktop app framework</b></h2>
|
|
27
28
|
|
|
28
29
|
## 💡 Key Features
|
|
29
30
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
pyloid/__init__.py,sha256=YKwMCSOds1QVi9N7EGfY0Z7BEjJn8j6HGqRblZlZClA,235
|
|
2
2
|
pyloid/api.py,sha256=A61Kmddh8BlpT3LfA6NbPQNzFmD95vQ4WKX53oKsGYU,2419
|
|
3
3
|
pyloid/autostart.py,sha256=K7DQYl4LHItvPp0bt1V9WwaaZmVSTeGvadkcwG-KKrI,3899
|
|
4
|
-
pyloid/browser_window.py,sha256=
|
|
4
|
+
pyloid/browser_window.py,sha256=brCNmrQB-sTUvIC8VR5glpg-__DYmkrpVlIN0jf29Sw,104377
|
|
5
5
|
pyloid/custom/titlebar.py,sha256=itzK9pJbZMQ7BKca9kdbuHMffurrw15UijR6OU03Xsk,3894
|
|
6
6
|
pyloid/filewatcher.py,sha256=3M5zWVUf1OhlkWJcDFC8ZA9agO4Q-U8WdgGpy6kaVz0,4601
|
|
7
7
|
pyloid/js_api/base.py,sha256=Z3ID-4AJ0eHusmljRltlSaK4m2RKvRNfmqX76NLF77o,8585
|
|
@@ -10,14 +10,14 @@ pyloid/js_api/window_api.py,sha256=-isphU3m2wGB5U0yZrSuK_4XiBz2mG45HsjYTUq7Fxs,7
|
|
|
10
10
|
pyloid/monitor.py,sha256=1mXvHm5deohnNlTLcRx4sT4x-stnOIb0dUQnnxN50Uo,28295
|
|
11
11
|
pyloid/pyloid.py,sha256=DSHpMRDW4WvZgAAo2nJMesMJuOkNTVEEsDhCFIvjB5w,84509
|
|
12
12
|
pyloid/rpc.py,sha256=OnF1sRGok9OJ-Q5519eQARD4oZTohyPhsPAT2Mg4_Gg,20377
|
|
13
|
-
pyloid/serve.py,sha256=
|
|
14
|
-
pyloid/store.py,sha256=
|
|
13
|
+
pyloid/serve.py,sha256=KOs9984J2qboEXsquorkXgcQvvao_C2c9F6N2gSLfiA,8122
|
|
14
|
+
pyloid/store.py,sha256=8PnBxtkUgbF8Lxh-iYlEuhbLE76bGBF8t5KV5u5NzoQ,4831
|
|
15
15
|
pyloid/thread_pool.py,sha256=fKOBb8jMfZn_7crA_fJCno8dObBRZE31EIWaNQ759aw,14616
|
|
16
16
|
pyloid/timer.py,sha256=RqMsChFUd93cxMVgkHWiIKrci0QDTBgJSTULnAtYT8M,8712
|
|
17
17
|
pyloid/tray.py,sha256=D12opVEc2wc2T4tK9epaN1oOdeziScsIVNM2uCN7C-A,1710
|
|
18
18
|
pyloid/url_interceptor.py,sha256=AFjPANDELc9-E-1TnVvkNVc-JZBJYf0677dWQ8LDaqw,726
|
|
19
19
|
pyloid/utils.py,sha256=J6owgVE1YDOEfcOPmoP9m9Q6nbYDyNEo9uqPsJs5p5g,6644
|
|
20
|
-
pyloid-0.24.
|
|
21
|
-
pyloid-0.24.
|
|
22
|
-
pyloid-0.24.
|
|
23
|
-
pyloid-0.24.
|
|
20
|
+
pyloid-0.24.9.dist-info/LICENSE,sha256=F96EzotgWhhpnQTW2TcdoqrMDir1jyEo6H915tGQ-QE,11524
|
|
21
|
+
pyloid-0.24.9.dist-info/METADATA,sha256=aUI7SwfcwgFgAoAfRfm1nfzgrtYX2vtH2BupDHqc8eo,2298
|
|
22
|
+
pyloid-0.24.9.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
23
|
+
pyloid-0.24.9.dist-info/RECORD,,
|
|
File without changes
|