mirror of
https://github.com/mcneel/opennurbs.git
synced 2026-03-01 11:36:09 +08:00
4672 lines
116 KiB
C++
4672 lines
116 KiB
C++
//
|
|
// Copyright (c) 1993-2022 Robert McNeel & Associates. All rights reserved.
|
|
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
|
|
// McNeel & Associates.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
|
|
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
|
|
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
|
|
//
|
|
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
#include "opennurbs.h"
|
|
|
|
#if !defined(ON_COMPILING_OPENNURBS)
|
|
// This check is included in all opennurbs source .c and .cpp files to insure
|
|
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
|
|
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined
|
|
// and the opennurbs .h files alter what is declared and how it is declared.
|
|
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
|
|
#endif
|
|
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
// November 2015: Visual Studo 2013 (and probably others)
|
|
// Shlwapi.h and Shlobj.h are not included in opennurbs_system.h
|
|
// because the have gems like "#define small ..." (Thank You Microsoft!).
|
|
// Turns out there is plenty of code that uses opennurbs where crazy
|
|
// developers thought "small" would be a reasonable name for a local
|
|
// variable. Reminds me of dealing with AutoDesk's old #define X 0
|
|
// in their headers from 20 years ago.
|
|
#pragma ON_PRAGMA_WARNING_BEFORE_DIRTY_INCLUDE
|
|
#include <Shlwapi.h>
|
|
#pragma ON_PRAGMA_WARNING_AFTER_DIRTY_INCLUDE
|
|
#pragma ON_PRAGMA_WARNING_BEFORE_DIRTY_INCLUDE
|
|
#include <Shlobj.h>
|
|
#pragma ON_PRAGMA_WARNING_AFTER_DIRTY_INCLUDE
|
|
#if defined(_M_X64) && defined(WIN32) && defined(WIN64)
|
|
// Shlwapi.h, Shlobj.h and perhaps others, unconditionally define WIN32
|
|
#undef WIN32
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(ON_RUNTIME_APPLE)
|
|
#include "unistd.h" //for unlink
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ON_String::SplitPath(
|
|
const char* path,
|
|
ON_String* drive,
|
|
ON_String* dir,
|
|
ON_String* fname,
|
|
ON_String* ext
|
|
)
|
|
{
|
|
ON_FileSystemPath::SplitPath(
|
|
path,
|
|
drive,
|
|
dir,
|
|
fname,
|
|
ext
|
|
);
|
|
}
|
|
|
|
bool ON_FileSystemPath::IsDirectorySeparator(
|
|
char c,
|
|
bool bAllowAlternate
|
|
)
|
|
{
|
|
return (c == ON_FileSystemPath::DirectorySeparatorAsChar || (bAllowAlternate && c == ON_FileSystemPath::DirectorySeparatorAsChar));
|
|
}
|
|
|
|
bool ON_FileSystemPath::IsDirectorySeparator(
|
|
wchar_t c,
|
|
bool bAllowAlternate
|
|
)
|
|
{
|
|
return (c == ON_FileSystemPath::DirectorySeparator || (bAllowAlternate && c == ON_FileSystemPath::AlternateDirectorySeparator));
|
|
}
|
|
|
|
void ON_FileSystemPath::SplitPath(
|
|
const char* path,
|
|
ON_String* drive,
|
|
ON_String* dir,
|
|
ON_String* file_name_stem,
|
|
ON_String* ext
|
|
)
|
|
{
|
|
const char* dr = 0;
|
|
const char* d = 0;
|
|
const char* f = 0;
|
|
const char* e = 0;
|
|
|
|
// Use local path in case drive, dir, file_name_stem or ext are being reused.
|
|
const ON_String local_path(path);
|
|
path = static_cast<const char*>(local_path);
|
|
on_splitpath(path,&dr,&d,&f,&e);
|
|
|
|
if ( 0 != drive )
|
|
{
|
|
if ( 0 != dr )
|
|
{
|
|
int length;
|
|
if ( 0 != d )
|
|
length = (int)(d-dr);
|
|
else if ( 0 != f )
|
|
length = (int)(f-dr);
|
|
else if ( 0 != e )
|
|
length = (int)(e-dr);
|
|
else
|
|
length = ON_String::Length(dr);
|
|
*drive = ON_String(dr,length);
|
|
}
|
|
else
|
|
drive->Empty();
|
|
}
|
|
|
|
if ( 0 != dir )
|
|
{
|
|
if ( 0 != d )
|
|
{
|
|
int length;
|
|
if ( 0 != f )
|
|
length = (int)(f-d);
|
|
else if ( 0 != e )
|
|
length = (int)(e-d);
|
|
else
|
|
length = ON_String::Length(d);
|
|
|
|
*dir = ON_String(d,length);
|
|
}
|
|
else
|
|
dir->Empty();
|
|
}
|
|
|
|
if ( 0 != file_name_stem )
|
|
{
|
|
if ( 0 != f )
|
|
{
|
|
int length;
|
|
if ( 0 != e )
|
|
length = (int)(e-f);
|
|
else
|
|
length = ON_String::Length(f);
|
|
|
|
*file_name_stem = ON_String(f,length);
|
|
}
|
|
else
|
|
file_name_stem->Empty();
|
|
}
|
|
|
|
if ( 0 != ext )
|
|
{
|
|
*ext = e;
|
|
}
|
|
}
|
|
|
|
void ON_wString::SplitPath(
|
|
const char* path,
|
|
ON_wString* drive,
|
|
ON_wString* dir,
|
|
ON_wString* fname,
|
|
ON_wString* ext
|
|
)
|
|
{
|
|
ON_FileSystemPath::SplitPath(
|
|
path,
|
|
drive,
|
|
dir,
|
|
fname,
|
|
ext
|
|
);
|
|
}
|
|
|
|
void ON_FileSystemPath::SplitPath(
|
|
const char* path,
|
|
ON_wString* drive,
|
|
ON_wString* dir,
|
|
ON_wString* file_name_stem,
|
|
ON_wString* ext
|
|
)
|
|
{
|
|
const char* dr = 0;
|
|
const char* d = 0;
|
|
const char* f = 0;
|
|
const char* e = 0;
|
|
|
|
// Use local path in case drive, dir, file_name_stem or ext are being reused.
|
|
const ON_String local_path(path);
|
|
path = static_cast<const char*>(local_path);
|
|
on_splitpath(path,&dr,&d,&f,&e);
|
|
|
|
if ( 0 != drive )
|
|
{
|
|
if ( 0 != dr )
|
|
{
|
|
int length;
|
|
if ( 0 != d )
|
|
length = (int)(d-dr);
|
|
else if ( 0 != f )
|
|
length = (int)(f-dr);
|
|
else if ( 0 != e )
|
|
length = (int)(e-dr);
|
|
else
|
|
length = ON_String::Length(dr);
|
|
|
|
*drive = ON_wString(dr,length);
|
|
}
|
|
else
|
|
drive->Empty();
|
|
}
|
|
|
|
if ( 0 != dir )
|
|
{
|
|
if ( 0 != d )
|
|
{
|
|
int length;
|
|
if ( 0 != f )
|
|
length = (int)(f-d);
|
|
else if ( 0 != e )
|
|
length = (int)(e-d);
|
|
else
|
|
length = ON_String::Length(d);
|
|
|
|
*dir = ON_wString(d,length);
|
|
}
|
|
else
|
|
dir->Empty();
|
|
}
|
|
|
|
if ( 0 != file_name_stem )
|
|
{
|
|
if ( 0 != f )
|
|
{
|
|
int length;
|
|
if ( 0 != e )
|
|
length = (int)(e-f);
|
|
else
|
|
length = ON_String::Length(f);
|
|
|
|
*file_name_stem = ON_wString(f,length);
|
|
}
|
|
else
|
|
file_name_stem->Empty();
|
|
}
|
|
|
|
if ( 0 != ext )
|
|
{
|
|
*ext = e;
|
|
}
|
|
}
|
|
|
|
|
|
void ON_wString::SplitPath(
|
|
const wchar_t* path,
|
|
ON_wString* drive,
|
|
ON_wString* dir,
|
|
ON_wString* fname,
|
|
ON_wString* ext
|
|
)
|
|
{
|
|
ON_FileSystemPath::SplitPath(
|
|
path,
|
|
drive,
|
|
dir,
|
|
fname,
|
|
ext
|
|
);
|
|
}
|
|
|
|
void ON_FileSystemPath::SplitPath(
|
|
const wchar_t* path,
|
|
ON_wString* drive,
|
|
ON_wString* dir,
|
|
ON_wString* file_name_stem_and_extension
|
|
)
|
|
{
|
|
const wchar_t* dr = 0;
|
|
const wchar_t* d = 0;
|
|
const wchar_t* f = 0;
|
|
const wchar_t* e = 0;
|
|
|
|
// Use local path in case drive, dir, file_name_stem or ext are being reused.
|
|
const ON_wString local_path(path);
|
|
path = static_cast<const wchar_t*>(local_path);
|
|
on_wsplitpath(path,&dr,&d,&f,&e);
|
|
|
|
if ( 0 != drive )
|
|
{
|
|
if ( 0 != dr )
|
|
{
|
|
int length;
|
|
if ( 0 != d )
|
|
length = (int)(d-dr);
|
|
else if ( 0 != f )
|
|
length = (int)(f-dr);
|
|
else if ( 0 != e )
|
|
length = (int)(e-dr);
|
|
else
|
|
length = ON_wString::Length(dr);
|
|
|
|
*drive = ON_wString(dr,length);
|
|
}
|
|
else
|
|
drive->Empty();
|
|
}
|
|
|
|
if ( 0 != dir )
|
|
{
|
|
if ( 0 != d )
|
|
{
|
|
int length;
|
|
if ( 0 != f )
|
|
length = (int)(f-d);
|
|
else if ( 0 != e )
|
|
length = (int)(e-d);
|
|
else
|
|
length = ON_wString::Length(d);
|
|
*dir = ON_wString(d,length);
|
|
}
|
|
else
|
|
dir->Empty();
|
|
}
|
|
|
|
if ( 0 != file_name_stem_and_extension )
|
|
{
|
|
if ( 0 != f )
|
|
{
|
|
*file_name_stem_and_extension = f;
|
|
}
|
|
else if ( 0 != e )
|
|
{
|
|
// "C:/dir/.abc" returns ".abc"
|
|
*file_name_stem_and_extension = e;
|
|
}
|
|
else
|
|
file_name_stem_and_extension->Empty();
|
|
}
|
|
}
|
|
|
|
|
|
void ON_FileSystemPath::SplitPath(
|
|
const wchar_t* path,
|
|
ON_wString* drive,
|
|
ON_wString* dir,
|
|
ON_wString* file_name_stem,
|
|
ON_wString* ext
|
|
)
|
|
{
|
|
const wchar_t* dr = 0;
|
|
const wchar_t* d = 0;
|
|
const wchar_t* f = 0;
|
|
const wchar_t* e = 0;
|
|
|
|
// Use local path in case drive, dir, file_name_stem or ext are being reused.
|
|
const ON_wString local_path(path);
|
|
path = static_cast<const wchar_t*>(local_path);
|
|
on_wsplitpath(path,&dr,&d,&f,&e);
|
|
|
|
if ( 0 != drive )
|
|
{
|
|
if ( 0 != dr )
|
|
{
|
|
int length;
|
|
if ( 0 != d )
|
|
length = (int)(d-dr);
|
|
else if ( 0 != f )
|
|
length = (int)(f-dr);
|
|
else if ( 0 != e )
|
|
length = (int)(e-dr);
|
|
else
|
|
length = ON_wString::Length(dr);
|
|
|
|
*drive = ON_wString(dr,length);
|
|
}
|
|
else
|
|
drive->Empty();
|
|
}
|
|
|
|
if ( 0 != dir )
|
|
{
|
|
if ( 0 != d )
|
|
{
|
|
int length;
|
|
if ( 0 != f )
|
|
length = (int)(f-d);
|
|
else if ( 0 != e )
|
|
length = (int)(e-d);
|
|
else
|
|
length = ON_wString::Length(d);
|
|
*dir = ON_wString(d,length);
|
|
}
|
|
else
|
|
dir->Empty();
|
|
}
|
|
|
|
if ( 0 != file_name_stem )
|
|
{
|
|
if ( 0 != f )
|
|
{
|
|
int length;
|
|
if ( 0 != e )
|
|
length = (int)(e-f);
|
|
else
|
|
length = ON_wString::Length(f);
|
|
*file_name_stem = ON_wString(f,length);
|
|
}
|
|
else
|
|
file_name_stem->Empty();
|
|
}
|
|
|
|
if ( 0 != ext )
|
|
{
|
|
*ext = e;
|
|
}
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::VolumeFromPath(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
ON_wString volume;
|
|
ON_FileSystemPath::SplitPath(path, &volume, nullptr, nullptr, nullptr);
|
|
return volume;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::DirectoryFromPath(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
ON_wString directory;
|
|
ON_FileSystemPath::SplitPath(path, nullptr, &directory, nullptr, nullptr);
|
|
return directory;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::VolumeAndDirectoryFromPath(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
ON_wString volume;
|
|
ON_wString directory;
|
|
ON_FileSystemPath::SplitPath(path, &volume, &directory, nullptr, nullptr);
|
|
const ON_wString volume_and_directory( volume + directory);
|
|
return volume_and_directory;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::FileNameFromPath(
|
|
const wchar_t* path,
|
|
bool bIncludeExtension
|
|
)
|
|
{
|
|
ON_wString fname;
|
|
if ( bIncludeExtension )
|
|
ON_FileSystemPath::SplitPath(path, nullptr, nullptr, &fname);
|
|
else
|
|
ON_FileSystemPath::SplitPath(path, nullptr, nullptr, &fname, nullptr);
|
|
return fname;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::FileNameExtensionFromPath(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
ON_wString ext;
|
|
ON_FileSystemPath::SplitPath(path, nullptr, nullptr, nullptr, &ext);
|
|
return ext;
|
|
}
|
|
|
|
static bool IsAtoZ(int c)
|
|
{
|
|
return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
|
}
|
|
|
|
static bool Is0to9(int c)
|
|
{
|
|
return (c >= '0' && c <= '9');
|
|
}
|
|
|
|
static bool IsPermittedInPathName(int c)
|
|
{
|
|
if ( c >= 0 && c < ON_wString::Space )
|
|
return false;
|
|
switch (c)
|
|
{
|
|
case '/':
|
|
case '\\':
|
|
case ':':
|
|
case '<':
|
|
case '>':
|
|
case '"':
|
|
case '?':
|
|
case '*':
|
|
case '|':
|
|
//case 127:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool IsPermittedInHostName(int c)
|
|
{
|
|
return IsPermittedInPathName(c);
|
|
}
|
|
|
|
|
|
static bool IsDirSep(int c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case '/':
|
|
case '\\':
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool IsDotDir(const wchar_t* path)
|
|
{
|
|
return (nullptr != path && '.' == path[0] && IsDirSep(path[1]));
|
|
}
|
|
|
|
static bool IsDotDotDir(const wchar_t* path)
|
|
{
|
|
return (nullptr != path && '.' == path[0] && '.' == path[1] && IsDirSep(path[2]));
|
|
}
|
|
|
|
|
|
bool ON_FileSystemPath::IsRelativePath(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
return ON_FileSystemPath::IsRelativePath(path, 0);
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::CurrentDirectory(
|
|
bool bWithTrailingDirectorySeparator
|
|
)
|
|
{
|
|
#if defined(ON_RUNTIME_WIN)
|
|
|
|
wchar_t* directory = nullptr;
|
|
const unsigned int directory_capacity = 2018;
|
|
const size_t sizeof_directory = directory_capacity * sizeof(directory[0]);
|
|
directory = (wchar_t*)onmalloc(sizeof_directory);
|
|
memset(directory, 0, sizeof_directory);
|
|
|
|
DWORD rc = ::GetCurrentDirectory(directory_capacity - 1, directory);
|
|
ON_wString fullpath = directory;
|
|
onfree(directory);
|
|
|
|
if (rc <= 0 || fullpath.IsEmpty() )
|
|
{
|
|
ON_ERROR("Windows API ::GetCurrentDirectory() failed.");
|
|
return ON_wString::EmptyString;
|
|
}
|
|
|
|
if (bWithTrailingDirectorySeparator)
|
|
fullpath += ON_FileSystemPath::DirectorySeparator;
|
|
return fullpath;
|
|
|
|
#elif defined(ON_RUNTIME_APPLE)
|
|
|
|
char sz[PATH_MAX];
|
|
getcwd(sz, PATH_MAX);
|
|
return sz;
|
|
|
|
#else
|
|
|
|
// unsupported OS
|
|
ON_ERROR("ON_FileSystemPath::CurrentDirectory() not implemented.");
|
|
return ON_wString::EmptyString;
|
|
#endif
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::RemoveVolumeName(
|
|
const wchar_t* path,
|
|
ON_wString* volume_name
|
|
)
|
|
{
|
|
const ON_wString local_path = ON_FileSystemPath::CleanPath(path);
|
|
path = static_cast<const wchar_t*>(local_path);
|
|
const wchar_t* vol = nullptr;
|
|
const wchar_t* dir = nullptr;
|
|
const wchar_t* fname = nullptr;
|
|
const wchar_t* fext = nullptr;
|
|
on_wsplitpath(path, &vol, &dir, &fname, &fext);
|
|
if (nullptr == dir)
|
|
{
|
|
if (nullptr != fname)
|
|
dir = fname;
|
|
else if (nullptr != fext)
|
|
dir = fext;
|
|
}
|
|
|
|
if (nullptr != volume_name)
|
|
{
|
|
size_t length
|
|
= (nullptr != vol && nullptr != dir && vol < dir)
|
|
? (int)(dir - vol)
|
|
: 0;
|
|
if (length > 0)
|
|
*volume_name = ON_wString(vol, (int)length);
|
|
else
|
|
*volume_name = ON_wString::EmptyString;
|
|
}
|
|
return ON_wString(dir);
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::RemoveFileName(
|
|
const wchar_t* path,
|
|
ON_wString* file_name
|
|
)
|
|
{
|
|
const ON_wString local_path = ON_FileSystemPath::CleanPath(path);
|
|
path = static_cast<const wchar_t*>(local_path);
|
|
const wchar_t* vol = nullptr;
|
|
const wchar_t* dir = nullptr;
|
|
const wchar_t* fname = nullptr;
|
|
on_wsplitpath(path, &vol, &dir, &fname, nullptr);
|
|
|
|
const size_t length
|
|
= (nullptr != fname && nullptr != path && path <= fname)
|
|
? (int)(fname - path)
|
|
: local_path.Length();
|
|
|
|
if (nullptr != file_name)
|
|
*file_name = fname;
|
|
|
|
return ON_wString(path,(int)length);
|
|
}
|
|
|
|
|
|
const ON_wString ON_FileSystemPath::CombinePaths(
|
|
const wchar_t* left_side,
|
|
bool bLeftSideContainsFileName,
|
|
const wchar_t* right_side,
|
|
bool bRightSideContainsFileName,
|
|
bool bAppendTrailingDirectorySeparator
|
|
)
|
|
{
|
|
ON_wString lhs_fname;
|
|
ON_wString lhs
|
|
= bLeftSideContainsFileName
|
|
? ON_FileSystemPath::RemoveFileName(left_side,&lhs_fname)
|
|
: ON_FileSystemPath::CleanPath(left_side);
|
|
|
|
ON_wString rhs_fname;
|
|
ON_wString rhs
|
|
= bRightSideContainsFileName
|
|
? ON_FileSystemPath::RemoveFileName(right_side,&rhs_fname)
|
|
: ON_FileSystemPath::CleanPath(right_side);
|
|
|
|
ON_wString rhs_volume;
|
|
if ( lhs.IsNotEmpty() )
|
|
rhs = ON_FileSystemPath::RemoveVolumeName(rhs, &rhs_volume);
|
|
|
|
if (rhs.IsNotEmpty() && ON_FileSystemPath::IsDirectorySeparator(rhs[0], true))
|
|
{
|
|
const ON_wString tmp(static_cast<const wchar_t*>(rhs) + 1);
|
|
rhs = tmp;
|
|
}
|
|
|
|
ON_wString path = lhs;
|
|
bool bPathEndsDirectorySeparator
|
|
= path.IsNotEmpty()
|
|
&& ON_FileSystemPath::IsDirectorySeparator(path[path.Length() - 1], true);
|
|
|
|
if (rhs.IsNotEmpty())
|
|
{
|
|
if (path.IsNotEmpty() && false == bPathEndsDirectorySeparator)
|
|
path += ON_FileSystemPath::DirectorySeparator;
|
|
path += rhs;
|
|
|
|
if (lhs.IsNotEmpty() && ON_FileSystemPath::IsRelativePath(rhs))
|
|
path = ON_FileSystemPath::CleanPath(path);
|
|
|
|
bPathEndsDirectorySeparator
|
|
= path.IsNotEmpty()
|
|
&& ON_FileSystemPath::IsDirectorySeparator(path[path.Length() - 1], true);
|
|
}
|
|
|
|
if (path.IsNotEmpty() && rhs_fname.IsNotEmpty())
|
|
bAppendTrailingDirectorySeparator = true;
|
|
|
|
if ((bPathEndsDirectorySeparator ? 0 : 1) != (bAppendTrailingDirectorySeparator ? 0 : 1))
|
|
{
|
|
if (bAppendTrailingDirectorySeparator)
|
|
{
|
|
const wchar_t* vol = nullptr;
|
|
const wchar_t* dir = nullptr;
|
|
// on_wsplitpath is called to avoid appending a directory separator to a
|
|
on_wsplitpath(static_cast<const wchar_t*>(path), &vol, &dir, nullptr, nullptr);
|
|
if (nullptr != dir && false == ON_FileSystemPath::IsDirectorySeparator(path[path.Length() - 1], true))
|
|
path += ON_FileSystemPath::DirectorySeparator;
|
|
}
|
|
else if ( bPathEndsDirectorySeparator )
|
|
{
|
|
path.SetLength(path.Length() - 1);
|
|
}
|
|
}
|
|
|
|
path += rhs_fname;
|
|
|
|
return path;
|
|
}
|
|
|
|
bool ON_FileSystemPath::IsRelativePath(
|
|
const wchar_t* path,
|
|
const wchar_t directory_separator
|
|
)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (nullptr == path)
|
|
break;
|
|
if ('.' != *path)
|
|
break;
|
|
path++;
|
|
if ('.' == *path)
|
|
path++;
|
|
if (0 != directory_separator)
|
|
{
|
|
if (directory_separator != *path)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (ON_FileSystemPath::DirectorySeparator != *path && ON_FileSystemPath::AlternateDirectorySeparator != *path)
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::CleanPath(
|
|
const wchar_t* dirty_path
|
|
)
|
|
{
|
|
bool bTrimLeft = true;
|
|
bool bTrimRight = true;
|
|
bool bAllowWindowsUNCHostNameOrDiskLetter = true;
|
|
bool bDeleteWindowsUNCHostNameOrDiskLetter = (ON_wString::Backslash != ON_FileSystemPath::DirectorySeparator);
|
|
const wchar_t directory_separator = ON_FileSystemPath::DirectorySeparator;
|
|
return ON_FileSystemPath::CleanPath(
|
|
bTrimLeft,
|
|
bTrimRight,
|
|
bAllowWindowsUNCHostNameOrDiskLetter,
|
|
bDeleteWindowsUNCHostNameOrDiskLetter,
|
|
directory_separator,
|
|
dirty_path
|
|
);
|
|
}
|
|
|
|
static const ON_wString ON_wString_CleanPathFailed()
|
|
{
|
|
return ON_wString::EmptyString;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::CleanPath(
|
|
bool bTrimLeft,
|
|
bool bTrimRight,
|
|
bool bAllowWindowsUNCHostNameOrDiskLetter,
|
|
bool bDeleteWindowsUNCHostNameOrDiskLetter,
|
|
const wchar_t directory_separator,
|
|
const wchar_t* dirty_path
|
|
)
|
|
{
|
|
return ON_FileSystemPath::CleanPath(
|
|
bTrimLeft,
|
|
bTrimRight,
|
|
bAllowWindowsUNCHostNameOrDiskLetter,
|
|
bDeleteWindowsUNCHostNameOrDiskLetter,
|
|
false,
|
|
directory_separator,
|
|
dirty_path
|
|
);
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::CleanPath(
|
|
bool bTrimLeft,
|
|
bool bTrimRight,
|
|
bool bAllowWindowsUNCHostNameOrDiskLetter,
|
|
bool bDeleteWindowsUNCHostNameOrDiskLetter,
|
|
bool bExpandUser,
|
|
const wchar_t directory_separator,
|
|
const wchar_t* dirty_path
|
|
)
|
|
{
|
|
ON_wString local_dirty_path(dirty_path);
|
|
if ( bTrimLeft )
|
|
local_dirty_path.TrimLeft();
|
|
if ( bTrimRight )
|
|
local_dirty_path.TrimRight();
|
|
if ( local_dirty_path.IsEmpty() )
|
|
return ON_wString_CleanPathFailed();
|
|
if (bExpandUser)
|
|
local_dirty_path = ON_FileSystemPath::ExpandUser(local_dirty_path);
|
|
|
|
dirty_path = local_dirty_path;
|
|
|
|
const bool bIsUNCHostName
|
|
= bAllowWindowsUNCHostNameOrDiskLetter
|
|
&& local_dirty_path.Length() >= 3
|
|
&& ON_wString::Backslash == local_dirty_path[0]
|
|
&& ON_wString::Backslash == local_dirty_path[1]
|
|
&& IsPermittedInHostName(local_dirty_path[2])
|
|
&& (IsAtoZ(local_dirty_path[2]) || Is0to9(local_dirty_path[2]) || local_dirty_path[2] > 127)
|
|
;
|
|
|
|
const bool bIsWindowsDrive
|
|
= bAllowWindowsUNCHostNameOrDiskLetter
|
|
&& (false == bIsUNCHostName)
|
|
&& local_dirty_path.Length() >= 3
|
|
&& ':' == local_dirty_path[1]
|
|
&& IsAtoZ(local_dirty_path[0])
|
|
;
|
|
|
|
ON_wString clean_path(dirty_path);
|
|
wchar_t* clean_head = clean_path.Array();
|
|
wchar_t* clean_start = clean_head;
|
|
if (bIsUNCHostName)
|
|
{
|
|
clean_start += 3; // skip \\ and first character of host name
|
|
|
|
// skip rest of host name
|
|
while ( IsPermittedInHostName(*clean_start) )
|
|
clean_start++;
|
|
if (false == IsDirSep(*clean_start))
|
|
return ON_wString_CleanPathFailed();
|
|
}
|
|
else if (bIsWindowsDrive)
|
|
{
|
|
// Windows drive letter = capital letter
|
|
*clean_start = ON_wString::MapCharacterOrdinal(ON_StringMapOrdinalType::UpperOrdinal,*clean_start);
|
|
clean_start += 2; // Skip drive letter and colon
|
|
if (false == IsDirSep(*clean_start))
|
|
return ON_wString_CleanPathFailed();
|
|
}
|
|
|
|
if (bDeleteWindowsUNCHostNameOrDiskLetter && (bIsUNCHostName || bIsWindowsDrive))
|
|
{
|
|
// Delete Windows UNC host name or drive letter
|
|
local_dirty_path = clean_start;
|
|
dirty_path = local_dirty_path;
|
|
clean_path = dirty_path;
|
|
clean_head = clean_path.Array();
|
|
clean_start = clean_head;
|
|
}
|
|
|
|
const size_t clean_start_offset = (clean_start - clean_head);
|
|
|
|
wchar_t* dst = clean_start;
|
|
wchar_t* src = dst;
|
|
for (;;)
|
|
{
|
|
wchar_t c;
|
|
// advance to directory separator
|
|
for (c = *src; false == IsDirSep(c) && 0 != c; c = *(++src))
|
|
{
|
|
*dst++ = c;
|
|
}
|
|
if ( 0 == c )
|
|
break;
|
|
// normalize directory separator
|
|
*dst++ = (0 != directory_separator) ? directory_separator : c;
|
|
// Condense /./ and //
|
|
for (src++; (IsDirSep(*src) || IsDotDir(src)); src++)
|
|
{
|
|
// empty body;
|
|
}
|
|
}
|
|
*dst = 0;
|
|
if (dst > clean_head)
|
|
{
|
|
clean_path.SetLength(dst - clean_head);
|
|
clean_head = clean_path.Array();
|
|
clean_start = clean_head + clean_start_offset;
|
|
}
|
|
else
|
|
{
|
|
return ON_wString_CleanPathFailed();
|
|
}
|
|
|
|
dst = clean_start;
|
|
if (IsDirSep(*dst))
|
|
{
|
|
// Skip over root directory separator
|
|
dst++;
|
|
}
|
|
else
|
|
{
|
|
// Skip over initial ../../../ ... at start of a relative path
|
|
while (IsDotDotDir(dst))
|
|
dst += 3;
|
|
}
|
|
if ( 0 == *dst )
|
|
return clean_path;
|
|
|
|
src = dst;
|
|
bool bDirty = false;
|
|
while (*src)
|
|
{
|
|
if (IsDotDotDir(src))
|
|
{
|
|
if (!bDirty)
|
|
{
|
|
ON_ERROR("Bug.");
|
|
return clean_path;
|
|
}
|
|
// will be dealt with in the recursive clean
|
|
*dst++ = *src++;
|
|
*dst++ = *src++;
|
|
*dst++ = *src++;
|
|
continue;
|
|
}
|
|
wchar_t* dir = dst;
|
|
while (false == IsDirSep(*src) && 0 != *src)
|
|
{
|
|
*dst++ = *src++;
|
|
}
|
|
if (dir == dst)
|
|
{
|
|
ON_ERROR("Bug.");
|
|
return clean_path;
|
|
}
|
|
if (0 == *src)
|
|
{
|
|
break;
|
|
}
|
|
if (IsDotDotDir(src + 1))
|
|
{
|
|
// replace dir/../ with ./ and recursively clean
|
|
dst = dir;
|
|
dst[0] = '.';
|
|
dst[1] = src[3];
|
|
dst += 2;
|
|
src += 4;
|
|
bDirty = true;
|
|
while (IsDotDotDir(src))
|
|
{
|
|
// will be dealt with in the recursive clean
|
|
*dst++ = *src++;
|
|
*dst++ = *src++;
|
|
*dst++ = *src++;
|
|
}
|
|
}
|
|
else if (IsDirSep(*src))
|
|
{
|
|
*dst++ = *src++;
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("Bug");
|
|
return clean_path;
|
|
}
|
|
}
|
|
|
|
if (dst > clean_head)
|
|
{
|
|
*dst = 0;
|
|
clean_path.SetLength(dst - clean_head);
|
|
clean_head = clean_path.Array();
|
|
clean_start = clean_head + clean_start_offset;
|
|
}
|
|
else
|
|
{
|
|
ON_ERROR("Bug.");
|
|
return clean_path;
|
|
}
|
|
|
|
if ( false == bDirty )
|
|
return clean_path;
|
|
|
|
if (dst >= src)
|
|
{
|
|
ON_ERROR("Bug.");
|
|
return clean_path;
|
|
}
|
|
|
|
// recursively clean
|
|
const ON_wString clean_tail = ON_FileSystemPath::CleanPath(false,false,false,false,0,clean_start);
|
|
if (clean_head < clean_start)
|
|
{
|
|
clean_path.SetLength(clean_start - clean_head);
|
|
clean_path += clean_tail;
|
|
}
|
|
else
|
|
clean_path = clean_tail;
|
|
|
|
return clean_path;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::ExpandUser(
|
|
const char* dirty_path
|
|
)
|
|
{
|
|
const ON_wString dirty_local_path(dirty_path);
|
|
return ON_FileSystemPath::ExpandUser(static_cast<const wchar_t*>(dirty_local_path));
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::ExpandUser(
|
|
const wchar_t* dirty_path
|
|
)
|
|
{
|
|
for(;;)
|
|
{
|
|
if (nullptr == dirty_path)
|
|
break;
|
|
if (ON_wString::Tilde != dirty_path[0])
|
|
break;
|
|
if (false == ON_FileSystemPath::IsDirectorySeparator(dirty_path[1], true))
|
|
break;
|
|
ON_wString expanduser_path = ON_FileSystemPath::PlatformPath(ON_FileSystemPath::PathId::HomeDirectory);
|
|
if (expanduser_path.IsEmpty())
|
|
break;
|
|
const wchar_t dir_seps[3]
|
|
=
|
|
{
|
|
ON_FileSystemPath::DirectorySeparator,
|
|
ON_FileSystemPath::AlternateDirectorySeparator,
|
|
0
|
|
};
|
|
expanduser_path.TrimRight(dir_seps);
|
|
if (expanduser_path.IsEmpty())
|
|
break;
|
|
expanduser_path += ON_wString(dirty_path + 1);
|
|
return expanduser_path;
|
|
}
|
|
return ON_wString(dirty_path);
|
|
}
|
|
|
|
bool ON_FileSystem::PathExists(
|
|
const char* path
|
|
)
|
|
{
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
return ::PathFileExistsA(path) ? true : false;
|
|
#else
|
|
struct stat s;
|
|
if (0 == stat(path, &s))
|
|
{
|
|
if (0 != (s.st_mode & (S_IFDIR|S_IFREG)))
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool ON_FileSystem::PathExists(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
return ::PathFileExistsW(path) ? true : false;
|
|
#else
|
|
const ON_String pathUTF8(path);
|
|
return ON_FileSystem::PathExists(static_cast<const char*>(pathUTF8));
|
|
#endif
|
|
}
|
|
|
|
bool ON_FileSystem::IsDirectory(
|
|
const char* path
|
|
)
|
|
{
|
|
if (0 == path || 0 == path[0])
|
|
return false;
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
return ::PathIsDirectoryA(path) ? true : false;
|
|
#else
|
|
struct stat s;
|
|
if (0 == stat(path, &s))
|
|
{
|
|
if (0 != (s.st_mode & S_IFDIR))
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool ON_FileSystem::IsDirectory(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
if (0 == path || 0 == path[0])
|
|
return false;
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
return ::PathIsDirectoryW(path) ? true : false;
|
|
#else
|
|
const ON_String pathUTF8(path);
|
|
return ON_FileSystem::IsDirectory(static_cast<const char*>(pathUTF8));
|
|
#endif
|
|
}
|
|
|
|
bool ON_FileSystem::IsFile(
|
|
const char* path
|
|
)
|
|
{
|
|
if (0 == path || 0 == path[0])
|
|
return false;
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
return ::PathFileExistsA(path) && 0 == ::PathIsDirectoryA(path);
|
|
#else
|
|
struct stat s;
|
|
if (0 == stat(path, &s))
|
|
{
|
|
if (0 == (s.st_mode & S_IFDIR) && 0 != (s.st_mode & S_IFREG))
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool ON_FileSystem::IsFile(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
if (0 == path || 0 == path[0])
|
|
return false;
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
return ::PathFileExistsW(path) && 0 == ::PathIsDirectoryW(path);
|
|
#else
|
|
const ON_String pathUTF8(path);
|
|
return ON_FileSystem::IsFile(static_cast<const char*>(pathUTF8));
|
|
#endif
|
|
}
|
|
|
|
|
|
bool ON_FileSystem::RemoveFile(
|
|
const char* file_path
|
|
)
|
|
{
|
|
for (;;)
|
|
{
|
|
if ( false == ON_FileSystem::IsFile(file_path) )
|
|
break;
|
|
int rc;
|
|
#if defined(ON_RUNTIME_WIN)
|
|
rc = ::_unlink(file_path);
|
|
#elif defined(ON_RUNTIME_APPLE)
|
|
rc = ::unlink(file_path);
|
|
#else
|
|
rc = std::remove(file_path);
|
|
#endif
|
|
if (0 == rc)
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ON_FileSystem::RemoveFile(
|
|
const wchar_t* file_path
|
|
)
|
|
{
|
|
for (;;)
|
|
{
|
|
if ( false == ON_FileSystem::IsFile(file_path) )
|
|
break;
|
|
int rc;
|
|
#if defined(ON_RUNTIME_WIN)
|
|
rc = ::_wunlink(file_path);
|
|
#elif defined(ON_RUNTIME_APPLE)
|
|
const ON_String utf8_file_path(file_path);
|
|
rc = ::unlink(static_cast<const char*>(utf8_file_path));
|
|
#else
|
|
const ON_String utf8_file_path(file_path);
|
|
rc = std::remove(static_cast<const char*>(utf8_file_path));
|
|
#endif
|
|
if (0 == rc)
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ON_FileSystem::IsDirectoryWithWriteAccess(
|
|
const char* path
|
|
)
|
|
{
|
|
const ON_wString wide_path(path);
|
|
return ON_FileSystem::IsDirectoryWithWriteAccess(static_cast<const wchar_t*>(wide_path));
|
|
}
|
|
|
|
bool ON_FileSystem::IsDirectoryWithWriteAccess(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
if (false == ON_FileSystem::IsDirectory(path))
|
|
return false;
|
|
|
|
// native OS tools that query file systems do not
|
|
// work on some network drives.
|
|
// According to Microsoft techs, the only failsafe way
|
|
// is to attempt to write a file.
|
|
// https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/f57928d3-d89b-426d-a174-d06d97355afc/how-to-check-if-a-filefolder-is-writable-or-not?forum=windowssdk
|
|
|
|
|
|
// try 2 uuids to get a file name that is not in use.
|
|
for (int attempt = 0; attempt < 2; attempt++)
|
|
{
|
|
const ON_UUID id = ON_CreateId();
|
|
wchar_t s[41];
|
|
memset(s, 0, sizeof(s));
|
|
ON_UuidToString(id, s);
|
|
s[36] = '.';
|
|
s[37] = 't';
|
|
s[38] = 'm';
|
|
s[39] = 'p';
|
|
s[40] = 0;
|
|
const ON_wString tmpfilename = ON_FileSystemPath::CombinePaths(
|
|
path, false, s, true, false
|
|
);
|
|
if (ON_FileSystem::PathExists(tmpfilename))
|
|
continue;
|
|
|
|
FILE* fp = ON_FileStream::Open(tmpfilename, L"wb");
|
|
if (nullptr == fp)
|
|
break; // cannot open a file in path
|
|
bool bIsDirectoryWithWriteAccess = false;
|
|
for ( ;; )
|
|
{
|
|
char c = 0;
|
|
const ON__UINT64 sizeof_c = sizeof(c);
|
|
const ON__UINT64 count = ON_FileStream::Write(fp, sizeof_c, &c);
|
|
if (0 != ON_FileStream::Close(fp))
|
|
break; // cannot close the file.
|
|
fp = nullptr;
|
|
if (count != sizeof_c)
|
|
break; // cannot write to the file in path
|
|
bIsDirectoryWithWriteAccess = ON_FileSystem::PathExists(tmpfilename);
|
|
// The purpose of this function is to test if a file can be opened and
|
|
// written to using the same tools that write .3dm files.
|
|
//
|
|
// It is possible to have create and write permissions but not have
|
|
// read permissions. For this reason, we do not attempt to read the tmp file.
|
|
break;
|
|
}
|
|
if (nullptr == fp)
|
|
{
|
|
// The purpose of this function is to test if a file can be opened and
|
|
// written to using the same tools that write .3dm files.
|
|
//
|
|
// There is speculation that when a directory is managed by dropbox
|
|
// or perhaps other network storage devices, there may be significant
|
|
// latency in the file systems that results in a time lag between calling
|
|
// unlink() and having ON_FileSystem::IsFile() report false.
|
|
// For that reason, we do not check success codes on unlink
|
|
// or verify the tmp file is gone.
|
|
ON_FileSystem::RemoveFile(tmpfilename);
|
|
}
|
|
return bIsDirectoryWithWriteAccess;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::FullPathFromRelativePath(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
const wchar_t* relative_path
|
|
)
|
|
{
|
|
if ( nullptr == relative_path || 0 == relative_path )
|
|
return ON_wString::EmptyString;
|
|
|
|
if ( nullptr == base_path || 0 == base_path[0] )
|
|
return ON_wString::EmptyString;
|
|
|
|
const wchar_t* base_path_end = nullptr;
|
|
if (bBasePathIncludesFileName)
|
|
{
|
|
on_wsplitpath(base_path, nullptr, nullptr, &base_path_end, nullptr);
|
|
}
|
|
else
|
|
{
|
|
base_path_end = base_path + ON_wString::Length(base_path);
|
|
}
|
|
if (nullptr == base_path_end)
|
|
return ON_wString::EmptyString;
|
|
if (!(base_path < base_path_end))
|
|
return ON_wString::EmptyString;
|
|
|
|
ON_wString dirty_full_path;
|
|
dirty_full_path.Append(base_path,(int)(base_path_end - base_path));
|
|
if ( false == ON_FileSystemPath::IsDirectorySeparator(base_path_end[-1],true) )
|
|
dirty_full_path += ON_FileSystemPath::DirectorySeparator;
|
|
dirty_full_path += relative_path;
|
|
return ON_FileSystemPath::CleanPath(dirty_full_path);
|
|
}
|
|
|
|
static bool CleanAndRemoveFileName(
|
|
const wchar_t* dirty_path,
|
|
bool bPathIncludesFileName,
|
|
ON_wString& volume,
|
|
ON_wString& clean_path,
|
|
ON_wString* file_name
|
|
)
|
|
{
|
|
ON_wString path = ON_FileSystemPath::CleanPath(dirty_path);
|
|
for (;;)
|
|
{
|
|
if (path.IsEmpty())
|
|
break;
|
|
|
|
if (false == bPathIncludesFileName && false == IsDirSep(path[path.Length() - 1]))
|
|
path += ON_FileSystemPath::DirectorySeparator;
|
|
|
|
const wchar_t* p = static_cast<const wchar_t*>(path);
|
|
const wchar_t* v = nullptr;
|
|
const wchar_t* d = nullptr;
|
|
const wchar_t* f = nullptr;
|
|
on_wsplitpath(p, &v, &d, bPathIncludesFileName ? &f : nullptr, nullptr);
|
|
|
|
if (nullptr == d || 0 == d[0])
|
|
break;
|
|
|
|
clean_path = d;
|
|
|
|
if (bPathIncludesFileName)
|
|
{
|
|
// remove trailing file name from base_path.
|
|
if (nullptr == f || 0 == f[0])
|
|
break;
|
|
const size_t path_length = (size_t)path.Length();
|
|
if ( path_length <= 0 )
|
|
break;
|
|
if (!(p <= d && d < f && f < p + path_length))
|
|
break;
|
|
if (!IsDirSep(f[-1]))
|
|
break;
|
|
size_t len = (f - d);
|
|
if (len <= 1 || len >= (size_t)clean_path.Length())
|
|
break;
|
|
if ( nullptr != file_name )
|
|
*file_name = f;
|
|
clean_path.SetLength(len);
|
|
}
|
|
else
|
|
{
|
|
if ( nullptr != file_name )
|
|
*file_name = ON_wString::EmptyString;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
volume = ON_wString::EmptyString;
|
|
clean_path = ON_wString::EmptyString;
|
|
if ( nullptr != file_name )
|
|
*file_name = ON_wString::EmptyString;
|
|
return false;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::RelativePath(
|
|
const wchar_t* full_path,
|
|
bool bFullPathIncludesFileName,
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName
|
|
)
|
|
{
|
|
ON_wString best_answer(full_path);
|
|
|
|
ON_wString full_volume;
|
|
ON_wString local_full;
|
|
ON_wString file_name;
|
|
if (false == CleanAndRemoveFileName(full_path,bFullPathIncludesFileName,full_volume,local_full,&file_name))
|
|
return best_answer;
|
|
|
|
best_answer = local_full;
|
|
best_answer += file_name;
|
|
|
|
ON_wString base_volume;
|
|
ON_wString local_base;
|
|
if (false == CleanAndRemoveFileName(base_path,bBasePathIncludesFileName,base_volume,local_base,nullptr))
|
|
return best_answer;
|
|
|
|
if (full_volume.IsNotEmpty() || base_volume.IsNotEmpty() )
|
|
{
|
|
if (false == ON_wString::EqualPath(full_volume,base_volume))
|
|
return best_answer;
|
|
}
|
|
|
|
const wchar_t* full_tail = static_cast<const wchar_t*>(local_full);
|
|
const wchar_t* base_tail = static_cast<const wchar_t*>(local_base);
|
|
if (false == IsDirSep(*full_tail) || false == IsDirSep(*base_tail))
|
|
{
|
|
// A double directory separator after the initial CleanAndRemoveFileName()
|
|
// calls indicates invalid file path information.
|
|
return best_answer;
|
|
}
|
|
|
|
// Skip initial directory separator
|
|
full_tail++;
|
|
base_tail++;
|
|
if (0 == *full_tail || 0 == *base_tail)
|
|
{
|
|
return best_answer;
|
|
}
|
|
if (IsDirSep(*full_tail) || IsDirSep(*base_tail))
|
|
{
|
|
// A double directory separator after the initial ON_FileSystemPath::CleanPath()
|
|
// calls indicates invalid file path information.
|
|
return best_answer;
|
|
}
|
|
|
|
int overlap_directory_count = 0;
|
|
if (0 != *full_tail && 0 != *base_tail)
|
|
{
|
|
const wchar_t* full1 = full_tail;
|
|
const wchar_t* base1 = base_tail;
|
|
while (0 != *full1 && 0 != *base1 )
|
|
{
|
|
if (IsDotDir(base1) || IsDotDotDir(base1))
|
|
{
|
|
overlap_directory_count = 0;
|
|
break;
|
|
}
|
|
bool bFullDirSep = IsDirSep(*full1);
|
|
bool bBaseDirSep = IsDirSep(*base1);
|
|
if (false == bFullDirSep && false == bBaseDirSep)
|
|
{
|
|
// skipping an element of a directory name
|
|
base1++;
|
|
full1++;
|
|
continue;
|
|
}
|
|
if (bFullDirSep && bBaseDirSep)
|
|
{
|
|
if (false == ON_wString::EqualPath(full_tail, (int)(full1 - full_tail), base_tail, (int)(base1 - base_tail)))
|
|
{
|
|
// directory names have identical lengths and different content
|
|
break;
|
|
}
|
|
|
|
// matching directory names
|
|
|
|
// skip directory separator
|
|
base1++;
|
|
full1++;
|
|
if (IsDirSep(*base1) || IsDirSep(*full1))
|
|
{
|
|
// damaged input
|
|
break;
|
|
}
|
|
base_tail = base1;
|
|
full_tail = full1;
|
|
overlap_directory_count++;
|
|
continue;
|
|
}
|
|
// directory names have different lengths
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (overlap_directory_count < 1)
|
|
return best_answer;
|
|
|
|
// It is reasonable for base_tail to be nullptr
|
|
if (nullptr == full_tail && IsDirSep(*full_tail) )
|
|
return best_answer;
|
|
|
|
// It is reasonable for base_tail to be nullptr
|
|
if (nullptr != base_tail && IsDirSep(*base_tail) )
|
|
return best_answer;
|
|
|
|
// set dotdot_count to number of directories left in base_tail
|
|
int dotdot_count = 0;
|
|
while (0 != *base_tail)
|
|
{
|
|
if (IsDotDir(base_tail) || IsDotDotDir(base_tail))
|
|
return best_answer;
|
|
if (IsDirSep(*base_tail))
|
|
{
|
|
if (IsDirSep(base_tail[1]))
|
|
return best_answer; // shouldn't be double dir seps after the initial clean
|
|
dotdot_count++;
|
|
}
|
|
base_tail++;
|
|
}
|
|
|
|
// buid relative path
|
|
ON_wString relative_path;
|
|
if (0 == dotdot_count)
|
|
{
|
|
relative_path = L".";
|
|
relative_path += ON_FileSystemPath::DirectorySeparator;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < dotdot_count; i++)
|
|
{
|
|
relative_path += L"..";
|
|
relative_path += ON_FileSystemPath::DirectorySeparator;
|
|
}
|
|
}
|
|
if (nullptr != full_tail && 0 != full_tail[0] )
|
|
relative_path += full_tail;
|
|
if (file_name.IsNotEmpty())
|
|
relative_path += file_name;
|
|
|
|
return relative_path;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ON_FileStream implementation
|
|
//
|
|
|
|
FILE* ON_FileStream::Open( const wchar_t* filename, const wchar_t* mode )
|
|
{
|
|
FILE* fp = 0;
|
|
|
|
if ( 0 == filename || 0 == filename[0] || 0 == mode || 0 == mode[0] )
|
|
return fp;
|
|
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
errno_t e = _wfopen_s(&fp,filename,mode);
|
|
if ( 0 != e && 0 == fp )
|
|
fp = 0; // reference e to keep lint quiet.
|
|
#else
|
|
// I can't find an wfopen() or _wfopen() in
|
|
// gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
|
|
ON_String fnameUTF8(filename);
|
|
ON_String modeUTF8(mode);
|
|
fp = fopen(fnameUTF8,modeUTF8);
|
|
#endif
|
|
|
|
return fp;
|
|
}
|
|
|
|
FILE* ON_FileStream::Open( const char* filename, const char* mode )
|
|
{
|
|
FILE* fp = 0;
|
|
|
|
if ( 0 == filename || 0 == filename[0] || 0 == mode || 0 == mode[0] )
|
|
return fp;
|
|
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
errno_t e = fopen_s(&fp,filename,mode);
|
|
if ( 0 != e && 0 == fp )
|
|
fp = 0; // reference e to keep lint quiet.
|
|
#else
|
|
fp = fopen(filename,mode);
|
|
#endif
|
|
|
|
return fp;
|
|
}
|
|
|
|
int ON_FileStream::Close( FILE* fp )
|
|
{
|
|
return ( ( 0 != fp ) ? fclose(fp) : -1 );
|
|
}
|
|
|
|
bool ON_FileStream::Is3dmFile(
|
|
const wchar_t* file_path,
|
|
bool bAllow3dmbakExtension
|
|
)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (false == ON_FileSystemPath::FilePathHas3dmExtension(file_path, bAllow3dmbakExtension))
|
|
break;
|
|
FILE* fp = ON_FileStream::Open3dmToRead(file_path);
|
|
if (nullptr == fp)
|
|
break;
|
|
ON_FileStream::Close(fp);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_FileStream::Is3dmFile(
|
|
const char* file_path,
|
|
bool bAllow3dmbakExtension
|
|
)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (false == ON_FileSystemPath::FilePathHas3dmExtension(file_path, bAllow3dmbakExtension))
|
|
break;
|
|
FILE* fp = ON_FileStream::Open3dmToRead(file_path);
|
|
if (nullptr == fp)
|
|
break;
|
|
ON_FileStream::Close(fp);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_FileSystemPath::FilePathHas3dmExtension(
|
|
const wchar_t* file_path,
|
|
bool bAllow3dmbakExtension
|
|
)
|
|
{
|
|
for (;;)
|
|
{
|
|
// test file name
|
|
const wchar_t* e = nullptr;
|
|
on_wsplitpath(file_path, nullptr, nullptr, nullptr, &e);
|
|
if (nullptr == e)
|
|
break;
|
|
if ('.' != e[0])
|
|
break;
|
|
if ('3' != e[1])
|
|
break;
|
|
if ('d' != e[2] && 'D' != e[2])
|
|
break;
|
|
if ('m' != e[3] && 'M' != e[3])
|
|
break;
|
|
if (0 == e[4])
|
|
return true;
|
|
if (false == bAllow3dmbakExtension)
|
|
break;
|
|
if ('b' != e[4] && 'B' != e[4])
|
|
break;
|
|
if ('a' != e[5] && 'A' != e[5])
|
|
break;
|
|
if ('k' != e[6] && 'K' != e[6])
|
|
break;
|
|
if (0 != e[7])
|
|
break;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_FileSystemPath::FilePathHas3dmExtension(
|
|
const char* file_path,
|
|
bool bAllow3dmbakExtension
|
|
)
|
|
{
|
|
for (;;)
|
|
{
|
|
// test file name
|
|
const char* e = nullptr;
|
|
on_splitpath(file_path, nullptr, nullptr, nullptr, &e);
|
|
if (nullptr == e)
|
|
break;
|
|
if ('.' != e[0])
|
|
break;
|
|
if ('3' != e[1])
|
|
break;
|
|
if ('d' != e[2] && 'D' != e[2])
|
|
break;
|
|
if ('m' != e[3] && 'M' != e[3])
|
|
break;
|
|
if (0 == e[4])
|
|
return true;
|
|
if (false == bAllow3dmbakExtension)
|
|
break;
|
|
if ('b' != e[4] && 'B' != e[4])
|
|
break;
|
|
if ('a' != e[5] && 'A' != e[5])
|
|
break;
|
|
if ('k' != e[6] && 'K' != e[6])
|
|
break;
|
|
if (0 != e[7])
|
|
break;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ON_FileSystemPath::IsValidFileName(
|
|
const char* file_name,
|
|
bool bAllPlatforms
|
|
)
|
|
{
|
|
const ON_wString wide_file_name(file_name);
|
|
return ON_FileSystemPath::IsValidFileName(static_cast<const wchar_t*>(wide_file_name), bAllPlatforms);
|
|
}
|
|
|
|
bool ON_FileSystemPath::IsValidFileName(
|
|
const wchar_t* file_name,
|
|
bool bAllPlatforms
|
|
)
|
|
{
|
|
if (nullptr == file_name || 0 == file_name[0])
|
|
return false;
|
|
|
|
bool bDoubleDot = false;
|
|
wchar_t prev_c = 0;
|
|
int file_name_length;
|
|
for (file_name_length = 0; 0 != file_name[file_name_length]; file_name_length++)
|
|
{
|
|
if (file_name_length > 256)
|
|
return false;
|
|
|
|
// note that all illegal symbols currently tested for have
|
|
// UNICODE code points <= U+07F, so we can simply test c
|
|
const wchar_t c = file_name[file_name_length];
|
|
if (ON_FileSystemPath::IsDirectorySeparator(c, bAllPlatforms))
|
|
return false;
|
|
|
|
switch (c)
|
|
{
|
|
case '.':
|
|
bDoubleDot = ('.' == prev_c);
|
|
break;
|
|
|
|
case ':':
|
|
#if defined(ON_RUNTIME_WIN)
|
|
return false;
|
|
#else
|
|
if (bAllPlatforms)
|
|
return false;
|
|
#endif
|
|
break;
|
|
|
|
//// Most windows apps have these restrictions, but the file system supports
|
|
//// names with these characters.
|
|
//// case ':':
|
|
//// case '~':
|
|
//// case '#':
|
|
//// case '%':
|
|
//// case '&':
|
|
//// case '*':
|
|
//// case '{':
|
|
//// case '}':
|
|
//// case '<':
|
|
//// case '>':
|
|
//// case '?':
|
|
//// case '|':
|
|
//// case '"':
|
|
////#if defined(ON_RUNTIME_WIN)
|
|
//// return false;
|
|
////#else
|
|
//// if (bAllPlatforms)
|
|
//// return false;
|
|
////#endif
|
|
////
|
|
}
|
|
|
|
prev_c = c;
|
|
}
|
|
|
|
switch(prev_c)
|
|
{
|
|
case '.':
|
|
if (1 == file_name_length)
|
|
return false;
|
|
if (2 == file_name_length && bDoubleDot)
|
|
return false;
|
|
break;
|
|
|
|
case '~':
|
|
if (1 == file_name_length)
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const ON_wString ON_FileSystemPath::PlatformPath(ON_FileSystemPath::PathId path_id)
|
|
{
|
|
#if defined(ON_RUNTIME_WIN)
|
|
KNOWNFOLDERID platform_path_id;
|
|
#define ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(win_fid,apple_fid) platform_path_id = win_fid
|
|
#elif defined(ON_RUNTIME_COCOA_AVAILABLE)
|
|
NSSearchPathDirectory platform_path_id;
|
|
#define ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(win_fid,apple_fid) platform_path_id = apple_fid
|
|
#endif
|
|
|
|
#if defined(ON_INTERNAL_SET_LOCAL_DIRECTORY_ID)
|
|
switch (path_id)
|
|
{
|
|
case ON_FileSystemPath::PathId::DesktopDirectory:
|
|
ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Desktop, NSDesktopDirectory);
|
|
break;
|
|
case ON_FileSystemPath::PathId::DocumentsDirectory:
|
|
ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Documents, NSDocumentDirectory);
|
|
break;
|
|
case ON_FileSystemPath::PathId::DownloadsDirectory:
|
|
ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Downloads, NSDownloadsDirectory);
|
|
break;
|
|
case ON_FileSystemPath::PathId::HomeDirectory:
|
|
//#if defined(ON_RUNTIME_WIN)
|
|
// platform_path_id = FOLDERID_Profile;
|
|
//#elif defined(ON_RUNTIME_APPLE_OBJECTIVE_C_AVAILABLE)
|
|
// platform_path_id = NSUserDirectory;
|
|
//#endif
|
|
ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Profile, NSUserDirectory);
|
|
//ON_INTERNAL_SET_LOCAL_DIRECTORY_ID(FOLDERID_Profile, NSHomeDirectory);
|
|
break;
|
|
default:
|
|
return ON_wString::EmptyString;
|
|
}
|
|
#undef ON_INTERNAL_SET_LOCAL_DIRECTORY_ID
|
|
#endif
|
|
|
|
ON_wString path;
|
|
|
|
#if defined(ON_RUNTIME_WIN)
|
|
|
|
const DWORD dwFlags = KF_FLAG_DEFAULT;
|
|
const HANDLE hToken = nullptr; // current user
|
|
wchar_t* windows_path = nullptr;
|
|
const HRESULT hr = ::SHGetKnownFolderPath(
|
|
platform_path_id,
|
|
dwFlags,
|
|
hToken,
|
|
&windows_path
|
|
);
|
|
if (nullptr != windows_path)
|
|
{
|
|
if (S_OK == hr)
|
|
path = windows_path;
|
|
CoTaskMemFree(windows_path);
|
|
}
|
|
|
|
#elif defined(ON_RUNTIME_COCOA_AVAILABLE)
|
|
|
|
NSArray *apple_paths = NSSearchPathForDirectoriesInDomains(platform_path_id, NSUserDomainMask, YES);
|
|
if ([apple_paths count] > 0)
|
|
{
|
|
NSString* apple_path = [apple_paths objectAtIndex : 0];
|
|
if (nullptr != apple_path)
|
|
{
|
|
ON_wString s;
|
|
const int len = (int)apple_path.length;
|
|
s.SetLength(len);
|
|
int idx;
|
|
for (idx = 0; idx < len; idx++)
|
|
s[idx] = [apple_path characterAtIndex : idx];
|
|
s[idx] = 0;
|
|
path = s;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
if (ON_FileSystemPath::PathId::HomeDirectory != path_id)
|
|
{
|
|
ON_ERROR("Function not implemented.");
|
|
}
|
|
|
|
#endif
|
|
|
|
path.TrimLeftAndRight();
|
|
|
|
// See if environment variables will
|
|
if (ON_FileSystemPath::PathId::HomeDirectory == path_id && path.IsEmpty())
|
|
{
|
|
const wchar_t dir_seps[4] = {
|
|
ON_FileSystemPath::DirectorySeparator,
|
|
ON_FileSystemPath::AlternateDirectorySeparator,
|
|
ON_wString::Space,
|
|
0
|
|
};
|
|
for (;;)
|
|
{
|
|
path = ON_wString(getenv("HOME"));
|
|
path.TrimLeftAndRight();
|
|
path.TrimRight(dir_seps);
|
|
if ( ON_FileSystem::IsDirectory(path) )
|
|
break;
|
|
|
|
#if defined(ON_RUNTIME_WIN)
|
|
path = ON_wString(getenv("USERPROFILE"));
|
|
path.TrimLeftAndRight();
|
|
path.TrimRight(dir_seps);
|
|
if ( ON_FileSystem::IsDirectory(path) )
|
|
break;
|
|
|
|
path = ON_wString(getenv("HOMEDRIVE")) + ON_wString(getenv("HOMEPATH"));
|
|
path.TrimLeftAndRight();
|
|
path.TrimRight(dir_seps);
|
|
if ( ON_FileSystem::IsDirectory(path) )
|
|
break;
|
|
#endif
|
|
path = ON_wString::EmptyString;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
static unsigned int ON_Internal_SeekTo3DGeometryFileFormatMark(
|
|
FILE* fp
|
|
)
|
|
{
|
|
const char* tag = "3D Geometry File Format ";
|
|
char buffer[33] = {};
|
|
|
|
for (;;)
|
|
{
|
|
if (32 != ON_FileStream::Read(fp, 32, buffer))
|
|
break;
|
|
|
|
if (0 != ON_String::CompareOrdinal(tag, 24, buffer, 24, false))
|
|
{
|
|
// it's not a "pure" .3DM file
|
|
// - see if we have a .3DM file with MS OLE-goo at the start
|
|
// (generally, there is around 6kb of goo. I keep looking
|
|
// for up to 32mb just in case.)
|
|
unsigned int offset = 0;
|
|
for (unsigned int n = 0; n < 33554432; n++)
|
|
{
|
|
for (int j = 0; j < 31; j++)
|
|
buffer[j] = buffer[j + 1];
|
|
if (!ON_FileStream::Read(fp, 1, &buffer[31]))
|
|
break;
|
|
if (0 == ON_String::CompareOrdinal(tag, 24, buffer, 24, false))
|
|
{
|
|
offset = n + 1;
|
|
break;
|
|
}
|
|
}
|
|
if (0 == offset)
|
|
break;
|
|
}
|
|
|
|
// get version
|
|
//char* sVersion = s3d+24;
|
|
// skip leading spaces
|
|
int ver = 0;
|
|
int i = 24;
|
|
while (i < 32 && buffer[i] == ' ')
|
|
i++;
|
|
while (i < 32)
|
|
{
|
|
// TEMPORARY 2 = X
|
|
if (i == 31 && buffer[i] == 'X')
|
|
{
|
|
buffer[i] = '2';
|
|
}
|
|
|
|
if (buffer[i] < '0' || buffer[i] > '9')
|
|
{
|
|
// it's not a valid .3DM file version
|
|
break;
|
|
}
|
|
ver = ver * 10 + ((int)(buffer[i] - '0'));
|
|
i++;
|
|
}
|
|
if (ver <= 0)
|
|
break;
|
|
|
|
if (false == ON_FileStream::SeekFromCurrentPosition(fp, -32))
|
|
break;
|
|
|
|
return (unsigned int)ver;
|
|
}
|
|
ON_FileStream::SeekFromStart(fp, 0);
|
|
return false;
|
|
}
|
|
|
|
FILE* ON_FileStream::Open3dmToRead(
|
|
const wchar_t* file_path
|
|
)
|
|
{
|
|
FILE* fp = nullptr;
|
|
|
|
for (;;)
|
|
{
|
|
fp = ON_FileStream::Open(file_path,L"rb");
|
|
if (nullptr == fp)
|
|
break;
|
|
|
|
if ( 0 == ON_Internal_SeekTo3DGeometryFileFormatMark(fp))
|
|
break;
|
|
|
|
return fp;
|
|
}
|
|
|
|
if ( nullptr != fp )
|
|
ON::CloseFile(fp);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FILE* ON_FileStream::ON_FileStream::Open3dmToRead(
|
|
const char* file_path
|
|
)
|
|
{
|
|
FILE* fp = nullptr;
|
|
|
|
for (;;)
|
|
{
|
|
fp = ON_FileStream::Open(file_path,"rb");
|
|
if (nullptr == fp)
|
|
break;
|
|
|
|
if ( 0 == ON_Internal_SeekTo3DGeometryFileFormatMark(fp))
|
|
break;
|
|
|
|
return fp;
|
|
}
|
|
|
|
if ( nullptr != fp )
|
|
ON::CloseFile(fp);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
ON__INT64 ON_FileStream::CurrentPosition( FILE* fp )
|
|
{
|
|
if ( 0 == fp )
|
|
return -1;
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
return _ftelli64(fp);
|
|
#else
|
|
return ftell(fp);
|
|
#endif
|
|
}
|
|
|
|
bool ON_FileStream::SeekFromCurrentPosition( FILE* fp, ON__INT64 offset )
|
|
{
|
|
return ON_FileStream::Seek(fp,offset,SEEK_CUR);
|
|
}
|
|
|
|
bool ON_FileStream::SeekFromStart( FILE* fp, ON__INT64 offset )
|
|
{
|
|
return ON_FileStream::Seek(fp,offset,SEEK_SET);
|
|
}
|
|
|
|
bool ON_FileStream::SeekFromEnd( FILE* fp, ON__INT64 offset )
|
|
{
|
|
return ON_FileStream::Seek(fp,offset,SEEK_END);
|
|
}
|
|
|
|
bool ON_FileStream::Seek( FILE* fp, ON__INT64 offset, int origin )
|
|
{
|
|
if ( 0 == fp )
|
|
return false;
|
|
|
|
if ( origin < 0 || origin > 2 )
|
|
return false;
|
|
|
|
if ( 0 == offset && SEEK_CUR == origin )
|
|
return true;
|
|
|
|
#if defined(ON_COMPILER_MSC) && defined(ON_RUNTIME_WIN)
|
|
if (0 != _fseeki64(fp, offset, origin))
|
|
return false;
|
|
#else
|
|
|
|
const int i = 2147483646;
|
|
const ON__INT64 i64 = i;
|
|
|
|
while ( offset > i64 )
|
|
{
|
|
if ( 0 != fseek( fp, i, origin ) )
|
|
return false;
|
|
if (SEEK_CUR != origin)
|
|
origin = SEEK_CUR;
|
|
offset -= i64;
|
|
}
|
|
|
|
while ( offset < -i64 )
|
|
{
|
|
if ( 0 != fseek( fp, -i, origin ) )
|
|
return false;
|
|
if (SEEK_CUR != origin)
|
|
origin = SEEK_CUR;
|
|
offset += i64;
|
|
}
|
|
|
|
if (0 != offset || SEEK_CUR != origin)
|
|
{
|
|
int ioffset = (int)offset;
|
|
if (0 != fseek(fp, ioffset, origin))
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
ON__UINT64 ON_FileStream::Read( FILE* fp, ON__UINT64 count, void* buffer )
|
|
{
|
|
ON__UINT64 rc = 0;
|
|
if ( 0 == fp || count <= 0 || 0 == buffer )
|
|
return rc;
|
|
|
|
if ( count <= ON_MAX_SIZE_T )
|
|
{
|
|
rc = (ON__UINT64)fread(buffer,1,(size_t)count,fp);
|
|
}
|
|
else
|
|
{
|
|
size_t sz, szread;
|
|
while ( count > 0 )
|
|
{
|
|
sz = ( count > ON_MAX_SIZE_T ) ? ON_MAX_SIZE_T : ((size_t)count);
|
|
szread = fread(buffer,1,sz,fp);
|
|
rc += szread;
|
|
if ( szread != sz )
|
|
break;
|
|
count -= sz;
|
|
buffer = ((unsigned char*)buffer) + sz;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
ON__UINT64 ON_FileStream::Write( FILE* fp, ON__UINT64 count, const void* buffer )
|
|
{
|
|
ON__UINT64 rc = 0;
|
|
if ( 0 == fp || count <= 0 || 0 == buffer )
|
|
return rc;
|
|
|
|
if ( count <= ON_MAX_SIZE_T )
|
|
{
|
|
rc = fwrite(buffer,1,(size_t)count,fp);
|
|
}
|
|
else
|
|
{
|
|
size_t sz, szwrite;
|
|
while ( count > 0 )
|
|
{
|
|
sz = ( count > ON_MAX_SIZE_T ) ? ON_MAX_SIZE_T : ((size_t)count);
|
|
szwrite = fwrite(buffer,1,sz,fp);
|
|
rc += szwrite;
|
|
if ( szwrite != sz )
|
|
break;
|
|
count -= sz;
|
|
buffer = ((unsigned char*)buffer) + sz;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool ON_FileStream::Flush( FILE* fp )
|
|
{
|
|
if ( 0 == fp )
|
|
return false;
|
|
if ( 0 != fflush(fp) )
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool ON_FileStream::GetFileInformation(
|
|
const wchar_t* file_name,
|
|
ON__UINT64* file_size,
|
|
ON__UINT64* file_metadata_last_modified_time,
|
|
ON__UINT64* file_contents_last_modified_time
|
|
)
|
|
{
|
|
FILE* fp = ON::OpenFile(file_name, L"rb");
|
|
bool rc = ON_FileStream::GetFileInformation(fp,file_size,file_metadata_last_modified_time,file_contents_last_modified_time);
|
|
ON::CloseFile(fp);
|
|
return rc;
|
|
}
|
|
|
|
bool ON_FileStream::GetFileInformation(
|
|
const char* file_name,
|
|
ON__UINT64* file_size,
|
|
ON__UINT64* file_metadata_last_modified_time,
|
|
ON__UINT64* file_contents_last_modified_time
|
|
)
|
|
{
|
|
FILE* fp = ON::OpenFile(file_name, "rb");
|
|
bool rc = ON_FileStream::GetFileInformation(fp,file_size,file_metadata_last_modified_time,file_contents_last_modified_time);
|
|
ON::CloseFile(fp);
|
|
return rc;
|
|
}
|
|
|
|
bool ON_FileStream::GetFileInformation(
|
|
FILE* fp,
|
|
ON__UINT64* file_size,
|
|
ON__UINT64* file_metadata_last_modified_time,
|
|
ON__UINT64* file_contents_last_modified_time
|
|
)
|
|
{
|
|
bool rc = false;
|
|
|
|
if (file_size)
|
|
*file_size = 0;
|
|
if (file_metadata_last_modified_time)
|
|
*file_metadata_last_modified_time = 0;
|
|
if (file_contents_last_modified_time)
|
|
*file_contents_last_modified_time = 0;
|
|
|
|
if ( fp )
|
|
{
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
|
|
// Microsoft compilers
|
|
#if (_MSC_VER >= 1400)
|
|
// VC 8 (2005)
|
|
// works for file sizes > 4GB
|
|
// when size_t is a 64 bit integer
|
|
struct _stat64 sb;
|
|
memset(&sb,0,sizeof(sb));
|
|
int fd = _fileno(fp);
|
|
int fstat_rc = _fstat64(fd, &sb);
|
|
#else
|
|
// VC6 compiler
|
|
struct _stat sb;
|
|
memset(&sb,0,sizeof(sb));
|
|
int fd = _fileno(fp);
|
|
int fstat_rc = _fstat(fd, &sb);
|
|
#endif
|
|
|
|
#else
|
|
// works on most compilers
|
|
int fd = fileno(fp);
|
|
struct stat sb;
|
|
memset(&sb,0,sizeof(sb));
|
|
int fstat_rc = fstat(fd, &sb);
|
|
#endif
|
|
|
|
|
|
if (0 == fstat_rc)
|
|
{
|
|
if (file_size)
|
|
*file_size = (ON__UINT64)sb.st_size;
|
|
if (file_metadata_last_modified_time)
|
|
*file_metadata_last_modified_time = (ON__UINT64)sb.st_ctime;
|
|
if (file_contents_last_modified_time)
|
|
*file_contents_last_modified_time = (ON__UINT64)sb.st_mtime;
|
|
rc = true;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ON_FileIterator implementation
|
|
//
|
|
|
|
class ON_DirectoryIteratorImpl
|
|
{
|
|
private:
|
|
friend class ON_FileIterator;
|
|
|
|
ON_DirectoryIteratorImpl();
|
|
~ON_DirectoryIteratorImpl();
|
|
ON_DirectoryIteratorImpl(const ON_DirectoryIteratorImpl&) = delete;
|
|
ON_DirectoryIteratorImpl& operator=(const ON_DirectoryIteratorImpl&) = delete;
|
|
|
|
const wchar_t* CurrentFileNameAsPointer() const;
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
// Used by Windows ::Find
|
|
//ON__UINT32 m_file_attributes_mask = 0;
|
|
HANDLE m_h = 0;
|
|
WIN32_FIND_DATA m_fd;
|
|
#else
|
|
// Apple and gcc
|
|
ON_wString m_ws_file_name_filter;
|
|
ON_String m_utf8_file_name_filter;
|
|
DIR* m_dir = nullptr;
|
|
struct dirent m_dirent;
|
|
char m_dirent_name_buffer[NAME_MAX+1]; // < this field provide storage for m_dirent.d_name[]
|
|
|
|
// information about the current file
|
|
wchar_t m_current_name[1024];
|
|
ON__UINT64 m_current_file_attributes = 0; // 1 = regular file, 2 = directory
|
|
ON__UINT64 m_current_file_size = 0;
|
|
ON__UINT64 m_current_content_last_modified_time = 0;
|
|
#endif
|
|
};
|
|
|
|
ON_DirectoryIteratorImpl::ON_DirectoryIteratorImpl()
|
|
{
|
|
#if defined(ON_COMPILER_MSC)
|
|
memset(&m_fd,0,sizeof(m_fd));
|
|
#else
|
|
memset(&m_dirent,0,sizeof(m_dirent));
|
|
memset(&m_dirent_name_buffer[0],0,sizeof(m_dirent_name_buffer));
|
|
memset(&m_current_name[0],0,sizeof(m_current_name));
|
|
#endif
|
|
}
|
|
|
|
ON_DirectoryIteratorImpl::~ON_DirectoryIteratorImpl()
|
|
{
|
|
#if defined(ON_COMPILER_MSC)
|
|
if ( 0 != m_h )
|
|
::FindClose(m_h);
|
|
#else
|
|
if ( 0 != m_dir )
|
|
closedir(m_dir);
|
|
#endif
|
|
}
|
|
|
|
void ON_FileIterator::Reset()
|
|
{
|
|
m_state = 0;
|
|
m_directory = ON_wString::EmptyString;
|
|
m_item_name_filter = ON_wString::EmptyString;
|
|
m_item_name = ON_wString::EmptyString;
|
|
m_full_path_name = ON_wString::EmptyString;
|
|
m_count = 0;
|
|
if (nullptr != m_impl)
|
|
{
|
|
delete m_impl;
|
|
m_impl = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
ON__UINT64 ON_FileIterator::CurrentItemCount() const
|
|
{
|
|
return m_count;
|
|
}
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
static bool IsDotOrDotDotDir( const wchar_t* s )
|
|
#else
|
|
static bool IsDotOrDotDotDir( const char* s )
|
|
#endif
|
|
{
|
|
bool rc = false;
|
|
for (;;)
|
|
{
|
|
if ( 0 == s )
|
|
break;
|
|
if ( '.' != s[0] )
|
|
break;
|
|
if ( 0 != s[1] )
|
|
{
|
|
if ( '.' != s[1] )
|
|
break;
|
|
if ( 0 != s[2] )
|
|
break;
|
|
}
|
|
rc = true; // s = "." or s = ".."
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_FileIterator::Initialize(
|
|
const wchar_t* directory_name
|
|
)
|
|
{
|
|
const wchar_t* item_name_filter = nullptr;
|
|
return Initialize(directory_name,item_name_filter);
|
|
}
|
|
|
|
bool ON_FileIterator::Initialize(
|
|
const wchar_t* directory_name,
|
|
const wchar_t* item_name_filter
|
|
)
|
|
{
|
|
const ON_wString local_item_name_filter(item_name_filter);
|
|
item_name_filter = local_item_name_filter;
|
|
|
|
ON_wString local_directory_name(directory_name);
|
|
{
|
|
const wchar_t* dir_seps = L"/\\";
|
|
local_directory_name.TrimRight(dir_seps);
|
|
if ( local_directory_name.Length() <= 0 || local_directory_name.IsEmpty() )
|
|
local_directory_name = directory_name;
|
|
}
|
|
|
|
Reset();
|
|
m_directory = local_directory_name;
|
|
m_item_name_filter = local_item_name_filter;
|
|
if (m_directory.IsEmpty())
|
|
return false;
|
|
m_state = 1;
|
|
return true;
|
|
}
|
|
|
|
bool ON_FileIterator::Initialize(
|
|
const char* directory_name
|
|
)
|
|
{
|
|
const char* item_name_filter = nullptr;
|
|
return Initialize(directory_name,item_name_filter);
|
|
}
|
|
|
|
bool ON_FileIterator::Initialize(
|
|
const char* directory_name,
|
|
const char* item_name_filter
|
|
)
|
|
{
|
|
const ON_wString local_directory_name(directory_name);
|
|
const ON_wString local_item_name_filter(item_name_filter);
|
|
return Initialize(
|
|
static_cast<const wchar_t*>(local_directory_name),
|
|
static_cast<const wchar_t*>(local_item_name_filter)
|
|
);
|
|
}
|
|
|
|
bool ON_FileIterator::FirstItem()
|
|
{
|
|
const ON_wString saved_directory(m_directory);
|
|
const ON_wString saved_item_name_filter(m_item_name_filter);
|
|
if (m_state > 1)
|
|
{
|
|
Reset();
|
|
m_directory = saved_directory;
|
|
m_item_name_filter = saved_item_name_filter;
|
|
if (saved_directory.IsEmpty())
|
|
return false;
|
|
m_state = 1;
|
|
}
|
|
|
|
if ( 1 != m_state || nullptr != m_impl )
|
|
return false;
|
|
|
|
m_impl = new ON_DirectoryIteratorImpl();
|
|
m_state = 2;
|
|
|
|
const wchar_t* item_name_filter = static_cast<const wchar_t*>(m_item_name_filter);
|
|
if ( nullptr != item_name_filter && 0 == item_name_filter[0] )
|
|
item_name_filter = nullptr;
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
for (;;)
|
|
{
|
|
ON_wString s(m_directory);
|
|
|
|
if (0 == item_name_filter)
|
|
{
|
|
// A null file file_name_filter means iterate
|
|
// through all items in the directory. To do
|
|
// this using Windows' ::FindFirstFile, set the
|
|
// filter to "*.*", even though some items will
|
|
// not contain a "dot".
|
|
item_name_filter = L"*.*";
|
|
}
|
|
|
|
if (0 != item_name_filter[0] && s.IsNotEmpty())
|
|
{
|
|
s += ON_FileSystemPath::DirectorySeparator;
|
|
s += item_name_filter;
|
|
}
|
|
|
|
m_impl->m_h = ::FindFirstFile(static_cast<const wchar_t*>(s), &m_impl->m_fd);
|
|
if (0 == m_impl->m_h || INVALID_HANDLE_VALUE == m_impl->m_h || 0 == m_impl->m_fd.cFileName[0])
|
|
{
|
|
// Happens on "fake" directories like "My Music" and "My Videos"
|
|
m_impl->m_h = 0;
|
|
break;
|
|
}
|
|
|
|
if (IsDotOrDotDotDir(m_impl->m_fd.cFileName))
|
|
{
|
|
return NextItem();
|
|
}
|
|
|
|
m_count++;
|
|
m_impl->m_fd.cFileName[(sizeof(m_impl->m_fd.cFileName) / sizeof(m_impl->m_fd.cFileName[0])) - 1] = 0;
|
|
m_item_name = m_impl->m_fd.cFileName;
|
|
m_full_path_name = ON_wString::EmptyString;
|
|
return true;
|
|
}
|
|
|
|
#else
|
|
// gcc code
|
|
m_impl->m_utf8_file_name_filter = item_name_filter;
|
|
const ON_String utf8_str(m_directory); // convert wchar_t to utf8 string
|
|
const char* s = utf8_str;
|
|
m_impl->m_dir = (0 != s && 0 != s[0]) ? opendir(s) : 0;
|
|
if ( 0 != m_impl->m_dir )
|
|
{
|
|
return NextItem();
|
|
}
|
|
#endif
|
|
|
|
Reset();
|
|
m_directory = saved_directory;
|
|
m_item_name_filter = saved_item_name_filter;
|
|
m_state = 3;
|
|
return false;
|
|
}
|
|
|
|
bool ON_FileIterator::NextItem()
|
|
{
|
|
m_item_name = ON_wString::EmptyString;
|
|
m_full_path_name = ON_wString::EmptyString;
|
|
if ( 1 == m_state )
|
|
return FirstItem();
|
|
if ( 2 != m_state )
|
|
return false;
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
for (;;)
|
|
{
|
|
if (0 == m_impl->m_h || INVALID_HANDLE_VALUE == m_impl->m_h || 0 == m_impl->m_fd.cFileName[0])
|
|
{
|
|
break;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
if (!::FindNextFile(m_impl->m_h, &m_impl->m_fd) || 0 == m_impl->m_fd.cFileName[0])
|
|
break;
|
|
|
|
if (IsDotOrDotDotDir(m_impl->m_fd.cFileName))
|
|
continue;
|
|
|
|
m_count++;
|
|
m_impl->m_fd.cFileName[(sizeof(m_impl->m_fd.cFileName) / sizeof(m_impl->m_fd.cFileName[0])) - 1] = 0;
|
|
m_item_name = m_impl->m_fd.cFileName;
|
|
m_full_path_name = ON_wString::EmptyString;
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
#else
|
|
|
|
// gcc code
|
|
ON__UINT64 current_file_attributes = 0;
|
|
wchar_t current_name[ sizeof(m_impl->m_current_name)/sizeof(m_impl->m_current_name[0]) ];
|
|
for(;;)
|
|
{
|
|
current_file_attributes = 0;
|
|
struct dirent* dp = 0;
|
|
int readdir_errno = readdir_r(m_impl->m_dir, &m_impl->m_dirent, &dp);
|
|
if ( 0 != readdir_errno )
|
|
break;
|
|
if ( 0 == dp )
|
|
break;
|
|
if ( 0 == m_impl->m_dirent.d_name[0] )
|
|
break;
|
|
|
|
if ( IsDotOrDotDotDir(m_impl->m_dirent.d_name) )
|
|
continue;
|
|
|
|
memset( current_name, 0, sizeof(current_name) );
|
|
ON_ConvertUTF8ToWideChar(
|
|
false, // no BOM in input file name as utf8 string
|
|
&m_impl->m_dirent.d_name[0],
|
|
-1, // null terminated utf8 string
|
|
¤t_name[0], ((int)(sizeof(current_name)/sizeof(current_name[0]))) - 1, // output wchar_t string
|
|
0, // null output error status
|
|
(4|8|16), // mask common conversion errors
|
|
0, // error_code_point = null terminator inserted at point of conversion error
|
|
0 // null ouput end-of-string pointer
|
|
);
|
|
// TODO
|
|
// Test m_dirent.d_name to make sure it passes m_ws/utf8_file_name_filter
|
|
|
|
ON_wString wpath = m_directory;
|
|
wpath += '/';
|
|
wpath += current_name;
|
|
|
|
// get a utf8 version of the full path to pass to stat
|
|
const ON_String utf8buffer(wpath);
|
|
const char* utf8path = utf8buffer;
|
|
if ( 0 == utf8path )
|
|
continue;
|
|
|
|
struct stat buf;
|
|
memset(&buf,0,sizeof(buf));
|
|
int stat_errno = stat( utf8path, &buf);
|
|
if ( 0 != stat_errno )
|
|
continue;
|
|
|
|
if ( S_ISDIR(buf.st_mode) )
|
|
{
|
|
current_file_attributes = 2;
|
|
}
|
|
else if ( S_ISREG(buf.st_mode) )
|
|
{
|
|
// Only *.ext filters work at this time for non-windows
|
|
const wchar_t* file_name_filter = m_impl->m_ws_file_name_filter;
|
|
if ( 0 != file_name_filter
|
|
&& '*' == file_name_filter[0]
|
|
&& '.' == file_name_filter[1]
|
|
&& 0 != file_name_filter[2]
|
|
&& '*' != file_name_filter[2] )
|
|
{
|
|
// assume this is a *.extension filter
|
|
const wchar_t* current_name_ext = 0;
|
|
on_wsplitpath(current_name,0,0,0,¤t_name_ext);
|
|
if ( 0 == current_name_ext
|
|
|| 0 != wcscmp(file_name_filter+1,current_name_ext)
|
|
)
|
|
{
|
|
// current_name does pass match file_name_filter
|
|
continue;
|
|
}
|
|
}
|
|
current_file_attributes = 1;
|
|
}
|
|
else
|
|
continue;
|
|
|
|
// save current item information
|
|
memcpy( m_impl->m_current_name, current_name, sizeof(m_impl->m_current_name) );
|
|
m_impl->m_current_file_attributes = current_file_attributes;
|
|
m_impl->m_current_file_size = buf.st_size;
|
|
m_impl->m_current_content_last_modified_time = buf.st_mtime;
|
|
|
|
m_item_name = m_impl->m_current_name;
|
|
m_full_path_name = ON_wString::EmptyString;
|
|
return true;
|
|
}
|
|
|
|
#endif
|
|
const ON__UINT64 saved_count = m_count;
|
|
Reset();
|
|
m_count = saved_count;
|
|
m_state = 3;
|
|
return false;
|
|
}
|
|
|
|
const wchar_t* ON_DirectoryIteratorImpl::CurrentFileNameAsPointer() const
|
|
{
|
|
#if defined(ON_COMPILER_MSC)
|
|
return ( 0 != m_h && 0 != m_fd.cFileName[0] ) ? m_fd.cFileName : nullptr;
|
|
#else
|
|
return ( 0 != m_current_name[0] ) ? m_current_name : nullptr;
|
|
#endif
|
|
}
|
|
|
|
const ON_wString ON_FileIterator::CurrentItemName() const
|
|
{
|
|
return m_item_name;
|
|
}
|
|
|
|
ON__UINT64 ON_FileIterator::CurrentItemSize() const
|
|
{
|
|
ON__UINT64 file_size = 0;
|
|
|
|
if (nullptr != m_impl)
|
|
{
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
if (0 != m_impl->CurrentFileNameAsPointer())
|
|
{
|
|
file_size = m_impl->m_fd.nFileSizeHigh;
|
|
file_size *= ((ON__UINT64)0xFFFFFFFF);
|
|
file_size += m_impl->m_fd.nFileSizeLow;
|
|
}
|
|
#else
|
|
file_size = m_impl->m_current_file_size;
|
|
#endif
|
|
}
|
|
|
|
return file_size;
|
|
}
|
|
|
|
bool ON_FileIterator::CurrentItemIsDirectory() const
|
|
{
|
|
bool rc = false;
|
|
if (nullptr != m_impl)
|
|
{
|
|
const wchar_t* current_file_name = m_impl->CurrentFileNameAsPointer();
|
|
if (0 != current_file_name && 0 != current_file_name[0])
|
|
{
|
|
#if defined(ON_COMPILER_MSC)
|
|
if (0 != (FILE_ATTRIBUTE_DIRECTORY & m_impl->m_fd.dwFileAttributes))
|
|
{
|
|
rc = true;
|
|
}
|
|
#else
|
|
if ( 2 == m_impl->m_current_file_attributes)
|
|
{
|
|
rc = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
bool ON_FileIterator::CurrentItemIsFile() const
|
|
{
|
|
bool rc = false;
|
|
if (nullptr != m_impl)
|
|
{
|
|
const wchar_t* current_file_name = m_impl->CurrentFileNameAsPointer();
|
|
if (0 != current_file_name && 0 != current_file_name[0])
|
|
{
|
|
#if defined(ON_COMPILER_MSC)
|
|
if (0 == (FILE_ATTRIBUTE_DIRECTORY & m_impl->m_fd.dwFileAttributes))
|
|
{
|
|
rc = true;
|
|
}
|
|
#else
|
|
if ( 1 == m_impl->m_current_file_attributes)
|
|
{
|
|
rc = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
bool ON_FileIterator::CurrentItemIsHidden() const
|
|
{
|
|
bool rc = false;
|
|
if (nullptr != m_impl)
|
|
{
|
|
const wchar_t* current_file_name = m_impl->CurrentFileNameAsPointer();
|
|
if (0 != current_file_name && 0 != current_file_name[0])
|
|
{
|
|
if ('.' == current_file_name[0])
|
|
{
|
|
rc = true;
|
|
}
|
|
#if defined(ON_COMPILER_MSC)
|
|
else if (0 != (FILE_ATTRIBUTE_HIDDEN & m_impl->m_fd.dwFileAttributes))
|
|
{
|
|
rc = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
const ON_wString ON_FileIterator::CurrentItemFullPathName() const
|
|
{
|
|
if (m_full_path_name.IsEmpty() && m_item_name.IsNotEmpty())
|
|
{
|
|
if (m_directory.IsNotEmpty())
|
|
{
|
|
m_full_path_name = m_directory;
|
|
m_full_path_name += ON_FileSystemPath::DirectorySeparator;
|
|
m_full_path_name += m_item_name;
|
|
}
|
|
}
|
|
return m_full_path_name;
|
|
}
|
|
|
|
ON__UINT64 ON_SecondsSinceJanOne1970UTC()
|
|
{
|
|
#if defined(ON_COMPILER_MSC)
|
|
|
|
__time64_t t = _time64(nullptr);
|
|
return (ON__UINT64)t;
|
|
|
|
#elif defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
|
|
|
|
//__time64_t t = _time64(nullptr);
|
|
time_t t = time(nullptr);
|
|
return (ON__UINT64)t;
|
|
|
|
#else
|
|
|
|
__time64_t t = _time64(nullptr);
|
|
return (ON__UINT64)t;
|
|
|
|
#endif
|
|
}
|
|
|
|
const ON_wString SecondsSinceJanOne1970UTCToString(
|
|
ON__UINT64 seconds_since_epoch
|
|
)
|
|
{
|
|
int year = 0;
|
|
int month = 0;
|
|
int mday = 0;
|
|
int hour = 0;
|
|
int min = 0;
|
|
int sec = 0;
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
|
|
const time_t t = (time_t)seconds_since_epoch;
|
|
const struct tm* ptr = _gmtime64( &t );
|
|
if (nullptr != ptr)
|
|
{
|
|
const struct tm uct = *ptr;
|
|
year = uct.tm_year;
|
|
month = uct.tm_mon;
|
|
mday = uct.tm_mday;
|
|
hour = uct.tm_hour;
|
|
min = uct.tm_min;
|
|
sec = uct.tm_sec;
|
|
}
|
|
|
|
#elif defined(ON_COMPILER_CLANG) || defined(ON_COMPILER_GNU)
|
|
|
|
const time_t t = (time_t)seconds_since_epoch;
|
|
const struct tm* ptr = gmtime( &t );
|
|
if (nullptr != ptr)
|
|
{
|
|
const struct tm uct = *ptr;
|
|
year = uct.tm_year;
|
|
month = uct.tm_mon;
|
|
mday = uct.tm_mday;
|
|
hour = uct.tm_hour;
|
|
min = uct.tm_min;
|
|
sec = uct.tm_sec;
|
|
}
|
|
|
|
#else
|
|
|
|
const time_t t = (time_t)seconds_since_epoch;
|
|
const struct tm* ptr = _gmtime64( &t );
|
|
if (nullptr != ptr)
|
|
{
|
|
const struct tm uct = *ptr;
|
|
year = uct.tm_year;
|
|
month = uct.tm_mon;
|
|
mday = uct.tm_mday;
|
|
hour = uct.tm_hour;
|
|
min = uct.tm_min;
|
|
sec = uct.tm_sec;
|
|
}
|
|
|
|
#endif
|
|
|
|
if (
|
|
year >= 1970
|
|
&& month >= 1 && month <= 12
|
|
&& mday >= 1 && mday <= 31
|
|
&& hour >= 0 && hour <= 24
|
|
&& min >= 0 && min <= 60
|
|
&& sec >= 0 && sec <= 60
|
|
)
|
|
{
|
|
ON_wString sUTC;
|
|
// yyyy-mm-dd hh:mm:ss
|
|
sUTC.Format(L"%04d-%02d-%02d %02d:%02d:%02d UTC",year,month,mday,hour,min,sec);
|
|
return sUTC;
|
|
}
|
|
|
|
return ON_wString::EmptyString;
|
|
}
|
|
|
|
#if defined(ON_COMPILER_MSC)
|
|
static ON__UINT64 SecondsSinceJanOne1970( FILETIME ft )
|
|
{
|
|
// The FILETIME is in 100-nanosecond intervals since January 1, 1601 UCT.
|
|
//
|
|
// Between midnight January 1, 1601 and midnight January 1, 1970 there
|
|
// were 134774 days = 11644473600 seconds. Each second has 10^7 intervals
|
|
// that are one hundred nanoseconds long. So, if N = number of one hundred
|
|
// nanosecond intervals since midnight January 1, 1601, then
|
|
// (N / 10000000) - 11644473600 = number of seconds since midnight
|
|
// January 1, 1970.
|
|
//
|
|
// January 1, 1601 was the start of a Gregorian calendary 400 year cycle
|
|
// and "the internet" sometimes cites that as the reason that date is
|
|
// the "beginning of time" for Windows' FILETIME values. This convention
|
|
// would slightly simplify the formulae used to account for leap years,
|
|
// so it is plausable this might might even be true.
|
|
|
|
ON__UINT64 ft_since_jan_1_1601 = ft.dwHighDateTime;
|
|
ft_since_jan_1_1601 *= 0xFFFFFFFF;
|
|
ft_since_jan_1_1601 += ft.dwLowDateTime;
|
|
|
|
ON__UINT64 hundrednanoseconds_per_second = 10000000;
|
|
|
|
ON__UINT64 seconds_since_jan_1_1601 = ft_since_jan_1_1601 / hundrednanoseconds_per_second;
|
|
|
|
ON__UINT64 seconds_since_jan_1_1970 = seconds_since_jan_1_1601 - 11644473600;
|
|
|
|
return seconds_since_jan_1_1970;
|
|
}
|
|
#endif
|
|
|
|
ON__UINT64 ON_FileIterator::CurrentItemLastModifiedTime() const
|
|
{
|
|
if ( nullptr == m_impl)
|
|
return 0;
|
|
#if defined(ON_COMPILER_MSC)
|
|
return SecondsSinceJanOne1970(m_impl->m_fd.ftLastWriteTime);
|
|
#else
|
|
return m_impl->m_current_content_last_modified_time;
|
|
#endif
|
|
}
|
|
|
|
ON_FileIterator::~ON_FileIterator()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
|
|
ON_ContentHash ON_ContentHash::Create(
|
|
ON_SHA1_Hash sha1_name_hash,
|
|
ON__UINT64 byte_count,
|
|
ON_SHA1_Hash sha1_content_hash,
|
|
ON__UINT64 hash_time,
|
|
ON__UINT64 content_last_modified_time
|
|
)
|
|
{
|
|
ON_ContentHash hash;
|
|
|
|
if ( 0 == hash_time )
|
|
hash_time = ON_SecondsSinceJanOne1970UTC();
|
|
hash.m_byte_count = (byte_count > 0) ? byte_count : 0;
|
|
hash.m_hash_time = hash_time;
|
|
|
|
hash.m_content_time
|
|
= (content_last_modified_time <= hash_time)
|
|
? content_last_modified_time
|
|
: 0;
|
|
|
|
hash.m_sha1_name_hash = sha1_name_hash;
|
|
|
|
hash.m_sha1_content_hash
|
|
= (hash.m_byte_count > 0)
|
|
? sha1_content_hash
|
|
: ON_SHA1_Hash::EmptyContentHash
|
|
;
|
|
|
|
return hash;
|
|
}
|
|
|
|
ON_ContentHash ON_ContentHash::CreateFromBuffer(
|
|
ON_SHA1_Hash sha1_name_hash,
|
|
const void* buffer,
|
|
size_t byte_count
|
|
)
|
|
{
|
|
ON__UINT64 hash_time = ON_SecondsSinceJanOne1970UTC();
|
|
ON__UINT64 hash_byte_count = (nullptr != buffer && byte_count >0) ? ((ON__UINT64)byte_count) : 0;
|
|
ON__UINT64 content_last_modifed_time = 0;
|
|
ON_SHA1_Hash sha1_content_hash = ON_SHA1_Hash::BufferContentHash(buffer,(size_t)hash_byte_count);
|
|
return ON_ContentHash::Create(sha1_name_hash,hash_byte_count,sha1_content_hash,hash_time,content_last_modifed_time);
|
|
}
|
|
|
|
ON_ContentHash ON_ContentHash::CreateFromFile(
|
|
ON_SHA1_Hash sha1_file_name_hash,
|
|
FILE* fp
|
|
)
|
|
{
|
|
ON__UINT64 hash_time = ON_SecondsSinceJanOne1970UTC();
|
|
ON__UINT64 file_byte_count = 0;
|
|
ON__UINT64 file_metadata_last_modified_time = 0;
|
|
ON__UINT64 file_contents_last_modified_time = 0;
|
|
if ( false == ON_FileStream::GetFileInformation(fp,&file_byte_count,&file_metadata_last_modified_time,&file_contents_last_modified_time) )
|
|
return ON_ContentHash::Create(sha1_file_name_hash,0,ON_SHA1_Hash::EmptyContentHash,hash_time,0);
|
|
|
|
ON__UINT64 hash_byte_count = 0;
|
|
ON_SHA1_Hash sha1_hash = ON_SHA1_Hash::FileContentHash(fp,hash_byte_count);
|
|
return ON_ContentHash::Create(sha1_file_name_hash,hash_byte_count,sha1_hash,hash_time,file_contents_last_modified_time);
|
|
}
|
|
|
|
#include <unordered_map>
|
|
|
|
using ContentHashMap = std::unordered_map<std::wstring, ON_ContentHash>;
|
|
|
|
std::weak_ptr<ContentHashMap> g_pContentHashCache;
|
|
|
|
class ON_ContentHash::Cache::Private
|
|
{
|
|
public:
|
|
std::shared_ptr<ContentHashMap> p;
|
|
};
|
|
|
|
ON_ContentHash::Cache::Cache()
|
|
: m_private(new Private)
|
|
{
|
|
|
|
m_private->p = g_pContentHashCache.lock();
|
|
|
|
|
|
if (!m_private->p)
|
|
{
|
|
m_private->p.reset(new ContentHashMap);
|
|
g_pContentHashCache = m_private->p;
|
|
}
|
|
}
|
|
|
|
ON_ContentHash::Cache::~Cache()
|
|
{
|
|
delete m_private;
|
|
}
|
|
|
|
void ON_ContentHash::Cache::Add(const wchar_t* path, const ON_ContentHash& hash)
|
|
{
|
|
auto map = g_pContentHashCache.lock();
|
|
if (map)
|
|
{
|
|
map->insert(std::make_pair(path, hash));
|
|
}
|
|
}
|
|
|
|
|
|
const ON_ContentHash* ON_ContentHash::Cache::FromFile(const wchar_t* path)
|
|
{
|
|
auto map = g_pContentHashCache.lock();
|
|
if (map)
|
|
{
|
|
auto it = map->find(path);
|
|
if (it != map->end())
|
|
{
|
|
return &it->second;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const ON_ContentHash* ON_ContentHash::Cache::FromFile(const char* p)
|
|
{
|
|
ON_wString s(p);
|
|
return FromFile((const wchar_t*)s);
|
|
}
|
|
|
|
void ON_ContentHash::Cache::Add(const char* p, const ON_ContentHash& h)
|
|
{
|
|
ON_wString s(p);
|
|
Add(s, h);
|
|
}
|
|
|
|
ON_ContentHash ON_ContentHash::CreateFromFile(
|
|
const wchar_t* filename
|
|
)
|
|
{
|
|
if (auto pHash = Cache::FromFile(filename))
|
|
{
|
|
return *pHash;
|
|
}
|
|
|
|
ON_SHA1_Hash sha1_file_name_hash = (nullptr == filename) ? ON_SHA1_Hash::ZeroDigest : ON_SHA1_Hash::FileSystemPathHash(filename);
|
|
FILE* fp = ON_FileStream::Open(filename, L"rb");
|
|
ON_ContentHash hash = ON_ContentHash::CreateFromFile(sha1_file_name_hash,fp);
|
|
ON_FileStream::Close(fp);
|
|
|
|
Cache::Add(filename, hash);
|
|
|
|
return hash;
|
|
}
|
|
|
|
ON_ContentHash ON_ContentHash::CreateFromFile(
|
|
const char* filename
|
|
)
|
|
{
|
|
if (auto pHash = Cache::FromFile(filename))
|
|
{
|
|
return *pHash;
|
|
}
|
|
|
|
ON_SHA1_Hash sha1_file_name_hash = (nullptr == filename) ? ON_SHA1_Hash::ZeroDigest : ON_SHA1_Hash::FileSystemPathHash(filename);
|
|
FILE* fp = ON_FileStream::Open(filename, "rb");
|
|
ON_ContentHash hash = ON_ContentHash::CreateFromFile(sha1_file_name_hash,fp);
|
|
ON_FileStream::Close(fp);
|
|
|
|
Cache::Add(filename, hash);
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
bool ON_ContentHash::EqualContent(
|
|
const ON_ContentHash& a,
|
|
const ON_ContentHash& b
|
|
)
|
|
{
|
|
// Do not compare times
|
|
return (a.m_byte_count == b.m_byte_count && a.m_sha1_content_hash == b.m_sha1_content_hash);
|
|
}
|
|
|
|
|
|
bool ON_ContentHash::DifferentContent(
|
|
const ON_ContentHash& a,
|
|
const ON_ContentHash& b
|
|
)
|
|
{
|
|
// Do not compare times
|
|
return (a.m_byte_count != b.m_byte_count || a.m_sha1_content_hash != b.m_sha1_content_hash);
|
|
}
|
|
|
|
int ON_ContentHash::CompareContent(
|
|
const ON_ContentHash& a,
|
|
const ON_ContentHash& b
|
|
)
|
|
{
|
|
if (a.m_byte_count < b.m_byte_count)
|
|
return -1;
|
|
if (a.m_byte_count > b.m_byte_count)
|
|
return 1;
|
|
if (a.m_byte_count < b.m_byte_count)
|
|
return -1;
|
|
if (a.m_byte_count > b.m_byte_count)
|
|
return 1;
|
|
return ON_SHA1_Hash::Compare(a.m_sha1_content_hash, b.m_sha1_content_hash);
|
|
}
|
|
|
|
int ON_ContentHash::Compare(
|
|
const ON_ContentHash& a,
|
|
const ON_ContentHash& b
|
|
)
|
|
{
|
|
const int rc = ON_ContentHash::CompareContent(a, b);
|
|
if (0 != rc)
|
|
return rc;
|
|
|
|
if (a.m_hash_time < b.m_hash_time)
|
|
return -1;
|
|
if (a.m_hash_time > b.m_hash_time)
|
|
return 1;
|
|
|
|
if (a.m_content_time < b.m_content_time)
|
|
return -1;
|
|
if (a.m_content_time > b.m_content_time)
|
|
return 1;
|
|
|
|
return ON_SHA1_Hash::Compare(a.m_sha1_name_hash, b.m_sha1_name_hash);
|
|
}
|
|
|
|
bool ON_ContentHash::EqualFileNameSizeAndTime(
|
|
const wchar_t* filename
|
|
) const
|
|
{
|
|
if (IsNotSet())
|
|
return false;
|
|
if ( m_byte_count <= 0 || m_hash_time <= 0 || m_content_time < m_hash_time )
|
|
return false; // content time is not reliable.
|
|
if (nullptr == filename || 0 == filename[0])
|
|
return false;
|
|
const ON_SHA1_Hash sha1_name_hash = ON_SHA1_Hash::StringHash(filename);
|
|
if ( sha1_name_hash != m_sha1_name_hash )
|
|
return false;
|
|
ON__UINT64 file_byte_count = 0;
|
|
ON__UINT64 file_metadata_last_modified_time = 0;
|
|
ON__UINT64 file_contents_last_modified_time = 0;
|
|
if ( false == ON_FileStream::GetFileInformation(filename,&file_byte_count,&file_metadata_last_modified_time,&file_contents_last_modified_time) )
|
|
return false;
|
|
if ( file_contents_last_modified_time <= 0 )
|
|
return false; // content time is not reliable
|
|
return (file_byte_count == m_byte_count && file_contents_last_modified_time == m_content_time);
|
|
}
|
|
|
|
bool ON_ContentHash::IsSet() const
|
|
{
|
|
if ( 0 == m_hash_time )
|
|
return false;
|
|
return
|
|
(0 == m_byte_count)
|
|
? (ON_SHA1_Hash::EmptyContentHash == m_sha1_content_hash)
|
|
: (ON_SHA1_Hash::EmptyContentHash != m_sha1_content_hash);
|
|
}
|
|
|
|
|
|
bool ON_ContentHash::IsNotSet() const
|
|
{
|
|
return (false == IsSet());
|
|
}
|
|
|
|
ON__UINT64 ON_ContentHash::ByteCount() const
|
|
{
|
|
return m_byte_count;
|
|
}
|
|
|
|
|
|
ON__UINT64 ON_ContentHash::HashCalculationTime() const
|
|
{
|
|
return m_hash_time;
|
|
}
|
|
|
|
|
|
ON__UINT64 ON_ContentHash::ContentLastModifiedTime() const
|
|
{
|
|
return m_content_time;
|
|
}
|
|
|
|
|
|
ON_SHA1_Hash ON_ContentHash::ContentHash() const
|
|
{
|
|
return m_sha1_content_hash;
|
|
}
|
|
|
|
|
|
ON_SHA1_Hash ON_ContentHash::NameHash() const
|
|
{
|
|
return m_sha1_name_hash;
|
|
}
|
|
|
|
|
|
bool ON_ContentHash::IsSameBufferContent(
|
|
const void* buffer,
|
|
size_t byte_count
|
|
) const
|
|
{
|
|
return ON_ContentHash::EqualContent(*this, ON_ContentHash::CreateFromBuffer(ON_SHA1_Hash::ZeroDigest,buffer,byte_count));
|
|
}
|
|
|
|
|
|
bool ON_ContentHash::IsSameFileContent(
|
|
FILE* fp
|
|
) const
|
|
{
|
|
return ON_ContentHash::EqualContent(*this, ON_ContentHash::CreateFromFile(ON_SHA1_Hash::ZeroDigest,fp));
|
|
}
|
|
|
|
bool ON_ContentHash::IsSameFileContent(
|
|
const wchar_t* filename
|
|
) const
|
|
{
|
|
return ON_ContentHash::EqualContent(*this, ON_ContentHash::CreateFromFile(filename));
|
|
}
|
|
|
|
bool ON_ContentHash::IsSameFileContent(
|
|
const char* filename
|
|
) const
|
|
{
|
|
return ON_ContentHash::EqualContent(*this, ON_ContentHash::CreateFromFile(filename));
|
|
}
|
|
|
|
ON_ContentHash::CompareResult ON_ContentHash::CompareResultFromUnsigned(
|
|
unsigned int compare_result_as_unsigned
|
|
)
|
|
{
|
|
switch (compare_result_as_unsigned)
|
|
{
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_ContentHash::CompareResult::Unset);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_ContentHash::CompareResult::EqualContent);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_ContentHash::CompareResult::DifferentContent);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_ContentHash::CompareResult::DifferentContentFileIsOlder);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_ContentHash::CompareResult::ContentDifferentFileIsNewer);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_ContentHash::CompareResult::FileDoesNotExist);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_ContentHash::CompareResult::FileSystemFailure);
|
|
}
|
|
|
|
ON_ERROR("Invalid compare_result_as_unsigned parameter.");
|
|
return ON_ContentHash::CompareResult::Unset;
|
|
}
|
|
|
|
ON_ContentHash::CompareResult ON_ContentHash::Compare(
|
|
const wchar_t* file_name,
|
|
bool bFastTest
|
|
) const
|
|
{
|
|
if ( false == ON_FileSystem::IsFile(file_name) )
|
|
return ON_ContentHash::CompareResult::FileDoesNotExist;
|
|
|
|
if (IsNotSet())
|
|
return ON_ContentHash::CompareResult::DifferentContent;
|
|
|
|
ON_ContentHash file_content_hash = ON_ContentHash::Unset;
|
|
ON__UINT64 file_metadata_last_modified_time = 0;
|
|
if ( false == ON_FileStream::GetFileInformation(file_name,&file_content_hash.m_byte_count,&file_metadata_last_modified_time,&file_content_hash.m_content_time) )
|
|
return ON_ContentHash::CompareResult::FileSystemFailure;
|
|
if (0 == file_content_hash.m_byte_count && 0 == file_metadata_last_modified_time && 0 == file_content_hash.m_content_time)
|
|
{
|
|
return ON_ContentHash::CompareResult::FileSystemFailure;
|
|
}
|
|
|
|
const ON__UINT64 current_time = ON_SecondsSinceJanOne1970UTC();
|
|
bool bValidContentHashTime = (m_content_time > 0 && m_hash_time >= m_content_time && current_time >= m_hash_time);
|
|
bool bValidFileTime = (file_content_hash.m_content_time > 0 && current_time >= file_content_hash.m_content_time);
|
|
if (file_content_hash.m_byte_count != m_byte_count)
|
|
{
|
|
if (bValidContentHashTime && bValidFileTime)
|
|
{
|
|
// assume time values are accurate.
|
|
if ( file_content_hash.m_content_time < m_content_time )
|
|
return ON_ContentHash::CompareResult::DifferentContentFileIsOlder;
|
|
if ( file_content_hash.m_content_time > m_content_time )
|
|
return ON_ContentHash::CompareResult::ContentDifferentFileIsNewer;
|
|
}
|
|
return ON_ContentHash::CompareResult::DifferentContent;
|
|
}
|
|
|
|
file_content_hash.m_sha1_name_hash = ON_SHA1_Hash::StringHash(file_name);
|
|
file_content_hash.m_sha1_content_hash = m_sha1_content_hash;
|
|
|
|
if (bValidContentHashTime && bValidFileTime
|
|
&& m_content_time == file_content_hash.m_content_time
|
|
&& m_byte_count == file_content_hash.m_byte_count
|
|
&& m_sha1_name_hash == file_content_hash.m_sha1_name_hash
|
|
)
|
|
{
|
|
if (bFastTest)
|
|
return ON_ContentHash::CompareResult::EqualContent;
|
|
}
|
|
|
|
// Have to calculate SHA1 content hash
|
|
file_content_hash = ON_ContentHash::CreateFromFile(file_name);
|
|
return ON_ContentHash::Compare(file_content_hash);
|
|
}
|
|
|
|
ON_ContentHash::CompareResult ON_ContentHash::Compare(
|
|
ON_ContentHash file_content_hash
|
|
) const
|
|
{
|
|
if (file_content_hash.IsNotSet())
|
|
{
|
|
return ( m_sha1_name_hash == ON_SHA1_Hash::EmptyContentHash)
|
|
? ON_ContentHash::CompareResult::FileDoesNotExist
|
|
: ON_ContentHash::CompareResult::FileSystemFailure;
|
|
}
|
|
if ( IsNotSet() )
|
|
return ON_ContentHash::CompareResult::DifferentContent;
|
|
if ( m_byte_count == file_content_hash.m_byte_count && m_sha1_content_hash == file_content_hash.m_sha1_content_hash )
|
|
return ON_ContentHash::CompareResult::EqualContent;
|
|
const ON__UINT64 current_time = ON_SecondsSinceJanOne1970UTC();
|
|
bool bValidTimes
|
|
= m_content_time > 0
|
|
&& m_hash_time >= m_content_time
|
|
&& current_time >= m_hash_time
|
|
&& file_content_hash.m_content_time > 0
|
|
&& file_content_hash.m_hash_time >= file_content_hash.m_content_time
|
|
&& current_time >= file_content_hash.m_hash_time
|
|
;
|
|
if (bValidTimes)
|
|
{
|
|
if ( file_content_hash.m_content_time < m_content_time )
|
|
return ON_ContentHash::CompareResult::DifferentContentFileIsOlder;
|
|
if ( file_content_hash.m_content_time > m_content_time )
|
|
return ON_ContentHash::CompareResult::ContentDifferentFileIsNewer;
|
|
}
|
|
return ON_ContentHash::CompareResult::DifferentContent;
|
|
}
|
|
|
|
bool ON_ContentHash::Read(
|
|
class ON_BinaryArchive& archive
|
|
)
|
|
{
|
|
*this = ON_ContentHash::Unset;
|
|
bool rc = false;
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version))
|
|
return rc;
|
|
for (;;)
|
|
{
|
|
if ( 1 != major_version )
|
|
break;
|
|
if (!archive.ReadBigInt(&m_byte_count))
|
|
break;
|
|
if (!archive.ReadBigInt(&m_hash_time))
|
|
break;
|
|
if (!archive.ReadBigInt(&m_content_time))
|
|
break;
|
|
if (!m_sha1_name_hash.Read(archive))
|
|
break;
|
|
if (!m_sha1_content_hash.Read(archive))
|
|
break;
|
|
rc = true;
|
|
break;
|
|
}
|
|
if (!archive.EndRead3dmChunk())
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
|
|
bool ON_ContentHash::Write(
|
|
class ON_BinaryArchive& archive
|
|
) const
|
|
{
|
|
if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,0))
|
|
return false;
|
|
bool rc = false;
|
|
for (;;)
|
|
{
|
|
if (!archive.WriteBigInt(m_byte_count))
|
|
break;
|
|
if (!archive.WriteBigInt(m_hash_time))
|
|
break;
|
|
if (!archive.WriteBigInt(m_content_time))
|
|
break;
|
|
if (!m_sha1_name_hash.Write(archive))
|
|
break;
|
|
if (!m_sha1_content_hash.Write(archive))
|
|
break;
|
|
rc = true;
|
|
break;
|
|
}
|
|
if (!archive.EndWrite3dmChunk())
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
void ON_ContentHash::Dump(
|
|
class ON_TextLog& text_log
|
|
) const
|
|
{
|
|
if (IsSet())
|
|
{
|
|
text_log.Print(L"ON_ContentHash:\n");
|
|
text_log.PushIndent();
|
|
|
|
text_log.Print(L"Name hash: ");
|
|
m_sha1_name_hash.Dump(text_log);
|
|
text_log.Print(L"Content byte count = %llu\n",m_byte_count);
|
|
text_log.Print(L"Content hash: ");
|
|
m_sha1_content_hash.Dump(text_log);
|
|
|
|
const ON_wString content_time
|
|
= ( m_content_time <= 0 )
|
|
? L"unknown"
|
|
: SecondsSinceJanOne1970UTCToString(m_content_time);
|
|
text_log.Print(L"Content last modified time = %ls\n",static_cast<const wchar_t*>(content_time));
|
|
|
|
const ON_wString hash_time
|
|
= ( m_hash_time <= 0 )
|
|
? L"unknown"
|
|
: SecondsSinceJanOne1970UTCToString(m_hash_time);
|
|
text_log.Print(L"Content hash calculated time = %ls\n",static_cast<const wchar_t*>(content_time));
|
|
|
|
text_log.PopIndent();
|
|
}
|
|
else
|
|
{
|
|
text_log.Print(L"ON_ContentHash::Unset\n");
|
|
}
|
|
}
|
|
|
|
int ON_FileReference::Compare(
|
|
const ON_FileReference& a,
|
|
const ON_FileReference& b
|
|
)
|
|
{
|
|
int rc;
|
|
for (;;)
|
|
{
|
|
// must compare every byte of every field.
|
|
// If you don't like that, add another clearly named compare function.
|
|
rc = ON_wString::ComparePath(a.m_full_path,b.m_full_path);
|
|
if (0 != rc)
|
|
break;
|
|
rc = ON_wString::CompareOrdinal(a.m_full_path,b.m_full_path,false);
|
|
if (0 != rc)
|
|
break;
|
|
rc = ON_wString::ComparePath(a.m_relative_path,b.m_relative_path);
|
|
if (0 != rc)
|
|
break;
|
|
rc = ON_wString::CompareOrdinal(a.m_relative_path,b.m_relative_path,false);
|
|
if (0 != rc)
|
|
break;
|
|
rc = ON_ContentHash::CompareContent(a.m_content_hash, b.m_content_hash);
|
|
if (0 != rc)
|
|
break;
|
|
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
ON_FileReference::Status ON_FileReference::StatusFromUnsigned(
|
|
unsigned int full_path_status_as_unsigned
|
|
)
|
|
{
|
|
switch (full_path_status_as_unsigned)
|
|
{
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_FileReference::Status::Unknown);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_FileReference::Status::FullPathValid);
|
|
ON_ENUM_FROM_UNSIGNED_CASE(ON_FileReference::Status::FileNotFound);
|
|
}
|
|
ON_ERROR("Invalid parameter.");
|
|
return ON_FileReference::Status::Unknown;
|
|
}
|
|
|
|
|
|
ON_FileReference::ON_FileReference(
|
|
const wchar_t* full_path,
|
|
const wchar_t* relative_path,
|
|
ON_ContentHash content_hash,
|
|
ON_FileReference::Status full_path_status
|
|
)
|
|
: m_full_path(full_path)
|
|
, m_relative_path(relative_path)
|
|
, m_content_hash(content_hash)
|
|
, m_full_path_status(full_path_status)
|
|
{}
|
|
|
|
ON_FileReference ON_FileReference::CreateFromFullPath(
|
|
const wchar_t* full_path,
|
|
bool bSetContentHash,
|
|
bool bSetFullPathStatus
|
|
)
|
|
{
|
|
ON_wString local_full_path(full_path);
|
|
local_full_path.TrimLeftAndRight();
|
|
if (local_full_path.IsEmpty())
|
|
return ON_FileReference::Unset;
|
|
full_path = local_full_path;
|
|
ON_FileReference::Status full_path_status = ON_FileReference::Unset.m_full_path_status;
|
|
ON_ContentHash content_hash = ON_FileReference::Unset.m_content_hash;
|
|
const bool bFileExists =
|
|
( bSetFullPathStatus || bSetContentHash )
|
|
? ON_FileSystem::IsFile(full_path)
|
|
: false;
|
|
if ( bSetFullPathStatus && bFileExists )
|
|
full_path_status = ON_FileReference::Status::FullPathValid;
|
|
if ( bSetContentHash && bFileExists )
|
|
content_hash = ON_ContentHash::CreateFromFile(full_path);
|
|
|
|
const wchar_t* relative_path = nullptr;
|
|
const wchar_t* v = nullptr;
|
|
const wchar_t* d = nullptr;
|
|
const wchar_t* f = nullptr;
|
|
const wchar_t* e = nullptr;
|
|
on_wsplitpath(full_path,&v,&d,&f,&e);
|
|
if (nullptr != d && nullptr != f && d < f && '.' == d[0])
|
|
{
|
|
relative_path = full_path;
|
|
full_path = nullptr;
|
|
}
|
|
|
|
return ON_FileReference(
|
|
full_path,
|
|
relative_path,
|
|
content_hash,
|
|
full_path_status
|
|
);
|
|
}
|
|
|
|
|
|
ON_FileReference::FindFilePreference ON_FileReference::FindFile(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
ON_wString& found_file_full_path
|
|
) const
|
|
{
|
|
const ON_FileReference::FindFilePreference* file_preference = nullptr;
|
|
const unsigned int file_preference_count = 0;
|
|
return Internal_FindFile(
|
|
base_path,
|
|
bBasePathIncludesFileName,
|
|
file_preference,
|
|
file_preference_count,
|
|
found_file_full_path,
|
|
nullptr
|
|
);
|
|
}
|
|
|
|
ON_FileReference::FindFilePreference ON_FileReference::FindFile(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
ON_FileReference::FindFilePreference first_choice,
|
|
ON_FileReference::FindFilePreference second_choice,
|
|
ON_FileReference::FindFilePreference third_choice,
|
|
ON_FileReference::FindFilePreference forth_choice,
|
|
ON_FileReference::FindFilePreference fifth_choice,
|
|
ON_wString& found_file_full_path
|
|
) const
|
|
{
|
|
const ON_FileReference::FindFilePreference file_preference[] = { first_choice, second_choice, third_choice, forth_choice, fifth_choice };
|
|
const unsigned int file_preference_count = (unsigned int)(sizeof(file_preference)/sizeof(file_preference[0]));
|
|
return Internal_FindFile(
|
|
base_path,
|
|
bBasePathIncludesFileName,
|
|
file_preference,
|
|
file_preference_count,
|
|
found_file_full_path,
|
|
nullptr
|
|
);
|
|
}
|
|
|
|
ON_FileReference::FindFilePreference ON_FileReference::FindFileAndUpdateReference(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
bool bUpdateContentHash
|
|
)
|
|
{
|
|
ON_wString found_file_full_path;
|
|
return FindFileAndUpdateReference(base_path,bBasePathIncludesFileName,bUpdateContentHash,found_file_full_path);
|
|
}
|
|
|
|
ON_FileReference::FindFilePreference ON_FileReference::FindFileAndUpdateReference(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
bool bUpdateContentHash,
|
|
ON_wString& found_file_full_path
|
|
)
|
|
{
|
|
return FindFileAndUpdateReference(
|
|
base_path,
|
|
bBasePathIncludesFileName,
|
|
ON_FileReference::FindFilePreference::None,
|
|
ON_FileReference::FindFilePreference::None,
|
|
ON_FileReference::FindFilePreference::None,
|
|
ON_FileReference::FindFilePreference::None,
|
|
ON_FileReference::FindFilePreference::None,
|
|
bUpdateContentHash,
|
|
found_file_full_path
|
|
);
|
|
}
|
|
|
|
ON_FileReference::FindFilePreference ON_FileReference::FindFileAndUpdateReference(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
ON_FileReference::FindFilePreference first_choice,
|
|
ON_FileReference::FindFilePreference second_choice,
|
|
ON_FileReference::FindFilePreference third_choice,
|
|
ON_FileReference::FindFilePreference forth_choice,
|
|
ON_FileReference::FindFilePreference fifth_choice,
|
|
bool bUpdateContentHash,
|
|
ON_wString& found_file_full_path
|
|
)
|
|
{
|
|
const ON_FileReference::FindFilePreference file_preference[] = { first_choice, second_choice, third_choice, forth_choice, fifth_choice };
|
|
const unsigned int file_preference_count = (unsigned int)(sizeof(file_preference)/sizeof(file_preference[0]));
|
|
ON_ContentHash found_file_content_hash = ON_ContentHash::Unset;
|
|
ON_FileReference::FindFilePreference rc = Internal_FindFile(
|
|
base_path,
|
|
bBasePathIncludesFileName,
|
|
file_preference,
|
|
file_preference_count,
|
|
found_file_full_path,
|
|
&found_file_content_hash
|
|
);
|
|
if (rc != ON_FileReference::FindFilePreference::None && found_file_full_path.IsNotEmpty())
|
|
{
|
|
m_full_path = found_file_full_path;
|
|
m_relative_path = ON_wString::EmptyString;
|
|
m_full_path_hash = ON_SHA1_Hash::EmptyContentHash;
|
|
m_embedded_file_id = ON_nil_uuid;
|
|
if ( bUpdateContentHash && found_file_content_hash.IsNotSet() )
|
|
found_file_content_hash = ON_ContentHash::CreateFromFile(m_full_path);
|
|
if ( found_file_content_hash.IsSet() )
|
|
m_content_hash = found_file_content_hash;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static ON_FileReference::FindFilePreference Internal_FindFileResult(
|
|
const ON_wString& file_name_result,
|
|
const ON_ContentHash& content_hash_result,
|
|
ON_FileReference::FindFilePreference rc,
|
|
ON_wString& found_file_full_path,
|
|
ON_ContentHash* found_file_content_hash
|
|
)
|
|
{
|
|
found_file_full_path = file_name_result;
|
|
if (nullptr != found_file_content_hash)
|
|
*found_file_content_hash = content_hash_result;
|
|
return rc;
|
|
}
|
|
|
|
ON_FileReference::FindFilePreference ON_FileReference::Internal_FindFile(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
const ON_FileReference::FindFilePreference* file_preference,
|
|
unsigned int file_preference_count,
|
|
ON_wString& found_file_full_path,
|
|
ON_ContentHash* found_file_content_hash
|
|
) const
|
|
{
|
|
for (;;)
|
|
{
|
|
if (m_full_path.IsEmpty())
|
|
break;
|
|
|
|
const wchar_t* filename = nullptr;
|
|
on_wsplitpath(static_cast<const wchar_t*>(m_full_path), nullptr, nullptr, &filename, nullptr);
|
|
if (nullptr == filename || 0 == filename[0])
|
|
break;
|
|
|
|
// Clean up base_path
|
|
ON_wString local_base_path(base_path);
|
|
base_path = nullptr;
|
|
local_base_path.TrimLeftAndRight();
|
|
|
|
if ( local_base_path.IsNotEmpty() )
|
|
{
|
|
// When the caller is confused and local_base_path identifies an existing file system element,
|
|
// the set the bBasePathIncludesFileName parameter correctly.
|
|
if (bBasePathIncludesFileName)
|
|
{
|
|
if (ON_FileSystem::IsDirectory(base_path))
|
|
bBasePathIncludesFileName = false;
|
|
}
|
|
else
|
|
{
|
|
if (ON_FileSystem::IsFile(base_path))
|
|
bBasePathIncludesFileName = true;
|
|
}
|
|
}
|
|
|
|
if (local_base_path.IsNotEmpty() && bBasePathIncludesFileName)
|
|
{
|
|
bBasePathIncludesFileName = false;
|
|
const wchar_t* start = static_cast<const wchar_t*>(local_base_path);
|
|
const wchar_t* end_mark = nullptr;
|
|
on_wsplitpath(start, nullptr, nullptr, &end_mark, nullptr);
|
|
if (nullptr != start && nullptr != end_mark && start < end_mark)
|
|
{
|
|
local_base_path.SetLength(end_mark - start);
|
|
if (false == ON_FileSystemPath::IsDirectorySeparator(local_base_path[local_base_path.Length() - 1], true))
|
|
local_base_path += ON_FileSystemPath::DirectorySeparator;
|
|
base_path = local_base_path;
|
|
}
|
|
}
|
|
|
|
|
|
// Clean up file preferences and append defaults
|
|
ON_FileReference::FindFilePreference default_pref[] =
|
|
{
|
|
ON_FileReference::FindFilePreference::RelativePath,
|
|
ON_FileReference::FindFilePreference::FullPath,
|
|
ON_FileReference::FindFilePreference::ContentMatch,
|
|
ON_FileReference::FindFilePreference::BasePath,
|
|
ON_FileReference::FindFilePreference::MostRecent
|
|
};
|
|
ON_FileReference::FindFilePreference pref[10 + (sizeof(default_pref) / sizeof(default_pref[0]))];
|
|
unsigned int pref_capacity = (unsigned int)(sizeof(pref) / sizeof(pref[0]));
|
|
unsigned int pref_count = 0;
|
|
for (unsigned int pass = 0; pass < 2; pass++)
|
|
{
|
|
const ON_FileReference::FindFilePreference* pref_source = nullptr;
|
|
unsigned int pref_source_count = 0;
|
|
if (0 == pass)
|
|
{
|
|
pref_source = file_preference;
|
|
pref_source_count = file_preference_count;
|
|
}
|
|
else if (1 == pass)
|
|
{
|
|
pref_source = default_pref;
|
|
pref_source_count = (unsigned int)(sizeof(default_pref) / sizeof(default_pref[0]));
|
|
}
|
|
if (nullptr != pref_source)
|
|
continue;
|
|
for (unsigned int i = 0; i < pref_source_count && pref_count < pref_capacity; i++)
|
|
{
|
|
if (ON_FileReference::FindFilePreference::None == pref_source[i])
|
|
continue;
|
|
unsigned int j;
|
|
for (j = 0; j < i; j++)
|
|
{
|
|
if (pref[j] == pref_source[i])
|
|
break;
|
|
}
|
|
if (j < i)
|
|
continue; // don't add duplicate
|
|
if (pref_count < i)
|
|
pref[pref_count] = pref[i];
|
|
pref_count++;
|
|
}
|
|
}
|
|
|
|
|
|
ON_wString candidate_file_name[3]; // full path, base path + relative path, base path + file_name
|
|
ON_FileReference::FindFilePreference candidate_file_pref[3] = { ON_FileReference::FindFilePreference::None, ON_FileReference::FindFilePreference::None, ON_FileReference::FindFilePreference::None };
|
|
unsigned int candidate_count = 0;
|
|
for (unsigned int pass = 0; pass < 3; pass++)
|
|
{
|
|
ON_FileReference::FindFilePreference ffp = ON_FileReference::FindFilePreference::None;
|
|
ON_wString name;
|
|
switch (pass)
|
|
{
|
|
case 0:
|
|
name = m_full_path;
|
|
ffp = ON_FileReference::FindFilePreference::FullPath;
|
|
break;
|
|
case 1:
|
|
if (nullptr != base_path && m_relative_path.IsNotEmpty())
|
|
{
|
|
name = ON_FileSystemPath::FullPathFromRelativePath(base_path,false,static_cast<const wchar_t*>(m_relative_path));
|
|
ffp = ON_FileReference::FindFilePreference::RelativePath;
|
|
}
|
|
break;
|
|
case 2:
|
|
if (nullptr != base_path)
|
|
{
|
|
name = ON_FileSystemPath::FullPathFromRelativePath(base_path,false,filename);
|
|
ffp = ON_FileReference::FindFilePreference::BasePath;
|
|
}
|
|
break;
|
|
}
|
|
if (name.IsEmpty() || ON_FileReference::FindFilePreference::None == ffp)
|
|
continue;
|
|
if ( false == ON_FileSystem::IsFile(name) )
|
|
continue;
|
|
if (ffp == pref[0])
|
|
{
|
|
// got lucky
|
|
return Internal_FindFileResult(
|
|
name,
|
|
ON_ContentHash::Unset,
|
|
ffp,
|
|
found_file_full_path,
|
|
found_file_content_hash);
|
|
}
|
|
candidate_file_name[candidate_count] = name;
|
|
candidate_file_pref[candidate_count] = ffp;
|
|
candidate_count++;
|
|
}
|
|
|
|
|
|
if (0 == candidate_count)
|
|
break;
|
|
|
|
if ( 1 == candidate_count )
|
|
{
|
|
return Internal_FindFileResult(
|
|
candidate_file_name[0],
|
|
ON_ContentHash::Unset,
|
|
candidate_file_pref[0],
|
|
found_file_full_path,
|
|
found_file_content_hash);
|
|
}
|
|
|
|
|
|
ON_ContentHash candidate_file_content[3] = { ON_ContentHash::Unset, ON_ContentHash::Unset, ON_ContentHash::Unset };
|
|
ON__UINT64 candidate_file_time[3] = { 0 };
|
|
|
|
for (unsigned int i = 0; i < pref_count; i++)
|
|
{
|
|
switch (pref[i])
|
|
{
|
|
case ON_FileReference::FindFilePreference::None:
|
|
break;
|
|
|
|
case ON_FileReference::FindFilePreference::FullPath:
|
|
case ON_FileReference::FindFilePreference::RelativePath:
|
|
case ON_FileReference::FindFilePreference::BasePath:
|
|
for (unsigned int j = 0; j < candidate_count; j++)
|
|
{
|
|
if (pref[i] == candidate_file_pref[j])
|
|
{
|
|
return Internal_FindFileResult(
|
|
candidate_file_name[j],
|
|
candidate_file_content[j],
|
|
candidate_file_pref[j],
|
|
found_file_full_path,
|
|
found_file_content_hash);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_FileReference::FindFilePreference::ContentMatch:
|
|
for (unsigned int j = 0; j < candidate_count; j++)
|
|
{
|
|
if (candidate_file_content[j].IsNotSet())
|
|
{
|
|
for (unsigned int k = 0; k < j; k++)
|
|
{
|
|
if (ON_wString::EqualPath(candidate_file_name[j], candidate_file_name[k]))
|
|
{
|
|
candidate_file_content[j] = candidate_file_content[k];
|
|
break;
|
|
}
|
|
}
|
|
if (candidate_file_content[j].IsNotSet())
|
|
{
|
|
// Use EqualFileNameSizeAndTime() to avoid expensive content calculation.
|
|
if (ON_FileReference::FindFilePreference::FullPath == candidate_file_pref[j]
|
|
&& m_content_hash.EqualFileNameSizeAndTime(candidate_file_name[j]))
|
|
candidate_file_content[j] = m_content_hash;
|
|
else
|
|
candidate_file_content[j] = ON_ContentHash::CreateFromFile(candidate_file_name[j]);
|
|
}
|
|
}
|
|
if (candidate_file_content[j].IsSet())
|
|
{
|
|
if (ON_ContentHash::EqualContent(m_content_hash, candidate_file_content[j]))
|
|
return Internal_FindFileResult(
|
|
candidate_file_name[j],
|
|
candidate_file_content[j],
|
|
ON_FileReference::FindFilePreference::ContentMatch,
|
|
found_file_full_path,
|
|
found_file_content_hash);
|
|
candidate_file_time[j] = candidate_file_content[j].ContentLastModifiedTime();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ON_FileReference::FindFilePreference::MostRecent:
|
|
{
|
|
unsigned int most_recent_dex = candidate_count;
|
|
ON__UINT64 most_recent_time = 0;
|
|
for (unsigned int j = 0; j < candidate_count; j++)
|
|
{
|
|
if (candidate_file_time[j] <= 0)
|
|
{
|
|
ON__UINT64 t = 0;
|
|
for (unsigned int k = 0; k < j; k++)
|
|
{
|
|
if (ON_wString::EqualPath(candidate_file_name[j], candidate_file_name[k]))
|
|
{
|
|
t = candidate_file_time[k];
|
|
break;
|
|
}
|
|
}
|
|
if (t <= 0)
|
|
ON_FileStream::GetFileInformation(candidate_file_name[j], nullptr, nullptr, &t);
|
|
candidate_file_time[j] = t;
|
|
}
|
|
if (candidate_file_time[j] > most_recent_time)
|
|
{
|
|
most_recent_dex = j;
|
|
most_recent_time = candidate_file_time[j];
|
|
}
|
|
}
|
|
if (most_recent_time > 0 && most_recent_dex < candidate_count)
|
|
return Internal_FindFileResult(
|
|
candidate_file_name[most_recent_dex],
|
|
candidate_file_content[most_recent_dex],
|
|
ON_FileReference::FindFilePreference::MostRecent,
|
|
found_file_full_path,
|
|
found_file_content_hash);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Internal_FindFileResult(
|
|
candidate_file_name[0],
|
|
candidate_file_content[0],
|
|
candidate_file_pref[0],
|
|
found_file_full_path,
|
|
found_file_content_hash);
|
|
break;
|
|
}
|
|
|
|
// file not found
|
|
return Internal_FindFileResult(
|
|
ON_wString::EmptyString,
|
|
ON_ContentHash::Unset,
|
|
ON_FileReference::FindFilePreference::None,
|
|
found_file_full_path,
|
|
found_file_content_hash);
|
|
}
|
|
|
|
bool ON_FileReference::IsSet() const
|
|
{
|
|
return m_full_path.IsNotEmpty();
|
|
}
|
|
|
|
bool ON_FileReference::IsNotSet() const
|
|
{
|
|
return m_full_path.IsEmpty();
|
|
}
|
|
|
|
|
|
bool ON_FileReference::Write(
|
|
bool bUseArchiveBasePath,
|
|
ON_BinaryArchive& archive
|
|
) const
|
|
{
|
|
const wchar_t* base_path
|
|
= bUseArchiveBasePath
|
|
? archive.ArchiveDirectoryNameAsPointer()
|
|
: nullptr;
|
|
bool bBasePathIncludesFileName = false;
|
|
return Write( base_path, bBasePathIncludesFileName, archive );
|
|
}
|
|
|
|
bool ON_FileReference::Write(
|
|
const wchar_t* base_path,
|
|
bool bBasePathIncludesFileName,
|
|
ON_BinaryArchive& archive
|
|
) const
|
|
{
|
|
const int major_version = 1;
|
|
|
|
// the embedded file id was added minor version 1
|
|
const int minor_version = archive.Archive3dmVersion() >= 60 ? 1 : 0;
|
|
|
|
if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,major_version,minor_version))
|
|
return false;
|
|
|
|
bool rc = false;
|
|
for (;;)
|
|
{
|
|
const bool bBasePathIsEmpty = (nullptr == base_path || 0 == base_path[0]);
|
|
|
|
const ON_wString full_path
|
|
= m_full_path.IsEmpty() && m_relative_path.IsNotEmpty() && false == bBasePathIsEmpty
|
|
? ON_FileSystemPath::CombinePaths(base_path, bBasePathIncludesFileName, m_relative_path, true, false)
|
|
: m_full_path;
|
|
if (!archive.WriteString(full_path))
|
|
break;
|
|
|
|
const ON_wString relative_path
|
|
= (bBasePathIsEmpty || m_full_path.IsEmpty() )
|
|
? m_relative_path
|
|
: ON_FileSystemPath::RelativePath(m_full_path,true,base_path,bBasePathIncludesFileName);
|
|
if (!archive.WriteString(relative_path))
|
|
break;
|
|
|
|
if (!m_content_hash.Write(archive))
|
|
break;
|
|
unsigned int i = static_cast<unsigned int>(m_full_path_status);
|
|
if (!archive.WriteInt(i))
|
|
break;
|
|
|
|
// embedded file id added at chunk version 1.1
|
|
ON_UUID embedded_file_id = m_embedded_file_id;
|
|
|
|
if ( IsSet() && archive.Active3dmTable() > ON_3dmArchiveTableType::bitmap_table )
|
|
{
|
|
const ON_ComponentManifestItem& embedded_file_item = archive.Manifest().ItemFromNameHash(
|
|
ON_ModelComponent::Type::Image,
|
|
ON_NameHash::CreateFilePathHash(*this)
|
|
);
|
|
if (embedded_file_item.IsValid())
|
|
{
|
|
// A file with identical full path is embedded in this archive.
|
|
// The embedded file can be used if the referenced file cannot
|
|
// be found when this archive is read.
|
|
embedded_file_id = embedded_file_item.Id();
|
|
}
|
|
}
|
|
|
|
if (!archive.WriteUuid(embedded_file_id))
|
|
break;
|
|
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if (!archive.EndWrite3dmChunk())
|
|
rc = false;
|
|
return rc;
|
|
}
|
|
|
|
bool ON_FileReference::Read(
|
|
ON_BinaryArchive& archive
|
|
)
|
|
{
|
|
*this = ON_FileReference::Unset;
|
|
int major_version = 0;
|
|
int minor_version = 0;
|
|
if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version))
|
|
return false;
|
|
bool rc = false;
|
|
for (;;)
|
|
{
|
|
if ( 1 != major_version)
|
|
break;
|
|
if (!archive.ReadString(m_full_path))
|
|
break;
|
|
if (!archive.ReadString(m_relative_path))
|
|
break;
|
|
if (!m_content_hash.Read(archive))
|
|
break;
|
|
|
|
unsigned int full_path_status_as_unsigned = 0;
|
|
if (!archive.ReadInt(&full_path_status_as_unsigned))
|
|
break;
|
|
//m_full_path_status = ON_FileReference::StatusFromUnsigned(full_path_status_as_unsigned);
|
|
// The full path status must be validated after each read.
|
|
m_full_path_status = ON_FileReference::Status::Unknown;
|
|
|
|
if (minor_version >= 1)
|
|
{
|
|
if (!archive.ReadUuid(m_embedded_file_id))
|
|
break;
|
|
}
|
|
rc = true;
|
|
break;
|
|
}
|
|
|
|
if (!archive.EndRead3dmChunk())
|
|
rc = false;
|
|
return rc;
|
|
|
|
}
|
|
|
|
void ON_FileReference::Dump(
|
|
class ON_TextLog& text_log
|
|
) const
|
|
{
|
|
text_log.Print("Full path: \"%ls\"\n", static_cast<const wchar_t*>(m_full_path));
|
|
text_log.PushIndent();
|
|
text_log.Print("Relative path: \"%ls\"\n", static_cast<const wchar_t*>(m_relative_path));
|
|
m_content_hash.Dump(text_log);
|
|
text_log.PopIndent();
|
|
}
|
|
|
|
unsigned int ON_FileReference::SizeOf() const
|
|
{
|
|
return m_full_path.SizeOf() + m_relative_path.SizeOf() + (unsigned int)(sizeof(*this) - sizeof(m_full_path) - sizeof(m_relative_path));
|
|
}
|
|
|
|
const ON_wString& ON_FileReference::FullPath() const
|
|
{
|
|
return m_full_path;
|
|
}
|
|
|
|
const wchar_t* ON_FileReference::FullPathAsPointer() const
|
|
{
|
|
return static_cast<const wchar_t*>(m_full_path);
|
|
}
|
|
|
|
void ON_FileReference::SetFullPath(
|
|
const wchar_t* full_path,
|
|
bool bSetContentHash
|
|
)
|
|
{
|
|
ON_wString local_full_path(full_path);
|
|
local_full_path.TrimLeftAndRight();
|
|
if (local_full_path.IsEmpty())
|
|
*this = ON_FileReference::Unset;
|
|
else
|
|
{
|
|
const ON_wString clean_full_path = ON_FileSystemPath::CleanPath(local_full_path);
|
|
const bool bFullPathChanged = (0 != ON_wString::CompareOrdinal(clean_full_path, m_full_path, false));
|
|
m_full_path = clean_full_path;
|
|
m_full_path_hash = ON_SHA1_Hash::EmptyContentHash;
|
|
m_embedded_file_id = ON_nil_uuid;
|
|
m_relative_path = ON_wString::EmptyString;
|
|
if (bSetContentHash)
|
|
{
|
|
if (m_content_hash.IsNotSet() || false == m_content_hash.EqualFileNameSizeAndTime(m_full_path))
|
|
m_content_hash = ON_ContentHash::CreateFromFile(m_full_path);
|
|
m_full_path_status
|
|
= (m_content_hash.IsSet())
|
|
? ON_FileReference::Status::FullPathValid
|
|
: ON_FileReference::Status::FileNotFound;
|
|
}
|
|
else if (bFullPathChanged)
|
|
{
|
|
m_content_hash = ON_ContentHash::Unset;
|
|
|
|
//https://mcneel.myjetbrains.com/youtrack/issue/RH-85181
|
|
//This used to check whether the file was there, eagerly, but this can be a big performance hit, especially on Google Drive.
|
|
//And the accessor for this property is basically never called.
|
|
m_full_path_status = ON_FileReference::Status::Unknown;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ON_FileReference::SetFullPath(
|
|
const char* full_path,
|
|
bool bSetContentHash
|
|
)
|
|
{
|
|
const ON_wString local_full_path(full_path);
|
|
SetFullPath(static_cast<const wchar_t*>(local_full_path),bSetContentHash);
|
|
}
|
|
|
|
|
|
|
|
void ON_FileReference::ClearFullPath()
|
|
{
|
|
m_full_path = ON_wString::EmptyString;
|
|
m_full_path_hash = ON_SHA1_Hash::EmptyContentHash;
|
|
m_embedded_file_id = ON_nil_uuid;
|
|
m_full_path_status = ON_FileReference::Status::Unknown;
|
|
}
|
|
|
|
const ON_wString& ON_FileReference::RelativePath() const
|
|
{
|
|
return m_relative_path;
|
|
}
|
|
|
|
const wchar_t* ON_FileReference::RelativePathAsPointer() const
|
|
{
|
|
return static_cast<const wchar_t*>(m_relative_path);
|
|
}
|
|
|
|
void ON_FileReference::SetRelativePath(
|
|
const wchar_t* relative_path
|
|
)
|
|
{
|
|
m_relative_path = relative_path;
|
|
m_relative_path.TrimLeftAndRight();
|
|
}
|
|
|
|
void ON_FileReference::SetRelativePath(
|
|
const char* relative_path
|
|
)
|
|
{
|
|
m_relative_path = relative_path;
|
|
m_relative_path.TrimLeftAndRight();
|
|
}
|
|
|
|
void ON_FileReference::SetRelativePathFromBasePath(
|
|
const wchar_t* base_path,
|
|
bool bBasePathContainsFileName
|
|
)
|
|
{
|
|
const ON_wString relative_path = ON_FileSystemPath::RelativePath(
|
|
m_full_path,
|
|
true,
|
|
base_path,
|
|
bBasePathContainsFileName
|
|
);
|
|
}
|
|
|
|
void ON_FileReference::SetRelativePathFromBasePath(
|
|
const char* base_path,
|
|
bool bBasePathContainsFileName
|
|
)
|
|
{
|
|
const ON_wString local_base_path(base_path);
|
|
SetRelativePathFromBasePath(static_cast<const wchar_t*>(local_base_path),bBasePathContainsFileName);
|
|
}
|
|
|
|
void ON_FileReference::ClearRelativePath()
|
|
{
|
|
m_relative_path = ON_wString::EmptyString;
|
|
}
|
|
|
|
const ON_ContentHash& ON_FileReference::ContentHash() const
|
|
{
|
|
return m_content_hash;
|
|
}
|
|
|
|
void ON_FileReference::SetContentHash(
|
|
ON_ContentHash content_hash
|
|
)
|
|
{
|
|
m_content_hash = content_hash;
|
|
}
|
|
|
|
void ON_FileReference::ClearContentHash()
|
|
{
|
|
m_content_hash = ON_ContentHash::Unset;
|
|
}
|
|
|
|
bool ON_FileReference::UpdateContentHash()
|
|
{
|
|
if (m_full_path.IsEmpty())
|
|
{
|
|
m_content_hash = ON_FileReference::Unset.ContentHash();
|
|
return true;
|
|
}
|
|
m_content_hash = ON_ContentHash::CreateFromFile(m_full_path);
|
|
m_recent_content_hash = m_content_hash;
|
|
return m_content_hash.IsSet();
|
|
}
|
|
|
|
const ON_ContentHash& ON_FileReference::RecentContentHash(
|
|
ON__UINT64 recent_time
|
|
) const
|
|
{
|
|
const ON__UINT64 current_time = ON_SecondsSinceJanOne1970UTC();
|
|
if (0 == recent_time || recent_time > current_time)
|
|
recent_time = current_time;
|
|
if (m_recent_content_hash.IsNotSet() || m_recent_content_hash.HashCalculationTime() < recent_time)
|
|
{
|
|
if (m_content_hash.IsSet() && m_content_hash.HashCalculationTime() >= recent_time)
|
|
m_recent_content_hash = m_content_hash;
|
|
else
|
|
m_recent_content_hash = ON_ContentHash::CreateFromFile(m_full_path);
|
|
}
|
|
return m_recent_content_hash;
|
|
}
|
|
|
|
const ON_SHA1_Hash& ON_FileReference::FullPathHash() const
|
|
{
|
|
if (m_full_path.IsNotEmpty() && m_full_path_hash == ON_SHA1_Hash::EmptyContentHash)
|
|
{
|
|
m_full_path_hash = ON_SHA1_Hash::FileSystemPathHash(m_full_path);
|
|
}
|
|
return m_full_path_hash;
|
|
}
|
|
|
|
ON_FileReference::Status ON_FileReference::FullPathStatus() const
|
|
{
|
|
return m_full_path_status;
|
|
}
|
|
|
|
void ON_FileReference::SetFullPathStatus(
|
|
ON_FileReference::Status full_path_status
|
|
)
|
|
{
|
|
m_full_path_status = full_path_status;
|
|
}
|
|
|
|
ON_UUID ON_FileReference::EmbeddedFileId() const
|
|
{
|
|
return m_embedded_file_id;
|
|
}
|
|
|
|
void ON_FileReference::SetEmbeddedFileId(
|
|
ON_UUID embedded_file_id
|
|
)
|
|
{
|
|
m_embedded_file_id = embedded_file_id;
|
|
}
|
|
|
|
// deprecated
|
|
bool ON_FileSystemPath::PathExists(
|
|
const char* path
|
|
)
|
|
{
|
|
return ON_FileSystem::PathExists(path);
|
|
}
|
|
|
|
// deprecated
|
|
bool ON_FileSystemPath::PathExists(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
return ON_FileSystem::PathExists(path);
|
|
}
|
|
|
|
// deprecated
|
|
bool ON_FileSystemPath::IsFile(
|
|
const char* path
|
|
)
|
|
{
|
|
return ON_FileSystem::IsFile(path);
|
|
}
|
|
|
|
// deprecated
|
|
bool ON_FileSystemPath::IsFile(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
return ON_FileSystem::IsFile(path);
|
|
}
|
|
|
|
// deprecated
|
|
bool ON_FileSystemPath::IsDirectory(
|
|
const char* path
|
|
)
|
|
{
|
|
return ON_FileSystem::IsDirectory(path);
|
|
}
|
|
|
|
// deprecated
|
|
bool ON_FileSystemPath::IsDirectory(
|
|
const wchar_t* path
|
|
)
|
|
{
|
|
return ON_FileSystem::IsDirectory(path);
|
|
}
|
|
|
|
// ON_UnicodeTextFile
|
|
|
|
class ON_File
|
|
{
|
|
public:
|
|
virtual ~ON_File() { }
|
|
|
|
bool Open(const wchar_t* filename, const wchar_t* mode)
|
|
{
|
|
_file = ON_FileStream::Open(filename, mode);
|
|
return nullptr != _file;
|
|
}
|
|
|
|
bool Close(void) const { return ON_FileStream::Close(_file) == 0; }
|
|
bool SeekFromCurrentPosition(ON__INT64 offset) const { return ON_FileStream::SeekFromCurrentPosition(_file, offset); }
|
|
bool SeekFromStart(ON__INT64 offset) const { return ON_FileStream::SeekFromStart(_file, offset); }
|
|
bool SeekFromEnd(ON__INT64 offset) const { return ON_FileStream::SeekFromEnd(_file, offset); }
|
|
bool Seek(ON__INT64 offset, int origin) const { return ON_FileStream::Seek(_file, offset, origin); }
|
|
ON__INT64 CurrentPosition(void) const { return ON_FileStream::CurrentPosition(_file); }
|
|
ON__UINT64 Read(ON__UINT64 count, void* buffer) const { return ON_FileStream::Read(_file, count, buffer); }
|
|
|
|
ON__UINT64 Write(ON__UINT64 count, const void* buffer) const
|
|
{
|
|
return ON_FileStream::Write(_file, count, buffer);
|
|
}
|
|
|
|
ON__UINT64 GetLength(void) const
|
|
{
|
|
const auto cur = CurrentPosition();
|
|
SeekFromEnd(0);
|
|
const auto end = CurrentPosition();
|
|
SeekFromStart(cur);
|
|
|
|
return end;
|
|
}
|
|
|
|
private:
|
|
FILE* _file = nullptr;
|
|
};
|
|
|
|
class ON_UnicodeTextFilePrivate final
|
|
{
|
|
public:
|
|
~ON_UnicodeTextFilePrivate() { Close(); }
|
|
|
|
bool Open(const wchar_t* wszFullPath, ON_UnicodeTextFile::Modes mode);
|
|
bool Close(void);
|
|
bool ReadString(ON_wString& s);
|
|
bool WriteString(const wchar_t* wsz);
|
|
bool ReadHeader(ON_UnicodeTextFile::Types& t);
|
|
bool WriteHeader(void);
|
|
bool ReadStringFromUTF8(ON_wString& s);
|
|
bool ReadStringFromUTF16(ON_wString& s);
|
|
bool WriteStringToUTF8(const wchar_t* wsz);
|
|
bool WriteStringToUTF16(const wchar_t* wsz);
|
|
size_t ReadData(void* buf, size_t bytes_to_read);
|
|
size_t WriteData(const void* buf, size_t bytes_to_write);
|
|
|
|
public:
|
|
ON_File _file;
|
|
ON_UnicodeTextFile::Types _type = ON_UnicodeTextFile::Types::Unknown;
|
|
};
|
|
|
|
size_t ON_UnicodeTextFilePrivate::ReadData(void* buf, size_t bytes_to_read)
|
|
{
|
|
return size_t(_file.Read(bytes_to_read, buf));
|
|
}
|
|
|
|
size_t ON_UnicodeTextFilePrivate::WriteData(const void* buf, size_t bytes_to_write)
|
|
{
|
|
return size_t(_file.Write(bytes_to_write, buf));
|
|
}
|
|
|
|
static const wchar_t* FileStreamMode(ON_UnicodeTextFile::Modes m)
|
|
{
|
|
if (m == ON_UnicodeTextFile::Modes::Read)
|
|
return L"rb";
|
|
|
|
if (m == ON_UnicodeTextFile::Modes::Write)
|
|
return L"wb";
|
|
|
|
ON_ASSERT(false);
|
|
return L"";
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::Open(const wchar_t* wszFullPath, ON_UnicodeTextFile::Modes mode)
|
|
{
|
|
bool ok = false;
|
|
int attemptsCounter = 0;
|
|
|
|
while (!ok && (attemptsCounter < 100))
|
|
{
|
|
if (_file.Open(wszFullPath, FileStreamMode(mode)))
|
|
{
|
|
ok = true;
|
|
}
|
|
else
|
|
{
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
attemptsCounter++;
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
if (ON_UnicodeTextFile::Modes::Write == mode)
|
|
{
|
|
ok = WriteHeader();
|
|
}
|
|
else
|
|
{
|
|
ok = ReadHeader(_type);
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::Close(void)
|
|
{
|
|
return _file.Close();
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::ReadHeader(ON_UnicodeTextFile::Types& t)
|
|
{
|
|
if (0 != _file.CurrentPosition())
|
|
return false;
|
|
|
|
ON__UINT8 pBuf[3] = { 0 };
|
|
|
|
if (2 != ReadData(pBuf, 2))
|
|
return false;
|
|
|
|
if (pBuf[0] == ON__UINT8(0xFF))
|
|
{
|
|
if (pBuf[1] == ON__UINT8(0xFE))
|
|
{
|
|
t = ON_UnicodeTextFile::Types::UTF16;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (pBuf[0] == ON__UINT8(0xEF))
|
|
{
|
|
if (pBuf[1] == ON__UINT8(0xBB))
|
|
{
|
|
if (1 == ReadData(pBuf + 2, 1))
|
|
{
|
|
if (pBuf[2] == ON__UINT8(0xBF))
|
|
{
|
|
t = ON_UnicodeTextFile::Types::UTF8;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// No BOM was found so rewind and assume UTF8. This allows testing with ASCII files.
|
|
_file.SeekFromStart(0);
|
|
t = ON_UnicodeTextFile::Types::UTF8;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::WriteHeader(void)
|
|
{
|
|
ON__UINT8 pBuf[3] = { 0 };
|
|
|
|
size_t sizeBOM = 2;
|
|
if (ON_UnicodeTextFile::Types::UTF8 == _type)
|
|
{
|
|
sizeBOM = 3;
|
|
pBuf[0] = ON__UINT8(0xEF);
|
|
pBuf[1] = ON__UINT8(0xBB);
|
|
pBuf[2] = ON__UINT8(0xBF);
|
|
}
|
|
else
|
|
if (ON_UnicodeTextFile::Types::UTF16 == _type)
|
|
{
|
|
pBuf[0] = ON__UINT8(0xFF);
|
|
pBuf[1] = ON__UINT8(0xFE);
|
|
}
|
|
else ON_ASSERT(false); // Did you forget to set the type in the constructor?
|
|
|
|
if (!WriteData(pBuf, sizeBOM))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::ReadString(ON_wString& s)
|
|
{
|
|
switch (_type)
|
|
{
|
|
case ON_UnicodeTextFile::Types::UTF8:
|
|
return ReadStringFromUTF8(s);
|
|
|
|
case ON_UnicodeTextFile::Types::UTF16:
|
|
return ReadStringFromUTF16(s);
|
|
|
|
case ON_UnicodeTextFile::Types::Unknown:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::WriteString(const wchar_t* wsz)
|
|
{
|
|
switch (_type)
|
|
{
|
|
case ON_UnicodeTextFile::Types::UTF8:
|
|
return WriteStringToUTF8(wsz);
|
|
|
|
case ON_UnicodeTextFile::Types::UTF16:
|
|
return WriteStringToUTF16(wsz);
|
|
|
|
case ON_UnicodeTextFile::Types::Unknown:
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::ReadStringFromUTF8(ON_wString& s)
|
|
{
|
|
const auto size_in_bytes = size_t(_file.GetLength() - _file.CurrentPosition());
|
|
auto p = std::unique_ptr<ON__UINT8[]>(new ON__UINT8[size_in_bytes + 1]);
|
|
auto* pBuffer = p.get();
|
|
|
|
ReadData(pBuffer, size_in_bytes);
|
|
pBuffer[size_in_bytes] = 0;
|
|
|
|
const char* pUTF8 = reinterpret_cast<const char*>(pBuffer);
|
|
|
|
const auto num_chars = ON_ConvertUTF8ToWideChar(false, pUTF8, -1, nullptr, 0, nullptr, 0, 0, nullptr);
|
|
|
|
auto* string_buf = s.SetLength(num_chars);
|
|
if (nullptr == string_buf)
|
|
return false;
|
|
|
|
ON_ConvertUTF8ToWideChar(false, pUTF8, -1, string_buf, num_chars+1, nullptr, 0, 0, nullptr);
|
|
|
|
return !s.IsEmpty();
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::ReadStringFromUTF16(ON_wString& s)
|
|
{
|
|
const auto char_size = sizeof(ON__UINT16);
|
|
const auto size_in_bytes = size_t(_file.GetLength() - _file.CurrentPosition());
|
|
const auto size_in_chars = size_t(size_in_bytes / char_size);
|
|
|
|
#ifdef ON_RUNTIME_WIN
|
|
// On Windows, wchar_t is UTF16 so we can load the file directly into the ON_wString.
|
|
ON_ASSERT(sizeof(wchar_t) == sizeof(ON__UINT16));
|
|
|
|
auto* buf = s.SetLength(size_in_chars);
|
|
if (nullptr == buf)
|
|
return false;
|
|
|
|
if (ReadData(buf, size_in_bytes) != size_in_bytes)
|
|
return false;
|
|
|
|
buf[size_in_chars] = 0;
|
|
#else
|
|
// On Mac wchar_t is UTF32 so we have to load the file into a buffer and then convert it to the ON_wString.
|
|
auto p = std::unique_ptr<ON__UINT16[]>(new ON__UINT16[size_in_chars + 1]);
|
|
auto* pUTF16 = p.get();
|
|
ReadData(pUTF16, size_in_bytes);
|
|
pUTF16[size_in_chars] = 0;
|
|
|
|
const auto num_chars = ON_ConvertUTF16ToUTF32(false, pUTF16, -1, nullptr, 0, nullptr, 0, 0, nullptr);
|
|
auto* string_buf = s.SetLength(num_chars);
|
|
if (nullptr == string_buf)
|
|
return false;
|
|
|
|
ON_ASSERT(sizeof(wchar_t) == sizeof(ON__UINT32));
|
|
auto* pWide = reinterpret_cast<ON__UINT32*>(string_buf);
|
|
ON_ConvertUTF16ToUTF32(false, pUTF16, -1, pWide, num_chars+1, nullptr, 0, 0, nullptr);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::WriteStringToUTF8(const wchar_t* wsz)
|
|
{
|
|
const auto num_chars = ON_ConvertWideCharToUTF8(false, wsz, -1, nullptr, 0, nullptr, 0, 0, nullptr);
|
|
|
|
auto p = std::unique_ptr<char[]>(new char[size_t(num_chars) + 1]);
|
|
auto* pBuffer = p.get();
|
|
|
|
ON_ConvertWideCharToUTF8(false, wsz, -1, pBuffer, num_chars + 1, nullptr, 0, 0, nullptr);
|
|
|
|
if (WriteData(pBuffer, num_chars) != num_chars)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ON_UnicodeTextFilePrivate::WriteStringToUTF16(const wchar_t* wsz)
|
|
{
|
|
#ifdef ON_RUNTIME_WIN
|
|
// On Windows, wchar_t is UTF16 so we can save the file directly from 'wsz'.
|
|
ON_ASSERT(sizeof(wchar_t) == sizeof(ON__UINT16));
|
|
|
|
const auto size_in_bytes = wcslen(wsz) * sizeof(wchar_t);
|
|
if (WriteData(wsz, size_in_bytes) != size_in_bytes)
|
|
return false;
|
|
#else
|
|
// On Mac wchar_t is UTF32 so we have to convert 'wsz' to UTF16 in a buffer and write the buffer to the file.
|
|
ON_ASSERT(sizeof(wchar_t) == sizeof(ON__UINT32));
|
|
auto* pWide = reinterpret_cast<const ON__UINT32*>(wsz);
|
|
|
|
const auto num_chars = ON_ConvertUTF32ToUTF16(false, pWide, -1, nullptr, 0, nullptr, 0, 0, nullptr);
|
|
|
|
const auto num_chars_inc_term = num_chars + 1;
|
|
auto p = std::unique_ptr<ON__UINT16[]>(new ON__UINT16[num_chars_inc_term]);
|
|
auto* pUTF16 = p.get();
|
|
|
|
ON_ConvertUTF32ToUTF16(false, pWide, -1, pUTF16, num_chars_inc_term, nullptr, 0, 0, nullptr);
|
|
|
|
const auto size_in_bytes = num_chars * sizeof(ON__UINT16);
|
|
if (WriteData(pUTF16, size_in_bytes) != size_in_bytes)
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
ON_UnicodeTextFile::ON_UnicodeTextFile(Types t)
|
|
{
|
|
_private = new ON_UnicodeTextFilePrivate;
|
|
_private->_type = t;
|
|
}
|
|
|
|
ON_UnicodeTextFile::~ON_UnicodeTextFile()
|
|
{
|
|
delete _private;
|
|
}
|
|
|
|
bool ON_UnicodeTextFile::Open(const wchar_t* wszFullPath, Modes mode)
|
|
{
|
|
return _private->Open(wszFullPath, mode);
|
|
}
|
|
|
|
bool ON_UnicodeTextFile::Close(void)
|
|
{
|
|
return _private->Close();
|
|
}
|
|
|
|
bool ON_UnicodeTextFile::ReadString(ON_wString& s)
|
|
{
|
|
return _private->ReadString(s);
|
|
}
|
|
|
|
bool ON_UnicodeTextFile::WriteString(const wchar_t* wsz)
|
|
{
|
|
return _private->WriteString(wsz);
|
|
}
|