We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
GET /%E8%AE%A1%E7%AE%97/HTTP/1.1" code 404  does not support Chinese name md documents
Questions are as follows:
I have encountered a problem when using Chinese to set the navigation, click on the navigation bar to prompt the page to display: 404-Not found

The CLI error is as follows: "GET /%E8%AE%A1%E7%AE%97/ HTTP/1.1" code 404

The mkdocs.yml configuration file is as follows:
```
site_name: My Docs
site_url: https://example.com/
theme:
name: material
nav:
- 首页: index.md
- 测试1: 测试1.md
- 测试2: 测试2.md
```

I tried windows system, ubuntu system, mac system, and I can’t quote markdown documents with using Chinese to set the navigation but I see other people’s blogs support Chinese, but theirs is not the latest version, the author is in the latest version. Do you choose not to support Chinese markdown documents?  

I checked the historical problem records, but did not find a clear instruction, if you know, please help me, thank you very much


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mkdocs/livereload/__init__.py`
Content:
```
1 import functools
2 import io
3 import logging
4 import mimetypes
5 import os
6 import os.path
7 import pathlib
8 import re
9 import socketserver
10 import threading
11 import time
12 import warnings
13 import wsgiref.simple_server
14 
15 import watchdog.events
16 import watchdog.observers
17 
18 
19 class _LoggerAdapter(logging.LoggerAdapter):
20     def process(self, msg, kwargs):
21         return time.strftime("[%H:%M:%S] ") + msg, kwargs
22 
23 
24 log = _LoggerAdapter(logging.getLogger(__name__), {})
25 
26 
27 class LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGIServer):
28     daemon_threads = True
29     poll_response_timeout = 60
30 
31     def __init__(
32         self,
33         builder,
34         host,
35         port,
36         root,
37         mount_path="/",
38         build_delay=0.25,
39         shutdown_delay=0.25,
40         **kwargs,
41     ):
42         self.builder = builder
43         self.server_name = host
44         self.server_port = port
45         self.root = os.path.abspath(root)
46         self.mount_path = ("/" + mount_path.lstrip("/")).rstrip("/") + "/"
47         self.url = f"http://{self.server_name}:{self.server_port}{self.mount_path}"
48         self.build_delay = build_delay
49         self.shutdown_delay = shutdown_delay
50         # To allow custom error pages.
51         self.error_handler = lambda code: None
52 
53         super().__init__((host, port), _Handler, **kwargs)
54         self.set_app(self.serve_request)
55 
56         self._wanted_epoch = _timestamp()  # The version of the site that started building.
57         self._visible_epoch = self._wanted_epoch  # Latest fully built version of the site.
58         self._epoch_cond = threading.Condition()  # Must be held when accessing _visible_epoch.
59 
60         self._to_rebuild = {}  # Used as an ordered set of functions to call.
61         self._rebuild_cond = threading.Condition()  # Must be held when accessing _to_rebuild.
62 
63         self._shutdown = False
64         self.serve_thread = threading.Thread(target=lambda: self.serve_forever(shutdown_delay))
65         self.observer = watchdog.observers.Observer(timeout=shutdown_delay)
66 
67     def watch(self, path, func=None, recursive=True):
68         """Add the 'path' to watched paths, call the function and reload when any file changes under it."""
69         path = os.path.abspath(path)
70         if func in (None, self.builder):
71             func = self.builder
72         else:
73             warnings.warn(
74                 "Plugins should not pass the 'func' parameter of watch(). "
75                 "The ability to execute custom callbacks will be removed soon.",
76                 DeprecationWarning,
77                 stacklevel=2,
78             )
79 
80         def callback(event, allowed_path=None):
81             if isinstance(event, watchdog.events.DirCreatedEvent):
82                 return
83             if allowed_path is not None and event.src_path != allowed_path:
84                 return
85             # Text editors always cause a "file close" event in addition to "modified" when saving
86             # a file. Some editors also have "swap" functionality that keeps writing into another
87             # file that's never closed. Prevent such write events from causing a rebuild.
88             if isinstance(event, watchdog.events.FileModifiedEvent):
89                 # But FileClosedEvent is implemented only on Linux, otherwise we mustn't skip this:
90                 if type(self.observer).__name__ == "InotifyObserver":
91                     return
92             log.debug(str(event))
93             with self._rebuild_cond:
94                 self._to_rebuild[func] = True
95                 self._rebuild_cond.notify_all()
96 
97         dir_handler = watchdog.events.FileSystemEventHandler()
98         dir_handler.on_any_event = callback
99 
100         seen = set()
101 
102         def schedule(path):
103             seen.add(path)
104             if path.is_file():
105                 # Watchdog doesn't support watching files, so watch its directory and filter by path
106                 handler = watchdog.events.FileSystemEventHandler()
107                 handler.on_any_event = lambda event: callback(event, allowed_path=os.fspath(path))
108 
109                 parent = path.parent
110                 log.debug(f"Watching file '{path}' through directory '{parent}'")
111                 self.observer.schedule(handler, parent)
112             else:
113                 log.debug(f"Watching directory '{path}'")
114                 self.observer.schedule(dir_handler, path, recursive=recursive)
115 
116         schedule(pathlib.Path(path).resolve())
117 
118         def watch_symlink_targets(path_obj):  # path is os.DirEntry or pathlib.Path
119             if path_obj.is_symlink():
120                 path_obj = pathlib.Path(path_obj).resolve()
121                 if path_obj in seen or not path_obj.exists():
122                     return
123                 schedule(path_obj)
124 
125             if path_obj.is_dir() and recursive:
126                 with os.scandir(os.fspath(path_obj)) as scan:
127                     for entry in scan:
128                         watch_symlink_targets(entry)
129 
130         watch_symlink_targets(pathlib.Path(path))
131 
132     def serve(self):
133         self.observer.start()
134 
135         log.info(f"Serving on {self.url}")
136         self.serve_thread.start()
137 
138         self._build_loop()
139 
140     def _build_loop(self):
141         while True:
142             with self._rebuild_cond:
143                 while not self._rebuild_cond.wait_for(
144                     lambda: self._to_rebuild or self._shutdown, timeout=self.shutdown_delay
145                 ):
146                     # We could have used just one wait instead of a loop + timeout, but we need
147                     # occasional breaks, otherwise on Windows we can't receive KeyboardInterrupt.
148                     pass
149                 if self._shutdown:
150                     break
151                 log.info("Detected file changes")
152                 while self._rebuild_cond.wait(timeout=self.build_delay):
153                     log.debug("Waiting for file changes to stop happening")
154 
155                 self._wanted_epoch = _timestamp()
156                 funcs = list(self._to_rebuild)
157                 self._to_rebuild.clear()
158 
159             for func in funcs:
160                 func()
161 
162             with self._epoch_cond:
163                 log.info("Reloading browsers")
164                 self._visible_epoch = self._wanted_epoch
165                 self._epoch_cond.notify_all()
166 
167     def shutdown(self):
168         self.observer.stop()
169         with self._rebuild_cond:
170             self._shutdown = True
171             self._rebuild_cond.notify_all()
172 
173         if self.serve_thread.is_alive():
174             super().shutdown()
175             self.serve_thread.join()
176             self.observer.join()
177 
178     def serve_request(self, environ, start_response):
179         try:
180             result = self._serve_request(environ, start_response)
181         except Exception:
182             code = 500
183             msg = "500 Internal Server Error"
184             log.exception(msg)
185         else:
186             if result is not None:
187                 return result
188             code = 404
189             msg = "404 Not Found"
190 
191         error_content = None
192         try:
193             error_content = self.error_handler(code)
194         except Exception:
195             log.exception("Failed to render an error message!")
196         if error_content is None:
197             error_content = msg.encode()
198 
199         start_response(msg, [("Content-Type", "text/html")])
200         return [error_content]
201 
202     def _serve_request(self, environ, start_response):
203         path = environ["PATH_INFO"]
204 
205         m = re.fullmatch(r"/livereload/([0-9]+)/[0-9]+", path)
206         if m:
207             epoch = int(m[1])
208             start_response("200 OK", [("Content-Type", "text/plain")])
209 
210             def condition():
211                 return self._visible_epoch > epoch
212 
213             with self._epoch_cond:
214                 if not condition():
215                     # Stall the browser, respond as soon as there's something new.
216                     # If there's not, respond anyway after a minute.
217                     self._log_poll_request(environ.get("HTTP_REFERER"), request_id=path)
218                     self._epoch_cond.wait_for(condition, timeout=self.poll_response_timeout)
219                 return [b"%d" % self._visible_epoch]
220 
221         if path == "/js/livereload.js":
222             file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "livereload.js")
223         elif path.startswith(self.mount_path):
224             if path.endswith("/"):
225                 path += "index.html"
226             path = path[len(self.mount_path):]
227             file_path = os.path.join(self.root, path.lstrip("/"))
228         elif path == "/":
229             start_response("302 Found", [("Location", self.mount_path)])
230             return []
231         else:
232             return None  # Not found
233 
234         # Wait until the ongoing rebuild (if any) finishes, so we're not serving a half-built site.
235         with self._epoch_cond:
236             self._epoch_cond.wait_for(lambda: self._visible_epoch == self._wanted_epoch)
237             epoch = self._visible_epoch
238 
239         try:
240             file = open(file_path, "rb")
241         except OSError:
242             return None  # Not found
243 
244         if path.endswith(".html"):
245             with file:
246                 content = file.read()
247             content = self._inject_js_into_html(content, epoch)
248             file = io.BytesIO(content)
249             content_length = len(content)
250         else:
251             content_length = os.path.getsize(file_path)
252 
253         content_type = self._guess_type(file_path)
254         start_response(
255             "200 OK", [("Content-Type", content_type), ("Content-Length", str(content_length))]
256         )
257         return wsgiref.util.FileWrapper(file)
258 
259     @classmethod
260     def _inject_js_into_html(cls, content, epoch):
261         try:
262             body_end = content.rindex(b"</body>")
263         except ValueError:
264             body_end = len(content)
265         # The page will reload if the livereload poller returns a newer epoch than what it knows.
266         # The other timestamp becomes just a unique identifier for the initiating page.
267         return (
268             b'%b<script src="/js/livereload.js"></script><script>livereload(%d, %d);</script>%b'
269             % (content[:body_end], epoch, _timestamp(), content[body_end:])
270         )
271 
272     @classmethod
273     @functools.lru_cache()  # "Cache" to not repeat the same message for the same browser tab.
274     def _log_poll_request(cls, url, request_id):
275         log.info(f"Browser connected: {url}")
276 
277     def _guess_type(cls, path):
278         # MkDocs only ensures a few common types (as seen in livereload_tests.py::test_mime_types).
279         # Other uncommon types will not be accepted.
280         if path.endswith((".js", ".JS")):
281             return "application/javascript"
282         if path.endswith(".gz"):
283             return "application/gzip"
284 
285         guess, _ = mimetypes.guess_type(path)
286         if guess:
287             return guess
288         return "application/octet-stream"
289 
290 
291 class _Handler(wsgiref.simple_server.WSGIRequestHandler):
292     def log_request(self, code="-", size="-"):
293         level = logging.DEBUG if str(code) == "200" else logging.WARNING
294         log.log(level, f'"{self.requestline}" code {code}')
295 
296     def log_message(self, format, *args):
297         log.debug(format, *args)
298 
299 
300 def _timestamp():
301     return round(time.monotonic() * 1000)
302
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mkdocs/livereload/__init__.py b/mkdocs/livereload/__init__.py
--- a/mkdocs/livereload/__init__.py
+++ b/mkdocs/livereload/__init__.py
@@ -200,7 +200,9 @@
         return [error_content]
 
     def _serve_request(self, environ, start_response):
-        path = environ["PATH_INFO"]
+        # https://bugs.python.org/issue16679
+        # https://github.com/bottlepy/bottle/blob/f9b1849db4/bottle.py#L984
+        path = environ["PATH_INFO"].encode("latin-1").decode("utf-8", "ignore")
 
         m = re.fullmatch(r"/livereload/([0-9]+)/[0-9]+", path)
         if m:

{"golden_diff": "diff --git a/mkdocs/livereload/__init__.py b/mkdocs/livereload/__init__.py\n--- a/mkdocs/livereload/__init__.py\n+++ b/mkdocs/livereload/__init__.py\n@@ -200,7 +200,9 @@\n         return [error_content]\n \n     def _serve_request(self, environ, start_response):\n-        path = environ[\"PATH_INFO\"]\n+        # https://bugs.python.org/issue16679\n+        # https://github.com/bottlepy/bottle/blob/f9b1849db4/bottle.py#L984\n+        path = environ[\"PATH_INFO\"].encode(\"latin-1\").decode(\"utf-8\", \"ignore\")\n \n         m = re.fullmatch(r\"/livereload/([0-9]+)/[0-9]+\", path)\n         if m:\n", "issue": "GET /%E8%AE%A1%E7%AE%97/HTTP/1.1\" code 404  does not support Chinese name md documents\nQuestions are as follows:\r\nI have encountered a problem when using Chinese to set the navigation, click on the navigation bar to prompt the page to display: 404-Not found\r\n\r\nThe CLI error is as follows: \"GET /%E8%AE%A1%E7%AE%97/ HTTP/1.1\" code 404\r\n\r\nThe mkdocs.yml configuration file is as follows:\r\n```\r\nsite_name: My Docs\r\nsite_url: https://example.com/\r\ntheme:\r\nname: material\r\nnav:\r\n- \u9996\u9875: index.md\r\n- \u6d4b\u8bd51: \u6d4b\u8bd51.md\r\n- \u6d4b\u8bd52: \u6d4b\u8bd52.md\r\n```\r\n\r\nI tried windows system, ubuntu system, mac system, and I can\u2019t quote markdown documents with using Chinese to set the navigation but I see other people\u2019s blogs support Chinese, but theirs is not the latest version, the author is in the latest version. Do you choose not to support Chinese markdown documents?  \r\n\r\nI checked the historical problem records, but did not find a clear instruction, if you know, please help me, thank you very much\r\n\n", "before_files": [{"content": "import functools\nimport io\nimport logging\nimport mimetypes\nimport os\nimport os.path\nimport pathlib\nimport re\nimport socketserver\nimport threading\nimport time\nimport warnings\nimport wsgiref.simple_server\n\nimport watchdog.events\nimport watchdog.observers\n\n\nclass _LoggerAdapter(logging.LoggerAdapter):\n    def process(self, msg, kwargs):\n        return time.strftime(\"[%H:%M:%S] \") + msg, kwargs\n\n\nlog = _LoggerAdapter(logging.getLogger(__name__), {})\n\n\nclass LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGIServer):\n    daemon_threads = True\n    poll_response_timeout = 60\n\n    def __init__(\n        self,\n        builder,\n        host,\n        port,\n        root,\n        mount_path=\"/\",\n        build_delay=0.25,\n        shutdown_delay=0.25,\n        **kwargs,\n    ):\n        self.builder = builder\n        self.server_name = host\n        self.server_port = port\n        self.root = os.path.abspath(root)\n        self.mount_path = (\"/\" + mount_path.lstrip(\"/\")).rstrip(\"/\") + \"/\"\n        self.url = f\"http://{self.server_name}:{self.server_port}{self.mount_path}\"\n        self.build_delay = build_delay\n        self.shutdown_delay = shutdown_delay\n        # To allow custom error pages.\n        self.error_handler = lambda code: None\n\n        super().__init__((host, port), _Handler, **kwargs)\n        self.set_app(self.serve_request)\n\n        self._wanted_epoch = _timestamp()  # The version of the site that started building.\n        self._visible_epoch = self._wanted_epoch  # Latest fully built version of the site.\n        self._epoch_cond = threading.Condition()  # Must be held when accessing _visible_epoch.\n\n        self._to_rebuild = {}  # Used as an ordered set of functions to call.\n        self._rebuild_cond = threading.Condition()  # Must be held when accessing _to_rebuild.\n\n        self._shutdown = False\n        self.serve_thread = threading.Thread(target=lambda: self.serve_forever(shutdown_delay))\n        self.observer = watchdog.observers.Observer(timeout=shutdown_delay)\n\n    def watch(self, path, func=None, recursive=True):\n        \"\"\"Add the 'path' to watched paths, call the function and reload when any file changes under it.\"\"\"\n        path = os.path.abspath(path)\n        if func in (None, self.builder):\n            func = self.builder\n        else:\n            warnings.warn(\n                \"Plugins should not pass the 'func' parameter of watch(). \"\n                \"The ability to execute custom callbacks will be removed soon.\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n\n        def callback(event, allowed_path=None):\n            if isinstance(event, watchdog.events.DirCreatedEvent):\n                return\n            if allowed_path is not None and event.src_path != allowed_path:\n                return\n            # Text editors always cause a \"file close\" event in addition to \"modified\" when saving\n            # a file. Some editors also have \"swap\" functionality that keeps writing into another\n            # file that's never closed. Prevent such write events from causing a rebuild.\n            if isinstance(event, watchdog.events.FileModifiedEvent):\n                # But FileClosedEvent is implemented only on Linux, otherwise we mustn't skip this:\n                if type(self.observer).__name__ == \"InotifyObserver\":\n                    return\n            log.debug(str(event))\n            with self._rebuild_cond:\n                self._to_rebuild[func] = True\n                self._rebuild_cond.notify_all()\n\n        dir_handler = watchdog.events.FileSystemEventHandler()\n        dir_handler.on_any_event = callback\n\n        seen = set()\n\n        def schedule(path):\n            seen.add(path)\n            if path.is_file():\n                # Watchdog doesn't support watching files, so watch its directory and filter by path\n                handler = watchdog.events.FileSystemEventHandler()\n                handler.on_any_event = lambda event: callback(event, allowed_path=os.fspath(path))\n\n                parent = path.parent\n                log.debug(f\"Watching file '{path}' through directory '{parent}'\")\n                self.observer.schedule(handler, parent)\n            else:\n                log.debug(f\"Watching directory '{path}'\")\n                self.observer.schedule(dir_handler, path, recursive=recursive)\n\n        schedule(pathlib.Path(path).resolve())\n\n        def watch_symlink_targets(path_obj):  # path is os.DirEntry or pathlib.Path\n            if path_obj.is_symlink():\n                path_obj = pathlib.Path(path_obj).resolve()\n                if path_obj in seen or not path_obj.exists():\n                    return\n                schedule(path_obj)\n\n            if path_obj.is_dir() and recursive:\n                with os.scandir(os.fspath(path_obj)) as scan:\n                    for entry in scan:\n                        watch_symlink_targets(entry)\n\n        watch_symlink_targets(pathlib.Path(path))\n\n    def serve(self):\n        self.observer.start()\n\n        log.info(f\"Serving on {self.url}\")\n        self.serve_thread.start()\n\n        self._build_loop()\n\n    def _build_loop(self):\n        while True:\n            with self._rebuild_cond:\n                while not self._rebuild_cond.wait_for(\n                    lambda: self._to_rebuild or self._shutdown, timeout=self.shutdown_delay\n                ):\n                    # We could have used just one wait instead of a loop + timeout, but we need\n                    # occasional breaks, otherwise on Windows we can't receive KeyboardInterrupt.\n                    pass\n                if self._shutdown:\n                    break\n                log.info(\"Detected file changes\")\n                while self._rebuild_cond.wait(timeout=self.build_delay):\n                    log.debug(\"Waiting for file changes to stop happening\")\n\n                self._wanted_epoch = _timestamp()\n                funcs = list(self._to_rebuild)\n                self._to_rebuild.clear()\n\n            for func in funcs:\n                func()\n\n            with self._epoch_cond:\n                log.info(\"Reloading browsers\")\n                self._visible_epoch = self._wanted_epoch\n                self._epoch_cond.notify_all()\n\n    def shutdown(self):\n        self.observer.stop()\n        with self._rebuild_cond:\n            self._shutdown = True\n            self._rebuild_cond.notify_all()\n\n        if self.serve_thread.is_alive():\n            super().shutdown()\n            self.serve_thread.join()\n            self.observer.join()\n\n    def serve_request(self, environ, start_response):\n        try:\n            result = self._serve_request(environ, start_response)\n        except Exception:\n            code = 500\n            msg = \"500 Internal Server Error\"\n            log.exception(msg)\n        else:\n            if result is not None:\n                return result\n            code = 404\n            msg = \"404 Not Found\"\n\n        error_content = None\n        try:\n            error_content = self.error_handler(code)\n        except Exception:\n            log.exception(\"Failed to render an error message!\")\n        if error_content is None:\n            error_content = msg.encode()\n\n        start_response(msg, [(\"Content-Type\", \"text/html\")])\n        return [error_content]\n\n    def _serve_request(self, environ, start_response):\n        path = environ[\"PATH_INFO\"]\n\n        m = re.fullmatch(r\"/livereload/([0-9]+)/[0-9]+\", path)\n        if m:\n            epoch = int(m[1])\n            start_response(\"200 OK\", [(\"Content-Type\", \"text/plain\")])\n\n            def condition():\n                return self._visible_epoch > epoch\n\n            with self._epoch_cond:\n                if not condition():\n                    # Stall the browser, respond as soon as there's something new.\n                    # If there's not, respond anyway after a minute.\n                    self._log_poll_request(environ.get(\"HTTP_REFERER\"), request_id=path)\n                    self._epoch_cond.wait_for(condition, timeout=self.poll_response_timeout)\n                return [b\"%d\" % self._visible_epoch]\n\n        if path == \"/js/livereload.js\":\n            file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"livereload.js\")\n        elif path.startswith(self.mount_path):\n            if path.endswith(\"/\"):\n                path += \"index.html\"\n            path = path[len(self.mount_path):]\n            file_path = os.path.join(self.root, path.lstrip(\"/\"))\n        elif path == \"/\":\n            start_response(\"302 Found\", [(\"Location\", self.mount_path)])\n            return []\n        else:\n            return None  # Not found\n\n        # Wait until the ongoing rebuild (if any) finishes, so we're not serving a half-built site.\n        with self._epoch_cond:\n            self._epoch_cond.wait_for(lambda: self._visible_epoch == self._wanted_epoch)\n            epoch = self._visible_epoch\n\n        try:\n            file = open(file_path, \"rb\")\n        except OSError:\n            return None  # Not found\n\n        if path.endswith(\".html\"):\n            with file:\n                content = file.read()\n            content = self._inject_js_into_html(content, epoch)\n            file = io.BytesIO(content)\n            content_length = len(content)\n        else:\n            content_length = os.path.getsize(file_path)\n\n        content_type = self._guess_type(file_path)\n        start_response(\n            \"200 OK\", [(\"Content-Type\", content_type), (\"Content-Length\", str(content_length))]\n        )\n        return wsgiref.util.FileWrapper(file)\n\n    @classmethod\n    def _inject_js_into_html(cls, content, epoch):\n        try:\n            body_end = content.rindex(b\"</body>\")\n        except ValueError:\n            body_end = len(content)\n        # The page will reload if the livereload poller returns a newer epoch than what it knows.\n        # The other timestamp becomes just a unique identifier for the initiating page.\n        return (\n            b'%b<script src=\"/js/livereload.js\"></script><script>livereload(%d, %d);</script>%b'\n            % (content[:body_end], epoch, _timestamp(), content[body_end:])\n        )\n\n    @classmethod\n    @functools.lru_cache()  # \"Cache\" to not repeat the same message for the same browser tab.\n    def _log_poll_request(cls, url, request_id):\n        log.info(f\"Browser connected: {url}\")\n\n    def _guess_type(cls, path):\n        # MkDocs only ensures a few common types (as seen in livereload_tests.py::test_mime_types).\n        # Other uncommon types will not be accepted.\n        if path.endswith((\".js\", \".JS\")):\n            return \"application/javascript\"\n        if path.endswith(\".gz\"):\n            return \"application/gzip\"\n\n        guess, _ = mimetypes.guess_type(path)\n        if guess:\n            return guess\n        return \"application/octet-stream\"\n\n\nclass _Handler(wsgiref.simple_server.WSGIRequestHandler):\n    def log_request(self, code=\"-\", size=\"-\"):\n        level = logging.DEBUG if str(code) == \"200\" else logging.WARNING\n        log.log(level, f'\"{self.requestline}\" code {code}')\n\n    def log_message(self, format, *args):\n        log.debug(format, *args)\n\n\ndef _timestamp():\n    return round(time.monotonic() * 1000)\n", "path": "mkdocs/livereload/__init__.py"}], "after_files": [{"content": "import functools\nimport io\nimport logging\nimport mimetypes\nimport os\nimport os.path\nimport pathlib\nimport re\nimport socketserver\nimport threading\nimport time\nimport warnings\nimport wsgiref.simple_server\n\nimport watchdog.events\nimport watchdog.observers\n\n\nclass _LoggerAdapter(logging.LoggerAdapter):\n    def process(self, msg, kwargs):\n        return time.strftime(\"[%H:%M:%S] \") + msg, kwargs\n\n\nlog = _LoggerAdapter(logging.getLogger(__name__), {})\n\n\nclass LiveReloadServer(socketserver.ThreadingMixIn, wsgiref.simple_server.WSGIServer):\n    daemon_threads = True\n    poll_response_timeout = 60\n\n    def __init__(\n        self,\n        builder,\n        host,\n        port,\n        root,\n        mount_path=\"/\",\n        build_delay=0.25,\n        shutdown_delay=0.25,\n        **kwargs,\n    ):\n        self.builder = builder\n        self.server_name = host\n        self.server_port = port\n        self.root = os.path.abspath(root)\n        self.mount_path = (\"/\" + mount_path.lstrip(\"/\")).rstrip(\"/\") + \"/\"\n        self.url = f\"http://{self.server_name}:{self.server_port}{self.mount_path}\"\n        self.build_delay = build_delay\n        self.shutdown_delay = shutdown_delay\n        # To allow custom error pages.\n        self.error_handler = lambda code: None\n\n        super().__init__((host, port), _Handler, **kwargs)\n        self.set_app(self.serve_request)\n\n        self._wanted_epoch = _timestamp()  # The version of the site that started building.\n        self._visible_epoch = self._wanted_epoch  # Latest fully built version of the site.\n        self._epoch_cond = threading.Condition()  # Must be held when accessing _visible_epoch.\n\n        self._to_rebuild = {}  # Used as an ordered set of functions to call.\n        self._rebuild_cond = threading.Condition()  # Must be held when accessing _to_rebuild.\n\n        self._shutdown = False\n        self.serve_thread = threading.Thread(target=lambda: self.serve_forever(shutdown_delay))\n        self.observer = watchdog.observers.Observer(timeout=shutdown_delay)\n\n    def watch(self, path, func=None, recursive=True):\n        \"\"\"Add the 'path' to watched paths, call the function and reload when any file changes under it.\"\"\"\n        path = os.path.abspath(path)\n        if func in (None, self.builder):\n            func = self.builder\n        else:\n            warnings.warn(\n                \"Plugins should not pass the 'func' parameter of watch(). \"\n                \"The ability to execute custom callbacks will be removed soon.\",\n                DeprecationWarning,\n                stacklevel=2,\n            )\n\n        def callback(event, allowed_path=None):\n            if isinstance(event, watchdog.events.DirCreatedEvent):\n                return\n            if allowed_path is not None and event.src_path != allowed_path:\n                return\n            # Text editors always cause a \"file close\" event in addition to \"modified\" when saving\n            # a file. Some editors also have \"swap\" functionality that keeps writing into another\n            # file that's never closed. Prevent such write events from causing a rebuild.\n            if isinstance(event, watchdog.events.FileModifiedEvent):\n                # But FileClosedEvent is implemented only on Linux, otherwise we mustn't skip this:\n                if type(self.observer).__name__ == \"InotifyObserver\":\n                    return\n            log.debug(str(event))\n            with self._rebuild_cond:\n                self._to_rebuild[func] = True\n                self._rebuild_cond.notify_all()\n\n        dir_handler = watchdog.events.FileSystemEventHandler()\n        dir_handler.on_any_event = callback\n\n        seen = set()\n\n        def schedule(path):\n            seen.add(path)\n            if path.is_file():\n                # Watchdog doesn't support watching files, so watch its directory and filter by path\n                handler = watchdog.events.FileSystemEventHandler()\n                handler.on_any_event = lambda event: callback(event, allowed_path=os.fspath(path))\n\n                parent = path.parent\n                log.debug(f\"Watching file '{path}' through directory '{parent}'\")\n                self.observer.schedule(handler, parent)\n            else:\n                log.debug(f\"Watching directory '{path}'\")\n                self.observer.schedule(dir_handler, path, recursive=recursive)\n\n        schedule(pathlib.Path(path).resolve())\n\n        def watch_symlink_targets(path_obj):  # path is os.DirEntry or pathlib.Path\n            if path_obj.is_symlink():\n                path_obj = pathlib.Path(path_obj).resolve()\n                if path_obj in seen or not path_obj.exists():\n                    return\n                schedule(path_obj)\n\n            if path_obj.is_dir() and recursive:\n                with os.scandir(os.fspath(path_obj)) as scan:\n                    for entry in scan:\n                        watch_symlink_targets(entry)\n\n        watch_symlink_targets(pathlib.Path(path))\n\n    def serve(self):\n        self.observer.start()\n\n        log.info(f\"Serving on {self.url}\")\n        self.serve_thread.start()\n\n        self._build_loop()\n\n    def _build_loop(self):\n        while True:\n            with self._rebuild_cond:\n                while not self._rebuild_cond.wait_for(\n                    lambda: self._to_rebuild or self._shutdown, timeout=self.shutdown_delay\n                ):\n                    # We could have used just one wait instead of a loop + timeout, but we need\n                    # occasional breaks, otherwise on Windows we can't receive KeyboardInterrupt.\n                    pass\n                if self._shutdown:\n                    break\n                log.info(\"Detected file changes\")\n                while self._rebuild_cond.wait(timeout=self.build_delay):\n                    log.debug(\"Waiting for file changes to stop happening\")\n\n                self._wanted_epoch = _timestamp()\n                funcs = list(self._to_rebuild)\n                self._to_rebuild.clear()\n\n            for func in funcs:\n                func()\n\n            with self._epoch_cond:\n                log.info(\"Reloading browsers\")\n                self._visible_epoch = self._wanted_epoch\n                self._epoch_cond.notify_all()\n\n    def shutdown(self):\n        self.observer.stop()\n        with self._rebuild_cond:\n            self._shutdown = True\n            self._rebuild_cond.notify_all()\n\n        if self.serve_thread.is_alive():\n            super().shutdown()\n            self.serve_thread.join()\n            self.observer.join()\n\n    def serve_request(self, environ, start_response):\n        try:\n            result = self._serve_request(environ, start_response)\n        except Exception:\n            code = 500\n            msg = \"500 Internal Server Error\"\n            log.exception(msg)\n        else:\n            if result is not None:\n                return result\n            code = 404\n            msg = \"404 Not Found\"\n\n        error_content = None\n        try:\n            error_content = self.error_handler(code)\n        except Exception:\n            log.exception(\"Failed to render an error message!\")\n        if error_content is None:\n            error_content = msg.encode()\n\n        start_response(msg, [(\"Content-Type\", \"text/html\")])\n        return [error_content]\n\n    def _serve_request(self, environ, start_response):\n        # https://bugs.python.org/issue16679\n        # https://github.com/bottlepy/bottle/blob/f9b1849db4/bottle.py#L984\n        path = environ[\"PATH_INFO\"].encode(\"latin-1\").decode(\"utf-8\", \"ignore\")\n\n        m = re.fullmatch(r\"/livereload/([0-9]+)/[0-9]+\", path)\n        if m:\n            epoch = int(m[1])\n            start_response(\"200 OK\", [(\"Content-Type\", \"text/plain\")])\n\n            def condition():\n                return self._visible_epoch > epoch\n\n            with self._epoch_cond:\n                if not condition():\n                    # Stall the browser, respond as soon as there's something new.\n                    # If there's not, respond anyway after a minute.\n                    self._log_poll_request(environ.get(\"HTTP_REFERER\"), request_id=path)\n                    self._epoch_cond.wait_for(condition, timeout=self.poll_response_timeout)\n                return [b\"%d\" % self._visible_epoch]\n\n        if path == \"/js/livereload.js\":\n            file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"livereload.js\")\n        elif path.startswith(self.mount_path):\n            if path.endswith(\"/\"):\n                path += \"index.html\"\n            path = path[len(self.mount_path):]\n            file_path = os.path.join(self.root, path.lstrip(\"/\"))\n        elif path == \"/\":\n            start_response(\"302 Found\", [(\"Location\", self.mount_path)])\n            return []\n        else:\n            return None  # Not found\n\n        # Wait until the ongoing rebuild (if any) finishes, so we're not serving a half-built site.\n        with self._epoch_cond:\n            self._epoch_cond.wait_for(lambda: self._visible_epoch == self._wanted_epoch)\n            epoch = self._visible_epoch\n\n        try:\n            file = open(file_path, \"rb\")\n        except OSError:\n            return None  # Not found\n\n        if path.endswith(\".html\"):\n            with file:\n                content = file.read()\n            content = self._inject_js_into_html(content, epoch)\n            file = io.BytesIO(content)\n            content_length = len(content)\n        else:\n            content_length = os.path.getsize(file_path)\n\n        content_type = self._guess_type(file_path)\n        start_response(\n            \"200 OK\", [(\"Content-Type\", content_type), (\"Content-Length\", str(content_length))]\n        )\n        return wsgiref.util.FileWrapper(file)\n\n    @classmethod\n    def _inject_js_into_html(cls, content, epoch):\n        try:\n            body_end = content.rindex(b\"</body>\")\n        except ValueError:\n            body_end = len(content)\n        # The page will reload if the livereload poller returns a newer epoch than what it knows.\n        # The other timestamp becomes just a unique identifier for the initiating page.\n        return (\n            b'%b<script src=\"/js/livereload.js\"></script><script>livereload(%d, %d);</script>%b'\n            % (content[:body_end], epoch, _timestamp(), content[body_end:])\n        )\n\n    @classmethod\n    @functools.lru_cache()  # \"Cache\" to not repeat the same message for the same browser tab.\n    def _log_poll_request(cls, url, request_id):\n        log.info(f\"Browser connected: {url}\")\n\n    def _guess_type(cls, path):\n        # MkDocs only ensures a few common types (as seen in livereload_tests.py::test_mime_types).\n        # Other uncommon types will not be accepted.\n        if path.endswith((\".js\", \".JS\")):\n            return \"application/javascript\"\n        if path.endswith(\".gz\"):\n            return \"application/gzip\"\n\n        guess, _ = mimetypes.guess_type(path)\n        if guess:\n            return guess\n        return \"application/octet-stream\"\n\n\nclass _Handler(wsgiref.simple_server.WSGIRequestHandler):\n    def log_request(self, code=\"-\", size=\"-\"):\n        level = logging.DEBUG if str(code) == \"200\" else logging.WARNING\n        log.log(level, f'\"{self.requestline}\" code {code}')\n\n    def log_message(self, format, *args):\n        log.debug(format, *args)\n\n\ndef _timestamp():\n    return round(time.monotonic() * 1000)\n", "path": "mkdocs/livereload/__init__.py"}]}