karma365 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- karma365-0.2.0/PKG-INFO +194 -0
- karma365-0.2.0/README.md +179 -0
- karma365-0.2.0/karma365/__init__.py +56 -0
- karma365-0.2.0/karma365/__main__.py +76 -0
- karma365-0.2.0/karma365/client.py +704 -0
- karma365-0.2.0/karma365/errors.py +161 -0
- karma365-0.2.0/karma365/errors_catalog.json +112 -0
- karma365-0.2.0/karma365/link.py +393 -0
- karma365-0.2.0/karma365/models.py +166 -0
- karma365-0.2.0/karma365/upload.py +247 -0
- karma365-0.2.0/karma365.egg-info/PKG-INFO +194 -0
- karma365-0.2.0/karma365.egg-info/SOURCES.txt +15 -0
- karma365-0.2.0/karma365.egg-info/dependency_links.txt +1 -0
- karma365-0.2.0/karma365.egg-info/requires.txt +2 -0
- karma365-0.2.0/karma365.egg-info/top_level.txt +1 -0
- karma365-0.2.0/pyproject.toml +30 -0
- karma365-0.2.0/setup.cfg +4 -0
karma365-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: karma365
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Add to your KARMA 365 library from Python. Your data stays on your phone.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/ayushkotadiya5499/karma365-sdk
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/ayushkotadiya5499/karma365-sdk/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: cryptography>=42
|
|
14
|
+
Requires-Dist: httpx>=0.25
|
|
15
|
+
|
|
16
|
+
# KARMA 365 SDK for Python
|
|
17
|
+
|
|
18
|
+
Add to your KARMA 365 library from Python: results, plots, videos, notes and tasks.
|
|
19
|
+
|
|
20
|
+
Your data stays on your phone. This SDK has no database and no cloud. It talks to the
|
|
21
|
+
app over your own Wi-Fi, hotspot or USB, and everything on the way is encrypted.
|
|
22
|
+
|
|
23
|
+
## Before you start
|
|
24
|
+
|
|
25
|
+
1. Open KARMA 365 on your phone.
|
|
26
|
+
2. Go to Settings, AI MCP, and turn on **Allow PC connection**.
|
|
27
|
+
3. Copy the token, and put it in an environment variable:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
export KARMA365_TOKEN="your token" # macOS, Linux
|
|
31
|
+
setx KARMA365_TOKEN "your token" # Windows, then open a new terminal
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Keep the token out of your code, and off GitHub.** Whoever holds it, on the same
|
|
35
|
+
network as your phone, can read and change everything that is not PIN-locked. If it
|
|
36
|
+
leaks, make a new one in the app. The old one stops working at once.
|
|
37
|
+
|
|
38
|
+
The phone must be on, with the app unlocked, on the same network as this PC.
|
|
39
|
+
|
|
40
|
+
## Is it connected?
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python -m karma365 status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
status = await karma.status()
|
|
48
|
+
print(status.connected, status.reason, status.message, status.hint)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`connected` is true only after your phone has answered with your token's key, so
|
|
52
|
+
another device cannot pass as your phone. The answer is true for that moment; if the
|
|
53
|
+
Wi-Fi goes later, the next call says so.
|
|
54
|
+
|
|
55
|
+
In the app, Settings, AI MCP shows the last connection, and a file being received.
|
|
56
|
+
|
|
57
|
+
## Use it
|
|
58
|
+
|
|
59
|
+
With `await`:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import asyncio
|
|
63
|
+
from karma365 import AsyncKarmaClient
|
|
64
|
+
|
|
65
|
+
async def main():
|
|
66
|
+
async with AsyncKarmaClient() as karma:
|
|
67
|
+
topic = await karma.find_minor_id("Machine Learning")
|
|
68
|
+
await karma.add_image(topic, "outputs/confusion_matrix.png", caption="Accuracy 96.4%")
|
|
69
|
+
await karma.create_task("Train ResNet-50", priority="high")
|
|
70
|
+
|
|
71
|
+
asyncio.run(main())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Without `await`, for scripts and notebooks. It works inside Jupyter too:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from karma365 import KarmaClient
|
|
78
|
+
|
|
79
|
+
karma = KarmaClient()
|
|
80
|
+
karma.create_task("Train ResNet-50", priority="high")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Large files
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
await karma.add_video(
|
|
87
|
+
"Machine Learning", "lecture_8gb.mp4", title="Lecture 1",
|
|
88
|
+
on_progress=lambda sent, total: print(f"{sent * 100 // total}%"),
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- Any size. The limit is your phone's free space, which is checked before anything is sent.
|
|
93
|
+
- The file goes in encrypted pieces, so it never fills your memory.
|
|
94
|
+
- **If it stops, run the same call again.** Only the missing pieces are sent.
|
|
95
|
+
- Nothing appears in the app until the whole file has arrived.
|
|
96
|
+
- On the phone, a notification keeps it going while the screen is off. Android can
|
|
97
|
+
still close the app, Oppo phones especially. Open it again and repeat the call.
|
|
98
|
+
|
|
99
|
+
`await` does not itself make a file faster. It keeps your program free, and lets
|
|
100
|
+
several pieces travel at once. The speed comes from your Wi-Fi and your phone. Measure
|
|
101
|
+
yours, using a test file that is deleted afterwards:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python -m karma365 speedtest 100
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This needs a minor topic to exist, which the test file passes through.
|
|
108
|
+
|
|
109
|
+
## Locked items
|
|
110
|
+
|
|
111
|
+
The phone checks every PIN, and allows 6 wrong tries before a short wait.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import getpass
|
|
115
|
+
await karma.unlock(getpass.getpass("PIN: ")) # kept in memory until lock() or close()
|
|
116
|
+
await karma.list_resources("Private", pin=my_pin) # or for one call
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Do not write the PIN in your code. Without it, locked rows say only that they are
|
|
120
|
+
locked, and `is_locked` is true on them. The PIN can only be set or changed in the app.
|
|
121
|
+
|
|
122
|
+
## Errors
|
|
123
|
+
|
|
124
|
+
Every error says what happened and what to do, and has a `code` that never changes.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from karma365 import KarmaError, PhoneNotFoundError
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
await karma.add_video("ML", "lecture.mp4")
|
|
131
|
+
except PhoneNotFoundError as e:
|
|
132
|
+
print(e.hint)
|
|
133
|
+
except KarmaError as e:
|
|
134
|
+
print(e.code, e.message, e.details)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Code | Meaning |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `TOKEN_MISSING`, `TOKEN_REJECTED` | No token, or the phone did not accept it |
|
|
140
|
+
| `CLOCK_OUT_OF_SYNC` | This PC's clock and the phone's are too far apart |
|
|
141
|
+
| `PHONE_NOT_FOUND` | No answer. App closed, Allow PC off, or another network |
|
|
142
|
+
| `NOT_YOUR_PHONE` | Something answered, but could not prove it is your phone |
|
|
143
|
+
| `APP_LOCKED` | Unlock the app on your phone |
|
|
144
|
+
| `CONNECTION_LOST`, `PHONE_TIMEOUT` | Lost during a call. A change may have happened, so check before repeating it |
|
|
145
|
+
| `APP_UPDATE_NEEDED` | The app on your phone is too old for this |
|
|
146
|
+
| `PIN_REQUIRED`, `PIN_WRONG`, `PIN_LOCKED_OUT`, `PARENT_LOCKED` | About locks |
|
|
147
|
+
| `NOT_FOUND`, `AMBIGUOUS_NAME`, `INVALID_INPUT` | About what you passed |
|
|
148
|
+
| `FILE_NOT_FOUND`, `FILE_NOT_ALLOWED`, `FILE_CHANGED` | About the file on this PC |
|
|
149
|
+
| `PHONE_STORAGE_FULL`, `UPLOAD_INTERRUPTED` | About sending a file |
|
|
150
|
+
| `PHONE_ERROR` | Unexpected. Set `KARMA365_DEBUG=1` to see more |
|
|
151
|
+
|
|
152
|
+
Reads, and pieces of a file, are tried again by themselves. A create or a delete is
|
|
153
|
+
not, because the first try may have worked. A wrong PIN is never tried again, so a
|
|
154
|
+
script cannot use up your tries.
|
|
155
|
+
|
|
156
|
+
## Deleting locked items
|
|
157
|
+
|
|
158
|
+
Something locked is never deleted, not even with the PIN. Neither is something under a
|
|
159
|
+
lock, or something holding a locked item. Remove the lock first, then delete. Until then
|
|
160
|
+
the call fails with `PARENT_LOCKED`.
|
|
161
|
+
|
|
162
|
+
## Names and IDs
|
|
163
|
+
|
|
164
|
+
Anything that changes something needs an ID, or a whole name that fits only one item.
|
|
165
|
+
Part of a name works only in `find_id()` and `search()`, so a short word can never
|
|
166
|
+
delete the wrong thing.
|
|
167
|
+
|
|
168
|
+
## Methods
|
|
169
|
+
|
|
170
|
+
**Connection:** `status`, `is_connected`, `unlock`, `lock`, `close`
|
|
171
|
+
|
|
172
|
+
**Finding:** `find_id`, `find_minor_id`, `find_major_id`, `get_info`, `search`
|
|
173
|
+
|
|
174
|
+
**Topics:** `create_major`, `create_minor`, `list_majors`, `list_minors`, `get_major`,
|
|
175
|
+
`get_minor`, `change_name`, `change_minor_name`, `change_major_name`, `change_color`,
|
|
176
|
+
`change_minor_color`, `change_major_color`, `change_dp`, `change_difficulty`,
|
|
177
|
+
`transfer_minor_to`, `transfer_to`, `pin`, `unpin`, `star`, `unstar`, `delete`, `clean_empty`
|
|
178
|
+
|
|
179
|
+
**Resources:** `add_image`, `add_video`, `add_audio`, `add_pdf`, `add_youtube`, `add_link`,
|
|
180
|
+
`add_code`, `add_note`, `append_note`, `list_resources`, `view_all`, `get_resource`,
|
|
181
|
+
`change_description`, `mark_done`, `unmark_done`
|
|
182
|
+
|
|
183
|
+
**Tasks:** `create_task`, `list_tasks`, `get_task`, `change_priority`, `mark_done`,
|
|
184
|
+
`unmark_done`, `delete`, `clear_completed_tasks`
|
|
185
|
+
|
|
186
|
+
**Stats:** `get_stats`
|
|
187
|
+
|
|
188
|
+
## Settings
|
|
189
|
+
|
|
190
|
+
| Variable | Use |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `KARMA365_TOKEN` | Your token |
|
|
193
|
+
| `KARMA365_PHONE_URL` | The phone's address, if it is not found by itself |
|
|
194
|
+
| `KARMA365_DEBUG` | Set to 1 to add the technical cause to errors. Never shows your token or PIN |
|
karma365-0.2.0/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# KARMA 365 SDK for Python
|
|
2
|
+
|
|
3
|
+
Add to your KARMA 365 library from Python: results, plots, videos, notes and tasks.
|
|
4
|
+
|
|
5
|
+
Your data stays on your phone. This SDK has no database and no cloud. It talks to the
|
|
6
|
+
app over your own Wi-Fi, hotspot or USB, and everything on the way is encrypted.
|
|
7
|
+
|
|
8
|
+
## Before you start
|
|
9
|
+
|
|
10
|
+
1. Open KARMA 365 on your phone.
|
|
11
|
+
2. Go to Settings, AI MCP, and turn on **Allow PC connection**.
|
|
12
|
+
3. Copy the token, and put it in an environment variable:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
export KARMA365_TOKEN="your token" # macOS, Linux
|
|
16
|
+
setx KARMA365_TOKEN "your token" # Windows, then open a new terminal
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Keep the token out of your code, and off GitHub.** Whoever holds it, on the same
|
|
20
|
+
network as your phone, can read and change everything that is not PIN-locked. If it
|
|
21
|
+
leaks, make a new one in the app. The old one stops working at once.
|
|
22
|
+
|
|
23
|
+
The phone must be on, with the app unlocked, on the same network as this PC.
|
|
24
|
+
|
|
25
|
+
## Is it connected?
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m karma365 status
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
status = await karma.status()
|
|
33
|
+
print(status.connected, status.reason, status.message, status.hint)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`connected` is true only after your phone has answered with your token's key, so
|
|
37
|
+
another device cannot pass as your phone. The answer is true for that moment; if the
|
|
38
|
+
Wi-Fi goes later, the next call says so.
|
|
39
|
+
|
|
40
|
+
In the app, Settings, AI MCP shows the last connection, and a file being received.
|
|
41
|
+
|
|
42
|
+
## Use it
|
|
43
|
+
|
|
44
|
+
With `await`:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import asyncio
|
|
48
|
+
from karma365 import AsyncKarmaClient
|
|
49
|
+
|
|
50
|
+
async def main():
|
|
51
|
+
async with AsyncKarmaClient() as karma:
|
|
52
|
+
topic = await karma.find_minor_id("Machine Learning")
|
|
53
|
+
await karma.add_image(topic, "outputs/confusion_matrix.png", caption="Accuracy 96.4%")
|
|
54
|
+
await karma.create_task("Train ResNet-50", priority="high")
|
|
55
|
+
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Without `await`, for scripts and notebooks. It works inside Jupyter too:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from karma365 import KarmaClient
|
|
63
|
+
|
|
64
|
+
karma = KarmaClient()
|
|
65
|
+
karma.create_task("Train ResNet-50", priority="high")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Large files
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
await karma.add_video(
|
|
72
|
+
"Machine Learning", "lecture_8gb.mp4", title="Lecture 1",
|
|
73
|
+
on_progress=lambda sent, total: print(f"{sent * 100 // total}%"),
|
|
74
|
+
)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- Any size. The limit is your phone's free space, which is checked before anything is sent.
|
|
78
|
+
- The file goes in encrypted pieces, so it never fills your memory.
|
|
79
|
+
- **If it stops, run the same call again.** Only the missing pieces are sent.
|
|
80
|
+
- Nothing appears in the app until the whole file has arrived.
|
|
81
|
+
- On the phone, a notification keeps it going while the screen is off. Android can
|
|
82
|
+
still close the app, Oppo phones especially. Open it again and repeat the call.
|
|
83
|
+
|
|
84
|
+
`await` does not itself make a file faster. It keeps your program free, and lets
|
|
85
|
+
several pieces travel at once. The speed comes from your Wi-Fi and your phone. Measure
|
|
86
|
+
yours, using a test file that is deleted afterwards:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
python -m karma365 speedtest 100
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
This needs a minor topic to exist, which the test file passes through.
|
|
93
|
+
|
|
94
|
+
## Locked items
|
|
95
|
+
|
|
96
|
+
The phone checks every PIN, and allows 6 wrong tries before a short wait.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import getpass
|
|
100
|
+
await karma.unlock(getpass.getpass("PIN: ")) # kept in memory until lock() or close()
|
|
101
|
+
await karma.list_resources("Private", pin=my_pin) # or for one call
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Do not write the PIN in your code. Without it, locked rows say only that they are
|
|
105
|
+
locked, and `is_locked` is true on them. The PIN can only be set or changed in the app.
|
|
106
|
+
|
|
107
|
+
## Errors
|
|
108
|
+
|
|
109
|
+
Every error says what happened and what to do, and has a `code` that never changes.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from karma365 import KarmaError, PhoneNotFoundError
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
await karma.add_video("ML", "lecture.mp4")
|
|
116
|
+
except PhoneNotFoundError as e:
|
|
117
|
+
print(e.hint)
|
|
118
|
+
except KarmaError as e:
|
|
119
|
+
print(e.code, e.message, e.details)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Code | Meaning |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `TOKEN_MISSING`, `TOKEN_REJECTED` | No token, or the phone did not accept it |
|
|
125
|
+
| `CLOCK_OUT_OF_SYNC` | This PC's clock and the phone's are too far apart |
|
|
126
|
+
| `PHONE_NOT_FOUND` | No answer. App closed, Allow PC off, or another network |
|
|
127
|
+
| `NOT_YOUR_PHONE` | Something answered, but could not prove it is your phone |
|
|
128
|
+
| `APP_LOCKED` | Unlock the app on your phone |
|
|
129
|
+
| `CONNECTION_LOST`, `PHONE_TIMEOUT` | Lost during a call. A change may have happened, so check before repeating it |
|
|
130
|
+
| `APP_UPDATE_NEEDED` | The app on your phone is too old for this |
|
|
131
|
+
| `PIN_REQUIRED`, `PIN_WRONG`, `PIN_LOCKED_OUT`, `PARENT_LOCKED` | About locks |
|
|
132
|
+
| `NOT_FOUND`, `AMBIGUOUS_NAME`, `INVALID_INPUT` | About what you passed |
|
|
133
|
+
| `FILE_NOT_FOUND`, `FILE_NOT_ALLOWED`, `FILE_CHANGED` | About the file on this PC |
|
|
134
|
+
| `PHONE_STORAGE_FULL`, `UPLOAD_INTERRUPTED` | About sending a file |
|
|
135
|
+
| `PHONE_ERROR` | Unexpected. Set `KARMA365_DEBUG=1` to see more |
|
|
136
|
+
|
|
137
|
+
Reads, and pieces of a file, are tried again by themselves. A create or a delete is
|
|
138
|
+
not, because the first try may have worked. A wrong PIN is never tried again, so a
|
|
139
|
+
script cannot use up your tries.
|
|
140
|
+
|
|
141
|
+
## Deleting locked items
|
|
142
|
+
|
|
143
|
+
Something locked is never deleted, not even with the PIN. Neither is something under a
|
|
144
|
+
lock, or something holding a locked item. Remove the lock first, then delete. Until then
|
|
145
|
+
the call fails with `PARENT_LOCKED`.
|
|
146
|
+
|
|
147
|
+
## Names and IDs
|
|
148
|
+
|
|
149
|
+
Anything that changes something needs an ID, or a whole name that fits only one item.
|
|
150
|
+
Part of a name works only in `find_id()` and `search()`, so a short word can never
|
|
151
|
+
delete the wrong thing.
|
|
152
|
+
|
|
153
|
+
## Methods
|
|
154
|
+
|
|
155
|
+
**Connection:** `status`, `is_connected`, `unlock`, `lock`, `close`
|
|
156
|
+
|
|
157
|
+
**Finding:** `find_id`, `find_minor_id`, `find_major_id`, `get_info`, `search`
|
|
158
|
+
|
|
159
|
+
**Topics:** `create_major`, `create_minor`, `list_majors`, `list_minors`, `get_major`,
|
|
160
|
+
`get_minor`, `change_name`, `change_minor_name`, `change_major_name`, `change_color`,
|
|
161
|
+
`change_minor_color`, `change_major_color`, `change_dp`, `change_difficulty`,
|
|
162
|
+
`transfer_minor_to`, `transfer_to`, `pin`, `unpin`, `star`, `unstar`, `delete`, `clean_empty`
|
|
163
|
+
|
|
164
|
+
**Resources:** `add_image`, `add_video`, `add_audio`, `add_pdf`, `add_youtube`, `add_link`,
|
|
165
|
+
`add_code`, `add_note`, `append_note`, `list_resources`, `view_all`, `get_resource`,
|
|
166
|
+
`change_description`, `mark_done`, `unmark_done`
|
|
167
|
+
|
|
168
|
+
**Tasks:** `create_task`, `list_tasks`, `get_task`, `change_priority`, `mark_done`,
|
|
169
|
+
`unmark_done`, `delete`, `clear_completed_tasks`
|
|
170
|
+
|
|
171
|
+
**Stats:** `get_stats`
|
|
172
|
+
|
|
173
|
+
## Settings
|
|
174
|
+
|
|
175
|
+
| Variable | Use |
|
|
176
|
+
|---|---|
|
|
177
|
+
| `KARMA365_TOKEN` | Your token |
|
|
178
|
+
| `KARMA365_PHONE_URL` | The phone's address, if it is not found by itself |
|
|
179
|
+
| `KARMA365_DEBUG` | Set to 1 to add the technical cause to errors. Never shows your token or PIN |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""KARMA 365 SDK for Python. Your data stays on your phone; this talks to it."""
|
|
2
|
+
|
|
3
|
+
from .client import AsyncKarmaClient, KarmaClient
|
|
4
|
+
from .errors import (
|
|
5
|
+
AmbiguousNameError,
|
|
6
|
+
AppLockedError,
|
|
7
|
+
AppUpdateNeededError,
|
|
8
|
+
ConnectionLostError,
|
|
9
|
+
FileProblemError,
|
|
10
|
+
InvalidInputError,
|
|
11
|
+
KarmaError,
|
|
12
|
+
NotFoundError,
|
|
13
|
+
NotYourPhoneError,
|
|
14
|
+
ParentLockedError,
|
|
15
|
+
PhoneError,
|
|
16
|
+
PhoneNotFoundError,
|
|
17
|
+
PhoneStorageFullError,
|
|
18
|
+
PinLockedOutError,
|
|
19
|
+
PinRequiredError,
|
|
20
|
+
PinWrongError,
|
|
21
|
+
TokenError,
|
|
22
|
+
UploadInterruptedError,
|
|
23
|
+
)
|
|
24
|
+
from .link import Status
|
|
25
|
+
from .models import InfoResult, MajorTopic, Resource, Task, Topic, WorkspaceStats
|
|
26
|
+
|
|
27
|
+
__version__ = "0.2.0"
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AsyncKarmaClient",
|
|
30
|
+
"KarmaClient",
|
|
31
|
+
"Status",
|
|
32
|
+
"MajorTopic",
|
|
33
|
+
"Topic",
|
|
34
|
+
"Resource",
|
|
35
|
+
"Task",
|
|
36
|
+
"WorkspaceStats",
|
|
37
|
+
"InfoResult",
|
|
38
|
+
"KarmaError",
|
|
39
|
+
"TokenError",
|
|
40
|
+
"PhoneNotFoundError",
|
|
41
|
+
"NotYourPhoneError",
|
|
42
|
+
"AppLockedError",
|
|
43
|
+
"ConnectionLostError",
|
|
44
|
+
"AppUpdateNeededError",
|
|
45
|
+
"PinRequiredError",
|
|
46
|
+
"PinWrongError",
|
|
47
|
+
"PinLockedOutError",
|
|
48
|
+
"ParentLockedError",
|
|
49
|
+
"NotFoundError",
|
|
50
|
+
"AmbiguousNameError",
|
|
51
|
+
"InvalidInputError",
|
|
52
|
+
"FileProblemError",
|
|
53
|
+
"PhoneStorageFullError",
|
|
54
|
+
"UploadInterruptedError",
|
|
55
|
+
"PhoneError",
|
|
56
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""`python -m karma365 status` and `python -m karma365 speedtest`."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from .client import AsyncKarmaClient
|
|
10
|
+
from .errors import KarmaError
|
|
11
|
+
from .upload import cancel_file, send_file
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
async def status() -> int:
|
|
15
|
+
async with AsyncKarmaClient() as karma:
|
|
16
|
+
result = await karma.status()
|
|
17
|
+
if result.connected:
|
|
18
|
+
print(f"Connected to your phone at {result.phone_url} ({result.latency_ms} ms)")
|
|
19
|
+
return 0
|
|
20
|
+
print(f"Not connected: {result.message}")
|
|
21
|
+
if result.hint:
|
|
22
|
+
print(f"What to do: {result.hint}")
|
|
23
|
+
print(f"Code: {result.reason}")
|
|
24
|
+
return 1
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def speedtest(megabytes: int) -> int:
|
|
28
|
+
"""Sends a throwaway file over the real path and measures it. Wi-Fi, the phone's
|
|
29
|
+
decrypting and its storage all count, so this is the speed you will really get."""
|
|
30
|
+
async with AsyncKarmaClient() as karma:
|
|
31
|
+
minors = await karma.list_minors()
|
|
32
|
+
if not minors:
|
|
33
|
+
print("Create a minor topic first. The test needs one, and leaves nothing in it.")
|
|
34
|
+
return 1
|
|
35
|
+
handle, path = tempfile.mkstemp(suffix=".bin", prefix="karma_speedtest_")
|
|
36
|
+
try:
|
|
37
|
+
with os.fdopen(handle, "wb") as file:
|
|
38
|
+
for _ in range(megabytes):
|
|
39
|
+
file.write(os.urandom(1024 * 1024))
|
|
40
|
+
|
|
41
|
+
def progress(sent: int, total: int) -> None:
|
|
42
|
+
print(f"\r{sent * 100 // total}%", end="", flush=True)
|
|
43
|
+
|
|
44
|
+
started = time.perf_counter()
|
|
45
|
+
resource_id = await send_file(
|
|
46
|
+
karma._link,
|
|
47
|
+
path=path,
|
|
48
|
+
file_type="video",
|
|
49
|
+
topic_id=minors[0].id,
|
|
50
|
+
title="KARMA 365 speed test",
|
|
51
|
+
on_progress=progress,
|
|
52
|
+
)
|
|
53
|
+
seconds = time.perf_counter() - started
|
|
54
|
+
await karma.delete(resource_id)
|
|
55
|
+
print(f"\n{megabytes} MB in {seconds:.1f} s: {megabytes / seconds:.1f} MB/s")
|
|
56
|
+
return 0
|
|
57
|
+
finally:
|
|
58
|
+
os.unlink(path)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main() -> int:
|
|
62
|
+
command = sys.argv[1] if len(sys.argv) > 1 else "status"
|
|
63
|
+
try:
|
|
64
|
+
if command == "status":
|
|
65
|
+
return asyncio.run(status())
|
|
66
|
+
if command == "speedtest":
|
|
67
|
+
return asyncio.run(speedtest(int(sys.argv[2]) if len(sys.argv) > 2 else 100))
|
|
68
|
+
except KarmaError as error:
|
|
69
|
+
print(error)
|
|
70
|
+
return 1
|
|
71
|
+
print("Usage: python -m karma365 status\n python -m karma365 speedtest [MB]")
|
|
72
|
+
return 2
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
sys.exit(main())
|