Skip to content

Commit a5739a6

Browse files
committed
Add upload files implementation
1 parent 872fb99 commit a5739a6

2 files changed

Lines changed: 124 additions & 1 deletion

File tree

src/bin/qfieldcloud-cli

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,12 @@ def upload_files(ctx, project_id, project_path, filter_glob, exit_on_error):
246246
log(f'Uploading files "{project_id}" from {project_path}…')
247247

248248
files = ctx.obj["client"].upload_files(
249-
project_id, project_path, filter_glob, exit_on_error, show_progress=True,
249+
project_id,
250+
sdk.FileTransferType.PROJECT,
251+
project_path,
252+
filter_glob=filter_glob,
253+
exit_on_error=exit_on_error,
254+
show_progress=True,
250255
)
251256

252257
if ctx.obj["format_json"]:

src/qfieldcloud_sdk/sdk.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ class DownloadType(str, Enum):
4343
PACKAGED_FILES = "qfield-files"
4444

4545

46+
class FileTransferType(Enum):
47+
PROJECT = "project"
48+
PACKAGE = "package"
49+
50+
4651
class JobTypes(str, Enum):
4752
PACKAGE = "package"
4853
APPLY_DELTAS = "delta_apply"
@@ -170,6 +175,92 @@ def delete_project(self, project_id: str):
170175

171176
return resp
172177

178+
def upload_files(
179+
self,
180+
project_id: str,
181+
upload_type: FileTransferType,
182+
project_path: str,
183+
filter_glob: str,
184+
exit_on_error: bool = False,
185+
show_progress: bool = False,
186+
job_id: str = "",
187+
) -> List[Dict]:
188+
"""Upload files to a QFieldCloud project"""
189+
if not filter_glob:
190+
filter_glob = "*"
191+
192+
files = self.get_files_list(project_path, filter_glob)
193+
194+
if not files:
195+
return files
196+
197+
for file in files:
198+
try:
199+
local_filename = Path(file["name"])
200+
remote_filename = local_filename.relative_to(project_path)
201+
self.upload_file(
202+
project_id,
203+
upload_type,
204+
local_filename,
205+
remote_filename,
206+
show_progress,
207+
job_id,
208+
)
209+
file["status"] = UploadStatus.SUCCESS
210+
except Exception as err:
211+
file["status"] = UploadStatus.FAILED
212+
file["error"] = err
213+
214+
if exit_on_error:
215+
raise err
216+
else:
217+
continue
218+
219+
return files
220+
221+
def upload_file(
222+
self,
223+
project_id: str,
224+
upload_type: FileTransferType,
225+
local_filename: Path,
226+
remote_filename: Path,
227+
show_progress: bool,
228+
job_id: str = "",
229+
) -> requests.Response:
230+
with open(local_filename, "rb") as local_file:
231+
upload_file = local_file
232+
if show_progress:
233+
from tqdm import tqdm
234+
from tqdm.utils import CallbackIOWrapper
235+
236+
progress_bar = tqdm(
237+
total=local_filename.stat().st_size,
238+
unit_scale=True,
239+
desc=local_filename.stem,
240+
)
241+
upload_file = CallbackIOWrapper(progress_bar.update, local_file, "read")
242+
else:
243+
logging.info(f'Uploading file "{remote_filename}"…')
244+
245+
if upload_type == FileTransferType.PROJECT:
246+
url = f"files/{project_id}/{remote_filename}"
247+
elif upload_type == FileTransferType.PACKAGE:
248+
if not job_id:
249+
raise QfcException(
250+
'When the upload type is "package", you must pass the "job_id" parameter.'
251+
)
252+
253+
url = f"packages/{project_id}/{job_id}/files/{remote_filename}"
254+
else:
255+
raise NotImplementedError()
256+
257+
return self._request(
258+
"POST",
259+
url,
260+
files={
261+
"file": upload_file,
262+
},
263+
)
173264

174265
def download_files(
175266
self,
@@ -422,6 +513,33 @@ def _download_files(
422513

423514
return files_to_download
424515

516+
def get_files_list(
517+
self, project_path: str, filter_glob: str
518+
) -> List[Dict[str, Any]]:
519+
if not filter_glob:
520+
filter_glob = "*"
521+
522+
files: List[Dict[str, Any]] = []
523+
for path in Path(project_path).rglob(filter_glob):
524+
if not path.is_file():
525+
continue
526+
527+
if str(path.relative_to(project_path)).startswith(".qfieldsync"):
528+
continue
529+
530+
files.append(
531+
{
532+
"name": str(path),
533+
"status": UploadStatus.PENDING,
534+
"error": None,
535+
}
536+
)
537+
538+
# upload the QGIS project file at the end
539+
files.sort(key=lambda f: Path(f["name"]).suffix.lower() in (".qgs", ".qgz"))
540+
541+
return files
542+
425543
def _request(
426544
self,
427545
method: str,

0 commit comments

Comments
 (0)