We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
User already exists during setup (DAppNode)
## Problem Definition

Using DAppNode Package:

1. Install Rotki
2. Setup Rotki using Premium API Key with restore sync'd database option
3. Enter incorrect password
4. Get error that password could not decrypt database
5. Go back
6. Enter correct password
7. "User $USERNAME already exists. User data dir: /data/$USERNAME"

Can't proceed...have to delete and re-create Rotki package

## Logs

None

### System Description

Operating system: DAppNode
Rotki version: DAppNode Package `0.1.9 (v1.23.1 upstream)`


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rotkehlchen/premium/sync.py`
Content:
```
1 import base64
2 import logging
3 import shutil
4 from enum import Enum
5 from typing import Any, Dict, NamedTuple, Optional, Tuple
6 
7 from typing_extensions import Literal
8 
9 from rotkehlchen.data_handler import DataHandler
10 from rotkehlchen.errors import (
11     PremiumAuthenticationError,
12     RemoteError,
13     RotkehlchenPermissionError,
14     UnableToDecryptRemoteData,
15 )
16 from rotkehlchen.logging import RotkehlchenLogsAdapter
17 from rotkehlchen.premium.premium import Premium, PremiumCredentials, premium_create_and_verify
18 from rotkehlchen.utils.misc import ts_now
19 
20 logger = logging.getLogger(__name__)
21 log = RotkehlchenLogsAdapter(logger)
22 
23 
24 class CanSync(Enum):
25     YES = 0
26     NO = 1
27     ASK_USER = 2
28 
29 
30 class SyncCheckResult(NamedTuple):
31     # The result of the sync check
32     can_sync: CanSync
33     # If result is ASK_USER, what should the message be?
34     message: str
35     payload: Optional[Dict[str, Any]]
36 
37 
38 class PremiumSyncManager():
39 
40     def __init__(self, data: DataHandler, password: str) -> None:
41         # Initialize this with the value saved in the DB
42         self.last_data_upload_ts = data.db.get_last_data_upload_ts()
43         self.data = data
44         self.password = password
45         self.premium: Optional[Premium] = None
46 
47     def _can_sync_data_from_server(self, new_account: bool) -> SyncCheckResult:
48         """
49         Checks if the remote data can be pulled from the server.
50 
51         Returns a SyncCheckResult denoting whether we can pull for sure,
52         whether we can't pull or whether the user should be asked. If the user
53         should be asked a message is also returned
54         """
55         log.debug('can sync data from server -- start')
56         if self.premium is None:
57             return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)
58 
59         b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)
60 
61         try:
62             metadata = self.premium.query_last_data_metadata()
63         except RemoteError as e:
64             log.debug('can sync data from server failed', error=str(e))
65             return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)
66 
67         if new_account:
68             return SyncCheckResult(can_sync=CanSync.YES, message='', payload=None)
69 
70         if not self.data.db.get_premium_sync():
71             # If it's not a new account and the db setting for premium syncing is off stop
72             return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)
73 
74         log.debug(
75             'CAN_PULL',
76             ours=our_hash,
77             theirs=metadata.data_hash,
78         )
79         if our_hash == metadata.data_hash:
80             log.debug('sync from server stopped -- same hash')
81             # same hash -- no need to get anything
82             return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)
83 
84         our_last_write_ts = self.data.db.get_last_write_ts()
85         data_bytes_size = len(base64.b64decode(b64_encoded_data))
86 
87         local_more_recent = our_last_write_ts >= metadata.last_modify_ts
88 
89         if local_more_recent:
90             log.debug('sync from server stopped -- local is newer')
91             return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)
92 
93         # else remote is bigger
94         return SyncCheckResult(
95             can_sync=CanSync.ASK_USER,
96             message='Detected remote database with bigger size than the local one. ',
97             payload={
98                 'local_size': data_bytes_size,
99                 'remote_size': metadata.data_size,
100                 'local_last_modified': our_last_write_ts,
101                 'remote_last_modified': metadata.last_modify_ts,
102             },
103         )
104 
105     def _sync_data_from_server_and_replace_local(self) -> Tuple[bool, str]:
106         """
107         Performs syncing of data from server and replaces local db
108 
109         Returns true for success and False for error/failure
110 
111         May raise:
112         - PremiumAuthenticationError due to an UnableToDecryptRemoteData
113         coming from  decompress_and_decrypt_db. This happens when the given password
114         does not match the one on the saved DB.
115         """
116         if self.premium is None:
117             return False, 'Pulling failed. User does not have active premium.'
118 
119         try:
120             result = self.premium.pull_data()
121         except RemoteError as e:
122             log.debug('sync from server -- pulling failed.', error=str(e))
123             return False, f'Pulling failed: {str(e)}'
124 
125         if result['data'] is None:
126             log.debug('sync from server -- no data found.')
127             return False, 'No data found'
128 
129         try:
130             self.data.decompress_and_decrypt_db(self.password, result['data'])
131         except UnableToDecryptRemoteData as e:
132             raise PremiumAuthenticationError(
133                 'The given password can not unlock the database that was retrieved  from '
134                 'the server. Make sure to use the same password as when the account was created.',
135             ) from e
136 
137         return True, ''
138 
139     def maybe_upload_data_to_server(self, force_upload: bool = False) -> bool:
140         # if user has no premium do nothing
141         if self.premium is None:
142             return False
143 
144         if not self.data.db.get_premium_sync() and not force_upload:
145             return False
146 
147         # upload only once per hour
148         diff = ts_now() - self.last_data_upload_ts
149         if diff < 3600 and not force_upload:
150             return False
151 
152         try:
153             metadata = self.premium.query_last_data_metadata()
154         except RemoteError as e:
155             log.debug('upload to server -- fetching metadata error', error=str(e))
156             return False
157         b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)
158 
159         log.debug(
160             'CAN_PUSH',
161             ours=our_hash,
162             theirs=metadata.data_hash,
163         )
164         if our_hash == metadata.data_hash and not force_upload:
165             log.debug('upload to server stopped -- same hash')
166             # same hash -- no need to upload anything
167             return False
168 
169         our_last_write_ts = self.data.db.get_last_write_ts()
170         if our_last_write_ts <= metadata.last_modify_ts and not force_upload:
171             # Server's DB was modified after our local DB
172             log.debug(
173                 f'upload to server stopped -- remote db({metadata.last_modify_ts}) '
174                 f'more recent than local({our_last_write_ts})',
175             )
176             return False
177 
178         data_bytes_size = len(base64.b64decode(b64_encoded_data))
179         if data_bytes_size < metadata.data_size and not force_upload:
180             # Let's be conservative.
181             # TODO: Here perhaps prompt user in the future
182             log.debug(
183                 f'upload to server stopped -- remote db({metadata.data_size}) '
184                 f'bigger than local({data_bytes_size})',
185             )
186             return False
187 
188         try:
189             self.premium.upload_data(
190                 data_blob=b64_encoded_data,
191                 our_hash=our_hash,
192                 last_modify_ts=our_last_write_ts,
193                 compression_type='zlib',
194             )
195         except RemoteError as e:
196             log.debug('upload to server -- upload error', error=str(e))
197             return False
198 
199         # update the last data upload value
200         self.last_data_upload_ts = ts_now()
201         self.data.db.update_last_data_upload_ts(self.last_data_upload_ts)
202         log.debug('upload to server -- success')
203         return True
204 
205     def sync_data(self, action: Literal['upload', 'download']) -> Tuple[bool, str]:
206         msg = ''
207 
208         if action == 'upload':
209             success = self.maybe_upload_data_to_server(force_upload=True)
210 
211             if not success:
212                 msg = 'Upload failed'
213             return success, msg
214 
215         return self._sync_data_from_server_and_replace_local()
216 
217     def _sync_if_allowed(
218         self,
219         sync_approval: Literal['yes', 'no', 'unknown'],
220         result: SyncCheckResult,
221     ) -> None:
222         if result.can_sync == CanSync.ASK_USER:
223             if sync_approval == 'unknown':
224                 log.info('Remote DB is possibly newer. Ask user.')
225                 raise RotkehlchenPermissionError(result.message, result.payload)
226 
227             if sync_approval == 'yes':
228                 log.info('User approved data sync from server')
229                 # this may raise due to password
230                 self._sync_data_from_server_and_replace_local()
231 
232             else:
233                 log.debug('Could sync data from server but user refused')
234         elif result.can_sync == CanSync.YES:
235             log.info('User approved data sync from server')
236             self._sync_data_from_server_and_replace_local()  # this may raise due to password
237 
238     def try_premium_at_start(
239             self,
240             given_premium_credentials: Optional[PremiumCredentials],
241             username: str,
242             create_new: bool,
243             sync_approval: Literal['yes', 'no', 'unknown'],
244             sync_database: bool,
245     ) -> Optional[Premium]:
246         """
247         Check if new user provided api pair or we already got one in the DB
248 
249         Returns the created premium if user's premium credentials were fine.
250 
251         If not it will raise PremiumAuthenticationError.
252 
253         If no credentials were given it returns None
254         """
255 
256         if given_premium_credentials is not None:
257             assert create_new, 'We should never get here for an already existing account'
258 
259             try:
260                 self.premium = premium_create_and_verify(given_premium_credentials)
261             except PremiumAuthenticationError as e:
262                 log.error('Given API key is invalid')
263                 # At this point we are at a new user trying to create an account with
264                 # premium API keys and we failed. But a directory was created. Remove it.
265                 # But create a backup of it in case something went really wrong
266                 # and the directory contained data we did not want to lose
267                 shutil.move(
268                     self.data.user_data_dir,  # type: ignore
269                     self.data.data_directory / f'auto_backup_{username}_{ts_now()}',
270                 )
271                 raise PremiumAuthenticationError(
272                     'Could not verify keys for the new account. '
273                     '{}'.format(str(e)),
274                 ) from e
275 
276         # else, if we got premium data in the DB initialize it and try to sync with the server
277         db_credentials = self.data.db.get_rotkehlchen_premium()
278         if db_credentials:
279             assert not create_new, 'We should never get here for a new account'
280             try:
281                 self.premium = premium_create_and_verify(db_credentials)
282             except PremiumAuthenticationError as e:
283                 message = (
284                     f'Could not authenticate with the rotkehlchen server with '
285                     f'the API keys found in the Database. Error: {str(e)}'
286                 )
287                 log.error(message)
288                 raise PremiumAuthenticationError(message) from e
289 
290         if self.premium is None:
291             return None
292 
293         result = self._can_sync_data_from_server(new_account=create_new)
294         if create_new:
295             # if this is a new account, make sure the api keys are properly stored
296             # in the DB
297             if sync_database:
298                 self._sync_if_allowed(sync_approval, result)
299             self.data.db.set_rotkehlchen_premium(self.premium.credentials)
300         else:
301             self._sync_if_allowed(sync_approval, result)
302 
303         # Success, return premium
304         return self.premium
305
```
--- 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/rotkehlchen/premium/sync.py b/rotkehlchen/premium/sync.py
--- a/rotkehlchen/premium/sync.py
+++ b/rotkehlchen/premium/sync.py
@@ -235,6 +235,23 @@
             log.info('User approved data sync from server')
             self._sync_data_from_server_and_replace_local()  # this may raise due to password
 
+    def _abort_new_syncing_premium_user(
+            self,
+            username: str,
+            original_exception: PremiumAuthenticationError,
+    ) -> None:
+        """At this point we are at a new user trying to create an account with
+        premium API keys and we failed. But a directory was created. Remove it.
+        But create a backup of it in case something went really wrong
+        and the directory contained data we did not want to lose"""
+        shutil.move(
+            self.data.user_data_dir,  # type: ignore
+            self.data.data_directory / f'auto_backup_{username}_{ts_now()}',
+        )
+        raise PremiumAuthenticationError(
+            f'Could not verify keys for the new account. {str(original_exception)}',  # noqa: E501
+        ) from original_exception
+
     def try_premium_at_start(
             self,
             given_premium_credentials: Optional[PremiumCredentials],
@@ -259,19 +276,7 @@
             try:
                 self.premium = premium_create_and_verify(given_premium_credentials)
             except PremiumAuthenticationError as e:
-                log.error('Given API key is invalid')
-                # At this point we are at a new user trying to create an account with
-                # premium API keys and we failed. But a directory was created. Remove it.
-                # But create a backup of it in case something went really wrong
-                # and the directory contained data we did not want to lose
-                shutil.move(
-                    self.data.user_data_dir,  # type: ignore
-                    self.data.data_directory / f'auto_backup_{username}_{ts_now()}',
-                )
-                raise PremiumAuthenticationError(
-                    'Could not verify keys for the new account. '
-                    '{}'.format(str(e)),
-                ) from e
+                self._abort_new_syncing_premium_user(username=username, original_exception=e)
 
         # else, if we got premium data in the DB initialize it and try to sync with the server
         db_credentials = self.data.db.get_rotkehlchen_premium()
@@ -295,7 +300,11 @@
             # if this is a new account, make sure the api keys are properly stored
             # in the DB
             if sync_database:
-                self._sync_if_allowed(sync_approval, result)
+                try:
+                    self._sync_if_allowed(sync_approval, result)
+                except PremiumAuthenticationError as e:
+                    self._abort_new_syncing_premium_user(username=username, original_exception=e)
+
             self.data.db.set_rotkehlchen_premium(self.premium.credentials)
         else:
             self._sync_if_allowed(sync_approval, result)

{"golden_diff": "diff --git a/rotkehlchen/premium/sync.py b/rotkehlchen/premium/sync.py\n--- a/rotkehlchen/premium/sync.py\n+++ b/rotkehlchen/premium/sync.py\n@@ -235,6 +235,23 @@\n             log.info('User approved data sync from server')\n             self._sync_data_from_server_and_replace_local()  # this may raise due to password\n \n+    def _abort_new_syncing_premium_user(\n+            self,\n+            username: str,\n+            original_exception: PremiumAuthenticationError,\n+    ) -> None:\n+        \"\"\"At this point we are at a new user trying to create an account with\n+        premium API keys and we failed. But a directory was created. Remove it.\n+        But create a backup of it in case something went really wrong\n+        and the directory contained data we did not want to lose\"\"\"\n+        shutil.move(\n+            self.data.user_data_dir,  # type: ignore\n+            self.data.data_directory / f'auto_backup_{username}_{ts_now()}',\n+        )\n+        raise PremiumAuthenticationError(\n+            f'Could not verify keys for the new account. {str(original_exception)}',  # noqa: E501\n+        ) from original_exception\n+\n     def try_premium_at_start(\n             self,\n             given_premium_credentials: Optional[PremiumCredentials],\n@@ -259,19 +276,7 @@\n             try:\n                 self.premium = premium_create_and_verify(given_premium_credentials)\n             except PremiumAuthenticationError as e:\n-                log.error('Given API key is invalid')\n-                # At this point we are at a new user trying to create an account with\n-                # premium API keys and we failed. But a directory was created. Remove it.\n-                # But create a backup of it in case something went really wrong\n-                # and the directory contained data we did not want to lose\n-                shutil.move(\n-                    self.data.user_data_dir,  # type: ignore\n-                    self.data.data_directory / f'auto_backup_{username}_{ts_now()}',\n-                )\n-                raise PremiumAuthenticationError(\n-                    'Could not verify keys for the new account. '\n-                    '{}'.format(str(e)),\n-                ) from e\n+                self._abort_new_syncing_premium_user(username=username, original_exception=e)\n \n         # else, if we got premium data in the DB initialize it and try to sync with the server\n         db_credentials = self.data.db.get_rotkehlchen_premium()\n@@ -295,7 +300,11 @@\n             # if this is a new account, make sure the api keys are properly stored\n             # in the DB\n             if sync_database:\n-                self._sync_if_allowed(sync_approval, result)\n+                try:\n+                    self._sync_if_allowed(sync_approval, result)\n+                except PremiumAuthenticationError as e:\n+                    self._abort_new_syncing_premium_user(username=username, original_exception=e)\n+\n             self.data.db.set_rotkehlchen_premium(self.premium.credentials)\n         else:\n             self._sync_if_allowed(sync_approval, result)\n", "issue": "User already exists during setup (DAppNode)\n## Problem Definition\r\n\r\nUsing DAppNode Package:\r\n\r\n1. Install Rotki\r\n2. Setup Rotki using Premium API Key with restore sync'd database option\r\n3. Enter incorrect password\r\n4. Get error that password could not decrypt database\r\n5. Go back\r\n6. Enter correct password\r\n7. \"User $USERNAME already exists. User data dir: /data/$USERNAME\"\r\n\r\nCan't proceed...have to delete and re-create Rotki package\r\n\r\n## Logs\r\n\r\nNone\r\n\r\n### System Description\r\n\r\nOperating system: DAppNode\r\nRotki version: DAppNode Package `0.1.9 (v1.23.1 upstream)`\r\n\n", "before_files": [{"content": "import base64\nimport logging\nimport shutil\nfrom enum import Enum\nfrom typing import Any, Dict, NamedTuple, Optional, Tuple\n\nfrom typing_extensions import Literal\n\nfrom rotkehlchen.data_handler import DataHandler\nfrom rotkehlchen.errors import (\n    PremiumAuthenticationError,\n    RemoteError,\n    RotkehlchenPermissionError,\n    UnableToDecryptRemoteData,\n)\nfrom rotkehlchen.logging import RotkehlchenLogsAdapter\nfrom rotkehlchen.premium.premium import Premium, PremiumCredentials, premium_create_and_verify\nfrom rotkehlchen.utils.misc import ts_now\n\nlogger = logging.getLogger(__name__)\nlog = RotkehlchenLogsAdapter(logger)\n\n\nclass CanSync(Enum):\n    YES = 0\n    NO = 1\n    ASK_USER = 2\n\n\nclass SyncCheckResult(NamedTuple):\n    # The result of the sync check\n    can_sync: CanSync\n    # If result is ASK_USER, what should the message be?\n    message: str\n    payload: Optional[Dict[str, Any]]\n\n\nclass PremiumSyncManager():\n\n    def __init__(self, data: DataHandler, password: str) -> None:\n        # Initialize this with the value saved in the DB\n        self.last_data_upload_ts = data.db.get_last_data_upload_ts()\n        self.data = data\n        self.password = password\n        self.premium: Optional[Premium] = None\n\n    def _can_sync_data_from_server(self, new_account: bool) -> SyncCheckResult:\n        \"\"\"\n        Checks if the remote data can be pulled from the server.\n\n        Returns a SyncCheckResult denoting whether we can pull for sure,\n        whether we can't pull or whether the user should be asked. If the user\n        should be asked a message is also returned\n        \"\"\"\n        log.debug('can sync data from server -- start')\n        if self.premium is None:\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)\n\n        try:\n            metadata = self.premium.query_last_data_metadata()\n        except RemoteError as e:\n            log.debug('can sync data from server failed', error=str(e))\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        if new_account:\n            return SyncCheckResult(can_sync=CanSync.YES, message='', payload=None)\n\n        if not self.data.db.get_premium_sync():\n            # If it's not a new account and the db setting for premium syncing is off stop\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        log.debug(\n            'CAN_PULL',\n            ours=our_hash,\n            theirs=metadata.data_hash,\n        )\n        if our_hash == metadata.data_hash:\n            log.debug('sync from server stopped -- same hash')\n            # same hash -- no need to get anything\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        our_last_write_ts = self.data.db.get_last_write_ts()\n        data_bytes_size = len(base64.b64decode(b64_encoded_data))\n\n        local_more_recent = our_last_write_ts >= metadata.last_modify_ts\n\n        if local_more_recent:\n            log.debug('sync from server stopped -- local is newer')\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        # else remote is bigger\n        return SyncCheckResult(\n            can_sync=CanSync.ASK_USER,\n            message='Detected remote database with bigger size than the local one. ',\n            payload={\n                'local_size': data_bytes_size,\n                'remote_size': metadata.data_size,\n                'local_last_modified': our_last_write_ts,\n                'remote_last_modified': metadata.last_modify_ts,\n            },\n        )\n\n    def _sync_data_from_server_and_replace_local(self) -> Tuple[bool, str]:\n        \"\"\"\n        Performs syncing of data from server and replaces local db\n\n        Returns true for success and False for error/failure\n\n        May raise:\n        - PremiumAuthenticationError due to an UnableToDecryptRemoteData\n        coming from  decompress_and_decrypt_db. This happens when the given password\n        does not match the one on the saved DB.\n        \"\"\"\n        if self.premium is None:\n            return False, 'Pulling failed. User does not have active premium.'\n\n        try:\n            result = self.premium.pull_data()\n        except RemoteError as e:\n            log.debug('sync from server -- pulling failed.', error=str(e))\n            return False, f'Pulling failed: {str(e)}'\n\n        if result['data'] is None:\n            log.debug('sync from server -- no data found.')\n            return False, 'No data found'\n\n        try:\n            self.data.decompress_and_decrypt_db(self.password, result['data'])\n        except UnableToDecryptRemoteData as e:\n            raise PremiumAuthenticationError(\n                'The given password can not unlock the database that was retrieved  from '\n                'the server. Make sure to use the same password as when the account was created.',\n            ) from e\n\n        return True, ''\n\n    def maybe_upload_data_to_server(self, force_upload: bool = False) -> bool:\n        # if user has no premium do nothing\n        if self.premium is None:\n            return False\n\n        if not self.data.db.get_premium_sync() and not force_upload:\n            return False\n\n        # upload only once per hour\n        diff = ts_now() - self.last_data_upload_ts\n        if diff < 3600 and not force_upload:\n            return False\n\n        try:\n            metadata = self.premium.query_last_data_metadata()\n        except RemoteError as e:\n            log.debug('upload to server -- fetching metadata error', error=str(e))\n            return False\n        b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)\n\n        log.debug(\n            'CAN_PUSH',\n            ours=our_hash,\n            theirs=metadata.data_hash,\n        )\n        if our_hash == metadata.data_hash and not force_upload:\n            log.debug('upload to server stopped -- same hash')\n            # same hash -- no need to upload anything\n            return False\n\n        our_last_write_ts = self.data.db.get_last_write_ts()\n        if our_last_write_ts <= metadata.last_modify_ts and not force_upload:\n            # Server's DB was modified after our local DB\n            log.debug(\n                f'upload to server stopped -- remote db({metadata.last_modify_ts}) '\n                f'more recent than local({our_last_write_ts})',\n            )\n            return False\n\n        data_bytes_size = len(base64.b64decode(b64_encoded_data))\n        if data_bytes_size < metadata.data_size and not force_upload:\n            # Let's be conservative.\n            # TODO: Here perhaps prompt user in the future\n            log.debug(\n                f'upload to server stopped -- remote db({metadata.data_size}) '\n                f'bigger than local({data_bytes_size})',\n            )\n            return False\n\n        try:\n            self.premium.upload_data(\n                data_blob=b64_encoded_data,\n                our_hash=our_hash,\n                last_modify_ts=our_last_write_ts,\n                compression_type='zlib',\n            )\n        except RemoteError as e:\n            log.debug('upload to server -- upload error', error=str(e))\n            return False\n\n        # update the last data upload value\n        self.last_data_upload_ts = ts_now()\n        self.data.db.update_last_data_upload_ts(self.last_data_upload_ts)\n        log.debug('upload to server -- success')\n        return True\n\n    def sync_data(self, action: Literal['upload', 'download']) -> Tuple[bool, str]:\n        msg = ''\n\n        if action == 'upload':\n            success = self.maybe_upload_data_to_server(force_upload=True)\n\n            if not success:\n                msg = 'Upload failed'\n            return success, msg\n\n        return self._sync_data_from_server_and_replace_local()\n\n    def _sync_if_allowed(\n        self,\n        sync_approval: Literal['yes', 'no', 'unknown'],\n        result: SyncCheckResult,\n    ) -> None:\n        if result.can_sync == CanSync.ASK_USER:\n            if sync_approval == 'unknown':\n                log.info('Remote DB is possibly newer. Ask user.')\n                raise RotkehlchenPermissionError(result.message, result.payload)\n\n            if sync_approval == 'yes':\n                log.info('User approved data sync from server')\n                # this may raise due to password\n                self._sync_data_from_server_and_replace_local()\n\n            else:\n                log.debug('Could sync data from server but user refused')\n        elif result.can_sync == CanSync.YES:\n            log.info('User approved data sync from server')\n            self._sync_data_from_server_and_replace_local()  # this may raise due to password\n\n    def try_premium_at_start(\n            self,\n            given_premium_credentials: Optional[PremiumCredentials],\n            username: str,\n            create_new: bool,\n            sync_approval: Literal['yes', 'no', 'unknown'],\n            sync_database: bool,\n    ) -> Optional[Premium]:\n        \"\"\"\n        Check if new user provided api pair or we already got one in the DB\n\n        Returns the created premium if user's premium credentials were fine.\n\n        If not it will raise PremiumAuthenticationError.\n\n        If no credentials were given it returns None\n        \"\"\"\n\n        if given_premium_credentials is not None:\n            assert create_new, 'We should never get here for an already existing account'\n\n            try:\n                self.premium = premium_create_and_verify(given_premium_credentials)\n            except PremiumAuthenticationError as e:\n                log.error('Given API key is invalid')\n                # At this point we are at a new user trying to create an account with\n                # premium API keys and we failed. But a directory was created. Remove it.\n                # But create a backup of it in case something went really wrong\n                # and the directory contained data we did not want to lose\n                shutil.move(\n                    self.data.user_data_dir,  # type: ignore\n                    self.data.data_directory / f'auto_backup_{username}_{ts_now()}',\n                )\n                raise PremiumAuthenticationError(\n                    'Could not verify keys for the new account. '\n                    '{}'.format(str(e)),\n                ) from e\n\n        # else, if we got premium data in the DB initialize it and try to sync with the server\n        db_credentials = self.data.db.get_rotkehlchen_premium()\n        if db_credentials:\n            assert not create_new, 'We should never get here for a new account'\n            try:\n                self.premium = premium_create_and_verify(db_credentials)\n            except PremiumAuthenticationError as e:\n                message = (\n                    f'Could not authenticate with the rotkehlchen server with '\n                    f'the API keys found in the Database. Error: {str(e)}'\n                )\n                log.error(message)\n                raise PremiumAuthenticationError(message) from e\n\n        if self.premium is None:\n            return None\n\n        result = self._can_sync_data_from_server(new_account=create_new)\n        if create_new:\n            # if this is a new account, make sure the api keys are properly stored\n            # in the DB\n            if sync_database:\n                self._sync_if_allowed(sync_approval, result)\n            self.data.db.set_rotkehlchen_premium(self.premium.credentials)\n        else:\n            self._sync_if_allowed(sync_approval, result)\n\n        # Success, return premium\n        return self.premium\n", "path": "rotkehlchen/premium/sync.py"}], "after_files": [{"content": "import base64\nimport logging\nimport shutil\nfrom enum import Enum\nfrom typing import Any, Dict, NamedTuple, Optional, Tuple\n\nfrom typing_extensions import Literal\n\nfrom rotkehlchen.data_handler import DataHandler\nfrom rotkehlchen.errors import (\n    PremiumAuthenticationError,\n    RemoteError,\n    RotkehlchenPermissionError,\n    UnableToDecryptRemoteData,\n)\nfrom rotkehlchen.logging import RotkehlchenLogsAdapter\nfrom rotkehlchen.premium.premium import Premium, PremiumCredentials, premium_create_and_verify\nfrom rotkehlchen.utils.misc import ts_now\n\nlogger = logging.getLogger(__name__)\nlog = RotkehlchenLogsAdapter(logger)\n\n\nclass CanSync(Enum):\n    YES = 0\n    NO = 1\n    ASK_USER = 2\n\n\nclass SyncCheckResult(NamedTuple):\n    # The result of the sync check\n    can_sync: CanSync\n    # If result is ASK_USER, what should the message be?\n    message: str\n    payload: Optional[Dict[str, Any]]\n\n\nclass PremiumSyncManager():\n\n    def __init__(self, data: DataHandler, password: str) -> None:\n        # Initialize this with the value saved in the DB\n        self.last_data_upload_ts = data.db.get_last_data_upload_ts()\n        self.data = data\n        self.password = password\n        self.premium: Optional[Premium] = None\n\n    def _can_sync_data_from_server(self, new_account: bool) -> SyncCheckResult:\n        \"\"\"\n        Checks if the remote data can be pulled from the server.\n\n        Returns a SyncCheckResult denoting whether we can pull for sure,\n        whether we can't pull or whether the user should be asked. If the user\n        should be asked a message is also returned\n        \"\"\"\n        log.debug('can sync data from server -- start')\n        if self.premium is None:\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)\n\n        try:\n            metadata = self.premium.query_last_data_metadata()\n        except RemoteError as e:\n            log.debug('can sync data from server failed', error=str(e))\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        if new_account:\n            return SyncCheckResult(can_sync=CanSync.YES, message='', payload=None)\n\n        if not self.data.db.get_premium_sync():\n            # If it's not a new account and the db setting for premium syncing is off stop\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        log.debug(\n            'CAN_PULL',\n            ours=our_hash,\n            theirs=metadata.data_hash,\n        )\n        if our_hash == metadata.data_hash:\n            log.debug('sync from server stopped -- same hash')\n            # same hash -- no need to get anything\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        our_last_write_ts = self.data.db.get_last_write_ts()\n        data_bytes_size = len(base64.b64decode(b64_encoded_data))\n\n        local_more_recent = our_last_write_ts >= metadata.last_modify_ts\n\n        if local_more_recent:\n            log.debug('sync from server stopped -- local is newer')\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        # else remote is bigger\n        return SyncCheckResult(\n            can_sync=CanSync.ASK_USER,\n            message='Detected remote database with bigger size than the local one. ',\n            payload={\n                'local_size': data_bytes_size,\n                'remote_size': metadata.data_size,\n                'local_last_modified': our_last_write_ts,\n                'remote_last_modified': metadata.last_modify_ts,\n            },\n        )\n\n    def _sync_data_from_server_and_replace_local(self) -> Tuple[bool, str]:\n        \"\"\"\n        Performs syncing of data from server and replaces local db\n\n        Returns true for success and False for error/failure\n\n        May raise:\n        - PremiumAuthenticationError due to an UnableToDecryptRemoteData\n        coming from  decompress_and_decrypt_db. This happens when the given password\n        does not match the one on the saved DB.\n        \"\"\"\n        if self.premium is None:\n            return False, 'Pulling failed. User does not have active premium.'\n\n        try:\n            result = self.premium.pull_data()\n        except RemoteError as e:\n            log.debug('sync from server -- pulling failed.', error=str(e))\n            return False, f'Pulling failed: {str(e)}'\n\n        if result['data'] is None:\n            log.debug('sync from server -- no data found.')\n            return False, 'No data found'\n\n        try:\n            self.data.decompress_and_decrypt_db(self.password, result['data'])\n        except UnableToDecryptRemoteData as e:\n            raise PremiumAuthenticationError(\n                'The given password can not unlock the database that was retrieved  from '\n                'the server. Make sure to use the same password as when the account was created.',\n            ) from e\n\n        return True, ''\n\n    def maybe_upload_data_to_server(self, force_upload: bool = False) -> bool:\n        # if user has no premium do nothing\n        if self.premium is None:\n            return False\n\n        if not self.data.db.get_premium_sync() and not force_upload:\n            return False\n\n        # upload only once per hour\n        diff = ts_now() - self.last_data_upload_ts\n        if diff < 3600 and not force_upload:\n            return False\n\n        try:\n            metadata = self.premium.query_last_data_metadata()\n        except RemoteError as e:\n            log.debug('upload to server -- fetching metadata error', error=str(e))\n            return False\n        b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)\n\n        log.debug(\n            'CAN_PUSH',\n            ours=our_hash,\n            theirs=metadata.data_hash,\n        )\n        if our_hash == metadata.data_hash and not force_upload:\n            log.debug('upload to server stopped -- same hash')\n            # same hash -- no need to upload anything\n            return False\n\n        our_last_write_ts = self.data.db.get_last_write_ts()\n        if our_last_write_ts <= metadata.last_modify_ts and not force_upload:\n            # Server's DB was modified after our local DB\n            log.debug(\n                f'upload to server stopped -- remote db({metadata.last_modify_ts}) '\n                f'more recent than local({our_last_write_ts})',\n            )\n            return False\n\n        data_bytes_size = len(base64.b64decode(b64_encoded_data))\n        if data_bytes_size < metadata.data_size and not force_upload:\n            # Let's be conservative.\n            # TODO: Here perhaps prompt user in the future\n            log.debug(\n                f'upload to server stopped -- remote db({metadata.data_size}) '\n                f'bigger than local({data_bytes_size})',\n            )\n            return False\n\n        try:\n            self.premium.upload_data(\n                data_blob=b64_encoded_data,\n                our_hash=our_hash,\n                last_modify_ts=our_last_write_ts,\n                compression_type='zlib',\n            )\n        except RemoteError as e:\n            log.debug('upload to server -- upload error', error=str(e))\n            return False\n\n        # update the last data upload value\n        self.last_data_upload_ts = ts_now()\n        self.data.db.update_last_data_upload_ts(self.last_data_upload_ts)\n        log.debug('upload to server -- success')\n        return True\n\n    def sync_data(self, action: Literal['upload', 'download']) -> Tuple[bool, str]:\n        msg = ''\n\n        if action == 'upload':\n            success = self.maybe_upload_data_to_server(force_upload=True)\n\n            if not success:\n                msg = 'Upload failed'\n            return success, msg\n\n        return self._sync_data_from_server_and_replace_local()\n\n    def _sync_if_allowed(\n        self,\n        sync_approval: Literal['yes', 'no', 'unknown'],\n        result: SyncCheckResult,\n    ) -> None:\n        if result.can_sync == CanSync.ASK_USER:\n            if sync_approval == 'unknown':\n                log.info('Remote DB is possibly newer. Ask user.')\n                raise RotkehlchenPermissionError(result.message, result.payload)\n\n            if sync_approval == 'yes':\n                log.info('User approved data sync from server')\n                # this may raise due to password\n                self._sync_data_from_server_and_replace_local()\n\n            else:\n                log.debug('Could sync data from server but user refused')\n        elif result.can_sync == CanSync.YES:\n            log.info('User approved data sync from server')\n            self._sync_data_from_server_and_replace_local()  # this may raise due to password\n\n    def _abort_new_syncing_premium_user(\n            self,\n            username: str,\n            original_exception: PremiumAuthenticationError,\n    ) -> None:\n        \"\"\"At this point we are at a new user trying to create an account with\n        premium API keys and we failed. But a directory was created. Remove it.\n        But create a backup of it in case something went really wrong\n        and the directory contained data we did not want to lose\"\"\"\n        shutil.move(\n            self.data.user_data_dir,  # type: ignore\n            self.data.data_directory / f'auto_backup_{username}_{ts_now()}',\n        )\n        raise PremiumAuthenticationError(\n            f'Could not verify keys for the new account. {str(original_exception)}',  # noqa: E501\n        ) from original_exception\n\n    def try_premium_at_start(\n            self,\n            given_premium_credentials: Optional[PremiumCredentials],\n            username: str,\n            create_new: bool,\n            sync_approval: Literal['yes', 'no', 'unknown'],\n            sync_database: bool,\n    ) -> Optional[Premium]:\n        \"\"\"\n        Check if new user provided api pair or we already got one in the DB\n\n        Returns the created premium if user's premium credentials were fine.\n\n        If not it will raise PremiumAuthenticationError.\n\n        If no credentials were given it returns None\n        \"\"\"\n\n        if given_premium_credentials is not None:\n            assert create_new, 'We should never get here for an already existing account'\n\n            try:\n                self.premium = premium_create_and_verify(given_premium_credentials)\n            except PremiumAuthenticationError as e:\n                self._abort_new_syncing_premium_user(username=username, original_exception=e)\n\n        # else, if we got premium data in the DB initialize it and try to sync with the server\n        db_credentials = self.data.db.get_rotkehlchen_premium()\n        if db_credentials:\n            assert not create_new, 'We should never get here for a new account'\n            try:\n                self.premium = premium_create_and_verify(db_credentials)\n            except PremiumAuthenticationError as e:\n                message = (\n                    f'Could not authenticate with the rotkehlchen server with '\n                    f'the API keys found in the Database. Error: {str(e)}'\n                )\n                log.error(message)\n                raise PremiumAuthenticationError(message) from e\n\n        if self.premium is None:\n            return None\n\n        result = self._can_sync_data_from_server(new_account=create_new)\n        if create_new:\n            # if this is a new account, make sure the api keys are properly stored\n            # in the DB\n            if sync_database:\n                try:\n                    self._sync_if_allowed(sync_approval, result)\n                except PremiumAuthenticationError as e:\n                    self._abort_new_syncing_premium_user(username=username, original_exception=e)\n\n            self.data.db.set_rotkehlchen_premium(self.premium.credentials)\n        else:\n            self._sync_if_allowed(sync_approval, result)\n\n        # Success, return premium\n        return self.premium\n", "path": "rotkehlchen/premium/sync.py"}]}