We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Rotki premium DB upload does not respect user setting
## Problem Definition

It seems that rotki users who use premium experienced encrypted DB uploads even with the setting off. 2 versions back we broke the functionality and made it always upload.

## Task

Fix it

--- 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 timestamp_to_date, 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         local_bigger = data_bytes_size >= metadata.data_size
89 
90         if local_more_recent and local_bigger:
91             log.debug('sync from server stopped -- local is both newer and bigger')
92             return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)
93 
94         if local_more_recent is False:  # remote is more recent
95             message = (
96                 'Detected remote database with more recent modification timestamp '
97                 'than the local one. '
98             )
99         else:  # remote is bigger
100             message = 'Detected remote database with bigger size than the local one. '
101 
102         return SyncCheckResult(
103             can_sync=CanSync.ASK_USER,
104             message=message,
105             payload={
106                 'local_size': data_bytes_size,
107                 'remote_size': metadata.data_size,
108                 'local_last_modified': timestamp_to_date(our_last_write_ts),
109                 'remote_last_modified': timestamp_to_date(metadata.last_modify_ts),
110             },
111         )
112 
113     def _sync_data_from_server_and_replace_local(self) -> Tuple[bool, str]:
114         """
115         Performs syncing of data from server and replaces local db
116 
117         Returns true for success and False for error/failure
118 
119         May raise:
120         - PremiumAuthenticationError due to an UnableToDecryptRemoteData
121         coming from  decompress_and_decrypt_db. This happens when the given password
122         does not match the one on the saved DB.
123         """
124         if self.premium is None:
125             return False, 'Pulling failed. User does not have active premium.'
126 
127         try:
128             result = self.premium.pull_data()
129         except RemoteError as e:
130             log.debug('sync from server -- pulling failed.', error=str(e))
131             return False, f'Pulling failed: {str(e)}'
132 
133         if result['data'] is None:
134             log.debug('sync from server -- no data found.')
135             return False, 'No data found'
136 
137         try:
138             self.data.decompress_and_decrypt_db(self.password, result['data'])
139         except UnableToDecryptRemoteData as e:
140             raise PremiumAuthenticationError(
141                 'The given password can not unlock the database that was retrieved  from '
142                 'the server. Make sure to use the same password as when the account was created.',
143             ) from e
144 
145         return True, ''
146 
147     def maybe_upload_data_to_server(self, force_upload: bool = False) -> bool:
148         # if user has no premium do nothing
149         if self.premium is None:
150             return False
151 
152         # upload only once per hour
153         diff = ts_now() - self.last_data_upload_ts
154         if diff < 3600 and not force_upload:
155             return False
156 
157         try:
158             metadata = self.premium.query_last_data_metadata()
159         except RemoteError as e:
160             log.debug('upload to server -- fetching metadata error', error=str(e))
161             return False
162         b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)
163 
164         log.debug(
165             'CAN_PUSH',
166             ours=our_hash,
167             theirs=metadata.data_hash,
168         )
169         if our_hash == metadata.data_hash and not force_upload:
170             log.debug('upload to server stopped -- same hash')
171             # same hash -- no need to upload anything
172             return False
173 
174         our_last_write_ts = self.data.db.get_last_write_ts()
175         if our_last_write_ts <= metadata.last_modify_ts and not force_upload:
176             # Server's DB was modified after our local DB
177             log.debug(
178                 f'upload to server stopped -- remote db({metadata.last_modify_ts}) '
179                 f'more recent than local({our_last_write_ts})',
180             )
181             return False
182 
183         data_bytes_size = len(base64.b64decode(b64_encoded_data))
184         if data_bytes_size < metadata.data_size and not force_upload:
185             # Let's be conservative.
186             # TODO: Here perhaps prompt user in the future
187             log.debug(
188                 f'upload to server stopped -- remote db({metadata.data_size}) '
189                 f'bigger than local({data_bytes_size})',
190             )
191             return False
192 
193         try:
194             self.premium.upload_data(
195                 data_blob=b64_encoded_data,
196                 our_hash=our_hash,
197                 last_modify_ts=our_last_write_ts,
198                 compression_type='zlib',
199             )
200         except RemoteError as e:
201             log.debug('upload to server -- upload error', error=str(e))
202             return False
203 
204         # update the last data upload value
205         self.last_data_upload_ts = ts_now()
206         self.data.db.update_last_data_upload_ts(self.last_data_upload_ts)
207         log.debug('upload to server -- success')
208         return True
209 
210     def sync_data(self, action: Literal['upload', 'download']) -> Tuple[bool, str]:
211         msg = ''
212 
213         if action == 'upload':
214             success = self.maybe_upload_data_to_server(force_upload=True)
215 
216             if not success:
217                 msg = 'Upload failed'
218             return success, msg
219 
220         return self._sync_data_from_server_and_replace_local()
221 
222     def try_premium_at_start(
223             self,
224             given_premium_credentials: Optional[PremiumCredentials],
225             username: str,
226             create_new: bool,
227             sync_approval: Literal['yes', 'no', 'unknown'],
228     ) -> Optional[Premium]:
229         """
230         Check if new user provided api pair or we already got one in the DB
231 
232         Returns the created premium if user's premium credentials were fine.
233 
234         If not it will raise PremiumAuthenticationError.
235 
236         If no credentials were given it returns None
237         """
238 
239         if given_premium_credentials is not None:
240             assert create_new, 'We should never get here for an already existing account'
241 
242             try:
243                 self.premium = premium_create_and_verify(given_premium_credentials)
244             except PremiumAuthenticationError as e:
245                 log.error('Given API key is invalid')
246                 # At this point we are at a new user trying to create an account with
247                 # premium API keys and we failed. But a directory was created. Remove it.
248                 # But create a backup of it in case something went really wrong
249                 # and the directory contained data we did not want to lose
250                 shutil.move(
251                     self.data.user_data_dir,  # type: ignore
252                     self.data.data_directory / f'auto_backup_{username}_{ts_now()}',
253                 )
254                 raise PremiumAuthenticationError(
255                     'Could not verify keys for the new account. '
256                     '{}'.format(str(e)),
257                 ) from e
258 
259         # else, if we got premium data in the DB initialize it and try to sync with the server
260         db_credentials = self.data.db.get_rotkehlchen_premium()
261         if db_credentials:
262             assert not create_new, 'We should never get here for a new account'
263             try:
264                 self.premium = premium_create_and_verify(db_credentials)
265             except PremiumAuthenticationError as e:
266                 message = (
267                     f'Could not authenticate with the rotkehlchen server with '
268                     f'the API keys found in the Database. Error: {str(e)}'
269                 )
270                 log.error(message)
271                 raise PremiumAuthenticationError(message) from e
272 
273         if self.premium is None:
274             return None
275 
276         result = self._can_sync_data_from_server(new_account=create_new)
277         if result.can_sync == CanSync.ASK_USER:
278             if sync_approval == 'unknown':
279                 log.info('Remote DB is possibly newer. Ask user.')
280                 raise RotkehlchenPermissionError(result.message, result.payload)
281 
282             if sync_approval == 'yes':
283                 log.info('User approved data sync from server')
284                 if self._sync_data_from_server_and_replace_local()[0]:
285                     if create_new:
286                         # if we successfully synced data from the server and this is
287                         # a new account, make sure the api keys are properly stored
288                         # in the DB
289                         self.data.db.set_rotkehlchen_premium(self.premium.credentials)
290 
291             else:
292                 log.debug('Could sync data from server but user refused')
293         elif result.can_sync == CanSync.YES:
294             log.info('User approved data sync from server')
295             if self._sync_data_from_server_and_replace_local()[0]:
296                 if create_new:
297                     # if we successfully synced data from the server and this is
298                     # a new account, make sure the api keys are properly stored
299                     # in the DB
300                     self.data.db.set_rotkehlchen_premium(self.premium.credentials)
301 
302         # else result.can_sync was no, so we do nothing
303 
304         # Success, return premium
305         return self.premium
306
```
--- 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
@@ -149,6 +149,9 @@
         if self.premium is None:
             return False
 
+        if not self.data.db.get_premium_sync() and not force_upload:
+            return False
+
         # upload only once per hour
         diff = ts_now() - self.last_data_upload_ts
         if diff < 3600 and not force_upload:

{"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@@ -149,6 +149,9 @@\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", "issue": "Rotki premium DB upload does not respect user setting\n## Problem Definition\r\n\r\nIt seems that rotki users who use premium experienced encrypted DB uploads even with the setting off. 2 versions back we broke the functionality and made it always upload.\r\n\r\n## Task\r\n\r\nFix it\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 timestamp_to_date, 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        local_bigger = data_bytes_size >= metadata.data_size\n\n        if local_more_recent and local_bigger:\n            log.debug('sync from server stopped -- local is both newer and bigger')\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        if local_more_recent is False:  # remote is more recent\n            message = (\n                'Detected remote database with more recent modification timestamp '\n                'than the local one. '\n            )\n        else:  # remote is bigger\n            message = 'Detected remote database with bigger size than the local one. '\n\n        return SyncCheckResult(\n            can_sync=CanSync.ASK_USER,\n            message=message,\n            payload={\n                'local_size': data_bytes_size,\n                'remote_size': metadata.data_size,\n                'local_last_modified': timestamp_to_date(our_last_write_ts),\n                'remote_last_modified': timestamp_to_date(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        # 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 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    ) -> 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 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                if self._sync_data_from_server_and_replace_local()[0]:\n                    if create_new:\n                        # if we successfully synced data from the server and this is\n                        # a new account, make sure the api keys are properly stored\n                        # in the DB\n                        self.data.db.set_rotkehlchen_premium(self.premium.credentials)\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            if self._sync_data_from_server_and_replace_local()[0]:\n                if create_new:\n                    # if we successfully synced data from the server and this is\n                    # a new account, make sure the api keys are properly stored\n                    # in the DB\n                    self.data.db.set_rotkehlchen_premium(self.premium.credentials)\n\n        # else result.can_sync was no, so we do nothing\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 timestamp_to_date, 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        local_bigger = data_bytes_size >= metadata.data_size\n\n        if local_more_recent and local_bigger:\n            log.debug('sync from server stopped -- local is both newer and bigger')\n            return SyncCheckResult(can_sync=CanSync.NO, message='', payload=None)\n\n        if local_more_recent is False:  # remote is more recent\n            message = (\n                'Detected remote database with more recent modification timestamp '\n                'than the local one. '\n            )\n        else:  # remote is bigger\n            message = 'Detected remote database with bigger size than the local one. '\n\n        return SyncCheckResult(\n            can_sync=CanSync.ASK_USER,\n            message=message,\n            payload={\n                'local_size': data_bytes_size,\n                'remote_size': metadata.data_size,\n                'local_last_modified': timestamp_to_date(our_last_write_ts),\n                'remote_last_modified': timestamp_to_date(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 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    ) -> 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 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                if self._sync_data_from_server_and_replace_local()[0]:\n                    if create_new:\n                        # if we successfully synced data from the server and this is\n                        # a new account, make sure the api keys are properly stored\n                        # in the DB\n                        self.data.db.set_rotkehlchen_premium(self.premium.credentials)\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            if self._sync_data_from_server_and_replace_local()[0]:\n                if create_new:\n                    # if we successfully synced data from the server and this is\n                    # a new account, make sure the api keys are properly stored\n                    # in the DB\n                    self.data.db.set_rotkehlchen_premium(self.premium.credentials)\n\n        # else result.can_sync was no, so we do nothing\n\n        # Success, return premium\n        return self.premium\n", "path": "rotkehlchen/premium/sync.py"}]}