not1mm 24.2.4__py3-none-any.whl → 24.2.13__py3-none-any.whl

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.
not1mm/lib/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """It's the version"""
2
- __version__ = "24.2.4"
2
+ __version__ = "24.2.13"
@@ -1,6 +1,7 @@
1
1
  """ARRL plugin"""
2
2
 
3
3
  # pylint: disable=invalid-name, unused-argument, unused-variable, c-extension-no-member, unused-import
4
+ # pylint: disable=logging-fstring-interpolation
4
5
 
5
6
  import datetime
6
7
  import logging
@@ -116,12 +117,12 @@ def points(self):
116
117
  result = self.cty_lookup(self.station.get("Call", ""))
117
118
  if result:
118
119
  for item in result.items():
119
- mycountry = item[1].get("entity", "")
120
+ mycountry = item[1].get("primary_pfx", "")
120
121
  # mycontinent = item[1].get("continent", "")
121
122
  result = self.cty_lookup(self.contact.get("Call", ""))
122
123
  if result:
123
124
  for item in result.items():
124
- entity = item[1].get("entity", "")
125
+ entity = item[1].get("primary_pfx", "")
125
126
  # continent = item[1].get("continent", "")
126
127
  if mycountry in ["K", "VE"]:
127
128
  if entity in ["K", "VE"]:
@@ -1,6 +1,7 @@
1
1
  """ARRL plugin"""
2
2
 
3
3
  # pylint: disable=invalid-name, unused-argument, unused-variable, c-extension-no-member, unused-import
4
+ # pylint: disable=logging-fstring-interpolation
4
5
 
5
6
  import datetime
6
7
  import logging
@@ -114,14 +115,16 @@ def prefill(self):
114
115
  def points(self):
115
116
  """Calc point"""
116
117
  result = self.cty_lookup(self.station.get("Call", ""))
118
+ logger.debug(f"our lookup result: {result}")
117
119
  if result:
118
120
  for item in result.items():
119
- mycountry = item[1].get("entity", "")
121
+ mycountry = item[1].get("primary_pfx", "")
120
122
  # mycontinent = item[1].get("continent", "")
121
123
  result = self.cty_lookup(self.contact.get("Call", ""))
124
+ logger.debug(f"their lookup result: {result}")
122
125
  if result:
123
126
  for item in result.items():
124
- entity = item[1].get("entity", "")
127
+ entity = item[1].get("primary_pfx", "")
125
128
  # continent = item[1].get("continent", "")
126
129
  if mycountry in ["K", "VE"]:
127
130
  if entity in ["K", "VE"]:
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env python3
2
+ """Simulated Field Day club participant"""
3
+
4
+ # pylint: disable=global-statement, raise-missing-from
5
+
6
+ import random
7
+ import socket
8
+ import uuid
9
+ import time
10
+ import threading
11
+ import queue
12
+ import argparse
13
+ from random import randint
14
+ from datetime import datetime
15
+ from json import dumps, loads, JSONDecodeError
16
+
17
+
18
+ parser = argparse.ArgumentParser(description="Simulate a Field Day participant.")
19
+ parser.add_argument("-c", "--call", type=str, help="Your Callsign")
20
+ parser.add_argument("-b", "--band", type=str, help="Your Band")
21
+ parser.add_argument("-m", "--mode", type=str, help="Your Mode")
22
+ parser.add_argument("-p", "--power", type=str, help="Your Power")
23
+
24
+ args = parser.parse_args()
25
+
26
+ MULTICAST_PORT = 2239
27
+ MULTICAST_GROUP = "239.1.1.1"
28
+ INTERFACE_IP = "0.0.0.0"
29
+ GROUP_CALL = None
30
+
31
+ eightymeterstalk = (
32
+ "What are the @stats?",
33
+ "That K6GTE guy is a jerk!",
34
+ "I worked your mama on 80 meters.",
35
+ "I have nothing interesting to add, I'm just running my keys.",
36
+ "Who's here that has gout?",
37
+ "Jim, go to 40",
38
+ "I gotta pee again, someone cover the GOTA station.",
39
+ "Who made that 'Chili'... Gawd aweful!",
40
+ ".. -.. - .- .--. - .... .- -",
41
+ "Why's no one covering 160?",
42
+ "That FT8, It's so enjoyable!",
43
+ "Yes Jim, you have to DISCONNECT the dummy load.",
44
+ )
45
+
46
+ bands = ("160", "80", "40", "20", "15", "10", "6", "2")
47
+ if args.band:
48
+ if args.band in bands:
49
+ BAND = args.band
50
+ else:
51
+ print('Allowed bands: "160", "80", "40", "20", "15", "10", "6", "2"')
52
+ raise SystemExit(1)
53
+ else:
54
+ BAND = bands[random.randint(0, len(bands) - 1)]
55
+
56
+ modes = ("CW", "PH", "DI")
57
+ if args.mode:
58
+ if args.mode.upper() in modes:
59
+ MODE = args.mode.upper()
60
+ else:
61
+ print('Allowed modes: "CW", "PH", "DI"')
62
+ raise SystemExit(1)
63
+ else:
64
+ MODE = modes[random.randint(0, len(modes) - 1)]
65
+
66
+ if args.power:
67
+ try:
68
+ POWER = int(args.power)
69
+ if POWER < 1 or POWER > 100:
70
+ raise ValueError
71
+ except ValueError:
72
+ print("Power is a number between 1 and 100")
73
+ raise SystemExit(1)
74
+ else:
75
+ POWER = 5
76
+
77
+ udp_fifo = queue.Queue()
78
+ server_commands = []
79
+
80
+
81
+ def generate_class():
82
+ """Generates a valid Field Day class"""
83
+ suffix = ["A", "B", "C", "D", "E", "F"][random.randint(0, 5)]
84
+ if "C" in suffix:
85
+ return "1C"
86
+ if "D" in suffix:
87
+ return "1D"
88
+ if "E" in suffix:
89
+ return "1E"
90
+ if "B" in suffix:
91
+ return str(random.randint(1, 2)) + suffix
92
+ if "A" in suffix:
93
+ return str(random.randint(3, 20)) + suffix
94
+
95
+ return str(random.randint(1, 20)) + suffix
96
+
97
+
98
+ def generate_callsign():
99
+ """Generates a US callsign, Need to add the land of maple syrup."""
100
+ prefix = ["A", "K", "N", "W"]
101
+ letters = [
102
+ "A",
103
+ "B",
104
+ "C",
105
+ "D",
106
+ "E",
107
+ "F",
108
+ "G",
109
+ "H",
110
+ "I",
111
+ "J",
112
+ "K",
113
+ "L",
114
+ "M",
115
+ "N",
116
+ "O",
117
+ "P",
118
+ "Q",
119
+ "R",
120
+ "S",
121
+ "T",
122
+ "U",
123
+ "V",
124
+ "W",
125
+ "X",
126
+ "Y",
127
+ "Z",
128
+ ]
129
+ callsign = prefix[random.randint(0, 3)]
130
+
131
+ add_second_prefix_letter = random.randint(0, 2) == 0
132
+ if "A" in callsign: # We have no choice. Must add second prefix.
133
+ callsign += letters[random.randint(0, 11)]
134
+ add_second_prefix_letter = False
135
+
136
+ if add_second_prefix_letter:
137
+ callsign += letters[random.randint(0, 25)]
138
+
139
+ callsign += str(random.randint(0, 9))
140
+ if "A" in callsign[0]:
141
+ suffix_length = random.randint(1, 2)
142
+ else:
143
+ length = [
144
+ 1,
145
+ 2,
146
+ 2,
147
+ 3,
148
+ 3,
149
+ 3,
150
+ ] # Stupid way to get a weighted result. But I'm stupid so it's normal.
151
+ suffix_length = length[random.randint(0, 5)]
152
+
153
+ for unused_variable in range(suffix_length):
154
+ callsign += letters[random.randint(0, 25)]
155
+
156
+ return callsign
157
+
158
+
159
+ def generate_section(call):
160
+ """Generate section based on call region"""
161
+ call_areas = {
162
+ "0": "CO MO IA ND KS NE MN SD",
163
+ "1": "CT RI EMA VT ME WMA NH",
164
+ "2": "ENY NNY NLI SNJ NNJ WNY",
165
+ "3": "DE MDC EPA WPA",
166
+ "4": "AL SC GA SFL KY TN NC VA NFL VI PR WCF",
167
+ "5": "AR NTX LA OK MS STX NM WTX",
168
+ "6": "EBA SCV LAX SDG ORG SF PAC SJV SB SV",
169
+ "7": "AK NV AZ OR EWA UT ID WWA MT WY",
170
+ "8": "MI WV OH",
171
+ "9": "IL WI IN",
172
+ }
173
+ if call[1].isdigit():
174
+ area = call[1]
175
+ else:
176
+ area = call[2]
177
+ sections = call_areas[area].split()
178
+ return sections[random.randint(0, len(sections) - 1)]
179
+
180
+
181
+ def fakefreq(band, mode):
182
+ """
183
+ If unable to obtain a frequency from the rig,
184
+ This will return a sane value for a frequency mainly for the cabrillo and adif log.
185
+ Takes a band and mode as input and returns freq in khz.
186
+ """
187
+ _modes = {"CW": 0, "DI": 1, "PH": 2, "FT8": 1, "SSB": 2}
188
+ fakefreqs = {
189
+ "160": ["1830", "1805", "1840"],
190
+ "80": ["3530", "3559", "3970"],
191
+ "60": ["5332", "5373", "5405"],
192
+ "40": ["7030", "7040", "7250"],
193
+ "30": ["10130", "10130", "0000"],
194
+ "20": ["14030", "14070", "14250"],
195
+ "17": ["18080", "18100", "18150"],
196
+ "15": ["21065", "21070", "21200"],
197
+ "12": ["24911", "24920", "24970"],
198
+ "10": ["28065", "28070", "28400"],
199
+ "6": ["50.030", "50300", "50125"],
200
+ "2": ["144030", "144144", "144250"],
201
+ "222": ["222100", "222070", "222100"],
202
+ "432": ["432070", "432200", "432100"],
203
+ "SAT": ["144144", "144144", "144144"],
204
+ }
205
+ freqtoreturn = fakefreqs[band][_modes[mode]]
206
+ return freqtoreturn
207
+
208
+
209
+ def log_contact():
210
+ """Send a contgact to the server."""
211
+ unique_id = uuid.uuid4().hex
212
+ callsign = generate_callsign()
213
+ contact = {
214
+ "cmd": "POST",
215
+ "hiscall": callsign,
216
+ "class": generate_class(),
217
+ "section": generate_section(callsign),
218
+ "mode": MODE,
219
+ "band": BAND,
220
+ "frequency": int(float(fakefreq(BAND, MODE)) * 1000),
221
+ "date_and_time": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S"),
222
+ "power": POWER,
223
+ "grid": "DM13at",
224
+ "opname": "John Doe",
225
+ "station": STATION_CALL,
226
+ "unique_id": unique_id,
227
+ }
228
+ server_commands.append(contact)
229
+ bytes_to_send = bytes(dumps(contact), encoding="ascii")
230
+ try:
231
+ s.sendto(bytes_to_send, (MULTICAST_GROUP, int(MULTICAST_PORT)))
232
+ except OSError as err:
233
+ print(f"Error: {err}")
234
+ # logging.warning("%s", err)
235
+
236
+
237
+ def remove_confirmed_commands(data):
238
+ """Removed confirmed commands from the sent commands list."""
239
+ for index, item in enumerate(server_commands):
240
+ if item.get("unique_id") == data.get("unique_id") and item.get(
241
+ "cmd"
242
+ ) == data.get("subject"):
243
+ server_commands.pop(index)
244
+ print(f"Confirmed {data.get('subject')}")
245
+
246
+
247
+ def watch_udp():
248
+ """Puts UDP datagrams in a FIFO queue"""
249
+ while True:
250
+ try:
251
+ datagram = s.recv(1500)
252
+ except socket.timeout:
253
+ time.sleep(1)
254
+ continue
255
+ if datagram:
256
+ udp_fifo.put(datagram)
257
+
258
+
259
+ def check_udp_queue():
260
+ """checks the UDP datagram queue."""
261
+ global GROUP_CALL
262
+ while not udp_fifo.empty():
263
+ datagram = udp_fifo.get()
264
+ try:
265
+ json_data = loads(datagram.decode())
266
+ except UnicodeDecodeError as err:
267
+ the_error = f"Not Unicode: {err}\n{datagram}"
268
+ print(the_error)
269
+ continue
270
+ except JSONDecodeError as err:
271
+ the_error = f"Not JSON: {err}\n{datagram}"
272
+ print(the_error)
273
+ continue
274
+ # logging.info("%s", json_data)
275
+ if json_data.get("cmd") == "PING":
276
+ pass
277
+ # print(f"[{strftime('%H:%M:%S', gmtime())}] {json_data}")
278
+ if json_data.get("cmd") == "RESPONSE":
279
+ if json_data.get("recipient") == STATION_CALL:
280
+ if json_data.get("subject") == "HOSTINFO":
281
+ GROUP_CALL = str(json_data.get("groupcall"))
282
+ return
283
+ if json_data.get("subject") == "LOG":
284
+ print("Server Generated Log.")
285
+
286
+ remove_confirmed_commands(json_data)
287
+
288
+ if json_data.get("cmd") == "CONFLICT":
289
+ band, mode = json_data.get("bandmode").split()
290
+ if (
291
+ band == BAND
292
+ and mode == MODE
293
+ and json_data.get("recipient") == STATION_CALL
294
+ ):
295
+ print(f"CONFLICT ON {json_data.get('bandmode')}")
296
+ if json_data.get("cmd") == "GROUPQUERY":
297
+ if GROUP_CALL:
298
+ send_status_udp()
299
+
300
+
301
+ def send_chat():
302
+ """Sends UDP chat packet with text entered in chat_entry field."""
303
+ message = eightymeterstalk[randint(0, len(eightymeterstalk) - 1)]
304
+ packet = {"cmd": "CHAT"}
305
+ packet["sender"] = STATION_CALL
306
+ packet["message"] = message
307
+ bytes_to_send = bytes(dumps(packet), encoding="ascii")
308
+ try:
309
+ s.sendto(bytes_to_send, (MULTICAST_GROUP, int(MULTICAST_PORT)))
310
+ except OSError as err:
311
+ print(f"{err}")
312
+
313
+
314
+ def query_group():
315
+ """Sends request to server asking for group call/class/section."""
316
+ update = {
317
+ "cmd": "GROUPQUERY",
318
+ "station": STATION_CALL,
319
+ }
320
+ bytes_to_send = bytes(dumps(update), encoding="ascii")
321
+ try:
322
+ s.sendto(bytes_to_send, (MULTICAST_GROUP, int(MULTICAST_PORT)))
323
+ except OSError as err:
324
+ print(f"{err}")
325
+
326
+
327
+ def send_status_udp():
328
+ """Send status update to server informing of our band and mode"""
329
+
330
+ if GROUP_CALL is None:
331
+ query_group()
332
+ # return
333
+
334
+ update = {
335
+ "cmd": "PING",
336
+ "mode": MODE,
337
+ "band": BAND,
338
+ "station": STATION_CALL,
339
+ }
340
+ bytes_to_send = bytes(dumps(update), encoding="ascii")
341
+ try:
342
+ s.sendto(bytes_to_send, (MULTICAST_GROUP, int(MULTICAST_PORT)))
343
+ except OSError as err:
344
+ print(f"Error: {err}")
345
+
346
+
347
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
348
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
349
+ s.bind(("", MULTICAST_PORT))
350
+ mreq = socket.inet_aton(MULTICAST_GROUP) + socket.inet_aton(INTERFACE_IP)
351
+ s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, bytes(mreq))
352
+ s.settimeout(0.01)
353
+
354
+ if args.call:
355
+ STATION_CALL = args.call.upper()
356
+ else:
357
+ STATION_CALL = generate_callsign()
358
+
359
+
360
+ def main():
361
+ """The main loop"""
362
+ _udpwatch = threading.Thread(
363
+ target=watch_udp,
364
+ daemon=True,
365
+ )
366
+ _udpwatch.start()
367
+ print(f"Station: {STATION_CALL} on {BAND}M {MODE}")
368
+ send_status_udp()
369
+ count = 0
370
+ while True:
371
+ count += 1
372
+ if count % 30 == 0:
373
+ log_contact()
374
+ if count % 15 == 0:
375
+ send_status_udp()
376
+ if count % 45 == 0:
377
+ send_chat()
378
+ check_udp_queue()
379
+
380
+ time.sleep(1)
381
+
382
+
383
+ if __name__ == "__main__":
384
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: not1mm
3
- Version: 24.2.4
3
+ Version: 24.2.13
4
4
  Summary: NOT1MM Logger
5
5
  Author-email: Michael Bridak <michael.bridak@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/mbridak/not1mm
@@ -165,6 +165,7 @@ I wish to thank those who've contributed to the project.
165
165
 
166
166
  ## Recent Changes
167
167
 
168
+ - [24-2-13] Fixed no points being assigned in ARRL DX, reported by NC8R.
168
169
  - [24-2-4] Fixed Cabrillo name in 10 10 Winter Phone. Added missing Club tag in all the cabrillo contest files.
169
170
  - [24-2-3] Merge PR from @wvolz.
170
171
  - [24-2-1] Fix bug in bandmap, quotation mark mismatch. Changed CAT timeout back to 0.5 seconds.
@@ -102,7 +102,7 @@ not1mm/lib/plugin_common.py,sha256=Oggv0Hnvh0CCAiNLygDdTSYV1rmzOptg8nqStFRUi_U,7
102
102
  not1mm/lib/select_contest.py,sha256=XQdRUkPAIHIMVsilm82M54b_v9yWpYrZ1nfInJrtZoo,363
103
103
  not1mm/lib/settings.py,sha256=t_JLJPnDBtMGAvJMAF1AL1eVB7MyucqlksVTU47yxvk,8933
104
104
  not1mm/lib/super_check_partial.py,sha256=GlXgtIblL602iW-V6Mmdf5S4FxtzJ95TbPMMa9xXUfg,1692
105
- not1mm/lib/version.py,sha256=07nYXqqebn2FdXgPNQUs8s2fNYKhcOq6YsUR_5VeF_g,46
105
+ not1mm/lib/version.py,sha256=mIJIkTfm9OS6buHK6J38xjWzfVhUxpeHmJosWhFsgsw,47
106
106
  not1mm/lib/versiontest.py,sha256=8vDNptuBBunn-1IGkjNaquehqBYUJyjrPSF8Igmd4_Y,1286
107
107
  not1mm/plugins/10_10_fall_cw.py,sha256=8cN7QmGKWzz_jgu48DzWwy5VbbY3-ntT2Q1DSVbgkwE,10692
108
108
  not1mm/plugins/10_10_spring_cw.py,sha256=cgCLyGw-w8SjKooDxY6PjJFUjVKgI48bzrOsyasOapY,10698
@@ -110,8 +110,8 @@ not1mm/plugins/10_10_summer_phone.py,sha256=WR7bMPY11vLleT8VuyMBElyjDq_KlfRL5g4t
110
110
  not1mm/plugins/10_10_winter_phone.py,sha256=ZXoUCXC-RhgirI-VHA7-s76LgS5mHcOaT29Jg_9zD_s,10709
111
111
  not1mm/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  not1mm/plugins/arrl_10m.py,sha256=Cp3COORlROvfLvRi5R5fA1yCmbDCrl2KOTu7t_Yt6u8,13501
113
- not1mm/plugins/arrl_dx_cw.py,sha256=vbk7cKBTRK98vBsQ5ixqv1-SdDWlwYJCxdWckcQsCzQ,11558
114
- not1mm/plugins/arrl_dx_ssb.py,sha256=nWXv-rqzPafXcWBAMZ8aNjbXKGyvwmimHCUyVMXJ59Y,11562
113
+ not1mm/plugins/arrl_dx_cw.py,sha256=BFQKseNO56vIdGGB3zGxN5-rvWA06-7YElTBFELDN9c,11616
114
+ not1mm/plugins/arrl_dx_ssb.py,sha256=jZSXD4ce6QLwo1OrNuSeqlfpqCHyg59T-UMsvsN6hOY,11720
115
115
  not1mm/plugins/arrl_field_day.py,sha256=aiQjnWsorjMk1kFHQZMFI6pBc1Z6q2-nx0hhSXN45pI,9884
116
116
  not1mm/plugins/arrl_rtty_ru.py,sha256=9v9wApmUZHAKX4t_O6hVqBnT7v5bqAGV8SjgDhfOuMs,7974
117
117
  not1mm/plugins/arrl_ss_cw.py,sha256=kmQ-DZnNCyivVmnRtW5H1z3b6tFFXXUljwfao5uLVnI,13001
@@ -138,12 +138,13 @@ not1mm/testing/fakeflrig.py,sha256=_vJHGjARpSNxSZngkHNO_kkHoVnqtf--T6gwTAYnnZQ,2
138
138
  not1mm/testing/flrigclient.py,sha256=24r_0HqpoTjyJ6Bqg_HIC8Nn9wjtnwwWQ26I7UprwgA,1658
139
139
  not1mm/testing/multicast_listener.py,sha256=2CkiyZ4EQxBX68_1QzGIX9g_UB9-CQq63OH-pUY3FiU,1051
140
140
  not1mm/testing/n1mm_listener.py,sha256=UD-qyKEnppQua330WEFKMvMJaNjnYKi7dDuX_RGB5lQ,1099
141
+ not1mm/testing/simulant.py,sha256=kBqCZTe3ADEuHUsh9ygY9usn-jKe4EKRh7-L6tY6iYE,10948
141
142
  not1mm/testing/test.py,sha256=wGblvMlyOCVkEiHbxE6wvLsorim15ehL72_EZLQeWkk,1660
142
143
  testing/test.py,sha256=q7socQaMu46q-I-1fYgmQhnygrrC5NjAUM5yuySo4fA,249
143
144
  usb_vfo_knob/code.py,sha256=h59iPPlcYbkXmRcYPQHDBP0yfLEl7fY3VkiIszdQeyI,1057
144
- not1mm-24.2.4.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
145
- not1mm-24.2.4.dist-info/METADATA,sha256=p-kXg3g-B9xEsaR0BOS5_iGU3GFGR1l_ioSj8W3H5gk,25670
146
- not1mm-24.2.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
147
- not1mm-24.2.4.dist-info/entry_points.txt,sha256=pMcZk_0dxFgLkcUkF0Q874ojpwOmF3OL6EKw9LgvocM,47
148
- not1mm-24.2.4.dist-info/top_level.txt,sha256=PBUZJeDgW5ta7ghk__UYh_ygOFIhe9ymJDaxEuVumFU,28
149
- not1mm-24.2.4.dist-info/RECORD,,
145
+ not1mm-24.2.13.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
146
+ not1mm-24.2.13.dist-info/METADATA,sha256=evuyRTVVDYHeUzYindGV40y9hRZLJbqHQb11JZcVgQA,25744
147
+ not1mm-24.2.13.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
148
+ not1mm-24.2.13.dist-info/entry_points.txt,sha256=pMcZk_0dxFgLkcUkF0Q874ojpwOmF3OL6EKw9LgvocM,47
149
+ not1mm-24.2.13.dist-info/top_level.txt,sha256=PBUZJeDgW5ta7ghk__UYh_ygOFIhe9ymJDaxEuVumFU,28
150
+ not1mm-24.2.13.dist-info/RECORD,,