zhmiscellany 6.2.3__py3-none-any.whl → 6.2.5__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.
@@ -1,24 +1,19 @@
1
1
  # these lines are purposefully the first thing to run when zhmiscellany is imported
2
- import threading, logging, os, inspect, tempfile, shutil
3
- from itertools import chain
4
- import zhmiscellany.fileio
5
- from io import StringIO
6
- import sys
7
- import io
8
- from unittest.mock import patch
2
+ import sys # cannot be moved
9
3
 
10
4
  # Ray availability check
11
5
  if sys.platform == "win32" or True:
12
- try:
13
- import ray
14
- RAY_AVAILABLE = True
15
- except ImportError:
16
- RAY_AVAILABLE = False
6
+ RAY_AVAILABLE = True
17
7
  else:
18
8
  RAY_AVAILABLE = False
19
9
 
10
+ import os # needed for module-level log clearing and cause detection
20
11
 
21
12
  def clear_logs():
13
+ import tempfile
14
+ import os
15
+ import shutil
16
+ import zhmiscellany.fileio
22
17
  ray_dir = tempfile.gettempdir()
23
18
  ray_dir = os.path.join(ray_dir, 'ray')
24
19
 
@@ -50,6 +45,7 @@ if 'ray_logs_cleared' not in os.environ:
50
45
 
51
46
 
52
47
  def safe_open_log(path, unbuffered=False, **kwargs):
48
+ import os
53
49
  try:
54
50
  kwargs.setdefault("buffering", 1)
55
51
  kwargs.setdefault("mode", "a")
@@ -69,6 +65,8 @@ def safe_open_log(path, unbuffered=False, **kwargs):
69
65
 
70
66
 
71
67
  def ray_init(auto=False):
68
+ import threading
69
+ import os
72
70
  if not RAY_AVAILABLE:
73
71
  print("ray_init() only supports Windows! Functionality disabled")
74
72
  return
@@ -100,6 +98,10 @@ def _ray_init():
100
98
 
101
99
  try:
102
100
  def safe_ray_init():
101
+ import sys
102
+ import io
103
+ import ray
104
+
103
105
  def ensure_valid_handles():
104
106
  """Ensure stdout and stderr are valid file-like objects"""
105
107
  if not hasattr(sys.stdout, 'write') or sys.stdout.closed:
@@ -140,6 +142,7 @@ def _ray_init():
140
142
 
141
143
 
142
144
  def get_import_chain():
145
+ import inspect
143
146
  frame = inspect.currentframe()
144
147
  chain = []
145
148
  while frame:
@@ -153,6 +156,8 @@ def get_import_chain():
153
156
  frame = frame.f_back
154
157
  return chain[::-1]
155
158
 
159
+
160
+ # Cause detection for auto-initializing ray
156
161
  cause_strings = [
157
162
  'processing.multiprocess(',
158
163
  'processing.batch_multiprocess(',
@@ -175,6 +180,7 @@ for file in cause_files:
175
180
  cause = True
176
181
  break
177
182
 
183
+ import threading
178
184
  _ray_init_thread = threading.Thread() # initialize variable to completed thread
179
185
  _ray_init_thread.start()
180
186
 
@@ -198,6 +204,8 @@ class ThreadWithResult(threading.Thread):
198
204
 
199
205
 
200
206
  def batch_multiprocess(targets_and_args, max_retries=0, expect_crashes=False, disable_warning=False, flatten=False):
207
+ import logging
208
+ from itertools import chain
201
209
  if not RAY_AVAILABLE:
202
210
  print("batch_multiprocess() only supports Windows! Returning empty list")
203
211
  return []
@@ -217,6 +225,7 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
217
225
  _ray_init_thread.join()
218
226
 
219
227
  if not expect_crashes:
228
+ import ray
220
229
  @ray.remote(max_retries=max_retries, num_cpus=0)
221
230
  def worker(func, *args):
222
231
  return func(*args)
@@ -227,12 +236,14 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
227
236
  results = list(chain.from_iterable(results))
228
237
  return results
229
238
  else:
239
+ import ray
230
240
  def wrap_exception(task, disable_warning, max_retries):
231
241
  try:
232
242
  result = multiprocess(*task, disable_warning=disable_warning, max_retries=max_retries)
233
243
  return result
234
244
  except ray.exceptions.WorkerCrashedError:
235
245
  return None
246
+ import threading # this import is explicitly in the original code
236
247
  threads = []
237
248
  for task in targets_and_args:
238
249
  t = ThreadWithResult(
@@ -248,6 +259,7 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
248
259
  results = list(chain.from_iterable(results))
249
260
  return results
250
261
 
262
+
251
263
  def multiprocess(target, args=(), max_retries=0, disable_warning=False):
252
264
  if not RAY_AVAILABLE:
253
265
  print("multiprocess() only supports Windows! Returning None")
@@ -259,10 +271,12 @@ class RayActorWrapper:
259
271
  def __init__(self, actor_instance):
260
272
  self._actor = actor_instance
261
273
 
274
+ import ray
262
275
  ray.get(self._actor._ready.remote())
263
276
 
264
277
  def __getattr__(self, name):
265
278
  # When you access an attribute, assume it's a remote method.
279
+ import ray
266
280
  remote_method = getattr(self._actor, name)
267
281
  if not callable(remote_method):
268
282
  # If it's not callable, try to get its value.
@@ -278,6 +292,8 @@ class RayActorWrapper:
278
292
 
279
293
 
280
294
  def synchronous_class_multiprocess(cls, *args, disable_warning=False, **kwargs):
295
+ import logging
296
+ import ray
281
297
  if not RAY_AVAILABLE:
282
298
  print("synchronous_class_multiprocess() only supports Windows! Returning None")
283
299
  return None
@@ -303,4 +319,4 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
303
319
 
304
320
  remote_cls = ray.remote(num_cpus=0)(cls)
305
321
  actor_instance = remote_cls.remote(*args, **kwargs)
306
- return RayActorWrapper(actor_instance)
322
+ return RayActorWrapper(actor_instance)
@@ -1,5 +1,5 @@
1
- import os, zlib
2
1
  def gen():
2
+ import os, zlib
3
3
  os.makedirs('resources', exist_ok=True)
4
4
  os.makedirs('resources\\random_header_generator', exist_ok=True)
5
5
  os.makedirs('resources\\random_header_generator\\data', exist_ok=True)
zhmiscellany/dict.py CHANGED
@@ -1,5 +1,3 @@
1
- import json
2
-
3
-
4
1
  def print_dict(ldict):
2
+ import json
5
3
  print(json.dumps(ldict, indent=4))
zhmiscellany/discord.py CHANGED
@@ -1,32 +1,16 @@
1
- import time
2
- import sys
3
- import requests
4
- import copy
5
- import zhmiscellany.fileio
6
- import zhmiscellany.netio
7
- import zhmiscellany.processing
1
+ import sys # cannot be touched because it's needed
8
2
  from ._discord_supportfuncs import scrape_guild
9
3
 
10
- import base64
11
- import os
12
- import json
13
- import re
14
-
15
4
  # Windows-specific imports
16
5
  if sys.platform == "win32":
17
- try:
18
- import win32crypt
19
- from Crypto.Cipher import AES
20
- WIN32_AVAILABLE = True
21
- except ImportError:
22
- WIN32_AVAILABLE = False
23
- print("Warning: Windows modules not available - local Discord user detection disabled")
6
+ WIN32_AVAILABLE = True
24
7
  else:
25
8
  WIN32_AVAILABLE = False
26
9
 
27
10
 
28
11
  def add_reactions_to_message(user_token, emojis, channel_id, message_id):
29
-
12
+ import time
13
+ import requests
30
14
  for emoji in emojis:
31
15
  url = f'https://discord.com/api/v9/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me'
32
16
  #headers = {**zhmiscellany.netio.generate_headers(url), 'Authorization': user_token}
@@ -54,7 +38,9 @@ def get_channel_messages(user_token, channel_id, limit=0, use_cache=True, show_p
54
38
  '''
55
39
  Function to get all client messages in a specific channel. Script by @z_h_ on discord.
56
40
  '''
57
-
41
+ import requests
42
+ import zhmiscellany.fileio
43
+ import os
58
44
  if use_cache:
59
45
  cache_folder = 'zhmiscellany_cache'
60
46
  zhmiscellany.fileio.create_folder(cache_folder)
@@ -118,10 +104,18 @@ def get_channel_messages(user_token, channel_id, limit=0, use_cache=True, show_p
118
104
 
119
105
 
120
106
  def get_local_discord_user(show_output=False):
107
+ import requests
108
+ import os
109
+ import json
110
+ import re
111
+ import base64
112
+ import win32crypt
113
+ from Crypto.Cipher import AES
114
+ import zhmiscellany.netio
121
115
  if not WIN32_AVAILABLE:
122
116
  print("get_local_discord_user() only supports Windows! Returning None")
123
117
  return None
124
-
118
+
125
119
  global _cached_user_info
126
120
  try:
127
121
  a = _cached_user_info
@@ -241,6 +235,9 @@ def get_local_discord_user(show_output=False):
241
235
 
242
236
 
243
237
  def get_guild_channels(user_token, guild_id, use_cache=True):
238
+ import requests
239
+ import os
240
+ import zhmiscellany.netio
244
241
  if use_cache:
245
242
  potential_path = os.path.join('zhmiscellany_cache', f'{guild_id}_channels.json')
246
243
  if os.path.exists(potential_path):
@@ -260,12 +257,17 @@ def get_guild_channels(user_token, guild_id, use_cache=True):
260
257
 
261
258
 
262
259
  def send_type(user_token, channel_id): # after sending the typing post request, the account will be shown as "typing" in the given channel for 10 seconds, or until a message is sent.
260
+ import requests
261
+ import zhmiscellany.netio
263
262
  url = f'https://discord.com/api/v9/channels/{channel_id}/typing'
264
263
  headers = {**zhmiscellany.netio.generate_headers(url), 'Authorization': user_token}
265
264
  return requests.post(url, headers=headers)
266
265
 
267
266
 
268
267
  def send_message(user_token, text, channel_id, attachments=None, typing_time=0):
268
+ import time
269
+ import requests
270
+ import zhmiscellany.processing
269
271
  typing_time_increments = 9.5 # not set to 10 because then every 10 seconds the typing would stop very briefly
270
272
  while typing_time > 0:
271
273
  zhmiscellany.processing.start_daemon(target=send_type, args=(user_token, channel_id))
@@ -294,6 +296,8 @@ def send_message(user_token, text, channel_id, attachments=None, typing_time=0):
294
296
 
295
297
 
296
298
  def get_message(user_token, channel_id, message_id):
299
+ import requests
300
+ import zhmiscellany.netio
297
301
  message_url = f'https://discord.com/api/v9/channels/{channel_id}/messages?limit=1&around={message_id}'
298
302
  message = requests.get(message_url, headers={**zhmiscellany.netio.generate_headers(message_url), 'Authorization': user_token})
299
303
  message = message.json()
@@ -308,6 +312,7 @@ def ids_to_message_url(channel_id, message_id, guild_id=None):
308
312
 
309
313
 
310
314
  def message_url_to_ids(message_url):
315
+ import re
311
316
  # Regular expressions to extract IDs
312
317
  guild_channel_message_regex = r'https:\/\/discord\.com\/channels\/(\d+)\/(\d+)\/(\d+)'
313
318
  channel_message_regex = r'https:\/\/discord\.com\/channels\/(\d+)\/(\d+)'
@@ -336,6 +341,8 @@ def decode_user_id(user_token):
336
341
  padded_base64 = payload_base64 + '=' * (4 - len(payload_base64) % 4)
337
342
 
338
343
  # Decoding the base64 and converting to a JSON object
344
+ import base64
345
+ import json
339
346
  payload_json = base64.b64decode(padded_base64).decode('utf-8')
340
347
  user_id = json.loads(payload_json)
341
348
 
@@ -343,6 +350,9 @@ def decode_user_id(user_token):
343
350
 
344
351
 
345
352
  def get_guilds(user_token, use_cache=True):
353
+ import requests
354
+ import os
355
+ import zhmiscellany.netio
346
356
  if use_cache:
347
357
  potential_path = os.path.join('zhmiscellany_cache', f'{decode_user_id(user_token)}_guilds.json')
348
358
  if os.path.exists(potential_path):
@@ -361,6 +371,9 @@ def get_guilds(user_token, use_cache=True):
361
371
 
362
372
 
363
373
  def get_dm_channels(user_token, use_cache=True):
374
+ import requests
375
+ import os
376
+ import zhmiscellany.netio
364
377
  if use_cache:
365
378
  potential_path = os.path.join('zhmiscellany_cache', f'{decode_user_id(user_token)}_dm_channels.json')
366
379
  if os.path.exists(potential_path):
@@ -379,6 +392,9 @@ def get_dm_channels(user_token, use_cache=True):
379
392
 
380
393
 
381
394
  def get_invite_info(user_token, invite_code, use_cache=True):
395
+ import requests
396
+ import os
397
+ import zhmiscellany.netio
382
398
  if use_cache:
383
399
  potential_path = os.path.join('zhmiscellany_cache', f'{invite_code}_invite.json')
384
400
  if os.path.exists(potential_path):
@@ -397,6 +413,8 @@ def get_invite_info(user_token, invite_code, use_cache=True):
397
413
 
398
414
 
399
415
  def generate_server_invite(user_token, channel_id):
416
+ import zhmiscellany.netio
417
+ import requests
400
418
  url = f"https://discord.com/api/v9/channels/{channel_id}/invites"
401
419
  response = requests.get(url, headers={**zhmiscellany.netio.generate_headers(url), 'Authorization': user_token})
402
420
 
@@ -408,6 +426,8 @@ def generate_server_invite(user_token, channel_id):
408
426
 
409
427
 
410
428
  def get_approximate_member_count(user_token, channel_id, use_cache=True):
429
+ import os
430
+ import zhmiscellany.netio
411
431
  if use_cache:
412
432
  potential_path = os.path.join('zhmiscellany_cache', f'{channel_id}_member_count.json')
413
433
  if os.path.exists(potential_path):
@@ -429,12 +449,14 @@ def id_to_timestamp(id):
429
449
 
430
450
 
431
451
  def timestamp_to_id(timestamp):
432
- id = int(id)
433
452
  DISCORD_EPOCH = 1420070400000
434
453
  return int((timestamp * 1000 - DISCORD_EPOCH) * 4194304)
435
454
 
436
455
 
437
456
  def get_user_avatar_url(user_token, user_id, use_cache=True):
457
+ import requests
458
+ import os
459
+ import zhmiscellany.netio
438
460
  url = f"https://discord.com/api/v10/users/{user_id}"
439
461
 
440
462
  if use_cache:
zhmiscellany/fileio.py CHANGED
@@ -1,22 +1,9 @@
1
- from ._fileio_supportfuncs import is_junction
2
- import json, os, shutil, dill, sys, pickle, base64, zlib
3
- import zhmiscellany.string
4
- import zhmiscellany.misc
5
- import hashlib
6
- from collections import defaultdict
7
- from itertools import chain
8
- import tempfile
9
- import random
10
- import string
11
- import orjson
12
- from datetime import datetime
13
- import inspect
14
-
15
-
16
1
  def read_json_file(file_path):
17
2
  """
18
3
  Reads JSON data from a file and returns it as a dictionary.
19
4
  """
5
+ import json
6
+ import os
20
7
  if os.path.exists(file_path):
21
8
  with open(file_path, 'r') as file:
22
9
  data = json.load(file)
@@ -31,61 +18,65 @@ def write_json_file(file_path, data):
31
18
  """
32
19
  Writes a dictionary to a JSON file.
33
20
  """
21
+ import json
34
22
  with open(file_path, 'w') as file:
35
23
  json.dump(data, file, indent=4)
36
24
 
37
25
 
38
26
  def create_folder(folder_name):
27
+ import os
39
28
  if not os.path.exists(folder_name):
40
29
  os.makedirs(folder_name)
41
30
 
42
31
 
43
32
  def remove_folder(folder_name):
33
+ import os
34
+ import shutil
44
35
  if os.path.exists(folder_name):
45
36
  shutil.rmtree(folder_name)
46
37
 
47
38
 
48
39
  def base_name_no_ext(file_path):
40
+ import os
49
41
  base_name = os.path.basename(file_path)
50
42
  base_name_without_extension, _ = os.path.splitext(base_name)
51
43
  return base_name_without_extension
52
44
 
53
45
 
54
46
  def convert_name_to_filename(name):
47
+ import zhmiscellany.string
55
48
  return zhmiscellany.string.multi_replace(name, [("/","["), (":","]"), (".","+")])
56
49
 
57
50
 
58
51
  def convert_filename_to_name(filename):
52
+ import zhmiscellany.string
59
53
  return zhmiscellany.string.multi_replace(filename, [("[","/"), ("]",":"), ("+",".")])
60
54
 
61
55
 
62
56
  def recursive_copy_files(source_dir, destination_dir, prints=False):
57
+ import os
58
+ import shutil
63
59
  if prints:
64
60
  print('Validating matching directory structure')
65
61
  for root, dirs, files in os.walk(source_dir):
66
62
  for dir in dirs:
67
63
  dir_path = os.path.join(root, dir)
68
64
  dest_dir_path = os.path.join(destination_dir, os.path.relpath(dir_path, source_dir))
69
-
70
65
  if not os.path.exists(dest_dir_path):
71
66
  print(f'Creating missing directory {dest_dir_path}')
72
67
  os.makedirs(dest_dir_path)
73
-
74
68
  if prints:
75
69
  print('Getting a list of files in the source directory')
76
70
  source_files = []
77
71
  for root, _, files in os.walk(source_dir):
78
72
  for file in files:
79
73
  source_files.append(os.path.join(root, file))
80
-
81
74
  if prints:
82
75
  print('Getting a list of files in the destination directory')
83
76
  dest_files = []
84
77
  for root, _, files in os.walk(destination_dir):
85
78
  for file in files:
86
79
  dest_files.append(os.path.join(root, file))
87
-
88
-
89
80
  if prints:
90
81
  print('Copying files from source to destination, skipping duplicates')
91
82
  for root, dirs, files in os.walk(source_dir):
@@ -93,7 +84,6 @@ def recursive_copy_files(source_dir, destination_dir, prints=False):
93
84
  source_file = os.path.join(root, file)
94
85
  rel_path = os.path.relpath(source_file, source_dir)
95
86
  dest_file = os.path.join(destination_dir, rel_path)
96
-
97
87
  if not os.path.exists(dest_file):
98
88
  if prints:
99
89
  print(f'Copying {source_file}')
@@ -105,10 +95,11 @@ def recursive_copy_files(source_dir, destination_dir, prints=False):
105
95
 
106
96
 
107
97
  def empty_directory(directory_path):
98
+ import os
99
+ import shutil
108
100
  # Iterate over all items in the directory
109
101
  for item in os.listdir(directory_path):
110
102
  item_path = os.path.join(directory_path, item)
111
-
112
103
  if os.path.isfile(item_path):
113
104
  # If it's a file, delete it
114
105
  os.unlink(item_path)
@@ -118,10 +109,12 @@ def empty_directory(directory_path):
118
109
 
119
110
 
120
111
  def abs_listdir(path):
112
+ import os
121
113
  return [os.path.join(path, file) for file in os.listdir(path)]
122
114
 
123
115
 
124
116
  def delete_ends_with(directory, string_endswith, avoid=[]):
117
+ import os
125
118
  files = abs_listdir(directory)
126
119
  for file in files:
127
120
  if file.endswith(string_endswith):
@@ -134,17 +127,20 @@ def read_bytes_section(file_path, section_start, section_end):
134
127
  file.seek(section_start) # Move the file pointer to the 'start' position
135
128
  bytes_to_read = section_end - section_start
136
129
  data = file.read(bytes_to_read) # Read 'bytes_to_read' number of bytes
137
-
138
130
  return data
139
131
 
140
132
 
141
133
  def copy_file_with_overwrite(src, dst):
134
+ import os
135
+ import shutil
142
136
  if os.path.exists(dst):
143
137
  os.remove(dst)
144
138
  shutil.copy2(src, dst)
145
139
 
146
140
 
147
141
  def fast_dill_dumps(object):
142
+ import pickle
143
+ import dill
148
144
  try:
149
145
  data = pickle.dumps(object, protocol=5) # pickle is much faster so at least attempt to use it at first
150
146
  except:
@@ -153,6 +149,8 @@ def fast_dill_dumps(object):
153
149
 
154
150
 
155
151
  def fast_dill_loads(data):
152
+ import pickle
153
+ import dill
156
154
  try:
157
155
  object = pickle.loads(data) # pickle is much faster so at least attempt to use it at first
158
156
  except:
@@ -161,6 +159,7 @@ def fast_dill_loads(data):
161
159
 
162
160
 
163
161
  def save_object_to_file(object, file_name, compressed=False):
162
+ import zlib
164
163
  with open(file_name, 'wb') as f:
165
164
  if compressed:
166
165
  f.write(zlib.compress(fast_dill_dumps(object)))
@@ -169,6 +168,7 @@ def save_object_to_file(object, file_name, compressed=False):
169
168
 
170
169
 
171
170
  def load_object_from_file(file_name, compressed=False):
171
+ import zlib
172
172
  with open(file_name, 'rb') as f:
173
173
  if compressed:
174
174
  return fast_dill_loads(zlib.decompress(f.read()))
@@ -178,26 +178,33 @@ def load_object_from_file(file_name, compressed=False):
178
178
 
179
179
  def pickle_and_encode(obj):
180
180
  """Pickles an object and URL-safe encodes it."""
181
+ import base64
182
+ import zlib
181
183
  pickled_data = zlib.compress(fast_dill_dumps(obj), 9) # Serialize the object
182
184
  encoded_data = base64.urlsafe_b64encode(pickled_data).decode() # Base64 encode
183
185
  return encoded_data
184
186
 
187
+
185
188
  def decode_and_unpickle(encoded_str):
186
189
  """Decodes a URL-safe encoded string and unpickles the object."""
190
+ import base64
191
+ import zlib
187
192
  pickled_data = base64.urlsafe_b64decode(encoded_str) # Decode from Base64
188
193
  obj = fast_dill_loads(zlib.decompress(pickled_data)) # Deserialize
189
194
  return obj
190
195
 
191
196
 
192
197
  def list_files_by_modified_time(directory):
198
+ import os
193
199
  files_with_times = [(file, os.path.getmtime(os.path.join(directory, file))) for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
194
200
  sorted_files = sorted(files_with_times, key=lambda x: x[1], reverse=True)
195
201
  sorted_file_names = [file for file, _ in sorted_files]
196
-
197
202
  return sorted_file_names
198
203
 
199
204
 
200
205
  def get_script_path():
206
+ """Returns the path to the current script or executable."""
207
+ import sys
201
208
  if getattr(sys, 'frozen', False):
202
209
  # Running as a standalone executable
203
210
  return sys.executable
@@ -207,10 +214,21 @@ def get_script_path():
207
214
 
208
215
 
209
216
  def chdir_to_script_dir():
217
+ import os
210
218
  os.chdir(os.path.dirname(get_script_path()))
211
219
 
212
220
 
213
221
  def cache(function, *args, **kwargs):
222
+ """
223
+ Caches the result of a function call to disk.
224
+ """
225
+ import os
226
+ import inspect
227
+ import orjson
228
+ import hashlib
229
+ from datetime import datetime
230
+ import zhmiscellany.fileio
231
+
214
232
  cache_folder = 'zhmiscellany_cache'
215
233
 
216
234
  def get_hash_orjson(data):
@@ -267,6 +285,10 @@ def cache(function, *args, **kwargs):
267
285
 
268
286
 
269
287
  def load_all_cached():
288
+ """
289
+ Loads all cached objects from the cache folder.
290
+ """
291
+ import os
270
292
  cache_folder = 'zhmiscellany_cache'
271
293
  if os.path.exists(cache_folder):
272
294
  files = abs_listdir(cache_folder)
@@ -280,6 +302,11 @@ def load_all_cached():
280
302
 
281
303
 
282
304
  def list_files_recursive(folder):
305
+ """
306
+ Recursively lists all files in a directory, excluding symlinks and junctions.
307
+ """
308
+ import os
309
+ from ._fileio_supportfuncs import is_junction
283
310
  files = []
284
311
  try:
285
312
  for entry in os.scandir(folder):
@@ -295,6 +322,9 @@ def list_files_recursive(folder):
295
322
 
296
323
 
297
324
  def list_files_recursive_multiprocessed(dir_path, return_folders=False):
325
+ import os
326
+ import zhmiscellany.processing
327
+
298
328
  def is_junction(entry):
299
329
  try:
300
330
  st = entry.stat(follow_symlinks=False)
@@ -342,6 +372,8 @@ def list_files_recursive_multiprocessed(dir_path, return_folders=False):
342
372
 
343
373
  def encode_safe_filename(s, max_length=16):
344
374
  """Encodes a string into a short, URL-safe, and file name-safe string."""
375
+ import base64
376
+ import hashlib
345
377
  encoded = base64.urlsafe_b64encode(s.encode()).decode().rstrip("=") # URL-safe encoding
346
378
  if len(encoded) > max_length: # Truncate if too long
347
379
  encoded = hashlib.md5(s.encode()).hexdigest()[:max_length] # Use a hash
@@ -349,6 +381,15 @@ def encode_safe_filename(s, max_length=16):
349
381
 
350
382
 
351
383
  def list_files_recursive_cache_optimised_multiprocessed(dir_path, show_timings=False, cache_in_temp=True):
384
+ import os
385
+ import zhmiscellany.processing
386
+ import zhmiscellany.fileio
387
+ import tempfile
388
+ from collections import defaultdict
389
+ import random
390
+ from itertools import chain
391
+ import zhmiscellany.misc
392
+
352
393
  def is_junction(entry):
353
394
  try:
354
395
  st = entry.stat(follow_symlinks=False)
@@ -501,7 +542,6 @@ def list_files_recursive_cache_optimised_multiprocessed(dir_path, show_timings=F
501
542
 
502
543
  groups = split_into_n_groups(changed_folders, scan_changed_folders_thread_group_count)
503
544
  tasks = [(atom, (group,)) for group in groups]
504
-
505
545
  if not tasks:
506
546
  results = []
507
547
  else:
@@ -516,19 +556,16 @@ def list_files_recursive_cache_optimised_multiprocessed(dir_path, show_timings=F
516
556
  if len(changed_folders) > fully_update_cache_threshold:
517
557
  new_folders.update(get_m_times(new_new_folders))
518
558
  if show_timings: zhmiscellany.misc.time_it(f'get m times of {len(new_new_folders)} new folders')
519
-
520
559
  zhmiscellany.fileio.save_object_to_file((files, new_folders), cache_file)
521
-
522
560
  if show_timings: zhmiscellany.misc.time_it(f'writing to cache')
523
561
 
524
562
  ret = list(chain.from_iterable(files.values()))
525
-
526
563
  if show_timings: zhmiscellany.misc.time_it('Everything together', 'lfrcomt')
527
-
528
564
  return ret
529
565
 
530
566
 
531
567
  def save_chunk(name, data):
568
+ import zhmiscellany.string
532
569
  create_folder(name)
533
570
  chunk_path = f'{name}/chunk_{zhmiscellany.string.get_universally_unique_string()}.pkl'
534
571
  save_object_to_file(data, chunk_path)
@@ -544,8 +581,12 @@ def load_chunks(name):
544
581
 
545
582
 
546
583
  def clear_chunks(name):
584
+ import os
547
585
  if os.path.exists(name):
548
586
  empty_directory(name)
549
587
 
588
+
550
589
  def list_drives():
590
+ import os
591
+ import string
551
592
  return [f"{d}:\\" for d in string.ascii_uppercase if os.path.exists(f"{d}:\\")]