girder-jsonforms 2.0.0__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.
- girder_jsonforms/__init__.py +83 -0
- girder_jsonforms/lib/__init__.py +0 -0
- girder_jsonforms/lib/google_drive.py +157 -0
- girder_jsonforms/lib/jq.py +134 -0
- girder_jsonforms/models/__init__.py +0 -0
- girder_jsonforms/models/entry.py +245 -0
- girder_jsonforms/models/form.py +287 -0
- girder_jsonforms/rest/__init__.py +0 -0
- girder_jsonforms/rest/entry.py +126 -0
- girder_jsonforms/rest/form.py +362 -0
- girder_jsonforms/web_client/dist/girder-plugin-jsonforms.umd.cjs +88 -0
- girder_jsonforms/web_client/dist/style.css +1 -0
- girder_jsonforms-2.0.0.dist-info/LICENSE +27 -0
- girder_jsonforms-2.0.0.dist-info/METADATA +33 -0
- girder_jsonforms-2.0.0.dist-info/RECORD +18 -0
- girder_jsonforms-2.0.0.dist-info/WHEEL +5 -0
- girder_jsonforms-2.0.0.dist-info/entry_points.txt +2 -0
- girder_jsonforms-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from girder import events
|
|
8
|
+
from girder.constants import AccessType
|
|
9
|
+
from girder.models.file import File
|
|
10
|
+
from girder.models.item import Item
|
|
11
|
+
from girder.plugin import GirderPlugin, registerPluginStaticContent
|
|
12
|
+
from girder.utility.model_importer import ModelImporter
|
|
13
|
+
|
|
14
|
+
from .lib.google_drive import authenticate_gdrive, upload_file_to_gdrive
|
|
15
|
+
from .models.entry import FormEntry as FormEntryModel
|
|
16
|
+
from .models.form import Form as FormModel
|
|
17
|
+
from .rest.entry import FormEntry
|
|
18
|
+
from .rest.form import Form
|
|
19
|
+
|
|
20
|
+
GDRIVE_SERVICE = None
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def annotate_uploads(event):
|
|
25
|
+
info = event.info
|
|
26
|
+
if "reference" not in info:
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
reference = json.loads(info["reference"])
|
|
31
|
+
except (ValueError, TypeError):
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
if reference.get("annotate"):
|
|
35
|
+
parent = Item().load(
|
|
36
|
+
info["file"]["itemId"], level=AccessType.WRITE, user=info["currentUser"]
|
|
37
|
+
)
|
|
38
|
+
reference.pop("file", None)
|
|
39
|
+
reference.pop("annotate", None)
|
|
40
|
+
Item().setMetadata(parent, reference)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def upload_to_gdrive(event):
|
|
44
|
+
global GDRIVE_SERVICE
|
|
45
|
+
info = event.info
|
|
46
|
+
file = info["file"]
|
|
47
|
+
|
|
48
|
+
with File().open(file) as fh:
|
|
49
|
+
gdrive_file_id = upload_file_to_gdrive(
|
|
50
|
+
GDRIVE_SERVICE,
|
|
51
|
+
info["gdriveFolderId"],
|
|
52
|
+
info["path"],
|
|
53
|
+
fh,
|
|
54
|
+
mimetype=file["mimeType"],
|
|
55
|
+
)
|
|
56
|
+
parent = Item().load(
|
|
57
|
+
file["itemId"], level=AccessType.WRITE, user=info["currentUser"]
|
|
58
|
+
)
|
|
59
|
+
Item().setMetadata(parent, {"gdriveFileId": gdrive_file_id})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class JSONFormsPlugin(GirderPlugin):
|
|
63
|
+
DISPLAY_NAME = "JSON Forms"
|
|
64
|
+
|
|
65
|
+
def load(self, info):
|
|
66
|
+
ModelImporter.registerModel("form", FormModel, plugin="jsonforms")
|
|
67
|
+
ModelImporter.registerModel("entry", FormEntryModel, plugin="jsonforms")
|
|
68
|
+
global GDRIVE_SERVICE
|
|
69
|
+
try:
|
|
70
|
+
GDRIVE_SERVICE = authenticate_gdrive()
|
|
71
|
+
except ValueError:
|
|
72
|
+
logger.exception("Failed to authenticate with Google Drive")
|
|
73
|
+
info["apiRoot"].form = Form()
|
|
74
|
+
info["apiRoot"].entry = FormEntry()
|
|
75
|
+
events.bind("data.process", "jsonforms", annotate_uploads)
|
|
76
|
+
events.bind("gdrive.upload", "jsonforms", upload_to_gdrive)
|
|
77
|
+
registerPluginStaticContent(
|
|
78
|
+
plugin="jsonforms",
|
|
79
|
+
css=["/style.css"],
|
|
80
|
+
js=["/girder-plugin-jsonforms.umd.cjs"],
|
|
81
|
+
staticDir=os.path.join(os.path.dirname(__file__), "web_client", "dist"),
|
|
82
|
+
tree=info["serverRoot"],
|
|
83
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pickle
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from google.auth.transport.requests import Request
|
|
6
|
+
|
|
7
|
+
# from google_auth_oauthlib.flow import InstalledAppFlow
|
|
8
|
+
from googleapiclient.discovery import build
|
|
9
|
+
from googleapiclient.http import MediaIoBaseUpload
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("google_drive")
|
|
12
|
+
logprint = logger
|
|
13
|
+
# If modifying these scopes, delete the file token.pickle.
|
|
14
|
+
SCOPES = ["https://www.googleapis.com/auth/drive"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_credentials():
|
|
18
|
+
"""Gets valid user credentials from storage.
|
|
19
|
+
|
|
20
|
+
If nothing has been stored, or if the stored credentials are invalid,
|
|
21
|
+
the OAuth2 flow is completed to obtain the new credentials.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Credentials, the obtained credential.
|
|
25
|
+
"""
|
|
26
|
+
creds = None
|
|
27
|
+
# The file token.pickle stores the user's access and refresh tokens, and is
|
|
28
|
+
# created automatically when the authorization flow completes for the first
|
|
29
|
+
# time.
|
|
30
|
+
|
|
31
|
+
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "token.pickle")
|
|
32
|
+
|
|
33
|
+
if os.path.exists(path):
|
|
34
|
+
with open(path, "rb") as token:
|
|
35
|
+
creds = pickle.load(token)
|
|
36
|
+
# If there are no (valid) credentials available, let the user log in.
|
|
37
|
+
if not creds or not creds.valid:
|
|
38
|
+
if creds and creds.expired and creds.refresh_token:
|
|
39
|
+
creds.refresh(Request())
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError("No valid credentials found")
|
|
42
|
+
# flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES)
|
|
43
|
+
# creds = flow.run_local_server(port=0)
|
|
44
|
+
# Save the credentials for the next run
|
|
45
|
+
with open(path, "wb") as token:
|
|
46
|
+
pickle.dump(creds, token)
|
|
47
|
+
return creds
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def authenticate_gdrive():
|
|
51
|
+
# Load credentials from a file or any other secure source
|
|
52
|
+
# Replace 'path_to_credentials.json' with the actual path to your credentials file
|
|
53
|
+
credentials = get_credentials()
|
|
54
|
+
return build("drive", "v3", credentials=credentials)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def create_folders(service, folder_id, path):
|
|
58
|
+
if service is None:
|
|
59
|
+
logger.exception("Service not initialized. Failed to create folder.")
|
|
60
|
+
return
|
|
61
|
+
current_folder_id = folder_id
|
|
62
|
+
folders = path.split("/")
|
|
63
|
+
for folder in folders:
|
|
64
|
+
if folder:
|
|
65
|
+
# Check if folder already exists
|
|
66
|
+
query = f"'{current_folder_id}' in parents and mimeType='application/vnd.google-apps.folder' and name='{folder}' and trashed=false"
|
|
67
|
+
response = (
|
|
68
|
+
service.files()
|
|
69
|
+
.list(
|
|
70
|
+
q=query,
|
|
71
|
+
fields="files(id)",
|
|
72
|
+
includeItemsFromAllDrives=True,
|
|
73
|
+
supportsAllDrives=True,
|
|
74
|
+
)
|
|
75
|
+
.execute()
|
|
76
|
+
)
|
|
77
|
+
existing_folders = response.get("files", [])
|
|
78
|
+
if existing_folders:
|
|
79
|
+
logprint.error(f"Folder '{folder}' already exists")
|
|
80
|
+
logprint.error(response)
|
|
81
|
+
current_folder_id = existing_folders[0]["id"]
|
|
82
|
+
else:
|
|
83
|
+
logprint.error(f"Creating folder '{folder}'")
|
|
84
|
+
file_metadata = {
|
|
85
|
+
"name": folder,
|
|
86
|
+
"mimeType": "application/vnd.google-apps.folder",
|
|
87
|
+
"parents": [current_folder_id],
|
|
88
|
+
}
|
|
89
|
+
folder_result = (
|
|
90
|
+
service.files()
|
|
91
|
+
.create(body=file_metadata, fields="id", supportsAllDrives=True)
|
|
92
|
+
.execute()
|
|
93
|
+
)
|
|
94
|
+
logprint.error(
|
|
95
|
+
f"Folder '{folder}' created with ID: {folder_result['id']}"
|
|
96
|
+
)
|
|
97
|
+
current_folder_id = folder_result["id"]
|
|
98
|
+
|
|
99
|
+
return current_folder_id
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def upload_file_to_gdrive(service, folder_id, path, file_handle, mimetype="text/plain"):
|
|
103
|
+
if service is None:
|
|
104
|
+
logger.exception("Service not initialized. Failed to upload file.")
|
|
105
|
+
return
|
|
106
|
+
file_path, file_name = os.path.split(path)
|
|
107
|
+
folder_id = create_folders(service, folder_id, file_path)
|
|
108
|
+
logprint.error(f"Uploading file '{file_name}' to folder '{folder_id}'")
|
|
109
|
+
|
|
110
|
+
# Check if file already exists in the folder
|
|
111
|
+
query = f"'{folder_id}' in parents and name='{file_name}' and trashed=false"
|
|
112
|
+
response = (
|
|
113
|
+
service.files()
|
|
114
|
+
.list(
|
|
115
|
+
q=query,
|
|
116
|
+
fields="files(id)",
|
|
117
|
+
includeItemsFromAllDrives=True,
|
|
118
|
+
supportsAllDrives=True,
|
|
119
|
+
)
|
|
120
|
+
.execute()
|
|
121
|
+
)
|
|
122
|
+
existing_files = response.get("files", [])
|
|
123
|
+
media = MediaIoBaseUpload(file_handle, mimetype=mimetype)
|
|
124
|
+
if existing_files:
|
|
125
|
+
file_id = existing_files[0]["id"]
|
|
126
|
+
file_result = (
|
|
127
|
+
service.files()
|
|
128
|
+
.update(fileId=file_id, media_body=media, supportsAllDrives=True)
|
|
129
|
+
.execute()
|
|
130
|
+
)
|
|
131
|
+
logprint.error(f'File {file_name} updated with ID: {file_result["id"]}')
|
|
132
|
+
else:
|
|
133
|
+
file_metadata = {"name": file_name, "parents": [folder_id]}
|
|
134
|
+
file_result = (
|
|
135
|
+
service.files()
|
|
136
|
+
.create(
|
|
137
|
+
body=file_metadata,
|
|
138
|
+
media_body=media,
|
|
139
|
+
fields="id",
|
|
140
|
+
supportsAllDrives=True,
|
|
141
|
+
)
|
|
142
|
+
.execute()
|
|
143
|
+
)
|
|
144
|
+
logprint.error(
|
|
145
|
+
f'File {file_metadata["name"]} uploaded with ID: {file_result["id"]}'
|
|
146
|
+
)
|
|
147
|
+
return file_result["id"]
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Example usage
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
# folder_id = "1WgY3HJ0bxzQc-IA_dBdiwb9Hvq6ho3HS" # Replace with the actual folder ID
|
|
153
|
+
folder_id = "13u4Citrr09FETrPDn2gJmBHWeaO0ylji"
|
|
154
|
+
path = "subfolder/subfolder2/subfolder4/file.txt"
|
|
155
|
+
file_handle = open("file.txt", "rb") # Replace 'file.txt' with the actual file path
|
|
156
|
+
service = authenticate_gdrive()
|
|
157
|
+
file_id = upload_file_to_gdrive(service, folder_id, path, file_handle)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
# find all occurences of a key in a nested json
|
|
4
|
+
def find_key_paths(json_data: dict, key: str, path: str = "") -> list[str]:
|
|
5
|
+
results = []
|
|
6
|
+
# Check if the input is a dictionary
|
|
7
|
+
if isinstance(json_data, dict):
|
|
8
|
+
# If the key is found, return the path to it
|
|
9
|
+
if key in json_data:
|
|
10
|
+
results.append(path + key)
|
|
11
|
+
# Recursively search through each value in the dictionary
|
|
12
|
+
for k, v in json_data.items():
|
|
13
|
+
results.extend(find_key_paths(v, key, path + k + "."))
|
|
14
|
+
# Check if the input is a list
|
|
15
|
+
elif isinstance(json_data, list):
|
|
16
|
+
# Recursively search through each item in the list
|
|
17
|
+
for i, item in enumerate(json_data):
|
|
18
|
+
if isinstance(item, dict):
|
|
19
|
+
results.extend(find_key_paths(item, key, path + f"[{i}]."))
|
|
20
|
+
elif isinstance(item, list):
|
|
21
|
+
for j, sub_item in enumerate(item):
|
|
22
|
+
results.extend(find_key_paths(sub_item, key, path + f"[{i}][{j}]."))
|
|
23
|
+
# Return None if the key is not found
|
|
24
|
+
return results
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# method to change the value of the key using key notation .[]
|
|
28
|
+
def get_value(json_data: dict, key: str) -> dict:
|
|
29
|
+
for path in key.split("."):
|
|
30
|
+
if path.startswith("["):
|
|
31
|
+
index = int(path[1:-1])
|
|
32
|
+
json_data = json_data[index]
|
|
33
|
+
else:
|
|
34
|
+
json_data = json_data[path]
|
|
35
|
+
return json_data
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_value(json_data: dict, key: str, value: Any) -> None:
|
|
39
|
+
for path in key.split(".")[:-1]:
|
|
40
|
+
if path.startswith("["):
|
|
41
|
+
index = int(path[1:-1])
|
|
42
|
+
json_data = json_data[index]
|
|
43
|
+
else:
|
|
44
|
+
json_data = json_data[path]
|
|
45
|
+
path = key.split(".")[-1]
|
|
46
|
+
json_data[path] = value
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def convert_to_jq_notation(data: dict, parent_key: str = "", sep: str = ".") -> dict:
|
|
50
|
+
"""
|
|
51
|
+
Convert a nested dictionary or list into a flat dictionary with jq-style keys.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
data (dict or list): The nested dictionary or list to flatten.
|
|
55
|
+
parent_key (str): The base key for recursion (used internally).
|
|
56
|
+
sep (str): The separator to use for keys (default is ".").
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
dict: A flattened dictionary with jq-like notation keys.
|
|
60
|
+
"""
|
|
61
|
+
items = []
|
|
62
|
+
|
|
63
|
+
if isinstance(data, dict):
|
|
64
|
+
for key, value in data.items():
|
|
65
|
+
new_key = f"{parent_key}{sep}{key}" if parent_key else key
|
|
66
|
+
items.extend(convert_to_jq_notation(value, new_key, sep).items())
|
|
67
|
+
elif isinstance(data, list):
|
|
68
|
+
for index, value in enumerate(data):
|
|
69
|
+
new_key = f"{parent_key}{sep}[{index}]"
|
|
70
|
+
items.extend(convert_to_jq_notation(value, new_key, sep).items())
|
|
71
|
+
else:
|
|
72
|
+
items.append((parent_key, data))
|
|
73
|
+
|
|
74
|
+
return dict(items)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_jq_notation(input_dict: dict) -> dict:
|
|
78
|
+
"""
|
|
79
|
+
Convert a dictionary with jq-style keys into a nested dictionary.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
input_dict (dict): The dictionary with jq-style keys.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
dict: A nested dictionary.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
def is_array_index(key):
|
|
89
|
+
return key.startswith("[") and key.endswith("]") and key[1:-1].isdigit()
|
|
90
|
+
|
|
91
|
+
output = {}
|
|
92
|
+
|
|
93
|
+
for key, value in input_dict.items():
|
|
94
|
+
keys = key.split(".")
|
|
95
|
+
current = output
|
|
96
|
+
|
|
97
|
+
for i, part in enumerate(keys[:-1]):
|
|
98
|
+
if is_array_index(part):
|
|
99
|
+
# Convert string digits to integers for lists
|
|
100
|
+
part = int(part.strip("[]"))
|
|
101
|
+
|
|
102
|
+
if "nestedlist" in key:
|
|
103
|
+
print(current, key, part)
|
|
104
|
+
if isinstance(part, int):
|
|
105
|
+
# If current is not a list, convert it into one
|
|
106
|
+
if not isinstance(current, list):
|
|
107
|
+
current[keys[i - 1]] = []
|
|
108
|
+
current = current[keys[i - 1]]
|
|
109
|
+
# Extend the list to ensure the index exists
|
|
110
|
+
|
|
111
|
+
if i + 1 <= len(keys[:-1]) and is_array_index(keys[i+1]):
|
|
112
|
+
append_object = []
|
|
113
|
+
else:
|
|
114
|
+
append_object = {}
|
|
115
|
+
|
|
116
|
+
while len(current) <= part:
|
|
117
|
+
# It has to be a placeholder for dictionary or list
|
|
118
|
+
current.append(append_object)
|
|
119
|
+
current = current[part]
|
|
120
|
+
else:
|
|
121
|
+
if part not in current:
|
|
122
|
+
# Create a list if the next key is a digit
|
|
123
|
+
next_part = keys[i + 1]
|
|
124
|
+
current[part] = [] if is_array_index(next_part) else {}
|
|
125
|
+
current = current[part]
|
|
126
|
+
|
|
127
|
+
# Handle the last key
|
|
128
|
+
last_key = keys[-1]
|
|
129
|
+
if is_array_index(last_key):
|
|
130
|
+
current.append(value)
|
|
131
|
+
else:
|
|
132
|
+
current[last_key] = value
|
|
133
|
+
|
|
134
|
+
return output
|
|
File without changes
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from girder import events
|
|
7
|
+
from girder.constants import AccessType
|
|
8
|
+
from girder.models.folder import Folder
|
|
9
|
+
from girder.models.item import Item
|
|
10
|
+
from girder.models.model_base import Model
|
|
11
|
+
from girder.models.upload import Upload
|
|
12
|
+
from girder.utility import acl_mixin, JsonEncoder, RequestBodyStream
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FormEntry(acl_mixin.AccessControlMixin, Model):
|
|
16
|
+
def initialize(self):
|
|
17
|
+
global GDRIVE_SERVICE
|
|
18
|
+
self.name = "entry"
|
|
19
|
+
# TODO: create indices for all pairs?
|
|
20
|
+
# self.ensureIndices(["formId", "data.sampleId"])
|
|
21
|
+
self.resourceColl = ("form", "jsonforms")
|
|
22
|
+
self.resourceParent = "formId"
|
|
23
|
+
|
|
24
|
+
self.exposeFields(
|
|
25
|
+
level=AccessType.READ,
|
|
26
|
+
fields=(
|
|
27
|
+
"_id",
|
|
28
|
+
"formId",
|
|
29
|
+
"folderId",
|
|
30
|
+
"data",
|
|
31
|
+
"created",
|
|
32
|
+
"updated",
|
|
33
|
+
"files",
|
|
34
|
+
"folders",
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def validate(self, doc):
|
|
39
|
+
return doc
|
|
40
|
+
|
|
41
|
+
def _getExtraPath(self, template, data):
|
|
42
|
+
# Define a safe set of built-in functions and variables
|
|
43
|
+
safe_globals = {"__builtins__": None}
|
|
44
|
+
safe_locals = {"data": data, "ord": ord}
|
|
45
|
+
|
|
46
|
+
# Evaluate the template
|
|
47
|
+
try:
|
|
48
|
+
result = eval(f'f"{template}"', safe_globals, safe_locals)
|
|
49
|
+
return result
|
|
50
|
+
except Exception as e:
|
|
51
|
+
print("Error:", e)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
def create(self, form, data, source, destination, creator):
|
|
55
|
+
now = datetime.datetime.utcnow()
|
|
56
|
+
unique_field = form.get("uniqueField")
|
|
57
|
+
entry = {
|
|
58
|
+
"formId": form["_id"],
|
|
59
|
+
"data": data,
|
|
60
|
+
"created": now,
|
|
61
|
+
"updated": now,
|
|
62
|
+
"folderId": destination["_id"],
|
|
63
|
+
"files": [],
|
|
64
|
+
"folders": [],
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if existing := self.findOne(
|
|
68
|
+
{
|
|
69
|
+
"formId": form["_id"],
|
|
70
|
+
f"data.{unique_field}": data[unique_field],
|
|
71
|
+
}
|
|
72
|
+
):
|
|
73
|
+
# Update the existing entry
|
|
74
|
+
entry.update(
|
|
75
|
+
{
|
|
76
|
+
"_id": existing["_id"],
|
|
77
|
+
"created": existing["created"],
|
|
78
|
+
"files": existing["files"],
|
|
79
|
+
"folders": existing["folders"],
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Move from temp to destination
|
|
84
|
+
path = entry["data"].get("targetPath")
|
|
85
|
+
known_targets = {
|
|
86
|
+
None: (
|
|
87
|
+
self.get_destination_folder(path, destination, creator),
|
|
88
|
+
entry["data"].get(unique_field),
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
if source is not None:
|
|
92
|
+
for child in Folder().childFolders(source, "folder", user=creator):
|
|
93
|
+
path = child.get("meta", {}).get("targetPath")
|
|
94
|
+
try:
|
|
95
|
+
target, _ = known_targets[path]
|
|
96
|
+
except KeyError:
|
|
97
|
+
target = self.get_destination_folder(path, destination, creator)
|
|
98
|
+
known_targets[path] = target, child.get("meta", {}).get(unique_field)
|
|
99
|
+
child = self.unique(child, target)
|
|
100
|
+
Folder().move(child, target, "folder")
|
|
101
|
+
# TODO upload to GDrive
|
|
102
|
+
entry["folders"].append(child["_id"])
|
|
103
|
+
|
|
104
|
+
for child in Folder().childItems(source):
|
|
105
|
+
path = child.get("meta", {}).get("targetPath")
|
|
106
|
+
try:
|
|
107
|
+
target, _ = known_targets[path]
|
|
108
|
+
except KeyError:
|
|
109
|
+
target = self.get_destination_folder(path, destination, creator)
|
|
110
|
+
known_targets[path] = target, child.get("meta", {}).get(unique_field)
|
|
111
|
+
child = self.unique(child, target)
|
|
112
|
+
child = Item().move(child, target)
|
|
113
|
+
for file in Item().childFiles(child):
|
|
114
|
+
# Upload to GDrive
|
|
115
|
+
gdrive_folder_id = child.get("meta", {}).get("gdriveFolderId")
|
|
116
|
+
if gdrive_folder_id:
|
|
117
|
+
events.daemon.trigger(
|
|
118
|
+
"gdrive.upload",
|
|
119
|
+
{
|
|
120
|
+
"file": file,
|
|
121
|
+
"gdriveFolderId": gdrive_folder_id,
|
|
122
|
+
"path": os.path.join(path, file["name"]),
|
|
123
|
+
"currentUser": creator,
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
entry["files"].append(child["_id"])
|
|
127
|
+
Folder().remove(source)
|
|
128
|
+
|
|
129
|
+
if not form.get("serialize", False):
|
|
130
|
+
return self.save(entry)
|
|
131
|
+
|
|
132
|
+
# Dump the entry into json file, by creating bytes buffer from json dump and
|
|
133
|
+
# Upload().uploadFromFile will create a file in each destination folder
|
|
134
|
+
if len(known_targets) > 1:
|
|
135
|
+
known_targets.pop(None)
|
|
136
|
+
|
|
137
|
+
processed = set()
|
|
138
|
+
for path, (target, uniqueId) in known_targets.items():
|
|
139
|
+
if target["_id"] in processed:
|
|
140
|
+
continue
|
|
141
|
+
path = path or entry["data"].get("targetPath")
|
|
142
|
+
with io.BytesIO(
|
|
143
|
+
json.dumps(
|
|
144
|
+
entry, sort_keys=True, allow_nan=False, cls=JsonEncoder
|
|
145
|
+
).encode("utf-8")
|
|
146
|
+
) as f:
|
|
147
|
+
reference = {
|
|
148
|
+
f"{unique_field}": uniqueId,
|
|
149
|
+
"targetPath": path,
|
|
150
|
+
"gdriveFolderId": form.get("gdriveFolderId"),
|
|
151
|
+
}
|
|
152
|
+
size = f.getbuffer().nbytes
|
|
153
|
+
upload = self._get_upload_for_entry(
|
|
154
|
+
form["entryFileName"], target, creator, size, reference
|
|
155
|
+
)
|
|
156
|
+
# not really chunking here as JSON is small
|
|
157
|
+
upload = Upload().handleChunk(upload, RequestBodyStream(f, size))
|
|
158
|
+
if form.get("gdriveFolderId"):
|
|
159
|
+
events.daemon.trigger(
|
|
160
|
+
"gdrive.upload",
|
|
161
|
+
{
|
|
162
|
+
"file": upload,
|
|
163
|
+
"gdriveFolderId": form["gdriveFolderId"],
|
|
164
|
+
"gdriveFileId": reference.get("gdriveFileId"),
|
|
165
|
+
"path": os.path.join(path, upload["name"]),
|
|
166
|
+
"currentUser": creator,
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
processed.add(target["_id"])
|
|
170
|
+
|
|
171
|
+
return self.save(entry)
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def _get_upload_for_entry(fname, target, creator, size, reference):
|
|
175
|
+
if existing_item := Item().findOne({"name": fname, "folderId": target["_id"]}):
|
|
176
|
+
file = Item().childFiles(existing_item)[0]
|
|
177
|
+
if "gdriveFileId" in existing_item.get("meta", {}):
|
|
178
|
+
reference["gdriveFileId"] = existing_item["meta"]["gdriveFileId"]
|
|
179
|
+
reference["itemId"] = existing_item["_id"]
|
|
180
|
+
serialized_reference = json.dumps(
|
|
181
|
+
reference, sort_keys=True, allow_nan=False, cls=JsonEncoder
|
|
182
|
+
)
|
|
183
|
+
upload = Upload().createUploadToFile(
|
|
184
|
+
file=file, user=creator, size=size, reference=serialized_reference
|
|
185
|
+
)
|
|
186
|
+
else:
|
|
187
|
+
serialized_reference = json.dumps(
|
|
188
|
+
reference, sort_keys=True, allow_nan=False, cls=JsonEncoder
|
|
189
|
+
)
|
|
190
|
+
upload = Upload().createUpload(
|
|
191
|
+
user=creator,
|
|
192
|
+
name=fname,
|
|
193
|
+
parentType="folder",
|
|
194
|
+
parent=target,
|
|
195
|
+
size=size,
|
|
196
|
+
mimeType="application/json",
|
|
197
|
+
reference=serialized_reference,
|
|
198
|
+
)
|
|
199
|
+
return upload
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def get_destination_folder(path, root, user):
|
|
203
|
+
if path is None:
|
|
204
|
+
return root
|
|
205
|
+
|
|
206
|
+
destination = root
|
|
207
|
+
for subfolder in path.split(os.path.sep):
|
|
208
|
+
destination = Folder().createFolder(
|
|
209
|
+
destination,
|
|
210
|
+
subfolder,
|
|
211
|
+
parentType="folder",
|
|
212
|
+
creator=user,
|
|
213
|
+
reuseExisting=True,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return destination
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def unique(child, destination):
|
|
220
|
+
name = child["name"]
|
|
221
|
+
n = 0
|
|
222
|
+
checkName = True
|
|
223
|
+
while checkName:
|
|
224
|
+
q = {
|
|
225
|
+
"name": name,
|
|
226
|
+
"folderId": destination["_id"],
|
|
227
|
+
"_id": {"$ne": child["_id"]},
|
|
228
|
+
}
|
|
229
|
+
dupItem = Item().findOne(q, fields=["_id"])
|
|
230
|
+
q = {
|
|
231
|
+
"name": name,
|
|
232
|
+
"parentId": destination["_id"],
|
|
233
|
+
"parentCollection": "folder",
|
|
234
|
+
}
|
|
235
|
+
dupFolder = Folder().findOne(q, fields=["_id"])
|
|
236
|
+
|
|
237
|
+
if dupItem is None and dupFolder is None:
|
|
238
|
+
child["name"] = name
|
|
239
|
+
checkName = False
|
|
240
|
+
else:
|
|
241
|
+
n += 1
|
|
242
|
+
name = f"{child['name']} ({n})"
|
|
243
|
+
|
|
244
|
+
child["lowerName"] = child["name"].lower()
|
|
245
|
+
return child
|