tornavis/source/blender/blenkernel/intern/packedFile.cc

907 lines
23 KiB
C++

/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <cstdio>
#include <fcntl.h>
#include <sys/stat.h>
#ifndef WIN32
# include <unistd.h>
#else
# include <io.h>
#endif
#include "MEM_guardedalloc.h"
#include <cstring>
#include "DNA_ID.h"
#include "DNA_image_types.h"
#include "DNA_packedFile_types.h"
#include "DNA_sound_types.h"
#include "DNA_vfont_types.h"
#include "DNA_volume_types.h"
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
#include "BKE_image.h"
#include "BKE_image_format.h"
#include "BKE_main.h"
#include "BKE_packedFile.h"
#include "BKE_report.h"
#include "BKE_sound.h"
#include "BKE_vfont.h"
#include "BKE_volume.hh"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#include "BLO_read_write.hh"
int BKE_packedfile_seek(PackedFile *pf, int offset, int whence)
{
int oldseek = -1, seek = 0;
if (pf) {
oldseek = pf->seek;
switch (whence) {
case SEEK_CUR:
seek = oldseek + offset;
break;
case SEEK_END:
seek = pf->size + offset;
break;
case SEEK_SET:
seek = offset;
break;
default:
oldseek = -1;
break;
}
if (seek < 0) {
seek = 0;
}
else if (seek > pf->size) {
seek = pf->size;
}
pf->seek = seek;
}
return oldseek;
}
void BKE_packedfile_rewind(PackedFile *pf)
{
BKE_packedfile_seek(pf, 0, SEEK_SET);
}
int BKE_packedfile_read(PackedFile *pf, void *data, int size)
{
if ((pf != nullptr) && (size >= 0) && (data != nullptr)) {
if (size + pf->seek > pf->size) {
size = pf->size - pf->seek;
}
if (size > 0) {
memcpy(data, ((char *)pf->data) + pf->seek, size);
}
else {
size = 0;
}
pf->seek += size;
}
else {
size = -1;
}
return size;
}
int BKE_packedfile_count_all(Main *bmain)
{
Image *ima;
VFont *vf;
bSound *sound;
Volume *volume;
int count = 0;
/* let's check if there are packed files... */
for (ima = static_cast<Image *>(bmain->images.first); ima;
ima = static_cast<Image *>(ima->id.next))
{
if (BKE_image_has_packedfile(ima)) {
count++;
}
}
for (vf = static_cast<VFont *>(bmain->fonts.first); vf; vf = static_cast<VFont *>(vf->id.next)) {
if (vf->packedfile) {
count++;
}
}
for (sound = static_cast<bSound *>(bmain->sounds.first); sound;
sound = static_cast<bSound *>(sound->id.next))
{
if (sound->packedfile) {
count++;
}
}
for (volume = static_cast<Volume *>(bmain->volumes.first); volume;
volume = static_cast<Volume *>(volume->id.next))
{
if (volume->packedfile) {
count++;
}
}
return count;
}
void BKE_packedfile_free(PackedFile *pf)
{
if (pf) {
BLI_assert(pf->data != nullptr);
MEM_SAFE_FREE(pf->data);
MEM_freeN(pf);
}
else {
printf("%s: Trying to free a nullptr pointer\n", __func__);
}
}
PackedFile *BKE_packedfile_duplicate(const PackedFile *pf_src)
{
BLI_assert(pf_src != nullptr);
BLI_assert(pf_src->data != nullptr);
PackedFile *pf_dst;
pf_dst = static_cast<PackedFile *>(MEM_dupallocN(pf_src));
pf_dst->data = MEM_dupallocN(pf_src->data);
return pf_dst;
}
PackedFile *BKE_packedfile_new_from_memory(void *mem, int memlen)
{
BLI_assert(mem != nullptr);
PackedFile *pf = static_cast<PackedFile *>(MEM_callocN(sizeof(*pf), "PackedFile"));
pf->data = mem;
pf->size = memlen;
return pf;
}
PackedFile *BKE_packedfile_new(ReportList *reports, const char *filepath_rel, const char *basepath)
{
char filepath[FILE_MAX];
/* render result has no filepath and can be ignored
* any other files with no name can be ignored too */
if (filepath_rel[0] == '\0') {
return nullptr;
}
// XXX waitcursor(1);
/* convert relative filenames to absolute filenames */
STRNCPY(filepath, filepath_rel);
BLI_path_abs(filepath, basepath);
/* open the file
* and create a PackedFile structure */
const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
if (file == -1) {
BKE_reportf(reports, RPT_ERROR, "Unable to pack file, source path '%s' not found", filepath);
return nullptr;
}
PackedFile *pf = nullptr;
const size_t file_size = BLI_file_descriptor_size(file);
if (file_size == size_t(-1)) {
BKE_reportf(reports, RPT_ERROR, "Unable to access the size of, source path '%s'", filepath);
}
else if (file_size > INT_MAX) {
BKE_reportf(reports, RPT_ERROR, "Unable to pack files over 2gb, source path '%s'", filepath);
}
else {
/* #MEM_mallocN complains about `MEM_mallocN(0, "...")`,
* a single allocation is harmless and doesn't cause any complications. */
void *data = MEM_mallocN(std::max(file_size, size_t(1)), "packFile");
if (BLI_read(file, data, file_size) == file_size) {
pf = BKE_packedfile_new_from_memory(data, file_size);
}
else {
MEM_freeN(data);
}
}
close(file);
// XXX waitcursor(0);
return pf;
}
void BKE_packedfile_pack_all(Main *bmain, ReportList *reports, bool verbose)
{
Image *ima;
VFont *vfont;
bSound *sound;
Volume *volume;
int tot = 0;
for (ima = static_cast<Image *>(bmain->images.first); ima;
ima = static_cast<Image *>(ima->id.next))
{
if (BKE_image_has_packedfile(ima) == false && !ID_IS_LINKED(ima)) {
if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_TILED)) {
BKE_image_packfiles(reports, ima, ID_BLEND_PATH(bmain, &ima->id));
tot++;
}
else if (ELEM(ima->source, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE) && verbose) {
BKE_reportf(reports,
RPT_WARNING,
"Image '%s' skipped, packing movies or image sequences not supported",
ima->id.name + 2);
}
}
}
for (vfont = static_cast<VFont *>(bmain->fonts.first); vfont;
vfont = static_cast<VFont *>(vfont->id.next))
{
if (vfont->packedfile == nullptr && !ID_IS_LINKED(vfont) &&
BKE_vfont_is_builtin(vfont) == false) {
vfont->packedfile = BKE_packedfile_new(
reports, vfont->filepath, BKE_main_blendfile_path(bmain));
tot++;
}
}
for (sound = static_cast<bSound *>(bmain->sounds.first); sound;
sound = static_cast<bSound *>(sound->id.next))
{
if (sound->packedfile == nullptr && !ID_IS_LINKED(sound)) {
sound->packedfile = BKE_packedfile_new(
reports, sound->filepath, BKE_main_blendfile_path(bmain));
tot++;
}
}
for (volume = static_cast<Volume *>(bmain->volumes.first); volume;
volume = static_cast<Volume *>(volume->id.next))
{
if (volume->packedfile == nullptr && !ID_IS_LINKED(volume)) {
volume->packedfile = BKE_packedfile_new(
reports, volume->filepath, BKE_main_blendfile_path(bmain));
tot++;
}
}
if (tot > 0) {
BKE_reportf(reports, RPT_INFO, "Packed %d file(s)", tot);
}
else if (verbose) {
BKE_report(reports, RPT_INFO, "No new files have been packed");
}
}
int BKE_packedfile_write_to_file(ReportList *reports,
const char *ref_file_name,
const char *filepath_rel,
PackedFile *pf)
{
int file, number;
int ret_value = RET_OK;
bool remove_tmp = false;
char filepath[FILE_MAX];
char filepath_temp[FILE_MAX];
// void *data;
STRNCPY(filepath, filepath_rel);
BLI_path_abs(filepath, ref_file_name);
if (BLI_exists(filepath)) {
for (number = 1; number <= 999; number++) {
SNPRINTF(filepath_temp, "%s.%03d_", filepath, number);
if (!BLI_exists(filepath_temp)) {
if (BLI_copy(filepath, filepath_temp) == RET_OK) {
remove_tmp = true;
}
break;
}
}
}
BLI_file_ensure_parent_dir_exists(filepath);
file = BLI_open(filepath, O_BINARY + O_WRONLY + O_CREAT + O_TRUNC, 0666);
if (file == -1) {
BKE_reportf(reports, RPT_ERROR, "Error creating file '%s'", filepath);
ret_value = RET_ERROR;
}
else {
if (write(file, pf->data, pf->size) != pf->size) {
BKE_reportf(reports, RPT_ERROR, "Error writing file '%s'", filepath);
ret_value = RET_ERROR;
}
else {
BKE_reportf(reports, RPT_INFO, "Saved packed file to: %s", filepath);
}
close(file);
}
if (remove_tmp) {
if (ret_value == RET_ERROR) {
if (BLI_rename_overwrite(filepath_temp, filepath) != 0) {
BKE_reportf(reports,
RPT_ERROR,
"Error restoring temp file (check files '%s' '%s')",
filepath_temp,
filepath);
}
}
else {
if (BLI_delete(filepath_temp, false, false) != 0) {
BKE_reportf(reports, RPT_ERROR, "Error deleting '%s' (ignored)", filepath_temp);
}
}
}
return ret_value;
}
enum ePF_FileCompare BKE_packedfile_compare_to_file(const char *ref_file_name,
const char *filepath_rel,
PackedFile *pf)
{
BLI_stat_t st;
enum ePF_FileCompare ret_val;
char buf[4096];
char filepath[FILE_MAX];
STRNCPY(filepath, filepath_rel);
BLI_path_abs(filepath, ref_file_name);
if (BLI_stat(filepath, &st) == -1) {
ret_val = PF_CMP_NOFILE;
}
else if (st.st_size != pf->size) {
ret_val = PF_CMP_DIFFERS;
}
else {
/* we'll have to compare the two... */
const int file = BLI_open(filepath, O_BINARY | O_RDONLY, 0);
if (file == -1) {
ret_val = PF_CMP_NOFILE;
}
else {
ret_val = PF_CMP_EQUAL;
for (int i = 0; i < pf->size; i += sizeof(buf)) {
int len = pf->size - i;
if (len > sizeof(buf)) {
len = sizeof(buf);
}
if (BLI_read(file, buf, len) != len) {
/* read error ... */
ret_val = PF_CMP_DIFFERS;
break;
}
if (memcmp(buf, ((const char *)pf->data) + i, len) != 0) {
ret_val = PF_CMP_DIFFERS;
break;
}
}
close(file);
}
}
return ret_val;
}
char *BKE_packedfile_unpack_to_file(ReportList *reports,
const char *ref_file_name,
const char *abs_name,
const char *local_name,
PackedFile *pf,
enum ePF_FileStatus how)
{
char *newname = nullptr;
const char *temp = nullptr;
if (pf != nullptr) {
switch (how) {
case PF_KEEP:
break;
case PF_REMOVE:
temp = abs_name;
break;
case PF_USE_LOCAL: {
char temp_abs[FILE_MAX];
STRNCPY(temp_abs, local_name);
BLI_path_abs(temp_abs, ref_file_name);
/* if file exists use it */
if (BLI_exists(temp_abs)) {
temp = local_name;
break;
}
/* else create it */
ATTR_FALLTHROUGH;
}
case PF_WRITE_LOCAL:
if (BKE_packedfile_write_to_file(reports, ref_file_name, local_name, pf) == RET_OK) {
temp = local_name;
}
break;
case PF_USE_ORIGINAL: {
char temp_abs[FILE_MAX];
STRNCPY(temp_abs, abs_name);
BLI_path_abs(temp_abs, ref_file_name);
/* if file exists use it */
if (BLI_exists(temp_abs)) {
BKE_reportf(reports, RPT_INFO, "Use existing file (instead of packed): %s", abs_name);
temp = abs_name;
break;
}
/* else create it */
ATTR_FALLTHROUGH;
}
case PF_WRITE_ORIGINAL:
if (BKE_packedfile_write_to_file(reports, ref_file_name, abs_name, pf) == RET_OK) {
temp = abs_name;
}
break;
default:
printf("%s: unknown return_value %d\n", __func__, how);
break;
}
if (temp) {
newname = BLI_strdup(temp);
}
}
return newname;
}
static void unpack_generate_paths(const char *filepath,
ID *id,
char *r_abspath,
size_t abspath_maxncpy,
char *r_relpath,
size_t relpath_maxncpy)
{
const short id_type = GS(id->name);
char temp_filename[FILE_MAX];
char temp_dirname[FILE_MAXDIR];
BLI_path_split_dir_file(
filepath, temp_dirname, sizeof(temp_dirname), temp_filename, sizeof(temp_filename));
if (temp_filename[0] == '\0') {
/* NOTE: we generally do not have any real way to re-create extension out of data. */
const size_t len = STRNCPY_RLEN(temp_filename, id->name + 2);
printf("%s\n", temp_filename);
/* For images ensure that the temporary filename contains tile number information as well as
* a file extension based on the file magic. */
if (id_type == ID_IM) {
Image *ima = (Image *)id;
ImagePackedFile *imapf = static_cast<ImagePackedFile *>(ima->packedfiles.last);
if (imapf != nullptr && imapf->packedfile != nullptr) {
const PackedFile *pf = imapf->packedfile;
enum eImbFileType ftype = eImbFileType(
IMB_ispic_type_from_memory((const uchar *)pf->data, pf->size));
if (ima->source == IMA_SRC_TILED) {
char tile_number[6];
SNPRINTF(tile_number, ".%d", imapf->tile_number);
BLI_strncpy(temp_filename + len, tile_number, sizeof(temp_filename) - len);
}
if (ftype != IMB_FTYPE_NONE) {
const int imtype = BKE_ftype_to_imtype(ftype, nullptr);
BKE_image_path_ext_from_imtype_ensure(temp_filename, sizeof(temp_filename), imtype);
}
}
}
BLI_path_make_safe_filename(temp_filename);
printf("%s\n", temp_filename);
}
if (temp_dirname[0] == '\0') {
/* Fallback to relative dir. */
STRNCPY(temp_dirname, "//");
}
{
const char *dir_name = nullptr;
switch (id_type) {
case ID_VF:
dir_name = "fonts";
break;
case ID_SO:
dir_name = "sounds";
break;
case ID_IM:
dir_name = "textures";
break;
case ID_VO:
dir_name = "volumes";
break;
default:
break;
}
if (dir_name) {
BLI_path_join(r_relpath, relpath_maxncpy, "//", dir_name, temp_filename);
}
}
{
size_t len = BLI_strncpy_rlen(r_abspath, temp_dirname, abspath_maxncpy);
BLI_strncpy(r_abspath + len, temp_filename, abspath_maxncpy - len);
}
}
char *BKE_packedfile_unpack(Main *bmain,
ReportList *reports,
ID *id,
const char *orig_file_path,
PackedFile *pf,
enum ePF_FileStatus how)
{
char localname[FILE_MAX], absname[FILE_MAX];
char *new_name = nullptr;
if (id != nullptr) {
unpack_generate_paths(
orig_file_path, id, absname, sizeof(absname), localname, sizeof(localname));
new_name = BKE_packedfile_unpack_to_file(
reports, BKE_main_blendfile_path(bmain), absname, localname, pf, how);
}
return new_name;
}
int BKE_packedfile_unpack_vfont(Main *bmain,
ReportList *reports,
VFont *vfont,
enum ePF_FileStatus how)
{
int ret_value = RET_ERROR;
if (vfont) {
char *new_file_path = BKE_packedfile_unpack(
bmain, reports, (ID *)vfont, vfont->filepath, vfont->packedfile, how);
if (new_file_path != nullptr) {
ret_value = RET_OK;
BKE_packedfile_free(vfont->packedfile);
vfont->packedfile = nullptr;
STRNCPY(vfont->filepath, new_file_path);
MEM_freeN(new_file_path);
}
}
return ret_value;
}
int BKE_packedfile_unpack_sound(Main *bmain,
ReportList *reports,
bSound *sound,
enum ePF_FileStatus how)
{
int ret_value = RET_ERROR;
if (sound != nullptr) {
char *new_file_path = BKE_packedfile_unpack(
bmain, reports, (ID *)sound, sound->filepath, sound->packedfile, how);
if (new_file_path != nullptr) {
STRNCPY(sound->filepath, new_file_path);
MEM_freeN(new_file_path);
BKE_packedfile_free(sound->packedfile);
sound->packedfile = nullptr;
BKE_sound_load(bmain, sound);
ret_value = RET_OK;
}
}
return ret_value;
}
int BKE_packedfile_unpack_image(Main *bmain,
ReportList *reports,
Image *ima,
enum ePF_FileStatus how)
{
int ret_value = RET_ERROR;
if (ima != nullptr) {
while (ima->packedfiles.last) {
ImagePackedFile *imapf = static_cast<ImagePackedFile *>(ima->packedfiles.last);
char *new_file_path = BKE_packedfile_unpack(
bmain, reports, (ID *)ima, imapf->filepath, imapf->packedfile, how);
if (new_file_path != nullptr) {
ImageView *iv;
ret_value = ret_value == RET_ERROR ? RET_ERROR : RET_OK;
BKE_packedfile_free(imapf->packedfile);
imapf->packedfile = nullptr;
/* update the new corresponding view filepath */
iv = static_cast<ImageView *>(
BLI_findstring(&ima->views, imapf->filepath, offsetof(ImageView, filepath)));
if (iv) {
STRNCPY(iv->filepath, new_file_path);
}
/* keep the new name in the image for non-pack specific reasons */
if (how != PF_REMOVE) {
STRNCPY(ima->filepath, new_file_path);
if (ima->source == IMA_SRC_TILED) {
/* Ensure that the Image filepath is kept in a tokenized format. */
BKE_image_ensure_tile_token(ima->filepath, sizeof(ima->filepath));
}
}
MEM_freeN(new_file_path);
}
else {
ret_value = RET_ERROR;
}
BLI_remlink(&ima->packedfiles, imapf);
MEM_freeN(imapf);
}
}
if (ret_value == RET_OK) {
BKE_image_signal(bmain, ima, nullptr, IMA_SIGNAL_RELOAD);
}
return ret_value;
}
int BKE_packedfile_unpack_volume(Main *bmain,
ReportList *reports,
Volume *volume,
enum ePF_FileStatus how)
{
int ret_value = RET_ERROR;
if (volume != nullptr) {
char *new_file_path = BKE_packedfile_unpack(
bmain, reports, (ID *)volume, volume->filepath, volume->packedfile, how);
if (new_file_path != nullptr) {
STRNCPY(volume->filepath, new_file_path);
MEM_freeN(new_file_path);
BKE_packedfile_free(volume->packedfile);
volume->packedfile = nullptr;
BKE_volume_unload(volume);
ret_value = RET_OK;
}
}
return ret_value;
}
int BKE_packedfile_unpack_all_libraries(Main *bmain, ReportList *reports)
{
Library *lib;
char *newname;
int ret_value = RET_ERROR;
for (lib = static_cast<Library *>(bmain->libraries.first); lib;
lib = static_cast<Library *>(lib->id.next))
{
if (lib->packedfile && lib->filepath[0]) {
newname = BKE_packedfile_unpack_to_file(reports,
BKE_main_blendfile_path(bmain),
lib->filepath_abs,
lib->filepath_abs,
lib->packedfile,
PF_WRITE_ORIGINAL);
if (newname != nullptr) {
ret_value = RET_OK;
printf("Unpacked .blend library: %s\n", newname);
BKE_packedfile_free(lib->packedfile);
lib->packedfile = nullptr;
MEM_freeN(newname);
}
}
}
return ret_value;
}
void BKE_packedfile_pack_all_libraries(Main *bmain, ReportList *reports)
{
Library *lib;
/* Test for relativeness. */
for (lib = static_cast<Library *>(bmain->libraries.first); lib;
lib = static_cast<Library *>(lib->id.next))
{
if (!BLI_path_is_rel(lib->filepath)) {
break;
}
}
if (lib) {
BKE_reportf(reports, RPT_ERROR, "Cannot pack absolute file: '%s'", lib->filepath);
return;
}
for (lib = static_cast<Library *>(bmain->libraries.first); lib;
lib = static_cast<Library *>(lib->id.next))
{
if (lib->packedfile == nullptr) {
lib->packedfile = BKE_packedfile_new(reports, lib->filepath, BKE_main_blendfile_path(bmain));
}
}
}
void BKE_packedfile_unpack_all(Main *bmain, ReportList *reports, enum ePF_FileStatus how)
{
Image *ima;
VFont *vf;
bSound *sound;
Volume *volume;
for (ima = static_cast<Image *>(bmain->images.first); ima;
ima = static_cast<Image *>(ima->id.next))
{
if (BKE_image_has_packedfile(ima)) {
BKE_packedfile_unpack_image(bmain, reports, ima, how);
}
}
for (vf = static_cast<VFont *>(bmain->fonts.first); vf; vf = static_cast<VFont *>(vf->id.next)) {
if (vf->packedfile) {
BKE_packedfile_unpack_vfont(bmain, reports, vf, how);
}
}
for (sound = static_cast<bSound *>(bmain->sounds.first); sound;
sound = static_cast<bSound *>(sound->id.next))
{
if (sound->packedfile) {
BKE_packedfile_unpack_sound(bmain, reports, sound, how);
}
}
for (volume = static_cast<Volume *>(bmain->volumes.first); volume;
volume = static_cast<Volume *>(volume->id.next))
{
if (volume->packedfile) {
BKE_packedfile_unpack_volume(bmain, reports, volume, how);
}
}
}
bool BKE_packedfile_id_check(const ID *id)
{
switch (GS(id->name)) {
case ID_IM: {
const Image *ima = (const Image *)id;
return BKE_image_has_packedfile(ima);
}
case ID_VF: {
const VFont *vf = (const VFont *)id;
return vf->packedfile != nullptr;
}
case ID_SO: {
const bSound *snd = (const bSound *)id;
return snd->packedfile != nullptr;
}
case ID_VO: {
const Volume *volume = (const Volume *)id;
return volume->packedfile != nullptr;
}
case ID_LI: {
const Library *li = (const Library *)id;
return li->packedfile != nullptr;
}
default:
break;
}
return false;
}
void BKE_packedfile_id_unpack(Main *bmain, ID *id, ReportList *reports, enum ePF_FileStatus how)
{
switch (GS(id->name)) {
case ID_IM: {
Image *ima = (Image *)id;
if (BKE_image_has_packedfile(ima)) {
BKE_packedfile_unpack_image(bmain, reports, ima, how);
}
break;
}
case ID_VF: {
VFont *vf = (VFont *)id;
if (vf->packedfile) {
BKE_packedfile_unpack_vfont(bmain, reports, vf, how);
}
break;
}
case ID_SO: {
bSound *snd = (bSound *)id;
if (snd->packedfile) {
BKE_packedfile_unpack_sound(bmain, reports, snd, how);
}
break;
}
case ID_VO: {
Volume *volume = (Volume *)id;
if (volume->packedfile) {
BKE_packedfile_unpack_volume(bmain, reports, volume, how);
}
break;
}
case ID_LI: {
Library *li = (Library *)id;
BKE_reportf(reports, RPT_ERROR, "Cannot unpack individual Library file, '%s'", li->filepath);
break;
}
default:
break;
}
}
void BKE_packedfile_blend_write(BlendWriter *writer, PackedFile *pf)
{
if (pf == nullptr) {
return;
}
BLO_write_struct(writer, PackedFile, pf);
BLO_write_raw(writer, pf->size, pf->data);
}
void BKE_packedfile_blend_read(BlendDataReader *reader, PackedFile **pf_p)
{
BLO_read_packed_address(reader, pf_p);
PackedFile *pf = *pf_p;
if (pf == nullptr) {
return;
}
BLO_read_packed_address(reader, &pf->data);
if (pf->data == nullptr) {
/* We cannot allow a PackedFile with a nullptr data field,
* the whole code assumes this is not possible. See #70315. */
printf("%s: nullptr packedfile data, cleaning up...\n", __func__);
MEM_SAFE_FREE(pf);
}
}