RemoteRF 0.0.6.14__tar.gz → 0.0.7.2__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.
Files changed (38) hide show
  1. {remoterf-0.0.6.14/src/remoteRF.egg-info → remoterf-0.0.7.2}/PKG-INFO +3 -8
  2. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/README.md +1 -5
  3. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/setup.py +3 -2
  4. remoterf-0.0.7.2/src/remoteRF/core/app.py +543 -0
  5. {remoterf-0.0.6.14 → remoterf-0.0.7.2/src/remoteRF.egg-info}/PKG-INFO +3 -8
  6. remoterf-0.0.6.14/src/remoteRF/core/app.py +0 -270
  7. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/LICENSE +0 -0
  8. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/MANIFEST.in +0 -0
  9. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/setup.cfg +0 -0
  10. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/__init__.py +0 -0
  11. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/__init__.py +0 -0
  12. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/grpc/__init__.py +0 -0
  13. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/grpc/grpc_pb2.py +0 -0
  14. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
  15. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/utils/__init__.py +0 -0
  16. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/utils/ansi_codes.py +0 -0
  17. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/utils/api_token.py +0 -0
  18. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/utils/list_string.py +0 -0
  19. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/common/utils/process_arg.py +0 -0
  20. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/__init__.py +0 -0
  21. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/acc_login.py +0 -0
  22. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/certs/__init__.py +0 -0
  23. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/certs/ca.crt +0 -0
  24. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/certs/ca.key +0 -0
  25. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/certs/cert.pem +0 -0
  26. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/certs/key.pem +0 -0
  27. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/certs/server.crt +0 -0
  28. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/certs/server.key +0 -0
  29. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/grpc_acc.py +0 -0
  30. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/core/grpc_client.py +0 -0
  31. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/drivers/__init__.py +0 -0
  32. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
  33. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
  34. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF.egg-info/SOURCES.txt +0 -0
  35. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF.egg-info/dependency_links.txt +0 -0
  36. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF.egg-info/entry_points.txt +0 -0
  37. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF.egg-info/requires.txt +0 -0
  38. {remoterf-0.0.6.14 → remoterf-0.0.7.2}/src/remoteRF.egg-info/top_level.txt +0 -0
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: remoteRF
3
- Version: 0.0.6.14
3
+ Version: 0.0.7.2
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only!
5
5
  Author: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
7
7
  Classifier: Programming Language :: Python :: 3
8
8
  Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.6
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
12
  Requires-Dist: grpcio
14
13
  Requires-Dist: protobuf
15
14
  Requires-Dist: numpy
@@ -106,11 +105,7 @@ perms # To view your permissions
106
105
  ```bash
107
106
  resdev # To reserve a device
108
107
 
109
- Enter the device ID you would like to reserve: 0
110
- Reserve Start Time (YYYY-MM-DD HH:MM): 2024-11-20 10:20
111
- Reserve End Time (YYYY-MM-DD HH:MM): 2024-11-20 11:20
112
-
113
- # Time is military time. You can also use arrow keys to cycle between previous commands.
108
+ # Input the number of days you want to view, and it will display available reservations in that time span.
114
109
 
115
110
  Reservation successful. Thy Token -> example_token
116
111
 
@@ -88,11 +88,7 @@ perms # To view your permissions
88
88
  ```bash
89
89
  resdev # To reserve a device
90
90
 
91
- Enter the device ID you would like to reserve: 0
92
- Reserve Start Time (YYYY-MM-DD HH:MM): 2024-11-20 10:20
93
- Reserve End Time (YYYY-MM-DD HH:MM): 2024-11-20 11:20
94
-
95
- # Time is military time. You can also use arrow keys to cycle between previous commands.
91
+ # Input the number of days you want to view, and it will display available reservations in that time span.
96
92
 
97
93
  Reservation successful. Thy Token -> example_token
98
94
 
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
6
6
 
7
7
  setup(
8
8
  name="remoteRF",
9
- version="0.0.6.14",
9
+ version="0.0.7.2",
10
10
  author="Ethan Ge",
11
11
  author_email="ethoGalaxy@gmail.com",
12
12
  description="A python API to remotely access signal centric hardware. Client-side only!",
@@ -14,6 +14,7 @@ setup(
14
14
  long_description_content_type="text/markdown", # Specify that it's Markdown
15
15
  packages=find_packages(where="src"), # Automatically finds subpackages like core, deviceA, deviceB
16
16
  package_dir={"": "src"},
17
+ license_file='MIT',
17
18
  include_package_data=True, # Includes files specified in MANIFEST.in
18
19
  install_requires=[
19
20
  "grpcio", "protobuf", "numpy", "prompt_toolkit", "python-dotenv"
@@ -23,7 +24,7 @@ setup(
23
24
  "License :: OSI Approved :: MIT License",
24
25
  "Operating System :: OS Independent",
25
26
  ],
26
- python_requires='>=3.6',
27
+ python_requires='>=3.10',
27
28
  entry_points={
28
29
  'console_scripts': [
29
30
  'remoterf-login=remoteRF.core.acc_login:main',
@@ -0,0 +1,543 @@
1
+ from . import *
2
+ from ..common.utils import *
3
+
4
+ import getpass
5
+ import os
6
+ import datetime
7
+ import time
8
+ import ast
9
+
10
+ from prompt_toolkit import PromptSession
11
+
12
+ account = RemoteRFAccount()
13
+ session = PromptSession()
14
+
15
+ def welcome():
16
+ printf("Welcome to Remote RF Account System.", (Sty.BOLD, Sty.BLUE))
17
+ try:
18
+ inpu = session.prompt(stylize("Please ", Sty.DEFAULT, "login", Sty.GREEN, " or ", Sty.DEFAULT, "register", Sty.RED, " to continue. (", Sty.DEFAULT, 'l', Sty.GREEN, "/", Sty.DEFAULT, 'r', Sty.RED, "):", Sty.DEFAULT))
19
+ if inpu == 'r':
20
+ print("Registering new account ...")
21
+ account.username = input("Username: ")
22
+ double_check = True
23
+ while double_check:
24
+ password = getpass.getpass("Password (Hidden): ")
25
+ password2 = getpass.getpass("Confirm Password: ")
26
+ if password == password2:
27
+ double_check = False
28
+ else:
29
+ print("Passwords do not match. Try again")
30
+
31
+ account.password = password
32
+ account.email = input("Email: ") # TODO: Email verification.
33
+ # check if login was valid
34
+ os.system('cls' if os.name == 'nt' else 'clear')
35
+
36
+ if not account.create_user():
37
+ welcome()
38
+ else:
39
+ account.username = input("Username: ")
40
+ account.password = getpass.getpass("Password (Hidden): ")
41
+ # check if login was valid
42
+ if not account.login_user():
43
+ os.system('cls' if os.name == 'nt' else 'clear')
44
+ print("Invalid login. Try again. Contact admin(s) if you forgot your password.")
45
+ welcome()
46
+ except KeyboardInterrupt:
47
+ exit()
48
+ except EOFError:
49
+ exit()
50
+
51
+ def title():
52
+ printf(f"Remote RF Account System", Sty.BOLD)
53
+ # printf(f"Logged in as: ", Sty.DEFAULT, f'{account.username}', Sty.MAGENTA)
54
+ printf(f"Input ", Sty.DEFAULT, "'help' ", Sty.BRIGHT_GREEN, "for avaliable commands.", Sty.DEFAULT)
55
+
56
+ def commands():
57
+ printf("Commands:", Sty.BOLD)
58
+ printf("'clear' ", Sty.MAGENTA, "- Clear Terminal", Sty.DEFAULT)
59
+ printf("'getdev' ", Sty.MAGENTA, "- View Devices", Sty.DEFAULT)
60
+ printf("'help' ", Sty.MAGENTA, "- Show this help message", Sty.DEFAULT)
61
+ printf("'perms' ", Sty.MAGENTA, "- View Permissions", Sty.DEFAULT)
62
+ printf("'exit' ", Sty.MAGENTA, "- Exit", Sty.DEFAULT)
63
+ printf("'getres' ", Sty.MAGENTA, "- View All Reservations", Sty.DEFAULT)
64
+ printf("'myres' ", Sty.MAGENTA, "- View My Reservations", Sty.DEFAULT)
65
+ printf("'cancelres' ", Sty.MAGENTA, "- Cancel a Reservation", Sty.DEFAULT)
66
+ printf("'resdev' ", Sty.MAGENTA, "- Reserve a Device", Sty.DEFAULT)
67
+ printf("'resdev s' ", Sty.MAGENTA, "- Reserve a Device (by single date)", Sty.DEFAULT)
68
+ # check if user is admin
69
+ if account.get_perms().results['UC'] == 'Admin':
70
+ printf("'naiveresdev' ", Sty.MAGENTA, "- Old implementation of reservations", Sty.DEFAULT)
71
+
72
+ def clear():
73
+ os.system('cls' if os.name == 'nt' else 'clear')
74
+ title()
75
+
76
+ def reservations():
77
+ data = account.get_reservations()
78
+ if 'ace' in data.results:
79
+ print(f"Error: {unmap_arg(data.results['ace'])}")
80
+ return
81
+ entries = []
82
+
83
+ for key, value in data.results.items():
84
+ parts = unmap_arg(value).split(',')
85
+ # Create a dictionary for each entry with named fields
86
+ entry = {
87
+ 'username': parts[0],
88
+ 'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
89
+ 'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
90
+ 'end_time': parts[3]
91
+ }
92
+ entries.append(entry)
93
+
94
+ if (entries == []):
95
+ printf("No reservations found.", Sty.BOLD)
96
+ return
97
+
98
+ printf("Reservations:", Sty.BOLD)
99
+
100
+ # Sort the entries by device_id and then by start_time
101
+ sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time']))
102
+
103
+ # Format the sorted entries into strings
104
+ for entry in sorted_entries:
105
+ printf(f'Device ID: ', Sty.RED, f'{entry["device_id"]}', Sty.MAGENTA, f', Start Time: ', Sty.RED, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f', End Time: ', Sty.RED, f'{entry["end_time"]}', Sty.BLUE)
106
+
107
+ def my_reservations():
108
+ data = account.get_reservations()
109
+ if 'ace' in data.results:
110
+ print(f"Error: {unmap_arg(data.results['ace'])}")
111
+ return
112
+ entries = []
113
+
114
+ for key, value in data.results.items():
115
+ parts = unmap_arg(value).split(',')
116
+ # Create a dictionary for each entry with named fields
117
+ entry = {
118
+ 'username': parts[0],
119
+ 'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
120
+ 'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
121
+ 'end_time': parts[3]
122
+ }
123
+ entries.append(entry)
124
+
125
+ if (entries == []):
126
+ printf("No reservations found.", Sty.BOLD)
127
+ return
128
+
129
+ printf("Reservations under: ", Sty.BOLD, f'{account.username}', Sty.MAGENTA)
130
+
131
+ # Sort the entries by device_id and then by start_time
132
+ sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time']))
133
+
134
+ for entry in sorted_entries:
135
+ if account.username == entry['username']:
136
+ printf(f'Device ID: ', Sty.RED, f'{entry["device_id"]}', Sty.MAGENTA, f', Start Time: ', Sty.RED, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f', End Time: ', Sty.RED, f'{entry["end_time"]}', Sty.BLUE)
137
+
138
+ def cancel_my_reservation():
139
+ ## print all of ur reservations and their ids
140
+ ## ask for id to cancel
141
+ ## remove said reservation
142
+ data = account.get_reservations()
143
+ if 'ace' in data.results:
144
+ print(f"Error: {unmap_arg(data.results['ace'])}")
145
+ return
146
+
147
+ entries:list = []
148
+
149
+ for key, value in data.results.items():
150
+ parts = unmap_arg(value).split(',')
151
+ # Create a dictionary for each entry with named fields
152
+ entry = {
153
+ 'id': -1,
154
+ 'internal_id': key,
155
+ 'username': parts[0],
156
+ 'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
157
+ 'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
158
+ 'end_time': parts[3]
159
+ }
160
+ if account.username == entry['username']:
161
+ entries.append(entry)
162
+
163
+ printf("Current Reservation(s) under ", Sty.BOLD, f'{account.username}:', Sty.MAGENTA)
164
+
165
+ sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time'])) # sort by device_id and start_time
166
+ for i, entry in enumerate(sorted_entries): # label all reservations with unique id
167
+ entry['id'] = i
168
+ printf(f'Reservation ID: ', Sty.GRAY, f'{i}', Sty.MAGENTA, f' Device ID: ', Sty.GRAY, f'{entry["device_id"]}', Sty.BRIGHT_GREEN, f' Start Time: ', Sty.GRAY, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f' End Time: ', Sty.GRAY, f'{entry["end_time"]}', Sty.BLUE)
169
+ # print(f"Reservation ID {i}, Device ID: {entry['device_id']}, Start Time: {entry['start_time'].strftime('%Y-%m-%d %H:%M:%S')}, End Time: {entry['end_time']}")
170
+
171
+ if sorted_entries == []:
172
+ printf("No reservations found.", Sty.BOLD)
173
+ return
174
+
175
+ inpu = session.prompt(stylize("Enter the ID of the reservation you would like to cancel ", Sty.BOLD, '(abort with any non number key input)', Sty.RED, ': ', Sty.BOLD))
176
+
177
+ if inpu.isdigit():
178
+ id = int(inpu)
179
+ if id >= len(sorted_entries):
180
+ print("Invalid ID.")
181
+ return
182
+
183
+ # grab the reservation
184
+ for entry in sorted_entries:
185
+ if entry['id'] == id:
186
+ db_id = entry['internal_id']
187
+ if session.prompt(stylize(f'Cancel reservation ID ', Sty.DEFAULT, f'{id}', Sty.MAGENTA, f' Device ID: ', Sty.DEFAULT, f'{entry["device_id"]}', Sty.BRIGHT_GREEN, f' Start Time: ', Sty.GRAY, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f' End Time: ', Sty.DEFAULT, f'{entry["end_time"]}', Sty.BLUE, f' ? (y/n):', Sty.DEFAULT)) == 'y':
188
+ response = account.cancel_reservation(db_id)
189
+ if 'ace' in response.results:
190
+ print(f"Error: {unmap_arg(response.results['ace'])}")
191
+ elif 'UC' in response.results:
192
+ printf(f"Reservation ID ", Sty.DEFAULT, f'{id}', Sty.BRIGHT_BLUE, ' successfully canceled.', Sty.DEFAULT)
193
+ else:
194
+ print("Aborting. User canceled action.")
195
+ return
196
+
197
+ print(f"Error: No reservation found with ID {id}.")
198
+ else:
199
+ print("Aborting. A non integer key was given.")
200
+
201
+ def devices():
202
+ data = account.get_devices()
203
+ if 'ace' in data.results:
204
+ print(f"Error: {unmap_arg(data.results['ace'])}")
205
+ return
206
+ printf("Devices:", Sty.BOLD)
207
+
208
+ for key in sorted(data.results, key=int):
209
+ printf(f"Device ID:", Sty.DEFAULT, f' {key}', Sty.MAGENTA, f" Device Name: ", Sty.DEFAULT, f"{unmap_arg(data.results[key])}", Sty.GRAY)
210
+
211
+ def get_datetime(question:str):
212
+ timestamp = session.prompt(stylize(f'{question}', Sty.DEFAULT, ' (YYYY-MM-DD HH:MM): ', Sty.GRAY))
213
+ return datetime.datetime.strptime(timestamp + ':00', '%Y-%m-%d %H:%M:%S')
214
+
215
+ def reserve():
216
+ try:
217
+ id = session.prompt(stylize("Enter the device ID you would like to reserve: ", Sty.DEFAULT))
218
+ token = account.reserve_device(int(id), get_datetime("Reserve Start Time"), get_datetime("Reserve End Time"))
219
+ if token != '':
220
+ printf(f"Reservation successful. Thy Token -> ", Sty.BOLD, f"{token}", Sty.BG_GREEN)
221
+ printf(f"Please keep this token safe, as it is not saved on server side, and cannot be regenerated/reretrieved. ", Sty.DEFAULT)
222
+ except Exception as e:
223
+ printf(f"Error: {e}", Sty.BRIGHT_RED)
224
+
225
+ def perms():
226
+ data = account.get_perms()
227
+ if 'ace' in data.results:
228
+ print(f"Error: {unmap_arg(data.results['ace'])}")
229
+ return
230
+
231
+ results = ast.literal_eval(unmap_arg(data.results['UC']))[0]
232
+ printf(f'Permission Level: ', Sty.BOLD, f'{results[0]}', Sty.BLUE)
233
+ if results[0] == 'Normal User':
234
+ print(unmap_arg(data.results['details']))
235
+ elif results[0] == 'Power User':
236
+ printf(f'Max Reservations: ', Sty.DEFAULT, f'{results[3]}', Sty.MAGENTA)
237
+ printf(f'Max Reservation Duration (min): ', Sty.DEFAULT, f'{int(results[4]/60)}', Sty.MAGENTA)
238
+ printf(f'Device IDs allowed Access to: ', Sty.DEFAULT, f'{results[5]}', Sty.MAGENTA)
239
+ elif results[0] == 'Admin':
240
+ printf(f'No restrictions on reservation count or duration.', Sty.DEFAULT)
241
+ else:
242
+ printf(f"Error: Unknown permission level {results[0]}", Sty.BRIGHT_RED)
243
+
244
+ # New block scheduling
245
+
246
+ def fetch_all_devices():
247
+ response = rpc_client(
248
+ function_name='ACC:get_dev',
249
+ args={
250
+ "un": map_arg(account.username),
251
+ "pw": map_arg(account.password)
252
+ }
253
+ )
254
+ devices = []
255
+ # Sort keys using numeric conversion when possible.
256
+ for key in sorted(response.results.keys(), key=lambda k: int(k) if k.isdigit() else k):
257
+ devices.append(key)
258
+ return devices
259
+
260
+ def fetch_all_reservations():
261
+ data = account.get_reservations()
262
+ if 'ace' in data.results:
263
+ print(f"Error: {unmap_arg(data.results['ace'])}")
264
+ return []
265
+ entries = []
266
+
267
+ for key, value in data.results.items():
268
+ parts = unmap_arg(value).split(',')
269
+ # Convert both start and end times to datetime objects.
270
+ entry = {
271
+ 'username': parts[0],
272
+ 'device_id': int(parts[1]), # Stored as an int
273
+ 'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'),
274
+ 'end_time': datetime.datetime.strptime(parts[3], '%Y-%m-%d %H:%M:%S')
275
+ }
276
+ entries.append(entry)
277
+ return entries
278
+
279
+ def fetch_device_reservations_by_date(device_id: str, date: datetime.date):
280
+ """
281
+ Filter all reservations to those for a specific device (by ID, as a string)
282
+ on the specified date.
283
+ """
284
+ all_res = fetch_all_reservations()
285
+ device_reservations = []
286
+ for res in all_res:
287
+ # Convert the stored device_id (an int) to a string for comparison.
288
+ if str(res['device_id']) == device_id and res['start_time'].date() == date:
289
+ device_reservations.append((res['start_time'], res['end_time']))
290
+
291
+ return device_reservations
292
+
293
+ def fetch_reservations_for_range(start_day: datetime.date, end_day: datetime.date):
294
+ """
295
+ Fetch all reservations (via fetch_all_reservations) and filter those whose start_time date falls between start_day and end_day (inclusive).
296
+ Returns a dictionary keyed by (device_id, day) (device_id as string, day as datetime.date) with a list of reservation tuples.
297
+ """
298
+ all_res = fetch_all_reservations() # This calls the network only once.
299
+ res_dict = {}
300
+ for res in all_res:
301
+ res_day = res['start_time'].date()
302
+ if start_day <= res_day <= end_day:
303
+ key = (str(res['device_id']), res_day)
304
+ res_dict.setdefault(key, []).append((res['start_time'], res['end_time']))
305
+ return res_dict
306
+
307
+ def build_hourly_slots(date: datetime.date, start_hour: int = 0, end_hour: int = 24):
308
+ """Generate 1-hour time slots for the given date."""
309
+ slots = []
310
+ for hour in range(start_hour, end_hour):
311
+ slot_start = datetime.datetime.combine(date, datetime.time(hour, 0))
312
+ slot_end = slot_start + datetime.timedelta(hours=1)
313
+ slots.append((slot_start, slot_end))
314
+ return slots
315
+
316
+ def is_slot_conflicting(slot: tuple, reservations: list):
317
+ """Return True if the slot overlaps with any reservation in the provided list."""
318
+ slot_start, slot_end = slot
319
+ for res_start, res_end in reservations:
320
+ if slot_start < res_end and slot_end > res_start:
321
+ return True
322
+ return False
323
+
324
+ def display_free_slots_all(date: datetime.date):
325
+ """
326
+ Display available 1-hour slots aggregated across all devices on a given date.
327
+ A slot is available if at least one device is free.
328
+ Omits any slots whose end time is in the past.
329
+ Returns a tuple (chosen_slot, chosen_device) where chosen_slot is (start_time, end_time)
330
+ and chosen_device is the ID (converted to int if possible) of an available device,
331
+ or (None, None) if no valid selection.
332
+ """
333
+ devices = fetch_all_devices()
334
+ # Build a mapping: device id -> its reservations for the specified date.
335
+ device_reservations = {}
336
+ for dev in devices:
337
+ device_reservations[dev] = fetch_device_reservations_by_date(dev, date)
338
+
339
+ all_slots = build_hourly_slots(date)
340
+ now = datetime.datetime.now()
341
+ available_slots = {} # key: slot tuple, value: list of available device IDs
342
+ for slot in all_slots:
343
+ if slot[1] <= now:
344
+ continue
345
+ free_devices = []
346
+ for dev in devices:
347
+ if not is_slot_conflicting(slot, device_reservations[dev]):
348
+ free_devices.append(dev)
349
+ if free_devices:
350
+ available_slots[slot] = free_devices
351
+
352
+ if not available_slots:
353
+ print("No available time slots for any device on that day.")
354
+ return None, None
355
+
356
+ print("Available time slots (aggregated across devices):")
357
+ sorted_slots = sorted(available_slots.keys())
358
+ for idx, slot in enumerate(sorted_slots):
359
+ start_str = slot[0].strftime('%I:%M %p')
360
+ end_str = slot[1].strftime('%I:%M %p')
361
+ num_available = len(available_slots[slot])
362
+ print(f"{idx + 1}: {start_str} - {end_str} (Devices available: {num_available})")
363
+
364
+ try:
365
+ selection = int(input("Select a slot by number: "))
366
+ if selection < 1 or selection > len(sorted_slots):
367
+ print("Invalid selection.")
368
+ return None, None
369
+ except ValueError:
370
+ print("Invalid input. Please enter a number.")
371
+ return None, None
372
+
373
+ chosen_slot = sorted_slots[selection - 1]
374
+ # Automatically choose one device.
375
+ candidate = sorted(available_slots[chosen_slot])[0]
376
+ try:
377
+ chosen_device = int(candidate)
378
+ except ValueError:
379
+ chosen_device = candidate
380
+ return chosen_slot, chosen_device
381
+
382
+ def interactive_reserve_all():
383
+ """
384
+ Interactive function that prompts for a reservation date,
385
+ displays aggregated free 1-hour slots (with an accurate count of available devices),
386
+ and reserves the chosen slot on one available device.
387
+ """
388
+ try:
389
+ date_input = input("Enter the date for reservation (YYYY-MM-DD): ")
390
+ reservation_date = datetime.datetime.strptime(date_input, '%Y-%m-%d').date()
391
+
392
+ chosen_slot, chosen_device = display_free_slots_all(reservation_date)
393
+ if chosen_slot is None:
394
+ return
395
+
396
+ token = account.reserve_device(chosen_device, chosen_slot[0], chosen_slot[1])
397
+ if token != '':
398
+ print(f"Reservation successful on device {chosen_device}. Thy Token -> {token}")
399
+ print("Please keep this token safe, as it is not saved on server side, and cannot be regenerated/reretrieved.")
400
+ except Exception as e:
401
+ print(f"Error: {e}")
402
+
403
+ def display_free_slots_next_days(num_days: int):
404
+ """
405
+ Display available 1-hour slots aggregated across all devices for the next num_days (starting today).
406
+ A slot is available if at least one device is free (i.e. not reserved during that slot).
407
+ Omits any slots whose end time is in the past (for today).
408
+ Returns a tuple (chosen_day, chosen_slot, chosen_device) where:
409
+ - chosen_day is a datetime.date for the reservation day,
410
+ - chosen_slot is a tuple (start_time, end_time), and
411
+ - chosen_device is one available device for that slot.
412
+ If no slot is available, returns (None, None, None).
413
+ """
414
+ today = datetime.date.today()
415
+ devices = fetch_all_devices()
416
+ now = datetime.datetime.now()
417
+
418
+ # Compute the end day (inclusive)
419
+ end_day = today + datetime.timedelta(days=num_days - 1)
420
+ # Fetch all reservations for the entire range (one network call)
421
+ reservations_range = fetch_reservations_for_range(today, end_day)
422
+
423
+ available_slots = [] # List of tuples: (day, slot, free_devices)
424
+
425
+ for i in range(num_days):
426
+ day = today + datetime.timedelta(days=i)
427
+ all_slots = build_hourly_slots(day)
428
+ for slot in all_slots:
429
+ # If the day is today, skip slots that have already ended.
430
+ if day == today and slot[1] <= now:
431
+ continue
432
+ free_devices = []
433
+ for dev in devices:
434
+ # Look up reservations for this device on this day.
435
+ key = (dev, day)
436
+ current_res = reservations_range.get(key, [])
437
+ if not is_slot_conflicting(slot, current_res):
438
+ free_devices.append(dev)
439
+ if free_devices:
440
+ available_slots.append((day, slot, free_devices))
441
+
442
+ if not available_slots:
443
+ print(f"No available time slots for any device in the next {num_days} days.")
444
+ return None, None, None
445
+
446
+ # Sort by day and slot start time.
447
+ available_slots.sort(key=lambda x: (x[0], x[1][0]))
448
+
449
+ print(f"Available time slots for the next {num_days} days:")
450
+ last_day = None
451
+ for idx, (day, slot, free_devices) in enumerate(available_slots):
452
+ # When a new day starts, print a header line with the date and formatted day.
453
+ if last_day is None or day != last_day:
454
+ # Format: YYYY-MM-DD (Abbreviated Weekday) AbbrevMonth. day
455
+ # Example: "2025-02-04 (Tue) Feb. 4"
456
+ day_header = f"{day.strftime('%Y-%m-%d')} ({day.strftime('%a')}) {day.strftime('%b')}. {day.day}"
457
+ print("\n" + day_header)
458
+ last_day = day
459
+ start_str = slot[0].strftime('%I:%M %p')
460
+ end_str = slot[1].strftime('%I:%M %p')
461
+ print(f" {idx+1}: {start_str} - {end_str} (Devices available: {len(free_devices)})")
462
+
463
+ try:
464
+ selection = int(input("Select a slot by number: "))
465
+ if selection < 1 or selection > len(available_slots):
466
+ print("Invalid selection.")
467
+ return None, None, None
468
+ except ValueError:
469
+ print("Invalid input. Please enter a number.")
470
+ return None, None, None
471
+
472
+ chosen_day, chosen_slot, free_devices_for_slot = available_slots[selection - 1]
473
+ candidate = sorted(free_devices_for_slot)[0]
474
+ try:
475
+ chosen_device = int(candidate)
476
+ except ValueError:
477
+ chosen_device = candidate
478
+ return chosen_day, chosen_slot, chosen_device
479
+
480
+
481
+ def interactive_reserve_next_days():
482
+ """
483
+ Interactive function that prompts the user for the number of days (starting today) to check for available reservations.
484
+ It displays aggregated free 1-hour slots over that period and reserves the chosen slot on one available device,
485
+ after confirming with the user.
486
+ """
487
+ try:
488
+ num_days = int(input("Enter the number of days to check for available reservations (starting today): "))
489
+ chosen_day, chosen_slot, chosen_device = display_free_slots_next_days(num_days)
490
+ if chosen_slot is None:
491
+ return
492
+
493
+ start_time = chosen_slot[0].strftime('%I:%M %p')
494
+ end_time = chosen_slot[1].strftime('%I:%M %p')
495
+ confirmation = input(f"You have selected a reservation on {chosen_day.strftime('%Y-%m-%d')} from {start_time} to {end_time} on device {chosen_device}. Confirm reservation? (y/n): ").strip().lower()
496
+ if confirmation != 'y':
497
+ print("Reservation cancelled.")
498
+ return
499
+
500
+ token = account.reserve_device(chosen_device, chosen_slot[0], chosen_slot[1])
501
+ if token != '':
502
+ print(f"Reservation successful on device {chosen_device} for {chosen_day.strftime('%Y-%m-%d')}. Thy Token -> {token}")
503
+ print("Please keep this token safe, as it is not saved on server side, and cannot be regenerated/reretrieved.")
504
+ except Exception as e:
505
+ print(f"Error: {e}")
506
+
507
+
508
+ welcome()
509
+ clear()
510
+
511
+ while True:
512
+ try:
513
+ inpu = session.prompt(stylize(f'{account.username}@remote_rf: ', Sty.BLUE))
514
+ if inpu == "clear":
515
+ clear()
516
+ elif inpu == "getdev":
517
+ devices()
518
+ elif inpu == "help" or inpu == "h":
519
+ commands()
520
+ elif inpu == "perms":
521
+ perms()
522
+ elif inpu == "quit" or inpu == "exit":
523
+ break
524
+ elif inpu == "getres":
525
+ reservations()
526
+ elif inpu == "myres":
527
+ my_reservations()
528
+ elif inpu == "resdev s":
529
+ interactive_reserve_all()
530
+ elif inpu == "resdev":
531
+ interactive_reserve_next_days()
532
+ elif inpu == 'cancelres':
533
+ cancel_my_reservation()
534
+ elif inpu == 'naiveresdev':
535
+ # check if user is admin
536
+ if account.get_perms().results['UC'] == 'Admin':
537
+ reserve()
538
+ else:
539
+ print(f"Unknown command: {inpu}")
540
+ except KeyboardInterrupt:
541
+ break
542
+ except EOFError:
543
+ break
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: remoteRF
3
- Version: 0.0.6.14
3
+ Version: 0.0.7.2
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only!
5
5
  Author: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
7
7
  Classifier: Programming Language :: Python :: 3
8
8
  Classifier: License :: OSI Approved :: MIT License
9
9
  Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.6
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
12
  Requires-Dist: grpcio
14
13
  Requires-Dist: protobuf
15
14
  Requires-Dist: numpy
@@ -106,11 +105,7 @@ perms # To view your permissions
106
105
  ```bash
107
106
  resdev # To reserve a device
108
107
 
109
- Enter the device ID you would like to reserve: 0
110
- Reserve Start Time (YYYY-MM-DD HH:MM): 2024-11-20 10:20
111
- Reserve End Time (YYYY-MM-DD HH:MM): 2024-11-20 11:20
112
-
113
- # Time is military time. You can also use arrow keys to cycle between previous commands.
108
+ # Input the number of days you want to view, and it will display available reservations in that time span.
114
109
 
115
110
  Reservation successful. Thy Token -> example_token
116
111
 
@@ -1,270 +0,0 @@
1
- from . import *
2
- from ..common.utils import *
3
-
4
- import getpass
5
- import os
6
- import datetime
7
- import time
8
- import ast
9
-
10
- from prompt_toolkit import PromptSession
11
-
12
- account = RemoteRFAccount()
13
- session = PromptSession()
14
-
15
- def welcome():
16
- printf("Welcome to Remote RF Account System.", (Sty.BOLD, Sty.BLUE))
17
- try:
18
- inpu = session.prompt(stylize("Please ", Sty.DEFAULT, "login", Sty.GREEN, " or ", Sty.DEFAULT, "register", Sty.RED, " to continue. (", Sty.DEFAULT, 'l', Sty.GREEN, "/", Sty.DEFAULT, 'r', Sty.RED, "):", Sty.DEFAULT))
19
- if inpu == 'r':
20
- print("Registering new account ...")
21
- account.username = input("Username: ")
22
- double_check = True
23
- while double_check:
24
- password = getpass.getpass("Password (Hidden): ")
25
- password2 = getpass.getpass("Confirm Password: ")
26
- if password == password2:
27
- double_check = False
28
- else:
29
- print("Passwords do not match. Try again")
30
-
31
- account.password = password
32
- account.email = input("Email: ") # TODO: Email verification.
33
- # check if login was valid
34
- os.system('cls' if os.name == 'nt' else 'clear')
35
-
36
- if not account.create_user():
37
- welcome()
38
- else:
39
- account.username = input("Username: ")
40
- account.password = getpass.getpass("Password (Hidden): ")
41
- # check if login was valid
42
- if not account.login_user():
43
- os.system('cls' if os.name == 'nt' else 'clear')
44
- print("Invalid login. Try again.")
45
- welcome()
46
- except KeyboardInterrupt:
47
- exit()
48
- except EOFError:
49
- exit()
50
-
51
- def title():
52
- printf(f"Remote RF Account System", Sty.BOLD)
53
- # printf(f"Logged in as: ", Sty.DEFAULT, f'{account.username}', Sty.MAGENTA)
54
- printf(f"Input ", Sty.DEFAULT, "'help' ", Sty.BRIGHT_GREEN, "for avaliable commands.", Sty.DEFAULT)
55
-
56
- def commands():
57
- printf("Commands:", Sty.BOLD)
58
- printf("'clear' ", Sty.MAGENTA, "- Clear Terminal", Sty.DEFAULT)
59
- printf("'getdev' ", Sty.MAGENTA, "- View Devices", Sty.DEFAULT)
60
- printf("'help' ", Sty.MAGENTA, "- Show this help message", Sty.DEFAULT)
61
- printf("'perms' ", Sty.MAGENTA, "- View Permissions", Sty.DEFAULT)
62
- printf("'exit' ", Sty.MAGENTA, "- Exit", Sty.DEFAULT)
63
- printf("'getres' ", Sty.MAGENTA, "- View All Reservations", Sty.DEFAULT)
64
- printf("'myres' ", Sty.MAGENTA, "- View My Reservations", Sty.DEFAULT)
65
- printf("'cancelres' ", Sty.MAGENTA, "- Cancel a Reservation", Sty.DEFAULT)
66
- printf("'resdev' ", Sty.MAGENTA, "- Reserve a Device", Sty.DEFAULT)
67
-
68
- def clear():
69
- os.system('cls' if os.name == 'nt' else 'clear')
70
- title()
71
-
72
- def reservations():
73
- data = account.get_reservations()
74
- if 'ace' in data.results:
75
- print(f"Error: {unmap_arg(data.results['ace'])}")
76
- return
77
- entries = []
78
-
79
- for key, value in data.results.items():
80
- parts = unmap_arg(value).split(',')
81
- # Create a dictionary for each entry with named fields
82
- entry = {
83
- 'username': parts[0],
84
- 'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
85
- 'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
86
- 'end_time': parts[3]
87
- }
88
- entries.append(entry)
89
-
90
- if (entries == []):
91
- printf("No reservations found.", Sty.BOLD)
92
- return
93
-
94
- printf("Reservations:", Sty.BOLD)
95
-
96
- # Sort the entries by device_id and then by start_time
97
- sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time']))
98
-
99
- # Format the sorted entries into strings
100
- for entry in sorted_entries:
101
- printf(f'Device ID: ', Sty.RED, f'{entry["device_id"]}', Sty.MAGENTA, f', Start Time: ', Sty.RED, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f', End Time: ', Sty.RED, f'{entry["end_time"]}', Sty.BLUE)
102
-
103
- def my_reservations():
104
- data = account.get_reservations()
105
- if 'ace' in data.results:
106
- print(f"Error: {unmap_arg(data.results['ace'])}")
107
- return
108
- entries = []
109
-
110
- for key, value in data.results.items():
111
- parts = unmap_arg(value).split(',')
112
- # Create a dictionary for each entry with named fields
113
- entry = {
114
- 'username': parts[0],
115
- 'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
116
- 'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
117
- 'end_time': parts[3]
118
- }
119
- entries.append(entry)
120
-
121
- if (entries == []):
122
- printf("No reservations found.", Sty.BOLD)
123
- return
124
-
125
- printf("Reservations under: ", Sty.BOLD, f'{account.username}', Sty.MAGENTA)
126
-
127
- # Sort the entries by device_id and then by start_time
128
- sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time']))
129
-
130
- for entry in sorted_entries:
131
- if account.username == entry['username']:
132
- printf(f'Device ID: ', Sty.RED, f'{entry["device_id"]}', Sty.MAGENTA, f', Start Time: ', Sty.RED, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f', End Time: ', Sty.RED, f'{entry["end_time"]}', Sty.BLUE)
133
-
134
- def cancel_my_reservation():
135
- ## print all of ur reservations and their ids
136
- ## ask for id to cancel
137
- ## remove said reservation
138
- data = account.get_reservations()
139
- if 'ace' in data.results:
140
- print(f"Error: {unmap_arg(data.results['ace'])}")
141
- return
142
-
143
- entries:list = []
144
-
145
- for key, value in data.results.items():
146
- parts = unmap_arg(value).split(',')
147
- # Create a dictionary for each entry with named fields
148
- entry = {
149
- 'id': -1,
150
- 'internal_id': key,
151
- 'username': parts[0],
152
- 'device_id': int(parts[1]), # Convert device_id to integer for proper numerical sorting
153
- 'start_time': datetime.datetime.strptime(parts[2], '%Y-%m-%d %H:%M:%S'), # Convert start_time to datetime
154
- 'end_time': parts[3]
155
- }
156
- if account.username == entry['username']:
157
- entries.append(entry)
158
-
159
- printf("Current Reservation(s) under ", Sty.BOLD, f'{account.username}:', Sty.MAGENTA)
160
-
161
- sorted_entries = sorted(entries, key=lambda x: (x['device_id'], x['start_time'])) # sort by device_id and start_time
162
- for i, entry in enumerate(sorted_entries): # label all reservations with unique id
163
- entry['id'] = i
164
- printf(f'Reservation ID: ', Sty.GRAY, f'{i}', Sty.MAGENTA, f' Device ID: ', Sty.GRAY, f'{entry["device_id"]}', Sty.BRIGHT_GREEN, f' Start Time: ', Sty.GRAY, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f' End Time: ', Sty.GRAY, f'{entry["end_time"]}', Sty.BLUE)
165
- # print(f"Reservation ID {i}, Device ID: {entry['device_id']}, Start Time: {entry['start_time'].strftime('%Y-%m-%d %H:%M:%S')}, End Time: {entry['end_time']}")
166
-
167
- if sorted_entries == []:
168
- printf("No reservations found.", Sty.BOLD)
169
- return
170
-
171
- inpu = session.prompt(stylize("Enter the ID of the reservation you would like to cancel ", Sty.BOLD, '(abort with any non number key input)', Sty.RED, ': ', Sty.BOLD))
172
-
173
- if inpu.isdigit():
174
- id = int(inpu)
175
- if id >= len(sorted_entries):
176
- print("Invalid ID.")
177
- return
178
-
179
- #grab the reservation
180
- for entry in sorted_entries:
181
- if entry['id'] == id:
182
- db_id = entry['internal_id']
183
- if session.prompt(stylize(f'Cancel reservation ID ', Sty.DEFAULT, f'{id}', Sty.MAGENTA, f' Device ID: ', Sty.DEFAULT, f'{entry["device_id"]}', Sty.BRIGHT_GREEN, f' Start Time: ', Sty.GRAY, f'{entry["start_time"].strftime("%Y-%m-%d %H:%M:%S")}', Sty.BLUE, f' End Time: ', Sty.DEFAULT, f'{entry["end_time"]}', Sty.BLUE, f' ? (y/n):', Sty.DEFAULT)) == 'y':
184
- response = account.cancel_reservation(db_id)
185
- if 'ace' in response.results:
186
- print(f"Error: {unmap_arg(response.results['ace'])}")
187
- elif 'UC' in response.results:
188
- printf(f"Reservation ID ", Sty.DEFAULT, f'{id}', Sty.BRIGHT_BLUE, ' successfully canceled.', Sty.DEFAULT)
189
- else:
190
- print("Aborting. User canceled action.")
191
- return
192
-
193
- print(f"Error: No reservation found with ID {id}.")
194
- else:
195
- print("Aborting. A non integer key was given.")
196
-
197
- def devices():
198
- data = account.get_devices()
199
- if 'ace' in data.results:
200
- print(f"Error: {unmap_arg(data.results['ace'])}")
201
- return
202
- printf("Devices:", Sty.BOLD)
203
-
204
- for key in sorted(data.results, key=int):
205
- printf(f"Device ID:", Sty.DEFAULT, f' {key}', Sty.MAGENTA, f" Device Name: ", Sty.DEFAULT, f"{unmap_arg(data.results[key])}", Sty.GRAY)
206
-
207
- def get_datetime(question:str):
208
- timestamp = session.prompt(stylize(f'{question}', Sty.DEFAULT, ' (YYYY-MM-DD HH:MM): ', Sty.GRAY))
209
- return datetime.datetime.strptime(timestamp + ':00', '%Y-%m-%d %H:%M:%S')
210
-
211
- def reserve():
212
- try:
213
- id = session.prompt(stylize("Enter the device ID you would like to reserve: ", Sty.DEFAULT))
214
- token = account.reserve_device(int(id), get_datetime("Reserve Start Time"), get_datetime("Reserve End Time"))
215
- if token != '':
216
- printf(f"Reservation successful. Thy Token -> ", Sty.BOLD, f"{token}", Sty.BG_GREEN)
217
- printf(f"Please keep this token safe, as it is not saved on server side, and cannot be regenerated/reretrieved. ", Sty.DEFAULT)
218
- except Exception as e:
219
- printf(f"Error: {e}", Sty.BRIGHT_RED)
220
-
221
- def perms():
222
- data = account.get_perms()
223
- if 'ace' in data.results:
224
- print(f"Error: {unmap_arg(data.results['ace'])}")
225
- return
226
-
227
- results = ast.literal_eval(unmap_arg(data.results['UC']))[0]
228
- printf(f'Permission Level: ', Sty.BOLD, f'{results[0]}', Sty.BLUE)
229
- if results[0] == 'Normal User':
230
- print(unmap_arg(data.results['details']))
231
- elif results[0] == 'Power User':
232
- printf(f'Max Reservations: ', Sty.DEFAULT, f'{results[3]}', Sty.MAGENTA)
233
- printf(f'Max Reservation Duration (min): ', Sty.DEFAULT, f'{int(results[4]/60)}', Sty.MAGENTA)
234
- printf(f'Device IDs allowed Access to: ', Sty.DEFAULT, f'{results[5]}', Sty.MAGENTA)
235
- elif results[0] == 'Admin':
236
- printf(f'No restrictions on reservation count or duration.', Sty.DEFAULT)
237
- else:
238
- printf(f"Error: Unknown permission level {results[0]}", Sty.BRIGHT_RED)
239
-
240
- welcome()
241
- clear()
242
-
243
- while True:
244
- try:
245
- inpu = session.prompt(stylize(f'{account.username}@remote_rf: ', Sty.BLUE))
246
- if inpu == "clear":
247
- clear()
248
- elif inpu == "getdev":
249
- devices()
250
- elif inpu == "help" or inpu == "h":
251
- commands()
252
- elif inpu == "perms":
253
- perms()
254
- elif inpu == "quit" or inpu == "exit":
255
- break
256
- elif inpu == "getres":
257
- reservations()
258
- elif inpu == "myres":
259
- my_reservations()
260
- elif inpu == "resdev":
261
- reserve()
262
- elif inpu == 'cancelres':
263
- cancel_my_reservation()
264
- else:
265
- print(f"Unknown command: {inpu}")
266
- except KeyboardInterrupt:
267
- break
268
- except EOFError:
269
- break
270
-
File without changes
File without changes
File without changes