We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Multiple processes competition on read/write update log
**Issue**

https://github.com/pypa/virtualenv/blob/0cd009b5a1338f66397f71c85a75f576a2f3eabf/src/virtualenv/app_data/via_disk_folder.py#L129-L141

When the file is opened for writing in another process, the JSON content can't be parsed, then the file will be removed. However, on Windows the removal will fail since the file is occupied by anther process.

**Environment**

Provide at least:
- OS: Windows 10
- ``pip list`` of the host python where ``virtualenv`` is installed:

  ```console
  appdirs           1.4.4
  argcomplete       1.11.1
  backcall          0.1.0
  better-exceptions 0.2.2
  certifi           2020.4.5.1
  chardet           3.0.4
  colorama          0.4.3
  decorator         4.4.2
  distlib           0.3.1
  filelock          3.0.12
  idna              2.10
  ipython           7.14.0
  ipython-genutils  0.2.0
  jedi              0.17.0
  naipyext          0.5.2
  parso             0.7.0
  pickleshare       0.7.5
  pip               20.1.1
  pipenv            2020.8.13.dev0 d:\workspace\pipenv
  pipx              0.15.1.3
  prompt-toolkit    3.0.5
  Pygments          2.6.1
  requests          2.24.0
  setuptools        46.4.0
  six               1.14.0
  traitlets         4.3.3
  urllib3           1.25.10
  userpath          1.3.0
  virtualenv        20.0.31
  virtualenv-clone  0.5.4
  wcwidth           0.1.9
  wheel             0.34.2
  ```

**Output of the virtual environment creation**

Make sure to run the creation with `-vvv --with-traceback`:

```pytb
Traceback (most recent call last):
  File "C:\Users\runneradmin\.virtualenvs\pipenv-6Kr0DpZ2\lib\site-packages\virtualenv\seed\embed\via_app_data\via_app_data.py", line 94, in _get
    do_periodic_update=self.periodic_update,
  File "C:\Users\runneradmin\.virtualenvs\pipenv-6Kr0DpZ2\lib\site-packages\virtualenv\seed\wheels\acquire.py", line 25, in get_wheel
    wheel = from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update)
  File "C:\Users\runneradmin\.virtualenvs\pipenv-6Kr0DpZ2\lib\site-packages\virtualenv\seed\wheels\bundle.py", line 20, in from_bundle
    wheel = periodic_update(distribution, for_py_version, wheel, search_dirs, app_data, do_periodic_update)
  File "C:\Users\runneradmin\.virtualenvs\pipenv-6Kr0DpZ2\lib\site-packages\virtualenv\seed\wheels\periodic_update.py", line 41, in periodic_update
    handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data)
  File "C:\Users\runneradmin\.virtualenvs\pipenv-6Kr0DpZ2\lib\site-packages\virtualenv\seed\wheels\periodic_update.py", line 62, in handle_auto_update
    u_log = UpdateLog.from_dict(embed_update_log.read())
  File "C:\Users\runneradmin\.virtualenvs\pipenv-6Kr0DpZ2\lib\site-packages\virtualenv\app_data\via_disk_folder.py", line 140, in read
    self.remove()
  File "C:\Users\runneradmin\.virtualenvs\pipenv-6Kr0DpZ2\lib\site-packages\virtualenv\app_data\via_disk_folder.py", line 144, in remove
    self.file.unlink()
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\pathlib.py", line 1284, in unlink
    self._accessor.unlink(self)
  File "C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\pathlib.py", line 387, in wrapped
    return strfunc(str(pathobj), *args)
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\Users\\runneradmin\\AppData\\Local\\pypa\\virtualenv\\wheel\\3.8\\embed\\1\\wheel.json'
PermissionError(13, 'The process cannot access the file because it is being used by another process')
RuntimeError: seed failed due to failing to download wheels wheel
```

To fix the issue, I prefer to change the writing into **atomic**, that is, before the writing is done, the content should be kept.

If that is an acceptable approach I can send a PR.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/virtualenv/app_data/via_disk_folder.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 """
3 A rough layout of the current storage goes as:
4 
5 virtualenv-app-data
6 ├── py - <version> <cache information about python interpreters>
7 │   └── *.json/lock
8 ├── wheel <cache wheels used for seeding>
9 │   ├── house
10 │   │   └── *.whl <wheels downloaded go here>
11 │   └── <python major.minor> -> 3.9
12 │       ├── img-<version>
13 │       │   └── image
14 │       │           └── <install class> -> CopyPipInstall / SymlinkPipInstall
15 │       │               └── <wheel name> -> pip-20.1.1-py2.py3-none-any
16 │       └── embed
17 │           └── 1
18 │               └── *.json -> for every distribution contains data about newer embed versions and releases
19 └─── unzip <in zip app we cannot refer to some internal files, so first extract them>
20      └── <virtualenv version>
21          ├── py_info.py
22          ├── debug.py
23          └── _virtualenv.py
24 """
25 from __future__ import absolute_import, unicode_literals
26 
27 import json
28 import logging
29 from abc import ABCMeta
30 from contextlib import contextmanager
31 from hashlib import sha256
32 
33 import six
34 
35 from virtualenv.util.lock import ReentrantFileLock
36 from virtualenv.util.path import safe_delete
37 from virtualenv.util.six import ensure_text
38 from virtualenv.util.zipapp import extract
39 from virtualenv.version import __version__
40 
41 from .base import AppData, ContentStore
42 
43 
44 class AppDataDiskFolder(AppData):
45     """
46     Store the application data on the disk within a folder layout.
47     """
48 
49     def __init__(self, folder):
50         self.lock = ReentrantFileLock(folder)
51 
52     def __repr__(self):
53         return "{}".format(self.lock.path)
54 
55     @property
56     def transient(self):
57         return False
58 
59     def reset(self):
60         logging.debug("reset app data folder %s", self.lock.path)
61         safe_delete(self.lock.path)
62 
63     def close(self):
64         """do nothing"""
65 
66     @contextmanager
67     def locked(self, path):
68         path_lock = self.lock / path
69         with path_lock:
70             yield path_lock.path
71 
72     @contextmanager
73     def extract(self, path, to_folder):
74         if to_folder is not None:
75             root = ReentrantFileLock(to_folder())
76         else:
77             root = self.lock / "unzip" / __version__
78         with root.lock_for_key(path.name):
79             dest = root.path / path.name
80             if not dest.exists():
81                 extract(path, dest)
82             yield dest
83 
84     @property
85     def py_info_at(self):
86         return self.lock / "py_info" / "1"
87 
88     def py_info(self, path):
89         return PyInfoStoreDisk(self.py_info_at, path)
90 
91     def py_info_clear(self):
92         """"""
93         py_info_folder = self.py_info_at
94         with py_info_folder:
95             for filename in py_info_folder.path.iterdir():
96                 if filename.suffix == ".json":
97                     with py_info_folder.lock_for_key(filename.stem):
98                         if filename.exists():
99                             filename.unlink()
100 
101     def embed_update_log(self, distribution, for_py_version):
102         return EmbedDistributionUpdateStoreDisk(self.lock / "wheel" / for_py_version / "embed" / "1", distribution)
103 
104     @property
105     def house(self):
106         path = self.lock.path / "wheel" / "house"
107         path.mkdir(parents=True, exist_ok=True)
108         return path
109 
110     def wheel_image(self, for_py_version, name):
111         return self.lock.path / "wheel" / for_py_version / "image" / "1" / name
112 
113 
114 @six.add_metaclass(ABCMeta)
115 class JSONStoreDisk(ContentStore):
116     def __init__(self, in_folder, key, msg, msg_args):
117         self.in_folder = in_folder
118         self.key = key
119         self.msg = msg
120         self.msg_args = msg_args + (self.file,)
121 
122     @property
123     def file(self):
124         return self.in_folder.path / "{}.json".format(self.key)
125 
126     def exists(self):
127         return self.file.exists()
128 
129     def read(self):
130         data, bad_format = None, False
131         try:
132             data = json.loads(self.file.read_text())
133             logging.debug("got {} from %s".format(self.msg), *self.msg_args)
134             return data
135         except ValueError:
136             bad_format = True
137         except Exception:  # noqa
138             pass
139         if bad_format:
140             self.remove()
141         return None
142 
143     def remove(self):
144         self.file.unlink()
145         logging.debug("removed {} at %s".format(self.msg), *self.msg_args)
146 
147     @contextmanager
148     def locked(self):
149         with self.in_folder.lock_for_key(self.key):
150             yield
151 
152     def write(self, content):
153         folder = self.file.parent
154         try:
155             folder.mkdir(parents=True, exist_ok=True)
156         except OSError:
157             pass
158         self.file.write_text(ensure_text(json.dumps(content, sort_keys=True, indent=2)))
159         logging.debug("wrote {} at %s".format(self.msg), *self.msg_args)
160 
161 
162 class PyInfoStoreDisk(JSONStoreDisk):
163     def __init__(self, in_folder, path):
164         key = sha256(str(path).encode("utf-8") if six.PY3 else str(path)).hexdigest()
165         super(PyInfoStoreDisk, self).__init__(in_folder, key, "python info of %s", (path,))
166 
167 
168 class EmbedDistributionUpdateStoreDisk(JSONStoreDisk):
169     def __init__(self, in_folder, distribution):
170         super(EmbedDistributionUpdateStoreDisk, self).__init__(
171             in_folder,
172             distribution,
173             "embed update of distribution %s",
174             (distribution,),
175         )
176
```
--- 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/src/virtualenv/app_data/via_disk_folder.py b/src/virtualenv/app_data/via_disk_folder.py
--- a/src/virtualenv/app_data/via_disk_folder.py
+++ b/src/virtualenv/app_data/via_disk_folder.py
@@ -137,7 +137,10 @@
         except Exception:  # noqa
             pass
         if bad_format:
-            self.remove()
+            try:
+                self.remove()
+            except OSError:  # reading and writing on the same file may cause race on multiple processes
+                pass
         return None
 
     def remove(self):

{"golden_diff": "diff --git a/src/virtualenv/app_data/via_disk_folder.py b/src/virtualenv/app_data/via_disk_folder.py\n--- a/src/virtualenv/app_data/via_disk_folder.py\n+++ b/src/virtualenv/app_data/via_disk_folder.py\n@@ -137,7 +137,10 @@\n         except Exception:  # noqa\n             pass\n         if bad_format:\n-            self.remove()\n+            try:\n+                self.remove()\n+            except OSError:  # reading and writing on the same file may cause race on multiple processes\n+                pass\n         return None\n \n     def remove(self):\n", "issue": "Multiple processes competition on read/write update log\n**Issue**\r\n\r\nhttps://github.com/pypa/virtualenv/blob/0cd009b5a1338f66397f71c85a75f576a2f3eabf/src/virtualenv/app_data/via_disk_folder.py#L129-L141\r\n\r\nWhen the file is opened for writing in another process, the JSON content can't be parsed, then the file will be removed. However, on Windows the removal will fail since the file is occupied by anther process.\r\n\r\n**Environment**\r\n\r\nProvide at least:\r\n- OS: Windows 10\r\n- ``pip list`` of the host python where ``virtualenv`` is installed:\r\n\r\n  ```console\r\n  appdirs           1.4.4\r\n  argcomplete       1.11.1\r\n  backcall          0.1.0\r\n  better-exceptions 0.2.2\r\n  certifi           2020.4.5.1\r\n  chardet           3.0.4\r\n  colorama          0.4.3\r\n  decorator         4.4.2\r\n  distlib           0.3.1\r\n  filelock          3.0.12\r\n  idna              2.10\r\n  ipython           7.14.0\r\n  ipython-genutils  0.2.0\r\n  jedi              0.17.0\r\n  naipyext          0.5.2\r\n  parso             0.7.0\r\n  pickleshare       0.7.5\r\n  pip               20.1.1\r\n  pipenv            2020.8.13.dev0 d:\\workspace\\pipenv\r\n  pipx              0.15.1.3\r\n  prompt-toolkit    3.0.5\r\n  Pygments          2.6.1\r\n  requests          2.24.0\r\n  setuptools        46.4.0\r\n  six               1.14.0\r\n  traitlets         4.3.3\r\n  urllib3           1.25.10\r\n  userpath          1.3.0\r\n  virtualenv        20.0.31\r\n  virtualenv-clone  0.5.4\r\n  wcwidth           0.1.9\r\n  wheel             0.34.2\r\n  ```\r\n\r\n**Output of the virtual environment creation**\r\n\r\nMake sure to run the creation with `-vvv --with-traceback`:\r\n\r\n```pytb\r\nTraceback (most recent call last):\r\n  File \"C:\\Users\\runneradmin\\.virtualenvs\\pipenv-6Kr0DpZ2\\lib\\site-packages\\virtualenv\\seed\\embed\\via_app_data\\via_app_data.py\", line 94, in _get\r\n    do_periodic_update=self.periodic_update,\r\n  File \"C:\\Users\\runneradmin\\.virtualenvs\\pipenv-6Kr0DpZ2\\lib\\site-packages\\virtualenv\\seed\\wheels\\acquire.py\", line 25, in get_wheel\r\n    wheel = from_bundle(distribution, version, for_py_version, search_dirs, app_data, do_periodic_update)\r\n  File \"C:\\Users\\runneradmin\\.virtualenvs\\pipenv-6Kr0DpZ2\\lib\\site-packages\\virtualenv\\seed\\wheels\\bundle.py\", line 20, in from_bundle\r\n    wheel = periodic_update(distribution, for_py_version, wheel, search_dirs, app_data, do_periodic_update)\r\n  File \"C:\\Users\\runneradmin\\.virtualenvs\\pipenv-6Kr0DpZ2\\lib\\site-packages\\virtualenv\\seed\\wheels\\periodic_update.py\", line 41, in periodic_update\r\n    handle_auto_update(distribution, for_py_version, wheel, search_dirs, app_data)\r\n  File \"C:\\Users\\runneradmin\\.virtualenvs\\pipenv-6Kr0DpZ2\\lib\\site-packages\\virtualenv\\seed\\wheels\\periodic_update.py\", line 62, in handle_auto_update\r\n    u_log = UpdateLog.from_dict(embed_update_log.read())\r\n  File \"C:\\Users\\runneradmin\\.virtualenvs\\pipenv-6Kr0DpZ2\\lib\\site-packages\\virtualenv\\app_data\\via_disk_folder.py\", line 140, in read\r\n    self.remove()\r\n  File \"C:\\Users\\runneradmin\\.virtualenvs\\pipenv-6Kr0DpZ2\\lib\\site-packages\\virtualenv\\app_data\\via_disk_folder.py\", line 144, in remove\r\n    self.file.unlink()\r\n  File \"C:\\hostedtoolcache\\windows\\Python\\3.6.8\\x64\\lib\\pathlib.py\", line 1284, in unlink\r\n    self._accessor.unlink(self)\r\n  File \"C:\\hostedtoolcache\\windows\\Python\\3.6.8\\x64\\lib\\pathlib.py\", line 387, in wrapped\r\n    return strfunc(str(pathobj), *args)\r\nPermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'C:\\\\Users\\\\runneradmin\\\\AppData\\\\Local\\\\pypa\\\\virtualenv\\\\wheel\\\\3.8\\\\embed\\\\1\\\\wheel.json'\r\nPermissionError(13, 'The process cannot access the file because it is being used by another process')\r\nRuntimeError: seed failed due to failing to download wheels wheel\r\n```\r\n\r\nTo fix the issue, I prefer to change the writing into **atomic**, that is, before the writing is done, the content should be kept.\r\n\r\nIf that is an acceptable approach I can send a PR.\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"\nA rough layout of the current storage goes as:\n\nvirtualenv-app-data\n\u251c\u2500\u2500 py - <version> <cache information about python interpreters>\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 *.json/lock\n\u251c\u2500\u2500 wheel <cache wheels used for seeding>\n\u2502 \u00a0 \u251c\u2500\u2500 house\n\u2502   \u2502   \u2514\u2500\u2500 *.whl <wheels downloaded go here>\n\u2502   \u2514\u2500\u2500 <python major.minor> -> 3.9\n\u2502       \u251c\u2500\u2500 img-<version>\n\u2502       \u2502   \u2514\u2500\u2500 image\n\u2502       \u2502           \u2514\u2500\u2500 <install class> -> CopyPipInstall / SymlinkPipInstall\n\u2502       \u2502               \u2514\u2500\u2500 <wheel name> -> pip-20.1.1-py2.py3-none-any\n\u2502       \u2514\u2500\u2500 embed\n\u2502           \u2514\u2500\u2500 1\n\u2502               \u2514\u2500\u2500 *.json -> for every distribution contains data about newer embed versions and releases\n\u2514\u2500\u2500\u2500 unzip <in zip app we cannot refer to some internal files, so first extract them>\n     \u2514\u2500\u2500 <virtualenv version>\n         \u251c\u2500\u2500 py_info.py\n         \u251c\u2500\u2500 debug.py\n         \u2514\u2500\u2500 _virtualenv.py\n\"\"\"\nfrom __future__ import absolute_import, unicode_literals\n\nimport json\nimport logging\nfrom abc import ABCMeta\nfrom contextlib import contextmanager\nfrom hashlib import sha256\n\nimport six\n\nfrom virtualenv.util.lock import ReentrantFileLock\nfrom virtualenv.util.path import safe_delete\nfrom virtualenv.util.six import ensure_text\nfrom virtualenv.util.zipapp import extract\nfrom virtualenv.version import __version__\n\nfrom .base import AppData, ContentStore\n\n\nclass AppDataDiskFolder(AppData):\n    \"\"\"\n    Store the application data on the disk within a folder layout.\n    \"\"\"\n\n    def __init__(self, folder):\n        self.lock = ReentrantFileLock(folder)\n\n    def __repr__(self):\n        return \"{}\".format(self.lock.path)\n\n    @property\n    def transient(self):\n        return False\n\n    def reset(self):\n        logging.debug(\"reset app data folder %s\", self.lock.path)\n        safe_delete(self.lock.path)\n\n    def close(self):\n        \"\"\"do nothing\"\"\"\n\n    @contextmanager\n    def locked(self, path):\n        path_lock = self.lock / path\n        with path_lock:\n            yield path_lock.path\n\n    @contextmanager\n    def extract(self, path, to_folder):\n        if to_folder is not None:\n            root = ReentrantFileLock(to_folder())\n        else:\n            root = self.lock / \"unzip\" / __version__\n        with root.lock_for_key(path.name):\n            dest = root.path / path.name\n            if not dest.exists():\n                extract(path, dest)\n            yield dest\n\n    @property\n    def py_info_at(self):\n        return self.lock / \"py_info\" / \"1\"\n\n    def py_info(self, path):\n        return PyInfoStoreDisk(self.py_info_at, path)\n\n    def py_info_clear(self):\n        \"\"\"\"\"\"\n        py_info_folder = self.py_info_at\n        with py_info_folder:\n            for filename in py_info_folder.path.iterdir():\n                if filename.suffix == \".json\":\n                    with py_info_folder.lock_for_key(filename.stem):\n                        if filename.exists():\n                            filename.unlink()\n\n    def embed_update_log(self, distribution, for_py_version):\n        return EmbedDistributionUpdateStoreDisk(self.lock / \"wheel\" / for_py_version / \"embed\" / \"1\", distribution)\n\n    @property\n    def house(self):\n        path = self.lock.path / \"wheel\" / \"house\"\n        path.mkdir(parents=True, exist_ok=True)\n        return path\n\n    def wheel_image(self, for_py_version, name):\n        return self.lock.path / \"wheel\" / for_py_version / \"image\" / \"1\" / name\n\n\n@six.add_metaclass(ABCMeta)\nclass JSONStoreDisk(ContentStore):\n    def __init__(self, in_folder, key, msg, msg_args):\n        self.in_folder = in_folder\n        self.key = key\n        self.msg = msg\n        self.msg_args = msg_args + (self.file,)\n\n    @property\n    def file(self):\n        return self.in_folder.path / \"{}.json\".format(self.key)\n\n    def exists(self):\n        return self.file.exists()\n\n    def read(self):\n        data, bad_format = None, False\n        try:\n            data = json.loads(self.file.read_text())\n            logging.debug(\"got {} from %s\".format(self.msg), *self.msg_args)\n            return data\n        except ValueError:\n            bad_format = True\n        except Exception:  # noqa\n            pass\n        if bad_format:\n            self.remove()\n        return None\n\n    def remove(self):\n        self.file.unlink()\n        logging.debug(\"removed {} at %s\".format(self.msg), *self.msg_args)\n\n    @contextmanager\n    def locked(self):\n        with self.in_folder.lock_for_key(self.key):\n            yield\n\n    def write(self, content):\n        folder = self.file.parent\n        try:\n            folder.mkdir(parents=True, exist_ok=True)\n        except OSError:\n            pass\n        self.file.write_text(ensure_text(json.dumps(content, sort_keys=True, indent=2)))\n        logging.debug(\"wrote {} at %s\".format(self.msg), *self.msg_args)\n\n\nclass PyInfoStoreDisk(JSONStoreDisk):\n    def __init__(self, in_folder, path):\n        key = sha256(str(path).encode(\"utf-8\") if six.PY3 else str(path)).hexdigest()\n        super(PyInfoStoreDisk, self).__init__(in_folder, key, \"python info of %s\", (path,))\n\n\nclass EmbedDistributionUpdateStoreDisk(JSONStoreDisk):\n    def __init__(self, in_folder, distribution):\n        super(EmbedDistributionUpdateStoreDisk, self).__init__(\n            in_folder,\n            distribution,\n            \"embed update of distribution %s\",\n            (distribution,),\n        )\n", "path": "src/virtualenv/app_data/via_disk_folder.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"\nA rough layout of the current storage goes as:\n\nvirtualenv-app-data\n\u251c\u2500\u2500 py - <version> <cache information about python interpreters>\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 *.json/lock\n\u251c\u2500\u2500 wheel <cache wheels used for seeding>\n\u2502 \u00a0 \u251c\u2500\u2500 house\n\u2502   \u2502   \u2514\u2500\u2500 *.whl <wheels downloaded go here>\n\u2502   \u2514\u2500\u2500 <python major.minor> -> 3.9\n\u2502       \u251c\u2500\u2500 img-<version>\n\u2502       \u2502   \u2514\u2500\u2500 image\n\u2502       \u2502           \u2514\u2500\u2500 <install class> -> CopyPipInstall / SymlinkPipInstall\n\u2502       \u2502               \u2514\u2500\u2500 <wheel name> -> pip-20.1.1-py2.py3-none-any\n\u2502       \u2514\u2500\u2500 embed\n\u2502           \u2514\u2500\u2500 1\n\u2502               \u2514\u2500\u2500 *.json -> for every distribution contains data about newer embed versions and releases\n\u2514\u2500\u2500\u2500 unzip <in zip app we cannot refer to some internal files, so first extract them>\n     \u2514\u2500\u2500 <virtualenv version>\n         \u251c\u2500\u2500 py_info.py\n         \u251c\u2500\u2500 debug.py\n         \u2514\u2500\u2500 _virtualenv.py\n\"\"\"\nfrom __future__ import absolute_import, unicode_literals\n\nimport json\nimport logging\nfrom abc import ABCMeta\nfrom contextlib import contextmanager\nfrom hashlib import sha256\n\nimport six\n\nfrom virtualenv.util.lock import ReentrantFileLock\nfrom virtualenv.util.path import safe_delete\nfrom virtualenv.util.six import ensure_text\nfrom virtualenv.util.zipapp import extract\nfrom virtualenv.version import __version__\n\nfrom .base import AppData, ContentStore\n\n\nclass AppDataDiskFolder(AppData):\n    \"\"\"\n    Store the application data on the disk within a folder layout.\n    \"\"\"\n\n    def __init__(self, folder):\n        self.lock = ReentrantFileLock(folder)\n\n    def __repr__(self):\n        return \"{}\".format(self.lock.path)\n\n    @property\n    def transient(self):\n        return False\n\n    def reset(self):\n        logging.debug(\"reset app data folder %s\", self.lock.path)\n        safe_delete(self.lock.path)\n\n    def close(self):\n        \"\"\"do nothing\"\"\"\n\n    @contextmanager\n    def locked(self, path):\n        path_lock = self.lock / path\n        with path_lock:\n            yield path_lock.path\n\n    @contextmanager\n    def extract(self, path, to_folder):\n        if to_folder is not None:\n            root = ReentrantFileLock(to_folder())\n        else:\n            root = self.lock / \"unzip\" / __version__\n        with root.lock_for_key(path.name):\n            dest = root.path / path.name\n            if not dest.exists():\n                extract(path, dest)\n            yield dest\n\n    @property\n    def py_info_at(self):\n        return self.lock / \"py_info\" / \"1\"\n\n    def py_info(self, path):\n        return PyInfoStoreDisk(self.py_info_at, path)\n\n    def py_info_clear(self):\n        \"\"\"\"\"\"\n        py_info_folder = self.py_info_at\n        with py_info_folder:\n            for filename in py_info_folder.path.iterdir():\n                if filename.suffix == \".json\":\n                    with py_info_folder.lock_for_key(filename.stem):\n                        if filename.exists():\n                            filename.unlink()\n\n    def embed_update_log(self, distribution, for_py_version):\n        return EmbedDistributionUpdateStoreDisk(self.lock / \"wheel\" / for_py_version / \"embed\" / \"1\", distribution)\n\n    @property\n    def house(self):\n        path = self.lock.path / \"wheel\" / \"house\"\n        path.mkdir(parents=True, exist_ok=True)\n        return path\n\n    def wheel_image(self, for_py_version, name):\n        return self.lock.path / \"wheel\" / for_py_version / \"image\" / \"1\" / name\n\n\n@six.add_metaclass(ABCMeta)\nclass JSONStoreDisk(ContentStore):\n    def __init__(self, in_folder, key, msg, msg_args):\n        self.in_folder = in_folder\n        self.key = key\n        self.msg = msg\n        self.msg_args = msg_args + (self.file,)\n\n    @property\n    def file(self):\n        return self.in_folder.path / \"{}.json\".format(self.key)\n\n    def exists(self):\n        return self.file.exists()\n\n    def read(self):\n        data, bad_format = None, False\n        try:\n            data = json.loads(self.file.read_text())\n            logging.debug(\"got {} from %s\".format(self.msg), *self.msg_args)\n            return data\n        except ValueError:\n            bad_format = True\n        except Exception:  # noqa\n            pass\n        if bad_format:\n            try:\n                self.remove()\n            except OSError:  # reading and writing on the same file may cause race on multiple processes\n                pass\n        return None\n\n    def remove(self):\n        self.file.unlink()\n        logging.debug(\"removed {} at %s\".format(self.msg), *self.msg_args)\n\n    @contextmanager\n    def locked(self):\n        with self.in_folder.lock_for_key(self.key):\n            yield\n\n    def write(self, content):\n        folder = self.file.parent\n        try:\n            folder.mkdir(parents=True, exist_ok=True)\n        except OSError:\n            pass\n        self.file.write_text(ensure_text(json.dumps(content, sort_keys=True, indent=2)))\n        logging.debug(\"wrote {} at %s\".format(self.msg), *self.msg_args)\n\n\nclass PyInfoStoreDisk(JSONStoreDisk):\n    def __init__(self, in_folder, path):\n        key = sha256(str(path).encode(\"utf-8\") if six.PY3 else str(path)).hexdigest()\n        super(PyInfoStoreDisk, self).__init__(in_folder, key, \"python info of %s\", (path,))\n\n\nclass EmbedDistributionUpdateStoreDisk(JSONStoreDisk):\n    def __init__(self, in_folder, distribution):\n        super(EmbedDistributionUpdateStoreDisk, self).__init__(\n            in_folder,\n            distribution,\n            \"embed update of distribution %s\",\n            (distribution,),\n        )\n", "path": "src/virtualenv/app_data/via_disk_folder.py"}]}