Skip to content

Commit 2051d4f

Browse files
committed
introduce fs handler for long paths
1 parent bcc7ae6 commit 2051d4f

7 files changed

Lines changed: 121 additions & 79 deletions

File tree

mergin/client.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import math
33
import os
44
import json
5-
import shutil
65
import zlib
76
import base64
87
import urllib.parse
@@ -67,8 +66,8 @@
6766
int_version,
6867
is_version_acceptable,
6968
normalize_role,
70-
long_path,
7169
)
70+
from . import fs
7271
from .version import __version__
7372

7473
try:
@@ -1238,7 +1237,7 @@ def get_file_diff(self, project_dir, file_path, output_diff, version_from, versi
12381237
# collect required versions from the cache
12391238
diffs = []
12401239
for v in versions_to_fetch[1:]:
1241-
diffs.append(long_path(mp.fpath_cache(file_history["history"][v]["diff"]["path"], v)))
1240+
diffs.append(mp.fpath_cache(file_history["history"][v]["diff"]["path"], v))
12421241

12431242
# concatenate diffs, if needed
12441243
output_dir = os.path.dirname(output_diff)
@@ -1247,7 +1246,7 @@ def get_file_diff(self, project_dir, file_path, output_diff, version_from, versi
12471246
if len(diffs) > 1:
12481247
mp.geodiff.concat_changes(diffs, output_diff)
12491248
elif len(diffs) == 1:
1250-
shutil.copy(diffs[0], output_diff)
1249+
fs.copy(diffs[0], output_diff)
12511250

12521251
def download_file_diffs(self, project_dir, file_path, versions):
12531252
"""Download file diffs for specified versions if they are not present
@@ -1378,7 +1377,7 @@ def reset_local_changes(self, directory: str, files_to_reset: typing.List[str] =
13781377
# remove all added files
13791378
for file in push_changes["added"]:
13801379
if all_files or file["path"] in files_to_reset:
1381-
os.remove(long_path(mp.fpath(file["path"])))
1380+
fs.remove(mp.fpath(file["path"]))
13821381

13831382
# update files get override with previous version
13841383
for file in push_changes["updated"]:

mergin/client_pull.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
from .common import CHUNK_SIZE, ClientError, DeltaChangeType, PullActionType
2626
from .models import ProjectDelta, ProjectDeltaChange, PullAction
2727
from .merginproject import MerginProject
28-
from .utils import cleanup_tmp_dir, save_to_file, long_path
28+
from .utils import cleanup_tmp_dir, save_to_file
29+
from . import fs
2930
from typing import List, Optional
3031

3132
# status = download_project_async(...)
@@ -93,9 +94,7 @@ def __init__(self, file_path, size, version, diff_only, part_index, download_fil
9394
self.version = version # version of the file ("v123")
9495
self.diff_only = diff_only # whether downloading diff or full version
9596
self.part_index = part_index # index of the chunk
96-
self.download_file_path = long_path(
97-
download_file_path
98-
) # full path to a temporary file which will receive the content
97+
self.download_file_path = download_file_path # full path to a temporary file which will receive the content
9998

10099
def __repr__(self):
101100
return "<DownloadQueueItem path={} version={} diff_only={} part_index={} size={} dest={}>".format(
@@ -130,9 +129,7 @@ class DownloadDiffQueueItem:
130129

131130
def __init__(self, diff_id, download_file_path):
132131
self.diff_id = diff_id # relative path to the file within project
133-
self.download_file_path = long_path(
134-
download_file_path
135-
) # full path to a temporary file which will receive the content
132+
self.download_file_path = download_file_path # full path to a temporary file which will receive the content
136133
self.size = 0 # size of the item in bytes
137134

138135
def __repr__(self):
@@ -146,7 +143,7 @@ def download_blocking(self, mc, mp):
146143
if resp.status in [200, 206]:
147144
mp.log.debug(f"Download finished: {self.diff_id}")
148145
save_to_file(resp, self.download_file_path)
149-
self.size = os.path.getsize(self.download_file_path)
146+
self.size = fs.getsize(self.download_file_path)
150147
else:
151148
mp.log.error(f"Download failed: {self.diff_id}")
152149
raise ClientError(f"Failed to download of diff file {self.diff_id} to {self.download_file_path}")
@@ -161,26 +158,26 @@ class DownloadFile:
161158
"""
162159

163160
def __init__(self, dest_file, downloaded_items: typing.List[DownloadQueueItem], size_check=True):
164-
self.dest_file = long_path(dest_file) # full path to the destination file to be created
161+
self.dest_file = dest_file # full path to the destination file to be created
165162
self.downloaded_items = downloaded_items # list of pieces of the destination file to be merged
166163
self.size_check = size_check # whether we want to do merged file size check
167164

168165
def from_chunks(self):
169166
"""Merges downloaded chunks into a single file at dest_file path"""
170167
file_dir = os.path.dirname(self.dest_file)
171-
os.makedirs(file_dir, exist_ok=True)
168+
fs.makedirs(file_dir, exist_ok=True)
172169

173-
with open(self.dest_file, "wb") as final:
170+
with fs.open_file(self.dest_file, "wb") as final:
174171
for item in self.downloaded_items:
175-
with open(item.download_file_path, "rb") as chunk:
172+
with fs.open_file(item.download_file_path, "rb") as chunk:
176173
shutil.copyfileobj(chunk, final)
177-
os.remove(item.download_file_path)
174+
fs.remove(item.download_file_path)
178175

179176
if not self.size_check:
180177
return
181178
expected_size = sum(item.size for item in self.downloaded_items)
182-
if os.path.getsize(self.dest_file) != expected_size:
183-
os.remove(self.dest_file)
179+
if fs.getsize(self.dest_file) != expected_size:
180+
fs.remove(self.dest_file)
184181
raise ClientError("Download of file {} failed. Please try it again.".format(self.dest_file))
185182

186183

@@ -200,7 +197,7 @@ def get_download_items(
200197

201198
items = []
202199
for part_index in range(chunks):
203-
download_file_path = long_path(os.path.join(file_dir, basename + ".{}".format(part_index)))
200+
download_file_path = os.path.join(file_dir, basename + ".{}".format(part_index))
204201
size = min(CHUNK_SIZE, file_size - part_index * CHUNK_SIZE)
205202
items.append(DownloadQueueItem(file_path, size, file_version, diff_only, part_index, download_file_path))
206203

@@ -483,7 +480,7 @@ def get_download_diff_files(delta_item: ProjectDeltaChange, target_dir: str) ->
483480
result = []
484481

485482
for diff in delta_item.diffs:
486-
dest_file_path = long_path(os.path.normpath(os.path.join(target_dir, diff.id)))
483+
dest_file_path = os.path.normpath(os.path.join(target_dir, diff.id))
487484
download_items = get_download_items(delta_item.path, diff.size, diff.version, target_dir, diff.id, True)
488485
result.append(DownloadFile(dest_file_path, download_items))
489486
return result
@@ -559,7 +556,7 @@ def pull_project_async(mc, directory) -> Optional[PullJob]:
559556
pull_action_type == PullActionType.COPY_CONFLICT and change.type == DeltaChangeType.UPDATE_DIFF
560557
):
561558
basefile = mp.fpath_meta(change.path)
562-
if not os.path.exists(long_path(basefile)):
559+
if not fs.exists(basefile):
563560
# The basefile does not exist for some reason. This should not happen normally (maybe user removed the file
564561
# or we removed it within previous pull because we failed to apply patch the older version for some reason).
565562
# But it's not a problem - we will download the newest version and we're sorted.
@@ -726,7 +723,7 @@ def pull_project_finalize(job: PullJob):
726723
basefile = job.mp.fpath_meta(file_path)
727724
server_file = job.mp.fpath(file_path, job.tmp_dir.name)
728725

729-
shutil.copy(long_path(basefile), long_path(server_file))
726+
fs.copy(basefile, server_file)
730727
diffs = [job.mp.fpath(f, job.tmp_dir.name) for f in file_diffs]
731728
patch_error = job.mp.apply_diffs(server_file, diffs)
732729
if patch_error:
@@ -739,7 +736,7 @@ def pull_project_finalize(job: PullJob):
739736
job.mp.log.error("Diffs we were applying: " + str(diffs))
740737
job.mp.log.error("Removing basefile because it would be corrupted anyway...")
741738
job.mp.log.info("--- pull aborted")
742-
os.remove(long_path(basefile))
739+
fs.remove(basefile)
743740
raise ClientError("Cannot patch basefile {}! Please try syncing again.".format(basefile))
744741
conflicts = []
745742
job.mp.log.info(f"--- applying pull actions {job.pull_actions}")
@@ -833,8 +830,8 @@ def download_diffs_async(mc, project_directory, file_path, versions):
833830
download_path=diff.get("path"),
834831
diff_only=True,
835832
)
836-
dest_file_path = long_path(mp.fpath_cache(diff["path"], version=file["version"]))
837-
if os.path.exists(dest_file_path):
833+
dest_file_path = mp.fpath_cache(diff["path"], version=file["version"])
834+
if fs.exists(dest_file_path):
838835
continue
839836
download_files.append(DownloadFile(dest_file_path, items))
840837
download_list.extend(items)

mergin/client_push.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import pprint
1919
import tempfile
2020
import concurrent.futures
21-
import os
2221
import time
2322
from typing import List, Tuple, Optional, ByteString
2423

@@ -34,7 +33,8 @@
3433
)
3534
from .merginproject import MerginProject, pygeodiff
3635
from .editor import filter_changes
37-
from .utils import get_data_checksum, cleanup_tmp_dir, long_path
36+
from .utils import get_data_checksum, cleanup_tmp_dir
37+
from . import fs
3838

3939
POST_JSON_HEADERS = {"Content-Type": "application/json"}
4040

@@ -114,7 +114,7 @@ def upload_chunk_v2_api(self, data: ByteString, checksum: str):
114114
self.mc.upload_chunks_cache.add(checksum, self.server_chunk_id)
115115

116116
def upload_blocking(self):
117-
with open(long_path(self.file_path), "rb") as file_handle:
117+
with fs.open_file(self.file_path, "rb") as file_handle:
118118
file_handle.seek(self.chunk_index * UPLOAD_CHUNK_SIZE)
119119
data = file_handle.read(UPLOAD_CHUNK_SIZE)
120120
checksum_str = get_data_checksum(data)
@@ -507,9 +507,9 @@ def remove_diff_files(job: UploadJob) -> None:
507507
for change in job.changes.updated:
508508
diff = change.get_diff()
509509
if diff:
510-
diff_file = long_path(job.mp.fpath_meta(diff.path))
511-
if os.path.exists(diff_file):
512-
os.remove(diff_file)
510+
diff_file = job.mp.fpath_meta(diff.path)
511+
if fs.exists(diff_file):
512+
fs.remove(diff_file)
513513

514514

515515
def get_push_changes_batch(mc, directory: str) -> Tuple[LocalProjectChanges, int]:

mergin/fs.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Thin wrappers around the standard-library filesystem calls used by the sync code.
3+
4+
Every wrapper applies utils.long_path() to its path argument(s) so that Windows paths
5+
longer than MAX_PATH are handled transparently.
6+
7+
Use these instead of calling os.* / shutil.* / open() / sqlite3.connect() directly on
8+
project file paths.
9+
"""
10+
11+
import os
12+
import shutil
13+
import sqlite3
14+
15+
from .utils import long_path
16+
17+
18+
def remove(path):
19+
os.remove(long_path(path))
20+
21+
22+
def exists(path) -> bool:
23+
return os.path.exists(long_path(path))
24+
25+
26+
def getsize(path) -> int:
27+
return os.path.getsize(long_path(path))
28+
29+
30+
def getmtime(path) -> float:
31+
return os.path.getmtime(long_path(path))
32+
33+
34+
def copy(src, dst):
35+
return shutil.copy(long_path(src), long_path(dst))
36+
37+
38+
def walk(path):
39+
return os.walk(long_path(path))
40+
41+
42+
def makedirs(path, exist_ok=False):
43+
os.makedirs(long_path(path), exist_ok=exist_ok)
44+
45+
46+
def connect(path):
47+
return sqlite3.connect(long_path(path))
48+
49+
50+
def open_file(path, *args, **kwargs):
51+
return open(long_path(path), *args, **kwargs)

0 commit comments

Comments
 (0)