Archived
initial commit
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# ***** 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 *****
|
||||
#
|
||||
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
||||
"""Commandline entry points."""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
|
||||
from . import blocks, common, pack, list_deps
|
||||
|
||||
|
||||
def cli_main():
|
||||
from blender_asset_tracer import __version__
|
||||
parser = argparse.ArgumentParser(description='BAT: Blender Asset Tracer v%s' % __version__)
|
||||
common.add_flag(parser, 'profile', help='Run the profiler, write to bam.prof')
|
||||
|
||||
# func is set by subparsers to indicate which function to run.
|
||||
parser.set_defaults(func=None,
|
||||
loglevel=logging.WARNING)
|
||||
loggroup = parser.add_mutually_exclusive_group()
|
||||
loggroup.add_argument('-v', '--verbose', dest='loglevel',
|
||||
action='store_const', const=logging.INFO,
|
||||
help='Log INFO level and higher')
|
||||
loggroup.add_argument('-d', '--debug', dest='loglevel',
|
||||
action='store_const', const=logging.DEBUG,
|
||||
help='Log everything')
|
||||
loggroup.add_argument('-q', '--quiet', dest='loglevel',
|
||||
action='store_const', const=logging.ERROR,
|
||||
help='Log at ERROR level and higher')
|
||||
subparsers = parser.add_subparsers(
|
||||
help='Choose a subcommand to actually make BAT do something. '
|
||||
'Global options go before the subcommand, '
|
||||
'whereas subcommand-specific options go after it. '
|
||||
'Use --help after the subcommand to get more info.')
|
||||
|
||||
blocks.add_parser(subparsers)
|
||||
pack.add_parser(subparsers)
|
||||
list_deps.add_parser(subparsers)
|
||||
|
||||
args = parser.parse_args()
|
||||
config_logging(args)
|
||||
|
||||
from blender_asset_tracer import __version__
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Make sure the things we log in our local logger are visible
|
||||
if args.profile and args.loglevel > logging.INFO:
|
||||
log.setLevel(logging.INFO)
|
||||
log.debug('Running BAT version %s', __version__)
|
||||
|
||||
if not args.func:
|
||||
parser.error('No subcommand was given')
|
||||
|
||||
start_time = time.time()
|
||||
if args.profile:
|
||||
import cProfile
|
||||
|
||||
prof_fname = 'bam.prof'
|
||||
log.info('Running profiler')
|
||||
cProfile.runctx('args.func(args)',
|
||||
globals=globals(),
|
||||
locals=locals(),
|
||||
filename=prof_fname)
|
||||
log.info('Profiler exported data to %s', prof_fname)
|
||||
log.info('Run "pyprof2calltree -i %r -k" to convert and open in KCacheGrind', prof_fname)
|
||||
else:
|
||||
retval = args.func(args)
|
||||
duration = datetime.timedelta(seconds=time.time() - start_time)
|
||||
log.info('Command took %s to complete', duration)
|
||||
|
||||
|
||||
def config_logging(args):
|
||||
"""Configures the logging system based on CLI arguments."""
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.WARNING,
|
||||
format='%(asctime)-15s %(levelname)8s %(name)-40s %(message)s',
|
||||
)
|
||||
# Only set the log level on our own logger. Otherwise
|
||||
# debug logging will be completely swamped.
|
||||
logging.getLogger('blender_asset_tracer').setLevel(args.loglevel)
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# ***** 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 *****
|
||||
#
|
||||
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
||||
"""List count and total size of datablocks in a blend file."""
|
||||
import collections
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
from blender_asset_tracer import blendfile
|
||||
from . import common
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BlockTypeInfo:
|
||||
def __init__(self):
|
||||
self.total_bytes = 0
|
||||
self.num_blocks = 0
|
||||
self.sizes = []
|
||||
self.blocks = []
|
||||
self.name = 'unset'
|
||||
|
||||
|
||||
def add_parser(subparsers):
|
||||
"""Add argparser for this subcommand."""
|
||||
|
||||
parser = subparsers.add_parser('blocks', help=__doc__)
|
||||
parser.set_defaults(func=cli_blocks)
|
||||
parser.add_argument('blendfile', type=pathlib.Path)
|
||||
parser.add_argument('-d', '--dump', default=False, action='store_true',
|
||||
help='Hex-dump the biggest block')
|
||||
parser.add_argument('-l', '--limit', default=10, type=int,
|
||||
help='Limit the number of DNA types shown, default is 10')
|
||||
|
||||
|
||||
def by_total_bytes(info: BlockTypeInfo) -> int:
|
||||
return info.total_bytes
|
||||
|
||||
|
||||
def block_key(block: blendfile.BlendFileBlock) -> str:
|
||||
return '%s-%s' % (block.dna_type_name, block.code.decode())
|
||||
|
||||
|
||||
def cli_blocks(args):
|
||||
bpath = args.blendfile
|
||||
if not bpath.exists():
|
||||
log.fatal('File %s does not exist', args.blendfile)
|
||||
return 3
|
||||
|
||||
per_blocktype = collections.defaultdict(BlockTypeInfo)
|
||||
|
||||
print('Opening %s' % bpath)
|
||||
bfile = blendfile.BlendFile(bpath)
|
||||
|
||||
print('Inspecting %s' % bpath)
|
||||
for block in bfile.blocks:
|
||||
if block.code == b'DNA1':
|
||||
continue
|
||||
index_as = block_key(block)
|
||||
|
||||
info = per_blocktype[index_as]
|
||||
info.name = index_as
|
||||
info.total_bytes += block.size
|
||||
info.num_blocks += 1
|
||||
info.sizes.append(block.size)
|
||||
info.blocks.append(block)
|
||||
|
||||
fmt = '%-35s %10s %10s %10s %10s'
|
||||
print(fmt % ('Block type', 'Total Size', 'Num blocks', 'Avg Size', 'Median'))
|
||||
print(fmt % (35 * '-', 10 * '-', 10 * '-', 10 * '-', 10 * '-'))
|
||||
infos = sorted(per_blocktype.values(), key=by_total_bytes, reverse=True)
|
||||
for info in infos[:args.limit]:
|
||||
median_size = sorted(info.sizes)[len(info.sizes) // 2]
|
||||
print(fmt % (info.name,
|
||||
common.humanize_bytes(info.total_bytes),
|
||||
info.num_blocks,
|
||||
common.humanize_bytes(info.total_bytes // info.num_blocks),
|
||||
common.humanize_bytes(median_size)
|
||||
))
|
||||
|
||||
print(70 * '-')
|
||||
# From the blocks of the most space-using category, the biggest block.
|
||||
biggest_block = sorted(infos[0].blocks,
|
||||
key=lambda blck: blck.size,
|
||||
reverse=True)[0]
|
||||
print('Biggest %s block is %s at address %s' % (
|
||||
block_key(biggest_block),
|
||||
common.humanize_bytes(biggest_block.size),
|
||||
biggest_block.addr_old,
|
||||
))
|
||||
|
||||
print('Finding what points there')
|
||||
addr_to_find = biggest_block.addr_old
|
||||
found_pointer = False
|
||||
for block in bfile.blocks:
|
||||
for prop_path, prop_value in block.items_recursive():
|
||||
if not isinstance(prop_value, int) or prop_value != addr_to_find:
|
||||
continue
|
||||
print(' ', block, prop_path)
|
||||
found_pointer = True
|
||||
|
||||
if not found_pointer:
|
||||
print('Nothing points there')
|
||||
|
||||
if args.dump:
|
||||
print('Hexdump:')
|
||||
bfile.fileobj.seek(biggest_block.file_offset)
|
||||
data = bfile.fileobj.read(biggest_block.size)
|
||||
line_len_bytes = 32
|
||||
import codecs
|
||||
for offset in range(0, len(data), line_len_bytes):
|
||||
line = codecs.encode(data[offset:offset + line_len_bytes], 'hex').decode()
|
||||
print('%6d -' % offset, ' '.join(line[i:i + 2] for i in range(0, len(line), 2)))
|
||||
@@ -0,0 +1,99 @@
|
||||
# ***** 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 *****
|
||||
#
|
||||
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
||||
"""Common functionality for CLI parsers."""
|
||||
import typing
|
||||
|
||||
import pathlib
|
||||
|
||||
|
||||
def add_flag(argparser, flag_name: str, **kwargs):
|
||||
"""Add a CLI argument for the flag.
|
||||
|
||||
The flag defaults to False, and when present on the CLI stores True.
|
||||
"""
|
||||
|
||||
argparser.add_argument('-%s' % flag_name[0],
|
||||
'--%s' % flag_name,
|
||||
default=False,
|
||||
action='store_true',
|
||||
**kwargs)
|
||||
|
||||
|
||||
def shorten(cwd: pathlib.Path, somepath: pathlib.Path) -> pathlib.Path:
|
||||
"""Return 'somepath' relative to CWD if possible."""
|
||||
try:
|
||||
return somepath.relative_to(cwd)
|
||||
except ValueError:
|
||||
return somepath
|
||||
|
||||
|
||||
def humanize_bytes(size_in_bytes: int, precision: typing.Optional[int]=None):
|
||||
"""Return a humanized string representation of a number of bytes.
|
||||
|
||||
Source: http://code.activestate.com/recipes/577081-humanized-representation-of-a-number-of-bytes
|
||||
|
||||
:param size_in_bytes: The size to humanize
|
||||
:param precision: How many digits are shown after the comma. When None,
|
||||
it defaults to 1 unless the entire number of bytes is shown, then
|
||||
it will be 0.
|
||||
|
||||
>>> humanize_bytes(1)
|
||||
'1 B'
|
||||
>>> humanize_bytes(1024)
|
||||
'1.0 kB'
|
||||
>>> humanize_bytes(1024*123, 0)
|
||||
'123 kB'
|
||||
>>> humanize_bytes(1024*123)
|
||||
'123.0 kB'
|
||||
>>> humanize_bytes(1024*12342)
|
||||
'12.1 MB'
|
||||
>>> humanize_bytes(1024*12342,2)
|
||||
'12.05 MB'
|
||||
>>> humanize_bytes(1024*1234,2)
|
||||
'1.21 MB'
|
||||
>>> humanize_bytes(1024*1234*1111,2)
|
||||
'1.31 GB'
|
||||
>>> humanize_bytes(1024*1234*1111,1)
|
||||
'1.3 GB'
|
||||
"""
|
||||
|
||||
if precision is None:
|
||||
precision = size_in_bytes >= 1024
|
||||
|
||||
abbrevs = (
|
||||
(1 << 50, 'PB'),
|
||||
(1 << 40, 'TB'),
|
||||
(1 << 30, 'GB'),
|
||||
(1 << 20, 'MB'),
|
||||
(1 << 10, 'kB'),
|
||||
(1, 'B')
|
||||
)
|
||||
for factor, suffix in abbrevs:
|
||||
if size_in_bytes >= factor:
|
||||
break
|
||||
else:
|
||||
factor = 1
|
||||
suffix = 'B'
|
||||
return '%.*f %s' % (precision, size_in_bytes / factor, suffix)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,152 @@
|
||||
# ***** 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 *****
|
||||
#
|
||||
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
||||
"""List dependencies of a blend file."""
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import pathlib
|
||||
import sys
|
||||
import time
|
||||
import typing
|
||||
|
||||
from blender_asset_tracer import trace, bpathlib
|
||||
from . import common
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def add_parser(subparsers):
|
||||
"""Add argparser for this subcommand."""
|
||||
|
||||
parser = subparsers.add_parser('list', help=__doc__)
|
||||
parser.set_defaults(func=cli_list)
|
||||
parser.add_argument('blendfile', type=pathlib.Path)
|
||||
common.add_flag(parser, 'json', help='Output as JSON instead of human-readable text')
|
||||
common.add_flag(parser, 'sha256',
|
||||
help='Include SHA256sums in the output. Note that those may differ from the '
|
||||
'SHA256sums in a BAT-pack when paths are rewritten.')
|
||||
common.add_flag(parser, 'timing', help='Include timing information in the output')
|
||||
|
||||
|
||||
def cli_list(args):
|
||||
bpath = args.blendfile
|
||||
if not bpath.exists():
|
||||
log.fatal('File %s does not exist', args.blendfile)
|
||||
return 3
|
||||
|
||||
if args.json:
|
||||
if args.sha256:
|
||||
log.fatal('--sha256 can currently not be used in combination with --json')
|
||||
if args.timing:
|
||||
log.fatal('--timing can currently not be used in combination with --json')
|
||||
report_json(bpath)
|
||||
else:
|
||||
report_text(bpath, include_sha256=args.sha256, show_timing=args.timing)
|
||||
|
||||
|
||||
def calc_sha_sum(filepath: pathlib.Path) -> typing.Tuple[str, float]:
|
||||
start = time.time()
|
||||
|
||||
if filepath.is_dir():
|
||||
for subfile in filepath.rglob('*'):
|
||||
calc_sha_sum(subfile)
|
||||
duration = time.time() - start
|
||||
return '-multiple-', duration
|
||||
|
||||
summer = hashlib.sha256()
|
||||
with filepath.open('rb') as infile:
|
||||
while True:
|
||||
block = infile.read(32 * 1024)
|
||||
if not block:
|
||||
break
|
||||
summer.update(block)
|
||||
|
||||
digest = summer.hexdigest()
|
||||
duration = time.time() - start
|
||||
|
||||
return digest, duration
|
||||
|
||||
|
||||
def report_text(bpath, *, include_sha256: bool, show_timing: bool):
|
||||
reported_assets = set() # type: typing.Set[pathlib.Path]
|
||||
last_reported_bfile = None
|
||||
shorten = functools.partial(common.shorten, pathlib.Path.cwd())
|
||||
|
||||
time_spent_on_shasums = 0.0
|
||||
start_time = time.time()
|
||||
|
||||
for usage in trace.deps(bpath):
|
||||
filepath = usage.block.bfile.filepath.absolute()
|
||||
if filepath != last_reported_bfile:
|
||||
if include_sha256:
|
||||
shasum, time_spent = calc_sha_sum(filepath)
|
||||
time_spent_on_shasums += time_spent
|
||||
print(shorten(filepath), shasum)
|
||||
else:
|
||||
print(shorten(filepath))
|
||||
|
||||
last_reported_bfile = filepath
|
||||
|
||||
for assetpath in usage.files():
|
||||
assetpath = bpathlib.make_absolute(assetpath)
|
||||
if assetpath in reported_assets:
|
||||
log.debug('Already reported %s', assetpath)
|
||||
continue
|
||||
|
||||
if include_sha256:
|
||||
shasum, time_spent = calc_sha_sum(assetpath)
|
||||
time_spent_on_shasums += time_spent
|
||||
print(' ', shorten(assetpath), shasum)
|
||||
else:
|
||||
print(' ', shorten(assetpath))
|
||||
reported_assets.add(assetpath)
|
||||
|
||||
if show_timing:
|
||||
duration = time.time() - start_time
|
||||
print('Spent %.2f seconds on producing this listing' % duration)
|
||||
if include_sha256:
|
||||
print('Spent %.2f seconds on calculating SHA sums' % time_spent_on_shasums)
|
||||
percentage = time_spent_on_shasums / duration * 100
|
||||
print(' (that is %d%% of the total time' % percentage)
|
||||
|
||||
|
||||
class JSONSerialiser(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
if isinstance(o, pathlib.Path):
|
||||
return str(o)
|
||||
if isinstance(o, set):
|
||||
return sorted(o)
|
||||
return super().default(o)
|
||||
|
||||
|
||||
def report_json(bpath):
|
||||
import collections
|
||||
|
||||
# Mapping from blend file to its dependencies.
|
||||
report = collections.defaultdict(set)
|
||||
|
||||
for usage in trace.deps(bpath):
|
||||
filepath = usage.block.bfile.filepath.absolute()
|
||||
for assetpath in usage.files():
|
||||
assetpath = assetpath.resolve()
|
||||
report[str(filepath)].add(assetpath)
|
||||
|
||||
json.dump(report, sys.stdout, cls=JSONSerialiser, indent=4)
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
# ***** 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 *****
|
||||
#
|
||||
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
||||
"""Create a BAT-pack for the given blend file."""
|
||||
import logging
|
||||
import pathlib
|
||||
import sys
|
||||
import typing
|
||||
|
||||
import blender_asset_tracer.pack.transfer
|
||||
from blender_asset_tracer import pack, bpathlib
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def add_parser(subparsers):
|
||||
"""Add argparser for this subcommand."""
|
||||
|
||||
parser = subparsers.add_parser('pack', help=__doc__)
|
||||
parser.set_defaults(func=cli_pack)
|
||||
parser.add_argument('blendfile', type=pathlib.Path,
|
||||
help='The Blend file to pack.')
|
||||
parser.add_argument('target', type=str,
|
||||
help="The target can be a directory, a ZIP file (does not have to exist "
|
||||
"yet, just use 'something.zip' as target), "
|
||||
"or a URL of S3 storage (s3://endpoint/path) "
|
||||
"or Shaman storage (shaman://endpoint/#checkoutID).")
|
||||
|
||||
parser.add_argument('-p', '--project', type=pathlib.Path,
|
||||
help='Root directory of your project. Paths to below this directory are '
|
||||
'kept in the BAT Pack as well, whereas references to assets from '
|
||||
'outside this directory will have to be rewitten. The blend file MUST '
|
||||
'be inside the project directory. If this option is ommitted, the '
|
||||
'directory containing the blend file is taken as the project '
|
||||
'directoy.')
|
||||
parser.add_argument('-n', '--noop', default=False, action='store_true',
|
||||
help="Don't copy files, just show what would be done.")
|
||||
parser.add_argument('-e', '--exclude', nargs='*', default='',
|
||||
help="Space-separated list of glob patterns (like '*.abc *.vbo') to "
|
||||
"exclude.")
|
||||
parser.add_argument('-c', '--compress', default=False, action='store_true',
|
||||
help='Compress blend files while copying. This option is only valid when '
|
||||
'packing into a directory (contrary to ZIP file or S3 upload). '
|
||||
'Note that files will NOT be compressed when the destination file '
|
||||
'already exists and has the same size as the original file.')
|
||||
parser.add_argument('-r', '--relative-only', default=False, action='store_true',
|
||||
help='Only pack assets that are referred to with a relative path (e.g. '
|
||||
'starting with `//`.')
|
||||
|
||||
|
||||
def cli_pack(args):
|
||||
bpath, ppath, tpath = paths_from_cli(args)
|
||||
|
||||
with create_packer(args, bpath, ppath, tpath) as packer:
|
||||
packer.strategise()
|
||||
try:
|
||||
packer.execute()
|
||||
except blender_asset_tracer.pack.transfer.FileTransferError as ex:
|
||||
log.error("%d files couldn't be copied, starting with %s",
|
||||
len(ex.files_remaining), ex.files_remaining[0])
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
def create_packer(args, bpath: pathlib.Path, ppath: pathlib.Path, target: str) -> pack.Packer:
|
||||
if target.startswith('s3:/'):
|
||||
if args.noop:
|
||||
raise ValueError('S3 uploader does not support no-op.')
|
||||
|
||||
if args.compress:
|
||||
raise ValueError('S3 uploader does not support on-the-fly compression')
|
||||
|
||||
if args.relative_only:
|
||||
raise ValueError('S3 uploader does not support the --relative-only option')
|
||||
|
||||
packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target))
|
||||
|
||||
elif target.startswith('shaman+http:/') or target.startswith('shaman+https:/') \
|
||||
or target.startswith('shaman:/'):
|
||||
if args.noop:
|
||||
raise ValueError('Shaman uploader does not support no-op.')
|
||||
|
||||
if args.compress:
|
||||
raise ValueError('Shaman uploader does not support on-the-fly compression')
|
||||
|
||||
if args.relative_only:
|
||||
raise ValueError('Shaman uploader does not support the --relative-only option')
|
||||
|
||||
packer = create_shamanpacker(bpath, ppath, target)
|
||||
|
||||
elif target.lower().endswith('.zip'):
|
||||
from blender_asset_tracer.pack import zipped
|
||||
|
||||
if args.compress:
|
||||
raise ValueError('ZIP packer does not support on-the-fly compression')
|
||||
|
||||
packer = zipped.ZipPacker(bpath, ppath, target, noop=args.noop,
|
||||
relative_only=args.relative_only)
|
||||
else:
|
||||
packer = pack.Packer(bpath, ppath, target, noop=args.noop,
|
||||
compress=args.compress, relative_only=args.relative_only)
|
||||
|
||||
if args.exclude:
|
||||
# args.exclude is a list, due to nargs='*', so we have to split and flatten.
|
||||
globs = [glob
|
||||
for globs in args.exclude
|
||||
for glob in globs.split()]
|
||||
log.info('Excluding: %s', ', '.join(repr(g) for g in globs))
|
||||
packer.exclude(*globs)
|
||||
return packer
|
||||
|
||||
|
||||
def create_s3packer(bpath, ppath, tpath) -> pack.Packer:
|
||||
from blender_asset_tracer.pack import s3
|
||||
|
||||
# Split the target path into 's3:/', hostname, and actual target path
|
||||
parts = tpath.parts
|
||||
endpoint = 'https://%s/' % parts[1]
|
||||
tpath = pathlib.Path(*tpath.parts[2:])
|
||||
log.info('Uploading to S3-compatible storage %s at %s', endpoint, tpath)
|
||||
|
||||
return s3.S3Packer(bpath, ppath, tpath, endpoint=endpoint)
|
||||
|
||||
|
||||
def create_shamanpacker(bpath: pathlib.Path, ppath: pathlib.Path, tpath: str) -> pack.Packer:
|
||||
"""Creates a package for sending files to a Shaman server.
|
||||
|
||||
URLs should have the form:
|
||||
shaman://hostname/base/url#jobID
|
||||
This uses HTTPS to connect to the server. To connect using HTTP, use:
|
||||
shaman+http://hostname/base-url#jobID
|
||||
"""
|
||||
from blender_asset_tracer.pack import shaman
|
||||
|
||||
endpoint, checkout_id = shaman.parse_endpoint(tpath)
|
||||
if not checkout_id:
|
||||
log.warning('No checkout ID given on the URL. Going to send BAT pack to Shaman, '
|
||||
'but NOT creating a checkout')
|
||||
|
||||
log.info('Uploading to Shaman server %s with job %s', endpoint, checkout_id)
|
||||
return shaman.ShamanPacker(bpath, ppath, '/', endpoint=endpoint, checkout_id=checkout_id)
|
||||
|
||||
|
||||
def paths_from_cli(args) -> typing.Tuple[pathlib.Path, pathlib.Path, str]:
|
||||
"""Return paths to blendfile, project, and pack target.
|
||||
|
||||
Calls sys.exit() if anything is wrong.
|
||||
"""
|
||||
bpath = args.blendfile
|
||||
if not bpath.exists():
|
||||
log.critical('File %s does not exist', bpath)
|
||||
sys.exit(3)
|
||||
if bpath.is_dir():
|
||||
log.critical('%s is a directory, should be a blend file')
|
||||
sys.exit(3)
|
||||
bpath = bpathlib.make_absolute(bpath)
|
||||
|
||||
tpath = args.target
|
||||
|
||||
if args.project is None:
|
||||
ppath = bpathlib.make_absolute(bpath).parent
|
||||
log.warning('No project path given, using %s', ppath)
|
||||
else:
|
||||
ppath = bpathlib.make_absolute(args.project)
|
||||
|
||||
if not ppath.exists():
|
||||
log.critical('Project directory %s does not exist', ppath)
|
||||
sys.exit(5)
|
||||
|
||||
if not ppath.is_dir():
|
||||
log.warning('Project path %s is not a directory; using the parent %s', ppath, ppath.parent)
|
||||
ppath = ppath.parent
|
||||
|
||||
try:
|
||||
bpath.relative_to(ppath)
|
||||
except ValueError:
|
||||
log.critical('Project directory %s does not contain blend file %s',
|
||||
args.project, bpath.absolute())
|
||||
sys.exit(5)
|
||||
|
||||
log.info('Blend file to pack: %s', bpath)
|
||||
log.info('Project path: %s', ppath)
|
||||
log.info('Pack will be created in: %s', tpath)
|
||||
|
||||
return bpath, ppath, tpath
|
||||
Reference in New Issue
Block a user