pygpt-net 2.7.4__py3-none-any.whl → 2.7.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.
Files changed (133) hide show
  1. pygpt_net/CHANGELOG.txt +7 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app_core.py +4 -2
  4. pygpt_net/controller/__init__.py +5 -1
  5. pygpt_net/controller/assistant/assistant.py +1 -4
  6. pygpt_net/controller/assistant/batch.py +5 -504
  7. pygpt_net/controller/assistant/editor.py +5 -5
  8. pygpt_net/controller/assistant/files.py +16 -16
  9. pygpt_net/controller/chat/handler/google_stream.py +307 -1
  10. pygpt_net/controller/chat/handler/worker.py +8 -1
  11. pygpt_net/controller/chat/image.py +2 -2
  12. pygpt_net/controller/dialogs/confirm.py +73 -101
  13. pygpt_net/controller/lang/mapping.py +9 -9
  14. pygpt_net/controller/painter/capture.py +50 -1
  15. pygpt_net/controller/presets/presets.py +2 -1
  16. pygpt_net/controller/remote_store/__init__.py +12 -0
  17. pygpt_net/{provider/core/assistant_file/db_sqlite → controller/remote_store/google}/__init__.py +2 -2
  18. pygpt_net/controller/remote_store/google/batch.py +402 -0
  19. pygpt_net/controller/remote_store/google/store.py +615 -0
  20. pygpt_net/controller/remote_store/openai/__init__.py +12 -0
  21. pygpt_net/controller/remote_store/openai/batch.py +524 -0
  22. pygpt_net/controller/{assistant → remote_store/openai}/store.py +63 -60
  23. pygpt_net/controller/remote_store/remote_store.py +35 -0
  24. pygpt_net/controller/ui/ui.py +20 -1
  25. pygpt_net/core/assistants/assistants.py +3 -15
  26. pygpt_net/core/db/database.py +5 -3
  27. pygpt_net/core/locale/placeholder.py +35 -0
  28. pygpt_net/core/remote_store/__init__.py +12 -0
  29. pygpt_net/core/remote_store/google/__init__.py +11 -0
  30. pygpt_net/core/remote_store/google/files.py +224 -0
  31. pygpt_net/core/remote_store/google/store.py +248 -0
  32. pygpt_net/core/remote_store/openai/__init__.py +11 -0
  33. pygpt_net/core/{assistants → remote_store/openai}/files.py +26 -19
  34. pygpt_net/core/{assistants → remote_store/openai}/store.py +32 -15
  35. pygpt_net/core/remote_store/remote_store.py +24 -0
  36. pygpt_net/data/config/config.json +8 -4
  37. pygpt_net/data/config/models.json +77 -3
  38. pygpt_net/data/config/settings.json +45 -0
  39. pygpt_net/data/locale/locale.de.ini +41 -41
  40. pygpt_net/data/locale/locale.en.ini +53 -43
  41. pygpt_net/data/locale/locale.es.ini +41 -41
  42. pygpt_net/data/locale/locale.fr.ini +41 -41
  43. pygpt_net/data/locale/locale.it.ini +41 -41
  44. pygpt_net/data/locale/locale.pl.ini +42 -42
  45. pygpt_net/data/locale/locale.uk.ini +41 -41
  46. pygpt_net/data/locale/locale.zh.ini +41 -41
  47. pygpt_net/data/locale/plugin.cmd_history.de.ini +1 -1
  48. pygpt_net/data/locale/plugin.cmd_history.en.ini +1 -1
  49. pygpt_net/data/locale/plugin.cmd_history.es.ini +1 -1
  50. pygpt_net/data/locale/plugin.cmd_history.fr.ini +1 -1
  51. pygpt_net/data/locale/plugin.cmd_history.it.ini +1 -1
  52. pygpt_net/data/locale/plugin.cmd_history.pl.ini +1 -1
  53. pygpt_net/data/locale/plugin.cmd_history.uk.ini +1 -1
  54. pygpt_net/data/locale/plugin.cmd_history.zh.ini +1 -1
  55. pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +14 -0
  56. pygpt_net/data/locale/plugin.cmd_web.de.ini +1 -1
  57. pygpt_net/data/locale/plugin.cmd_web.en.ini +1 -1
  58. pygpt_net/data/locale/plugin.cmd_web.es.ini +1 -1
  59. pygpt_net/data/locale/plugin.cmd_web.fr.ini +1 -1
  60. pygpt_net/data/locale/plugin.cmd_web.it.ini +1 -1
  61. pygpt_net/data/locale/plugin.cmd_web.pl.ini +1 -1
  62. pygpt_net/data/locale/plugin.cmd_web.uk.ini +1 -1
  63. pygpt_net/data/locale/plugin.cmd_web.zh.ini +1 -1
  64. pygpt_net/data/locale/plugin.idx_llama_index.de.ini +2 -2
  65. pygpt_net/data/locale/plugin.idx_llama_index.en.ini +2 -2
  66. pygpt_net/data/locale/plugin.idx_llama_index.es.ini +2 -2
  67. pygpt_net/data/locale/plugin.idx_llama_index.fr.ini +2 -2
  68. pygpt_net/data/locale/plugin.idx_llama_index.it.ini +2 -2
  69. pygpt_net/data/locale/plugin.idx_llama_index.pl.ini +2 -2
  70. pygpt_net/data/locale/plugin.idx_llama_index.uk.ini +2 -2
  71. pygpt_net/data/locale/plugin.idx_llama_index.zh.ini +2 -2
  72. pygpt_net/item/assistant.py +1 -211
  73. pygpt_net/item/ctx.py +3 -1
  74. pygpt_net/item/store.py +238 -0
  75. pygpt_net/migrations/Version20260102190000.py +35 -0
  76. pygpt_net/migrations/__init__.py +3 -1
  77. pygpt_net/plugin/cmd_mouse_control/config.py +470 -1
  78. pygpt_net/plugin/cmd_mouse_control/plugin.py +488 -22
  79. pygpt_net/plugin/cmd_mouse_control/worker.py +464 -87
  80. pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +729 -0
  81. pygpt_net/plugin/idx_llama_index/config.py +2 -2
  82. pygpt_net/provider/api/google/__init__.py +16 -54
  83. pygpt_net/provider/api/google/chat.py +546 -129
  84. pygpt_net/provider/api/google/computer.py +190 -0
  85. pygpt_net/provider/api/google/realtime/realtime.py +2 -2
  86. pygpt_net/provider/api/google/remote_tools.py +93 -0
  87. pygpt_net/provider/api/google/store.py +546 -0
  88. pygpt_net/provider/api/google/worker/__init__.py +0 -0
  89. pygpt_net/provider/api/google/worker/importer.py +392 -0
  90. pygpt_net/provider/api/openai/computer.py +10 -1
  91. pygpt_net/provider/api/openai/store.py +6 -6
  92. pygpt_net/provider/api/openai/worker/importer.py +24 -24
  93. pygpt_net/provider/core/config/patch.py +16 -1
  94. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +3 -3
  95. pygpt_net/provider/core/model/patch.py +17 -3
  96. pygpt_net/provider/core/preset/json_file.py +13 -7
  97. pygpt_net/provider/core/{assistant_file → remote_file}/__init__.py +1 -1
  98. pygpt_net/provider/core/{assistant_file → remote_file}/base.py +9 -9
  99. pygpt_net/provider/core/remote_file/db_sqlite/__init__.py +12 -0
  100. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/patch.py +1 -1
  101. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/provider.py +23 -20
  102. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/storage.py +35 -27
  103. pygpt_net/provider/core/{assistant_file → remote_file}/db_sqlite/utils.py +5 -4
  104. pygpt_net/provider/core/{assistant_store → remote_store}/__init__.py +1 -1
  105. pygpt_net/provider/core/{assistant_store → remote_store}/base.py +10 -10
  106. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/__init__.py +1 -1
  107. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/patch.py +1 -1
  108. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/provider.py +16 -15
  109. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/storage.py +30 -23
  110. pygpt_net/provider/core/{assistant_store → remote_store}/db_sqlite/utils.py +5 -4
  111. pygpt_net/provider/core/{assistant_store → remote_store}/json_file.py +9 -9
  112. pygpt_net/provider/llms/google.py +2 -2
  113. pygpt_net/ui/base/config_dialog.py +3 -2
  114. pygpt_net/ui/dialog/assistant.py +3 -3
  115. pygpt_net/ui/dialog/plugins.py +3 -1
  116. pygpt_net/ui/dialog/remote_store_google.py +539 -0
  117. pygpt_net/ui/dialog/{assistant_store.py → remote_store_openai.py} +95 -95
  118. pygpt_net/ui/dialogs.py +5 -3
  119. pygpt_net/ui/layout/chat/attachments_uploaded.py +3 -3
  120. pygpt_net/ui/layout/toolbox/computer_env.py +26 -8
  121. pygpt_net/ui/menu/tools.py +13 -5
  122. pygpt_net/ui/widget/dialog/remote_store_google.py +56 -0
  123. pygpt_net/ui/widget/dialog/{assistant_store.py → remote_store_openai.py} +9 -9
  124. pygpt_net/ui/widget/element/button.py +4 -4
  125. pygpt_net/ui/widget/lists/remote_store_google.py +248 -0
  126. pygpt_net/ui/widget/lists/{assistant_store.py → remote_store_openai.py} +21 -21
  127. pygpt_net/ui/widget/option/checkbox_list.py +47 -9
  128. pygpt_net/ui/widget/option/combo.py +39 -3
  129. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/METADATA +33 -2
  130. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/RECORD +133 -108
  131. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/LICENSE +0 -0
  132. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/WHEEL +0 -0
  133. {pygpt_net-2.7.4.dist-info → pygpt_net-2.7.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,546 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2026.01.02 20:00:00 #
10
+ # ================================================== #
11
+
12
+ import os
13
+ import time
14
+ from typing import Optional, List, Dict, Any
15
+
16
+ from pygpt_net.item.store import RemoteStoreItem
17
+
18
+ from .worker.importer import Importer
19
+
20
+ class Store:
21
+ def __init__(self, window=None):
22
+ """
23
+ Google (Gemini) File Search stores API wrapper
24
+
25
+ :param window: Window instance
26
+ """
27
+ self.window = window
28
+ self.importer = Importer(window)
29
+
30
+ def get_client(self):
31
+ """
32
+ Get Google GenAI client
33
+
34
+ :return: google.genai.Client
35
+ """
36
+ return self.window.core.api.google.get_client()
37
+
38
+ def log(self, msg: str, callback: Optional[callable] = None):
39
+ """
40
+ Log message
41
+
42
+ :param msg: message to log
43
+ :param callback: callback log function
44
+ """
45
+ if callback is not None:
46
+ callback(msg)
47
+ else:
48
+ print(msg)
49
+
50
+ # -----------------------------
51
+ # helpers
52
+ # -----------------------------
53
+
54
+ def _clamp(self, val: int, lo: int, hi: int) -> int:
55
+ """Clamp integer value into range [lo, hi]."""
56
+ try:
57
+ v = int(val or 0)
58
+ except Exception:
59
+ v = lo
60
+ if v < lo:
61
+ v = lo
62
+ if v > hi:
63
+ v = hi
64
+ return v
65
+
66
+ # -----------------------------
67
+ # Files service (global)
68
+ # -----------------------------
69
+
70
+ def get_file(self, file_name: str):
71
+ """
72
+ Get Files API file metadata by name (e.g. 'files/abc-123').
73
+
74
+ :param file_name: Files API resource name
75
+ :return: file metadata
76
+ """
77
+ client = self.get_client()
78
+ return client.files.get(name=file_name)
79
+
80
+ def upload(self, path: str) -> Optional[str]:
81
+ """
82
+ Upload file to Files API (not to a File Search Store). Use when you want to import later.
83
+
84
+ :param path: file path
85
+ :return: Files API resource name (e.g., 'files/abc-123') or None
86
+ """
87
+ client = self.get_client()
88
+ if not os.path.exists(path):
89
+ return None
90
+
91
+ cfg = {'name': os.path.basename(path)}
92
+ res = client.files.upload(file=path, config=cfg)
93
+ if res is not None and hasattr(res, "name"):
94
+ return res.name
95
+
96
+ def delete_file(self, file_name: str) -> Optional[str]:
97
+ """
98
+ Delete file from Files API
99
+
100
+ :param file_name: Files API resource name ('files/...').
101
+ :return: file_name if removed
102
+ """
103
+ client = self.get_client()
104
+ res = client.files.delete(name=file_name)
105
+ if res is not None:
106
+ return file_name
107
+
108
+ def get_files_ids_all(
109
+ self,
110
+ items: list,
111
+ order: str = "asc",
112
+ limit: int = 100,
113
+ after: Optional[str] = None,
114
+ ) -> list:
115
+ """
116
+ Get all Files API file names (paginated)
117
+
118
+ :param items: items accumulator
119
+ :param order: unused, for compatibility
120
+ :param limit: page size (max 100 for Files API)
121
+ :param after: page token
122
+ :return: file names list
123
+ """
124
+ client = self.get_client()
125
+ limit = self._clamp(limit, 1, 100) # Files API allows up to 100
126
+ cfg: Dict[str, Any] = {'page_size': limit}
127
+ if after:
128
+ cfg['page_token'] = after
129
+
130
+ pager = client.files.list(config=cfg)
131
+ for f in pager:
132
+ name = getattr(f, "name", None)
133
+ if name and name not in items:
134
+ items.append(name)
135
+
136
+ # The SDK pager auto-iterates pages; explicit tokens are optional
137
+ return items
138
+
139
+ def get_files_ids(self) -> List[str]:
140
+ """
141
+ Get Files API file names (single iterator over all pages)
142
+
143
+ :return: list of file names
144
+ """
145
+ client = self.get_client()
146
+ items = []
147
+ pager = client.files.list()
148
+ for f in pager:
149
+ name = getattr(f, "name", None)
150
+ if name and name not in items:
151
+ items.append(name)
152
+ return items
153
+
154
+ def remove_files(self, callback: Optional[callable] = None) -> int:
155
+ """
156
+ Remove all Files API files
157
+
158
+ :param callback: callback function
159
+ :return: number of deleted files
160
+ """
161
+ num = 0
162
+ files = self.get_files_ids()
163
+ for file_name in files:
164
+ self.log("Removing file: " + file_name, callback)
165
+ try:
166
+ res = self.delete_file(file_name)
167
+ if res:
168
+ num += 1
169
+ except Exception as e:
170
+ msg = "Error removing file {}: {}".format(file_name, str(e))
171
+ self.log(msg, callback)
172
+ return num
173
+
174
+ def remove_file(self, file_name: str, callback: Optional[callable] = None) -> bool:
175
+ """
176
+ Remove a single Files API file
177
+
178
+ :param file_name: Files API resource name ('files/...').
179
+ :param callback: callback function
180
+ :return: True if removed
181
+ """
182
+ self.log("Removing file: " + file_name, callback)
183
+ try:
184
+ res = self.delete_file(file_name)
185
+ return res is not None
186
+ except Exception as e:
187
+ msg = "Error removing file {}: {}".format(file_name, str(e))
188
+ self.log(msg, callback)
189
+ raise
190
+
191
+ # -----------------------------
192
+ # File Search stores
193
+ # -----------------------------
194
+
195
+ def import_stores(
196
+ self,
197
+ items: dict,
198
+ order: str = "asc",
199
+ limit: int = 100,
200
+ after: Optional[str] = None,
201
+ callback: Optional[callable] = None
202
+ ) -> dict:
203
+ """
204
+ Import File Search stores
205
+
206
+ :param items: items dict accumulator
207
+ :param order: unused
208
+ :param limit: page size (Google limit: 20)
209
+ :param after: page token
210
+ :param callback: callback
211
+ :return: items dict of RemoteStoreItem
212
+ """
213
+ client = self.get_client()
214
+ limit = self._clamp(limit, 1, 20) # API limit = 20
215
+ cfg: Dict[str, Any] = {'page_size': limit}
216
+ if after:
217
+ cfg['page_token'] = after
218
+
219
+ pager = client.file_search_stores.list(config=cfg)
220
+ for remote in pager:
221
+ name = getattr(remote, "name", None) # 'fileSearchStores/...'
222
+ if not name:
223
+ continue
224
+ if name not in items:
225
+ items[name] = RemoteStoreItem()
226
+ items[name].id = name
227
+ items[name].name = getattr(remote, "display_name", "") or ""
228
+ items[name].file_ids = []
229
+ items[name].provider = "google"
230
+
231
+ status = self.window.core.remote_store.google.parse_status(remote)
232
+ self.window.core.remote_store.google.append_status(items[name], status)
233
+ self.log("Imported file search store: " + name, callback)
234
+
235
+ return items
236
+
237
+ def create_store(self, name: str, expire_days: int = 0):
238
+ """
239
+ Create File Search store
240
+
241
+ :param name: display name
242
+ :param expire_days: ignored (not supported)
243
+ :return: store object
244
+ """
245
+ client = self.get_client()
246
+ store = client.file_search_stores.create(config={'display_name': name})
247
+ if store is not None:
248
+ return store
249
+
250
+ def update_store(self, id: str, name: str, expire_days: int = 0):
251
+ """
252
+ Update File Search store (no remote update available). Returns the current remote store.
253
+
254
+ :param id: store name ('fileSearchStores/...').
255
+ :param name: local alias (persisted locally)
256
+ :param expire_days: ignored
257
+ :return: store object (fresh get) or None if not found
258
+ """
259
+ try:
260
+ return self.get_store(id)
261
+ except Exception:
262
+ return None
263
+
264
+ def get_store(self, id: str):
265
+ """
266
+ Get File Search store
267
+
268
+ :param id: store name ('fileSearchStores/...').
269
+ :return: store object
270
+ """
271
+ client = self.get_client()
272
+ return client.file_search_stores.get(name=id)
273
+
274
+ def remove_store(self, id: str):
275
+ """
276
+ Delete File Search store (force delete related documents)
277
+
278
+ :param id: store name ('fileSearchStores/...').
279
+ :return: delete response
280
+ """
281
+ client = self.get_client()
282
+ return client.file_search_stores.delete(name=id, config={'force': True})
283
+
284
+ def get_stores_ids(
285
+ self,
286
+ items: list,
287
+ order: str = "asc",
288
+ limit: int = 100,
289
+ after: Optional[str] = None,
290
+ ) -> list:
291
+ """
292
+ Get all File Search store names
293
+
294
+ :param items: items accumulator
295
+ :param order: unused
296
+ :param limit: page size (Google limit 20)
297
+ :param after: page token
298
+ :return: list of store names
299
+ """
300
+ client = self.get_client()
301
+ limit = self._clamp(limit, 1, 20)
302
+ cfg: Dict[str, Any] = {'page_size': limit}
303
+ if after:
304
+ cfg['page_token'] = after
305
+
306
+ pager = client.file_search_stores.list(config=cfg)
307
+ for remote in pager:
308
+ name = getattr(remote, "name", None)
309
+ if name and name not in items:
310
+ items.append(name)
311
+ return items
312
+
313
+ # -----------------------------
314
+ # Documents within a store
315
+ # -----------------------------
316
+
317
+ def get_store_files_ids(
318
+ self,
319
+ store_id: str,
320
+ items: list,
321
+ order: str = "asc",
322
+ limit: int = 100,
323
+ after: Optional[str] = None,
324
+ ) -> list:
325
+ """
326
+ Get all Document names for a File Search store
327
+
328
+ :param store_id: store name ('fileSearchStores/...').
329
+ :param items: items accumulator
330
+ :param order: unused
331
+ :param limit: page size (Google limit 20)
332
+ :param after: page token
333
+ :return: list of Document names ('fileSearchStores/.../documents/...').
334
+ """
335
+ client = self.get_client()
336
+ limit = self._clamp(limit, 1, 20)
337
+ cfg: Dict[str, Any] = {'page_size': limit}
338
+ if after:
339
+ cfg['page_token'] = after
340
+
341
+ try:
342
+ pager = client.file_search_stores.documents.list(parent=store_id, config=cfg)
343
+ except Exception:
344
+ pager = client.documents.list(parent=store_id, config=cfg)
345
+
346
+ for remote in pager:
347
+ name = getattr(remote, "name", None)
348
+ if name and name not in items:
349
+ items.append(name)
350
+ return items
351
+
352
+ def remove_from_stores(self) -> int:
353
+ """
354
+ Remove all documents from all File Search stores
355
+
356
+ :return: number of deleted documents
357
+ """
358
+ stores = self.get_stores_ids([])
359
+ num = 0
360
+ for store_id in stores:
361
+ files = self.get_store_files_ids(store_id, [])
362
+ for doc_name in files:
363
+ self.log("Removing document from store [{}]:{} ".format(store_id, doc_name))
364
+ self.delete_store_file(store_id, doc_name)
365
+ num += 1
366
+ return num
367
+
368
+ def remove_from_store(self, store_id: str) -> int:
369
+ """
370
+ Remove all documents from a specific File Search store
371
+
372
+ :param store_id: store name ('fileSearchStores/...').
373
+ :return: number of deleted documents
374
+ """
375
+ files = self.get_store_files_ids(store_id, [])
376
+ num = 0
377
+ for doc_name in files:
378
+ self.log("Removing document from store [{}]:{} ".format(store_id, doc_name))
379
+ self.delete_store_file(store_id, doc_name)
380
+ num += 1
381
+ return num
382
+
383
+ def remove_all(self, callback: Optional[callable] = None) -> int:
384
+ """
385
+ Remove all File Search stores
386
+
387
+ :param callback: callback function
388
+ :return: number of deleted stores
389
+ """
390
+ num = 0
391
+ stores = self.get_stores_ids([])
392
+ for store_id in stores:
393
+ self.log("Removing file search store: " + store_id, callback)
394
+ try:
395
+ self.remove_store(store_id)
396
+ num += 1
397
+ except Exception as e:
398
+ msg = "Error removing file search store {}: {}".format(store_id, str(e))
399
+ self.log(msg, callback)
400
+ return num
401
+
402
+ def add_file(self, store_id: str, file_name: str):
403
+ """
404
+ Import a Files API file into a File Search store.
405
+
406
+ :param store_id: store name ('fileSearchStores/...').
407
+ :param file_name: Files API file name ('files/...').
408
+ :return: operation (long running)
409
+ """
410
+ client = self.get_client()
411
+ op = client.file_search_stores.import_file(
412
+ file_search_store_name=store_id,
413
+ file_name=file_name,
414
+ )
415
+ return op
416
+
417
+ def delete_store_file(self, store_id: str, document_name: str):
418
+ """
419
+ Delete a Document from a File Search store.
420
+
421
+ :param store_id: store name ('fileSearchStores/...').
422
+ :param document_name: document resource name ('fileSearchStores/.../documents/...').
423
+ :return: delete response
424
+ """
425
+ client = self.get_client()
426
+ return client.file_search_stores.documents.delete(name=document_name, config={'force': True})
427
+
428
+ def remove_store_file(self, store_id: str, document_name: str):
429
+ """
430
+ Compatibility alias used by controller: remove a document from a File Search store.
431
+ """
432
+ return self.delete_store_file(store_id, document_name)
433
+
434
+ def import_stores_files(self, callback: Optional[callable] = None) -> int:
435
+ """
436
+ Import all documents (files) from all File Search stores
437
+
438
+ :param callback: callback function
439
+ :return: number of imported documents
440
+ """
441
+ store_ids = self.get_stores_ids([])
442
+ num = 0
443
+ for store_id in store_ids:
444
+ items = []
445
+ try:
446
+ items = self.import_store_files(store_id, items, callback=callback)
447
+ except Exception as e:
448
+ msg = "Error importing store {} documents list: {}".format(store_id, str(e))
449
+ self.log(msg, callback)
450
+ num += len(items)
451
+ return num
452
+
453
+ def import_store_files(
454
+ self,
455
+ store_id: str,
456
+ items: list,
457
+ order: str = "asc",
458
+ limit: int = 100,
459
+ after: Optional[str] = None,
460
+ callback: Optional[callable] = None
461
+ ) -> list:
462
+ """
463
+ Import a store's documents and insert into local DB
464
+
465
+ :param store_id: store name ('fileSearchStores/...').
466
+ :param items: accumulator
467
+ :param order: unused
468
+ :param limit: page size (Google limit 20)
469
+ :param after: page token
470
+ :param callback: log callback
471
+ :return: list of imported document names
472
+ """
473
+ client = self.get_client()
474
+ limit = self._clamp(limit, 1, 20)
475
+ cfg: Dict[str, Any] = {'page_size': limit}
476
+ if after:
477
+ cfg['page_token'] = after
478
+
479
+ try:
480
+ pager = client.file_search_stores.documents.list(parent=store_id, config=cfg)
481
+ except Exception:
482
+ pager = client.documents.list(parent=store_id, config=cfg)
483
+
484
+ for remote in pager:
485
+ try:
486
+ doc_name = getattr(remote, "name", None)
487
+ if not doc_name:
488
+ continue
489
+ if doc_name not in items:
490
+ items.append(doc_name)
491
+ try:
492
+ doc = client.file_search_stores.documents.get(name=doc_name)
493
+ except Exception:
494
+ doc = remote
495
+ self.window.core.remote_store.google.files.insert(store_id, doc)
496
+ msg = "Imported document {} to store {}".format(doc_name, store_id)
497
+ self.log(msg, callback)
498
+ except Exception as e:
499
+ msg = "Error importing document {} to store {}: {}".format(
500
+ getattr(remote, "name", "?"), store_id, str(e)
501
+ )
502
+ self.log(msg, callback)
503
+ return items
504
+
505
+ # -----------------------------
506
+ # Convenience: direct upload to a store
507
+ # -----------------------------
508
+
509
+ def upload_to_store(self, store_id: str, path: str):
510
+ """
511
+ Directly upload a local file to a File Search store. This creates a Document.
512
+
513
+ :param store_id: store name ('fileSearchStores/...').
514
+ :param path: file path
515
+ :return: created document object or None
516
+ """
517
+ client = self.get_client()
518
+ if not os.path.exists(path):
519
+ return None
520
+
521
+ basename = os.path.basename(path)
522
+ op = client.file_search_stores.upload_to_file_search_store(
523
+ file=path,
524
+ file_search_store_name=store_id,
525
+ config={'display_name': basename},
526
+ )
527
+
528
+ for _ in range(180):
529
+ if getattr(op, "done", False):
530
+ break
531
+ time.sleep(2)
532
+ op = client.operations.get(op)
533
+
534
+ try:
535
+ docs = self.get_store_files_ids(store_id, [])
536
+ for doc_name in docs:
537
+ try:
538
+ d = client.file_search_stores.documents.get(name=doc_name)
539
+ except Exception:
540
+ continue
541
+ if getattr(d, "display_name", None) == basename:
542
+ return d
543
+ except Exception:
544
+ pass
545
+
546
+ return None
File without changes