[RELEASE] RexiMemo: Open Source Edition
This commit is contained in:
1 parent
5e2acc31c5
commit
a438dfc231
45 files changed
+8516
-169
No files matched your search
@@ -1,166 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Settings retained from the original project.
|
||||
useWSGI = False # Historical name: this creates a Twisted service application.
|
||||
port = 8080
|
||||
|
||||
|
||||
def _force_project_working_directory():
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
if base:
|
||||
os.chdir(base)
|
||||
|
||||
|
||||
def _import_twisted():
|
||||
try:
|
||||
from twisted.internet import reactor
|
||||
from twisted.web import server
|
||||
except ImportError as exc:
|
||||
raise SystemExit(
|
||||
"Twisted is required to run the server. Install dependencies with: "
|
||||
"python3 -m pip install -r requirements.txt"
|
||||
) from exc
|
||||
return reactor, server
|
||||
|
||||
|
||||
class Log:
|
||||
class filesplit:
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
|
||||
def write(self, data):
|
||||
for output in self.files:
|
||||
output.write(data)
|
||||
return len(data)
|
||||
|
||||
def flush(self):
|
||||
for output in self.files:
|
||||
output.flush()
|
||||
|
||||
def __init__(self, reactor):
|
||||
self.reactor = reactor
|
||||
minutes, seconds = map(int, time.strftime("%M %S").split(" "))
|
||||
minutes = 59 - minutes
|
||||
seconds = 59 - seconds
|
||||
reactor.callLater(60 * minutes + seconds + 5, self.HandleUpdate)
|
||||
reactor.callLater(60 * 5, self.AutoFlush)
|
||||
self._open_handles()
|
||||
|
||||
self.stderr = sys.stderr
|
||||
sys.stderr = self.filesplit()
|
||||
sys.stderr.files.extend((self.stderr, self.Errorhandle))
|
||||
self.write("Server startup...", True)
|
||||
|
||||
def _open_handles(self):
|
||||
directory = time.strftime("logs/%Y/%B")
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
self.Activityhandle = open(
|
||||
time.strftime("logs/%Y/%B/%d %B activity.log"), "a", encoding="utf-8"
|
||||
)
|
||||
self.Errorhandle = open(
|
||||
time.strftime("logs/%Y/%B/%d %B error.log"), "a", encoding="utf-8"
|
||||
)
|
||||
|
||||
def HandleUpdate(self):
|
||||
self.reactor.callLater(60 * 60, self.HandleUpdate)
|
||||
print(time.strftime("[%H:%M:%S] Handle update"))
|
||||
old_error = self.Errorhandle
|
||||
self.Activityhandle.close()
|
||||
old_error.close()
|
||||
self._open_handles()
|
||||
if isinstance(sys.stderr, self.filesplit) and len(sys.stderr.files) > 1:
|
||||
sys.stderr.files[1] = self.Errorhandle
|
||||
|
||||
def AutoFlush(self):
|
||||
self.reactor.callLater(60 * 5, self.AutoFlush)
|
||||
self.flush()
|
||||
|
||||
def flush(self):
|
||||
for handle in (self.Activityhandle, self.Errorhandle):
|
||||
handle.flush()
|
||||
os.fsync(handle.fileno())
|
||||
|
||||
def close(self):
|
||||
for handle in (self.Activityhandle, self.Errorhandle):
|
||||
if not handle.closed:
|
||||
handle.close()
|
||||
|
||||
def write(self, String, Silent=False):
|
||||
line = str(String).rstrip("\n")
|
||||
if not Silent:
|
||||
print(time.strftime("[%H:%M:%S]"), line)
|
||||
self.Activityhandle.write(time.strftime("[%H:%M:%S] ") + line + "\n")
|
||||
|
||||
Print = write
|
||||
|
||||
|
||||
|
||||
def create_site():
|
||||
_force_project_working_directory()
|
||||
reactor, twisted_server = _import_twisted()
|
||||
|
||||
print("Initializing flipnote database...", end=" ", flush=True)
|
||||
import DB # noqa: F401 - initialization is intentional
|
||||
print("Done!")
|
||||
|
||||
log = Log(reactor)
|
||||
|
||||
print("Setting up hatena site...", end=" ", flush=True)
|
||||
import hatena
|
||||
hatena.ServerLog = log
|
||||
|
||||
class ProxyCompatibleSite(twisted_server.Site):
|
||||
"""Accept the absolute-form request target sent by HTTP proxy clients."""
|
||||
|
||||
def buildProtocol(self, addr):
|
||||
protocol = super().buildProtocol(addr)
|
||||
old_data_received = protocol.dataReceived
|
||||
|
||||
def data_received(data):
|
||||
for check, repl in (
|
||||
(b"GET http://flipnote.hatena.com", b"GET "),
|
||||
(b"POST http://flipnote.hatena.com", b"POST "),
|
||||
):
|
||||
if check in data:
|
||||
data = data.replace(check, repl)
|
||||
return old_data_received(data)
|
||||
|
||||
protocol.dataReceived = data_received
|
||||
return protocol
|
||||
|
||||
site = ProxyCompatibleSite(hatena.Setup())
|
||||
print("Done!")
|
||||
return reactor, site, log
|
||||
|
||||
|
||||
def main():
|
||||
print("Importing modules...", end=" ", flush=True)
|
||||
_force_project_working_directory()
|
||||
reactor, site, log = create_site()
|
||||
print("Server start!\n")
|
||||
|
||||
if useWSGI:
|
||||
from twisted.application import internet, service
|
||||
|
||||
application = service.Application("web")
|
||||
internet.TCPServer(port, site).setServiceParent(service.IServiceCollection(application))
|
||||
atexit.register(log.write, String="Server shutdown", Silent=True)
|
||||
return application
|
||||
|
||||
reactor.listenTCP(port, site)
|
||||
try:
|
||||
reactor.run()
|
||||
finally:
|
||||
log.write("Server shutdown", True)
|
||||
log.flush()
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
PORT = int(os.environ.get("REXIMEMO_PORT", "8080"))
|
||||
DOCS_ARGUMENT = "docs:enabled"
|
||||
|
||||
|
||||
def docs_enabled_from_args():
|
||||
return DOCS_ARGUMENT in sys.argv[1:]
|
||||
|
||||
|
||||
def _force_project_working_directory():
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def _import_twisted():
|
||||
try:
|
||||
from twisted.internet import reactor
|
||||
from twisted.web import server
|
||||
except ImportError as exc:
|
||||
raise SystemExit("Twisted is required. Install dependencies with: python3 -m pip install -r requirements.txt") from exc
|
||||
return reactor, server
|
||||
|
||||
|
||||
class Log:
|
||||
def __init__(self):
|
||||
os.makedirs("logs", exist_ok=True)
|
||||
self.handle = open("logs/reximemo.log", "a", encoding="utf-8")
|
||||
|
||||
def write(self, text, silent=False):
|
||||
line = "[%s] %s" % (time.strftime("%H:%M:%S"), str(text).rstrip())
|
||||
if not silent:
|
||||
print(line)
|
||||
self.handle.write(line + "\n")
|
||||
self.handle.flush()
|
||||
|
||||
def close(self):
|
||||
self.handle.close()
|
||||
|
||||
|
||||
def create_site(*, docs_enabled=False):
|
||||
_force_project_working_directory()
|
||||
reactor, twisted_server = _import_twisted()
|
||||
import DB # initializes SQLite
|
||||
import hatena
|
||||
log = Log()
|
||||
hatena.ServerLog = log
|
||||
|
||||
class ProxyCompatibleSite(twisted_server.Site):
|
||||
def buildProtocol(self, addr):
|
||||
protocol = super().buildProtocol(addr)
|
||||
original = protocol.dataReceived
|
||||
|
||||
def data_received(data):
|
||||
for host in (b"flipnote.hatena.com", b"ugomemo.hatena.ne.jp"):
|
||||
data = data.replace(b"GET http://" + host, b"GET ")
|
||||
data = data.replace(b"POST http://" + host, b"POST ")
|
||||
return original(data)
|
||||
|
||||
protocol.dataReceived = data_received
|
||||
return protocol
|
||||
|
||||
return reactor, ProxyCompatibleSite(hatena.Setup(docs_enabled=docs_enabled)), log
|
||||
|
||||
|
||||
def main():
|
||||
docs_enabled = docs_enabled_from_args()
|
||||
reactor, site, log = create_site(docs_enabled=docs_enabled)
|
||||
log.write("RexiMemo OSE starting on port %d" % PORT)
|
||||
log.write("DSi mode uses the historical HTTP proxy path; NAS/DNS/auth services are not included.")
|
||||
if docs_enabled:
|
||||
log.write("Built-in documentation enabled at /docs")
|
||||
reactor.listenTCP(PORT, site)
|
||||
try:
|
||||
reactor.run()
|
||||
finally:
|
||||
log.write("Server shutdown", True)
|
||||
log.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user
