Skip to content

Commit 5430ab2

Browse files
authored
Merge pull request #72 from agruzdev/save-via-tmp
FreeImage_Save uses tmp file to avoid corrupted target file states
2 parents 13543ad + 94dd5c8 commit 5430ab2

3 files changed

Lines changed: 101 additions & 25 deletions

File tree

Source/FreeImage/Plugin.cpp

Lines changed: 92 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include <ctype.h>
3636
#endif // _WIN32
3737

38+
#include <filesystem>
39+
3840
#include "FreeImage.h"
3941
#include "Utilities.h"
4042
#include "FreeImageIO.h"
@@ -769,41 +771,107 @@ FreeImage_SaveToHandle(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, FreeImageIO *io, fi
769771
}
770772

771773

772-
FIBOOL DLL_CALLCONV
773-
FreeImage_Save(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const char *filename, int flags) {
774-
FreeImageIO io;
775-
SetDefaultIO(&io);
774+
namespace {
776775

777-
FIBOOL success{FALSE};
778-
if (auto *handle = fopen(filename, "w+b")) {
779-
success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);
780776

781-
fclose(handle);
782-
} else {
783-
FreeImage_OutputMessageProc((int)fif, "FreeImage_Save: failed to open file %s", filename);
777+
std::filesystem::path MakeRandomSuffix()
778+
{
779+
std::stringstream strs{};
780+
strs << ".fitmp" << std::hex << static_cast<uint32_t>(std::rand());
781+
return strs.str();
784782
}
785783

786-
return success;
787-
}
784+
785+
std::filesystem::path MakeTmpName(const std::filesystem::path& target)
786+
{
787+
std::filesystem::path res{};
788+
789+
int attempts = 16;
790+
do {
791+
res = target.stem();
792+
res += MakeRandomSuffix();
793+
res += target.extension();
794+
795+
if (std::filesystem::status(res).type() != std::filesystem::file_type::not_found) {
796+
continue;
797+
}
798+
799+
} while (--attempts);
800+
801+
return res;
802+
}
803+
804+
805+
bool MoveOrCopy(const std::filesystem::path& oldp, const std::filesystem::path& newp) noexcept
806+
{
807+
std::error_code err{};
808+
std::filesystem::rename(oldp, newp, err);
809+
if (!err) {
810+
return true;
811+
}
812+
const auto copied = std::filesystem::copy_file(oldp, newp, std::filesystem::copy_options::overwrite_existing, err);
813+
if (copied) {
814+
std::filesystem::remove(oldp, err);
815+
}
816+
return copied;
817+
}
818+
819+
820+
FIBOOL SaveImpl(FREE_IMAGE_FORMAT fif, FIBITMAP* dib, const std::filesystem::path& filename, int flags)
821+
try
822+
{
823+
FreeImageIO io;
824+
SetDefaultIO(&io);
825+
826+
FIBOOL success{ FALSE };
827+
828+
const auto tmpname = MakeTmpName(filename);
829+
if (auto* handle = FreeImage_FOpen(tmpname, "w+b")) {
830+
success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);
831+
fclose(handle);
832+
833+
if (success) {
834+
success = MoveOrCopy(tmpname, filename);
835+
if (!success) {
836+
FreeImage_OutputMessageProc((int)fif, "FreeImage_Save: failed to rename output file %s", filename.u8string().c_str());
837+
}
838+
}
839+
else {
840+
std::error_code err{};
841+
std::filesystem::remove(tmpname, err);
842+
}
843+
}
844+
else {
845+
FreeImage_OutputMessageProc((int)fif, "FreeImage_Save: failed to open file %s", filename.u8string().c_str());
846+
}
847+
848+
return success;
849+
}
850+
catch (...) {
851+
return FALSE;
852+
}
853+
854+
} // namespace
855+
788856

789857
FIBOOL DLL_CALLCONV
790-
FreeImage_SaveU(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const wchar_t *filename, int flags) {
791-
FreeImageIO io;
792-
SetDefaultIO(&io);
858+
FreeImage_Save(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const char *filename, int flags) {
859+
if (!dib || !filename) {
860+
return FALSE;
861+
}
862+
return SaveImpl(fif, dib, filename, flags);
863+
}
793864

794-
FIBOOL success{FALSE};
795-
#ifdef _WIN32
796-
if (auto *handle = _wfopen(filename, L"w+b")) {
797-
success = FreeImage_SaveToHandle(fif, dib, &io, (fi_handle)handle, flags);
798865

799-
fclose(handle);
800-
} else {
801-
FreeImage_OutputMessageProc((int)fif, "FreeImage_SaveU: failed to open output file");
866+
FIBOOL DLL_CALLCONV
867+
FreeImage_SaveU(FREE_IMAGE_FORMAT fif, FIBITMAP *dib, const wchar_t *filename, int flags) {
868+
if (!dib || !filename) {
869+
return FALSE;
802870
}
803-
#endif
804-
return success;
871+
return SaveImpl(fif, dib, filename, flags);
805872
}
806873

874+
807875
// =====================================================================
808876
// Plugin construction + enable/disable functions
809877
// =====================================================================

TestAPI/MainTestSuite.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ int main(int argc, char *argv[]) {
8989
// test internal image types
9090
testImageType(width, height);
9191

92+
93+
auto bmp = FreeImage_AllocateT(FIT_COMPLEX, 128, 128, 128);
94+
FreeImage_Save(FIF_JPEG, bmp, "failed_to_save.jpg");
95+
FreeImage_Unload(bmp);
96+
97+
9298
#if FREEIMAGE_WITH_LIBJPEG
9399
// test the clone function
94100
testAllocateCloneUnload("exif.jpg");

TestAPI/testHeaderOnly.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ testExifRawFile(const char *lpszPathName, int load_flags, int save_flags) {
174174
uint8_t *value = (uint8_t*)FreeImage_GetTagValue(tag);
175175

176176
// save as jpeg : Exif data should be preserved
177-
FreeImage_Save(fif, dib, lpszDstPathName, save_flags);
177+
if (!FreeImage_Save(fif, dib, lpszDstPathName, save_flags)) {
178+
throw (1);
179+
}
178180

179181
// load and check Exif raw data
180182
fif = FreeImage_GetFileType(lpszDstPathName);

0 commit comments

Comments
 (0)