We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
If local premium DB is larger and more recent than remote don't ask user for replacement
## Problem Definition

If local premium DB is larger and more recent than remote don't ask user for replacement and instead replace remote.

![2020-10-15_15-27](https://user-images.githubusercontent.com/1658405/96132684-f762eb00-0efa-11eb-9ca9-8d72df1d3c35.png)

## Task

Do it lol

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

{"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@@ -82,18 +82,21 @@\n \n         our_last_write_ts = self.data.db.get_last_write_ts()\n         data_bytes_size = len(base64.b64decode(b64_encoded_data))\n-        if our_last_write_ts >= metadata.last_modify_ts:\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 BUT with older last modification timestamp '\n+                'Detected remote database with more recent modification timestamp '\n                 'than the local one. '\n             )\n-        else:\n-            if data_bytes_size > metadata.data_size:\n-                message = (\n-                    'Detected newer remote database BUT with smaller size than the local one. '\n-                )\n-            else:\n-                message = 'Detected newer remote database. '\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@@ -254,7 +257,7 @@\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('DB data at server newer than local')\n+                log.info('Remote DB is possibly newer. Ask user.')\n                 raise RotkehlchenPermissionError(result.message, result.payload)\n             elif sync_approval == 'yes':\n                 log.info('User approved data sync from server')\n", "issue": "If local premium DB is larger and more recent than remote don't ask user for replacement\n## Problem Definition\r\n\r\nIf local premium DB is larger and more recent than remote don't ask user for replacement and instead replace remote.\r\n\r\n![2020-10-15_15-27](https://user-images.githubusercontent.com/1658405/96132684-f762eb00-0efa-11eb-9ca9-8d72df1d3c35.png)\r\n\r\n## Task\r\n\r\nDo it lol\n", "before_files": [{"content": "import base64\nimport logging\nimport shutil\nfrom enum import Enum\nfrom typing import Any, Dict, NamedTuple, Optional\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        self.last_data_upload_ts = 0\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        if our_last_write_ts >= metadata.last_modify_ts:\n            message = (\n                'Detected remote database BUT with older last modification timestamp '\n                'than the local one. '\n            )\n        else:\n            if data_bytes_size > metadata.data_size:\n                message = (\n                    'Detected newer remote database BUT with smaller size than the local one. '\n                )\n            else:\n                message = 'Detected newer remote database. '\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) -> bool:\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        assert self.premium, 'This function has to be called with a not None premium'\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\n\n        if result['data'] is None:\n            log.debug('sync from server -- no data found.')\n            return False\n\n        try:\n            self.data.decompress_and_decrypt_db(self.password, result['data'])\n        except UnableToDecryptRemoteData:\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            )\n\n        return True\n\n    def maybe_upload_data_to_server(self) -> None:\n        # if user has no premium do nothing\n        if self.premium is None:\n            return\n\n        # upload only once per hour\n        diff = ts_now() - self.last_data_upload_ts\n        if diff < 3600:\n            return\n\n        b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)\n        try:\n            metadata = self.premium.query_last_data_metadata()\n        except RemoteError as e:\n            log.debug(\n                'upload to server stopped -- query last metadata failed',\n                error=str(e),\n            )\n            return\n\n        log.debug(\n            'CAN_PUSH',\n            ours=our_hash,\n            theirs=metadata.data_hash,\n        )\n        if our_hash == metadata.data_hash:\n            log.debug('upload to server stopped -- same hash')\n            # same hash -- no need to upload anything\n            return\n\n        our_last_write_ts = self.data.db.get_last_write_ts()\n        if our_last_write_ts <= metadata.last_modify_ts:\n            # Server's DB was modified after our local DB\n            log.debug('upload to server stopped -- remote db more recent than local')\n            return\n\n        data_bytes_size = len(base64.b64decode(b64_encoded_data))\n        if data_bytes_size < metadata.data_size:\n            # Let's be conservative.\n            # TODO: Here perhaps prompt user in the future\n            log.debug('upload to server stopped -- remote db bigger than local')\n            return\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\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\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                )\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)\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('DB data at server newer than local')\n                raise RotkehlchenPermissionError(result.message, result.payload)\n            elif sync_approval == 'yes':\n                log.info('User approved data sync from server')\n                if self._sync_data_from_server_and_replace_local():\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            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():\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\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        self.last_data_upload_ts = 0\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) -> bool:\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        assert self.premium, 'This function has to be called with a not None premium'\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\n\n        if result['data'] is None:\n            log.debug('sync from server -- no data found.')\n            return False\n\n        try:\n            self.data.decompress_and_decrypt_db(self.password, result['data'])\n        except UnableToDecryptRemoteData:\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            )\n\n        return True\n\n    def maybe_upload_data_to_server(self) -> None:\n        # if user has no premium do nothing\n        if self.premium is None:\n            return\n\n        # upload only once per hour\n        diff = ts_now() - self.last_data_upload_ts\n        if diff < 3600:\n            return\n\n        b64_encoded_data, our_hash = self.data.compress_and_encrypt_db(self.password)\n        try:\n            metadata = self.premium.query_last_data_metadata()\n        except RemoteError as e:\n            log.debug(\n                'upload to server stopped -- query last metadata failed',\n                error=str(e),\n            )\n            return\n\n        log.debug(\n            'CAN_PUSH',\n            ours=our_hash,\n            theirs=metadata.data_hash,\n        )\n        if our_hash == metadata.data_hash:\n            log.debug('upload to server stopped -- same hash')\n            # same hash -- no need to upload anything\n            return\n\n        our_last_write_ts = self.data.db.get_last_write_ts()\n        if our_last_write_ts <= metadata.last_modify_ts:\n            # Server's DB was modified after our local DB\n            log.debug('upload to server stopped -- remote db more recent than local')\n            return\n\n        data_bytes_size = len(base64.b64decode(b64_encoded_data))\n        if data_bytes_size < metadata.data_size:\n            # Let's be conservative.\n            # TODO: Here perhaps prompt user in the future\n            log.debug('upload to server stopped -- remote db bigger than local')\n            return\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\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\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                )\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)\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            elif sync_approval == 'yes':\n                log.info('User approved data sync from server')\n                if self._sync_data_from_server_and_replace_local():\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            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():\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"}]}