282 lines
7.9 KiB
Python
282 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
|
|
"""
|
|
Module for remapping paths from one directory to another.
|
|
"""
|
|
|
|
import os
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# private utility functions
|
|
|
|
def _is_blend(f):
|
|
return f.lower().endswith(b'.blend')
|
|
|
|
|
|
def _warn__ascii(msg):
|
|
print(" warning: %s" % msg)
|
|
|
|
|
|
def _info__ascii(msg):
|
|
print(msg)
|
|
|
|
|
|
def _warn__json(msg):
|
|
import json
|
|
print(json.dumps(("warning", msg)), end=",\n")
|
|
|
|
def _info__json(msg):
|
|
import json
|
|
print(json.dumps(("info", msg)), end=",\n")
|
|
|
|
|
|
def _uuid_from_file(fn, block_size=1 << 20):
|
|
with open(fn, 'rb') as f:
|
|
# first get the size
|
|
f.seek(0, os.SEEK_END)
|
|
size = f.tell()
|
|
f.seek(0, os.SEEK_SET)
|
|
# done!
|
|
|
|
import hashlib
|
|
sha1 = hashlib.new('sha512')
|
|
while True:
|
|
data = f.read(block_size)
|
|
if not data:
|
|
break
|
|
sha1.update(data)
|
|
return (hex(size)[2:] + sha1.hexdigest()).encode()
|
|
|
|
|
|
def _iter_files(paths, check_ext=None):
|
|
# note, sorting isn't needed
|
|
# just gives predictable output
|
|
for p in paths:
|
|
p = os.path.abspath(p)
|
|
for dirpath, dirnames, filenames in sorted(os.walk(p)):
|
|
# skip '.svn'
|
|
if dirpath.startswith(b'.') and dirpath != b'.':
|
|
continue
|
|
|
|
for filename in sorted(filenames):
|
|
if check_ext is None or check_ext(filename):
|
|
filepath = os.path.join(dirpath, filename)
|
|
yield filepath
|
|
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Public Functions
|
|
|
|
def start(
|
|
paths,
|
|
is_quiet=False,
|
|
dry_run=False,
|
|
use_json=False,
|
|
):
|
|
|
|
if use_json:
|
|
warn = _warn__json
|
|
info = _info__json
|
|
else:
|
|
warn = _warn__ascii
|
|
info = _info__ascii
|
|
|
|
if use_json:
|
|
print("[")
|
|
|
|
# {(sha1, length): "filepath"}
|
|
remap_uuid = {}
|
|
|
|
# relative paths which don't exist,
|
|
# don't complain when they're missing on remap.
|
|
# {f_src: [relative path deps, ...]}
|
|
remap_lost = {}
|
|
|
|
# all files we need to map
|
|
# absolute paths
|
|
files_to_map = set()
|
|
|
|
# TODO, validate paths aren't nested! ["/foo", "/foo/bar"]
|
|
# it will cause problems touching files twice!
|
|
|
|
# ------------------------------------------------------------------------
|
|
# First walk over all blends
|
|
import blendfile_path_walker
|
|
|
|
for blendfile_src in _iter_files(paths, check_ext=_is_blend):
|
|
if not is_quiet:
|
|
info("blend read: %r" % blendfile_src)
|
|
|
|
remap_lost[blendfile_src] = remap_lost_blendfile_src = set()
|
|
|
|
for fp, (rootdir, fp_blend_basename) in blendfile_path_walker.FilePath.visit_from_blend(
|
|
blendfile_src,
|
|
readonly=True,
|
|
recursive=False,
|
|
):
|
|
# TODO. warn when referencing files outside 'paths'
|
|
|
|
# so we can update the reference
|
|
f_abs = fp.filepath_absolute
|
|
f_abs = os.path.normpath(f_abs)
|
|
if os.path.exists(f_abs):
|
|
files_to_map.add(f_abs)
|
|
else:
|
|
if not is_quiet:
|
|
warn("file %r not found!" % f_abs)
|
|
|
|
# don't complain about this file being missing on remap
|
|
remap_lost_blendfile_src.add(fp.filepath)
|
|
|
|
# so we can know where its moved to
|
|
files_to_map.add(blendfile_src)
|
|
del blendfile_path_walker
|
|
|
|
# ------------------------------------------------------------------------
|
|
# Store UUID
|
|
#
|
|
# note, sorting is only to give predictable warnings/behavior
|
|
for f in sorted(files_to_map):
|
|
f_uuid = _uuid_from_file(f)
|
|
|
|
f_match = remap_uuid.get(f_uuid)
|
|
if f_match is not None:
|
|
if not is_quiet:
|
|
warn("duplicate file found! (%r, %r)" % (f_match, f))
|
|
|
|
remap_uuid[f_uuid] = f
|
|
|
|
# now find all deps
|
|
remap_data_args = (
|
|
remap_uuid,
|
|
remap_lost,
|
|
)
|
|
|
|
if use_json:
|
|
if not remap_uuid:
|
|
print("\"nothing to remap!\"")
|
|
else:
|
|
print("\"complete\"")
|
|
print("]")
|
|
else:
|
|
if not remap_uuid:
|
|
print("Nothing to remap!")
|
|
|
|
return remap_data_args
|
|
|
|
|
|
def finish(
|
|
paths, remap_data_args,
|
|
is_quiet=False,
|
|
force_relative=False,
|
|
dry_run=False,
|
|
use_json=False,
|
|
):
|
|
|
|
if use_json:
|
|
warn = _warn__json
|
|
info = _info__json
|
|
else:
|
|
warn = _warn__ascii
|
|
info = _info__ascii
|
|
|
|
if use_json:
|
|
print("[")
|
|
|
|
(remap_uuid,
|
|
remap_lost,
|
|
) = remap_data_args
|
|
|
|
remap_src_to_dst = {}
|
|
remap_dst_to_src = {}
|
|
|
|
for f_dst in _iter_files(paths):
|
|
f_uuid = _uuid_from_file(f_dst)
|
|
f_src = remap_uuid.get(f_uuid)
|
|
if f_src is not None:
|
|
remap_src_to_dst[f_src] = f_dst
|
|
remap_dst_to_src[f_dst] = f_src
|
|
|
|
# now the fun begins, remap _all_ paths
|
|
import blendfile_path_walker
|
|
|
|
for blendfile_dst in _iter_files(paths, check_ext=_is_blend):
|
|
blendfile_src = remap_dst_to_src.get(blendfile_dst)
|
|
if blendfile_src is None:
|
|
if not is_quiet:
|
|
warn("new blendfile added since beginning 'remap': %r" % blendfile_dst)
|
|
continue
|
|
|
|
# not essential, just so we can give more meaningful errors
|
|
remap_lost_blendfile_src = remap_lost[blendfile_src]
|
|
|
|
if not is_quiet:
|
|
info("blend write: %r -> %r" % (blendfile_src, blendfile_dst))
|
|
|
|
blendfile_src_basedir = os.path.dirname(blendfile_src)
|
|
blendfile_dst_basedir = os.path.dirname(blendfile_dst)
|
|
for fp, (rootdir, fp_blend_basename) in blendfile_path_walker.FilePath.visit_from_blend(
|
|
blendfile_dst,
|
|
readonly=False,
|
|
recursive=False,
|
|
):
|
|
# TODO. warn when referencing files outside 'paths'
|
|
|
|
# so we can update the reference
|
|
f_src_orig = fp.filepath
|
|
|
|
if f_src_orig in remap_lost_blendfile_src:
|
|
# this file never existed, so we can't remap it
|
|
continue
|
|
|
|
is_relative = f_src_orig.startswith(b'//')
|
|
if is_relative:
|
|
f_src_abs = fp.filepath_absolute_resolve(basedir=blendfile_src_basedir)
|
|
else:
|
|
f_src_abs = f_src_orig
|
|
|
|
f_src_abs = os.path.normpath(f_src_abs)
|
|
f_dst_abs = remap_src_to_dst.get(f_src_abs)
|
|
|
|
if f_dst_abs is None:
|
|
if not is_quiet:
|
|
warn("file %r not found in map!" % f_src_abs)
|
|
continue
|
|
|
|
# now remap!
|
|
if is_relative or force_relative:
|
|
f_dst_final = b'//' + os.path.relpath(f_dst_abs, blendfile_dst_basedir)
|
|
else:
|
|
f_dst_final = f_dst_abs
|
|
|
|
if f_dst_final != f_src_orig:
|
|
if not dry_run:
|
|
fp.filepath = f_dst_final
|
|
if not is_quiet:
|
|
info("remap %r -> %r" % (f_src_abs, f_dst_abs))
|
|
|
|
del blendfile_path_walker
|
|
|
|
if use_json:
|
|
print("\"complete\"\n]")
|
|
|