diff --git a/include/lgi/common/LgiCommon.h b/include/lgi/common/LgiCommon.h
--- a/include/lgi/common/LgiCommon.h
+++ b/include/lgi/common/LgiCommon.h
@@ -1,417 +1,413 @@
/**
\file
\author Matthew Allen
\date 27/3/2000
\brief Common LGI include
Copyright (C) 2000-2004, Matthew Allen
*/
/**
* \defgroup Base Foundation tools
* \ingroup Lgi
*/
/**
* \defgroup Text Text handling
* \ingroup Lgi
*/
#ifndef _LGI_COMMON_H
#define _LGI_COMMON_H
#if defined LINUX
#include
#endif
#include "lgi/common/Mem.h"
#include "lgi/common/Array.h"
#include "lgi/common/LgiString.h"
#include "lgi/common/StringClass.h"
#include "lgi/common/CurrentTime.h"
#include "lgi/common/LgiUiBase.h"
/// Returns the system path specified
/// \ingroup Base
LgiExtern LString LGetSystemPath(
/// Which path to retreive
LSystemPath Which,
int WordSize = 0
);
/// Gets the path of the currently running executable
/// \ingroup Base
LgiExtern LString LGetExePath();
/// Gets the file of the currently running executable
/// \ingroup Base
LgiExtern LString LGetExeFile();
/// Returns the mime type of the file
/// \ingroup Mime
LgiExtern LString LGetFileMimeType
(
/// File to find mime type for
const char *File
);
/// Finds a file in the applications directory or nearby
/// \ingroup Base
LgiExtern LString LFindFile(const char *Name);
/// Returns the application associated with the mime type
/// \ingroup Mime
LgiExtern LString LGetAppForMimeType
(
/// Type of the file to find and app for
const char *Mime
);
/// \return a formatted file size
LgiExtern LString LFormatSize(int64_t Size);
/// URL encode a string
LgiExtern LString LUrlEncode(const char *s, const char *delim);
/// URL decode a string
LgiExtern LString LUrlDecode(const char *s);
/// Gets the current user
LgiExtern LString LCurrentUserName();
/// Returns an environment variable.
LgiExtern LString LGetEnv(const char *Var);
/// Gets the system path..
LgiExtern LString::Array LGetPath();
/// Check for a valid email string
LgiExtern bool LIsValidEmail(LString Email);
/// Finds an application to handle a protocol request (e.g. 'mailto', 'http' etc)
LgiExtern LString LGetAppForProtocol(const char *Protocol);
/// Converts a string to the native 8bit charset of the OS from utf-8
/// \ingroup Text
LgiExtern LString LToNativeCp(const char *In, ssize_t InLen = -1);
/// Converts a string from the native 8bit charset of the OS to utf-8
/// \ingroup Text
LgiExtern LString LFromNativeCp(const char *In, ssize_t InLen = -1);
LgiExtern LString LStrConvertCp
(
/// Output charset
const char *OutCp,
/// Input buffer
const void *In,
/// The input data's charset
const char *InCp,
/// Bytes of valid data in the input
ssize_t InLen = -1
);
#ifdef __cplusplus
extern "C"
{
#endif
/////////////////////////////////////////////////////////////
// Externs
// Codepages
/// Converts a buffer of text to a different charset
/// \ingroup Text
/// \returns the bytes written to the location pointed to by 'Out'
LgiFunc ssize_t LBufConvertCp(void *Out, const char *OutCp, ssize_t OutLen, const void *&In, const char *InCp, ssize_t &InLen);
/// \brief Converts a string to a new charset
/// \return A dynamically allocate, null terminated string in the new charset
/// \ingroup Text
LgiFunc void *LNewConvertCp
(
/// Output charset
const char *OutCharset,
/// Input buffer
const void *In,
/// The input data's charset
const char *InCharset,
/// Bytes of valid data in the input
ssize_t InLen = -1
);
/// Return true if Lgi support the charset
/// \ingroup Text
LgiFunc bool LIsCpImplemented(const char *Cp);
/// Converts the ANSI code page to a charset name
/// \ingroup Text
LgiFunc const char *LAnsiToLgiCp(int AnsiCodePage = -1);
/// Calculate the number of characters in a string
/// \ingroup Text
LgiFunc int LCharLen(const void *Str, const char *Cp, int Bytes = -1);
/// Move a pointer along a utf-8 string by characters
/// \ingroup Text
LgiFunc char *LSeekUtf8
(
/// Pointer to the current character
const char *Ptr,
/// The number of characters to move forward or back
ssize_t D,
/// The start of the memory buffer if you known
char *Start = 0
);
-/// Return true if the string is valid utf-8
-/// \ingroup Text
-LgiFunc bool LIsUtf8(const char *s, ssize_t len = -1);
-
/// Returns the next token in a string, leaving the argument pointing to the end of the token
/// \ingroup Text
LgiFunc char *LTokStr(const char *&s);
/// Formats a data size into appropriate units
/// \ingroup Base
LgiFunc void LFormatSize
(
/// Output string
char *Str,
/// Output string buffer length
int SLen,
/// Input size in bytes
int64_t Size
);
/// \returns true if the path is a volume root.
LgiFunc bool LIsVolumeRoot(const char *Path);
/// Converts a string from URI encoding (ala %20 -> ' ')
/// \returns a dynamically allocated string or NULL on error
/// \ingroup Text
LgiFunc char *LDecodeUri
(
/// The URI
const char *uri,
/// The length or -1 if NULL terminated
int len = -1
);
/// Converts a string to URI encoding (ala %20 -> ' ')
/// \returns a dynamically allocated string or NULL on error
/// \ingroup Text
LgiFunc char *LEncodeUri
(
/// The URI
const char *uri,
/// The length or -1 if NULL terminated
int len = -1
);
// Path
#if LGI_COCOA || defined(__GTK_H__) || defined(HAIKU)
LgiExtern LString LgiArgsAppPath;
#endif
/// Returns the system path specified
/// \ingroup Base
LgiFunc bool LGetSystemPath
(
/// Which path to retreive
LSystemPath Which,
/// The buffer to receive the path into
char *Dst,
/// The size of the receive buffer in bytes
ssize_t DstSize
);
/// \brief Recursively search for files
/// \return Non zero if something was found
/// \ingroup Base
LgiFunc bool LRecursiveFileSearch
(
/// Start search in this dir
const char *Root,
/// Extensions to match
LArray *Ext = NULL,
/// [optional] Output filenames
LArray *Files = NULL,
/// [optional] Output total size
uint64 *Size = NULL,
/// [optional] File count
uint64 *Count = NULL,
/// [optional] Callback for match
std::function Callback = NULL,
/// [optional] Cancel object
LCancel *Cancel = NULL
);
// Resources
/// Gets the currently selected language
/// \ingroup Resources
LgiFunc struct LLanguage *LGetLanguageId();
// Os version functions
/// Gets the current operating system and optionally it's version.
/// \returns One of the defines starting with #LGI_OS_UNKNOWN in LgiDefs.h
/// \ingroup Base
LgiFunc int LGetOs(LArray *Ver = 0);
/// Gets the current operation systems name.
/// \ingroup Base
LgiFunc const char *LGetOsName();
// System
/// \brief Opens a file or directory.
///
/// If the input is an executable then it is run. If the input file
/// is a document then an appropriate application is found to open the
/// file and the file is passed to that application. If the input is
/// a directory then the OS's file manager is openned to browse the
/// directory.
///
/// \ingroup Base
LgiFunc bool LExecute
(
/// The file to open
const char *File,
/// The arguments to pass to the program
const char *Arguments="",
/// The directory to run in
const char *Dir = NULL,
/// An error message
LString *ErrorMsg = NULL
);
/// Initializes the random number generator
/// \ingroup Base
LgiFunc void LRandomize(uint Seed);
/// Returns a random number between 0 and Max-1
/// \ingroup Base
LgiFunc uint LRand(uint Max = 0);
LgiFunc bool _lgi_read_colour_config(const char *Tag, uint32_t *c);
#ifndef SND_ASYNC
#define SND_ASYNC 0x0001
#endif
/// Plays a sound
/// \ingroup Base
LgiFunc bool LPlaySound
(
/// File name of the sound to play
const char *FileName,
/// 0 or SND_ASYNC. If 0 the function blocks till the sound finishes.
int Flags = 0
);
/**
* \defgroup Mime Mime handling support.
* \ingroup Lgi
*/
/// Returns the file extensions associated with the mimetype
/// \ingroup Mime
LgiExtern bool LGetMimeTypeExtensions
(
/// The returned mime type
const char *Mime,
/// The extensions
LArray &Ext
);
inline bool LGetAppForMimeType(const char *Mime, char *AppPath, int BufSize)
{
LString p = LGetAppForMimeType(Mime);
if (AppPath && p) strcpy_s(AppPath, BufSize, p);
return p.Length() > 0;
}
/// Returns the all applications that can open a given mime type.
/// \ingroup Mime
LgiFunc bool LGetAppsForMimeType
(
/// The type of files to match apps to.
///
/// Two special cases exist:
/// - application/email gets the default email client
/// - application/browser get the default web browser
const char *Mime,
/// The applications that can handle the
LArray &Apps,
/// Limit the length of the results, i.e. stop looking after 'Limit' matches.
/// -1 means return all matches.
int Limit = -1
);
// Debug
/// Returns true if the build is for release.
/// \ingroup Base
LgiFunc int LIsReleaseBuild();
#if defined WIN32
/// Registers an active x control
LgiFunc bool RegisterActiveXControl(const char *Dll);
enum HWBRK_TYPE
{
HWBRK_TYPE_CODE,
HWBRK_TYPE_READWRITE,
HWBRK_TYPE_WRITE,
};
enum HWBRK_SIZE
{
HWBRK_SIZE_1,
HWBRK_SIZE_2,
HWBRK_SIZE_4,
HWBRK_SIZE_8,
};
/// Set a hardware breakpoint.
LgiFunc HANDLE SetHardwareBreakpoint
(
/// Use GetCurrentThread()
HANDLE hThread,
/// Type of breakpoint
HWBRK_TYPE Type,
/// Size of breakpoint
HWBRK_SIZE Size,
/// The pointer to the data to break on
void *s
);
/// Deletes a hardware breakpoint
LgiFunc bool RemoveHardwareBreakpoint(HANDLE hBrk);
#elif defined LINUX
/// Window managers
enum WindowManager
{
WM_Unknown,
WM_Kde,
WM_Gnome
};
/// Returns the currently running window manager
WindowManager LGetWindowManager();
#elif defined(__OBJC__)
LgiFunc NSCursor *LCocoaCursor(LCursor lc);
#endif
#ifdef __cplusplus
}
#endif
#endif
diff --git a/include/lgi/common/Mail.h b/include/lgi/common/Mail.h
--- a/include/lgi/common/Mail.h
+++ b/include/lgi/common/Mail.h
@@ -1,829 +1,831 @@
/** \file
\author Matthew Allen
*/
#ifndef __MAIL_H
#define __MAIL_H
#include
#include "lgi/common/Net.h"
#include "lgi/common/Base64.h"
#include "lgi/common/Progress.h"
#include "lgi/common/Variant.h"
#include "lgi/common/OAuth2.h"
#include "lgi/common/Store3Defs.h"
#ifndef GPL_COMPATIBLE
#define GPL_COMPATIBLE 0
#endif
// Defines
#define MAX_LINE_SIZE 1024
#define MAX_NAME_SIZE 64
#define EMAIL_LINE_SIZE 76
// #define IsDigit(c) ((c) >= '0' AND (c) <= '9')
// Mail logging defines
#define MAIL_SEND_COLOUR Rgb24(0, 0, 0xff)
#define MAIL_RECEIVE_COLOUR Rgb24(0, 0x8f, 0)
#define MAIL_ERROR_COLOUR Rgb24(0xff, 0, 0)
#define MAIL_WARNING_COLOUR Rgb24(0xff, 0x7f, 0)
#define MAIL_INFO_COLOUR Rgb24(0, 0, 0)
// Helper functions
-extern void TokeniseStrList(char *Str, List &Output, const char *Delim);
+extern void TokeniseStrList(const char *Str, LString::Array &Output, const char *Delim);
extern char ConvHexToBin(char c);
#define ConvBinToHex(i) (((i)<10)?'0'+(i):'A'+(i)-10)
extern void DecodeAddrName(const char *Start, std::function cb, const char *DefaultDomain);
extern void DecodeAddrName(const char *Start, LAutoString &Name, LAutoString &Addr, const char *DefaultDomain);
extern void DecodeAddrName(const char *Start, LString &Name, LString &Addr, const char *DefaultDomain);
extern int MaxLineLen(char *Text);
extern char *EncodeImapString(const char *s);
extern char *DecodeImapString(const char *s);
extern bool UnBase64Str(LString &s);
extern bool Base64Str(LString &s);
extern const char *sTextPlain;
extern const char *sTextHtml;
extern const char *sTextXml;
extern const char *sApplicationInternetExplorer;
extern const char sMultipartMixed[];
extern const char sMultipartEncrypted[];
extern const char sMultipartSigned[];
extern const char sMultipartAlternative[];
extern const char sMultipartRelated[];
extern const char sAppOctetStream[];
// Classes
class MailProtocol;
struct MailProtocolError
{
int Code = 0;
LString ErrMsg;
};
class MailProtocolProgress
{
public:
uint64 Start = 0;
ssize_t Value = 0;
ssize_t Range = 0;
MailProtocolProgress()
{
}
void Empty()
{
Start = 0;
Value = 0;
Range = 0;
}
void StartTransfer(ssize_t Size)
{
Start = LCurrentTime();
Value = 0;
Range = Size;
}
};
class LogEntry
{
LColour c;
public:
LArray Txt;
LogEntry(LColour col);
LColour GetColour() { return c; }
bool Add(const char *t, ssize_t len = -1);
};
/// Attachment descriptor
class FileDescriptor : public LBase
{
protected:
// Global
int64 Size;
char *MimeType;
char *ContentId;
// Read from file
LFile File;
LStreamI *Embeded;
bool OwnEmbeded;
int64 Offset;
LMutex *Lock;
// Write to memory
uchar *Data;
LAutoPtr DataStream;
public:
FileDescriptor(LStreamI *embed, int64 Offset, int64 Size, char *Name);
FileDescriptor(char *name);
FileDescriptor(char *data, int64 len);
FileDescriptor();
~FileDescriptor();
void SetLock(LMutex *l);
LMutex *GetLock();
void SetOwnEmbeded(bool i);
// Access functions
LStreamI *GotoObject(); // Get data to read
uchar *GetData(); // Get data from write
int Sizeof();
char *GetMimeType() { return MimeType; }
void SetMimeType(char *s) { DeleteArray(MimeType); MimeType = NewStr(s); }
char *GetContentId() { return ContentId; }
void SetContentId(char *s) { DeleteArray(ContentId); ContentId = NewStr(s); }
// Decode MIME data to memory
bool Decode(char *ContentType,
char *ContentTransferEncoding,
char *MimeData,
int MimeDataLength);
};
/// Address descriptor
class AddressDescriptor
{
public:
uint8_t Status = false;
EmailAddressType CC = MAIL_ADDR_TO;
LString sName;
LString sAddr;
AddressDescriptor(const AddressDescriptor *Copy = NULL);
virtual ~AddressDescriptor();
void _Delete();
LString Print();
};
/// Base class for mail protocol implementations
class MailProtocol
{
protected:
char Buffer[4<<10] = "";
LMutex SocketLock;
LAutoPtr Socket;
LOAuth2::Params OAuth2;
LDom *SettingStore = NULL;
bool Error(const char *file, int line, const char *msg, ...);
bool Read();
bool Write(const char *Buf = NULL, bool Log = false);
virtual void OnUserMessage(char *Str) {}
public:
// Logging
LStreamI *Logger = NULL;
void Log(const char *Str, LSocketI::SocketMsgType type);
// Task Progress
MailProtocolProgress *Items = NULL;
MailProtocolProgress *Transfer = NULL;
// Settings
int ErrMsgId = 0; /// \sa #L_ERROR_ESMTP_NO_AUTHS, #L_ERROR_ESMTP_UNSUPPORTED_AUTHS
LString ErrMsgFmt; /// The format for the printf
LString ErrMsgParam; /// The arguments for the printf
LString ProgramName;
LString ExtraOutgoingHeaders;
LString::Array CharsetPrefs;
// Object
MailProtocol();
virtual ~MailProtocol();
// Methods
void SetOAuthParams(LOAuth2::Params &p) { OAuth2 = p; }
void SetSettingStore(LDom *store) { SettingStore = store; }
/// Thread safe hard close (quit now)
bool CloseSocket()
{
LMutex::Auto l(&SocketLock, _FL);
if (Socket != NULL)
return Socket->Close() != 0;
return false;
}
void SetError(int ResourceId, const char *Fmt, const char *Param = NULL)
{
ErrMsgId = ResourceId;
ErrMsgFmt = Fmt;
ErrMsgParam = Param;
}
};
/////////////////////////////////////////////////////////////////////
// Mail IO parent classes
/// Enable STARTTLS support (requires an SSL capable socket)
#define MAIL_USE_STARTTLS 0x01
/// Use authentication
#define MAIL_USE_AUTH 0x02
/// Force the use of PLAIN type authentication
#define MAIL_USE_PLAIN 0x04
/// Force the use of LOGIN type authentication
#define MAIL_USE_LOGIN 0x08
/// Force the use of NTLM type authentication
#define MAIL_USE_NTLM 0x10
/// Secure auth
#define MAIL_SECURE_AUTH 0x20
/// Use SSL
#define MAIL_SSL 0x40
/// OAUTH2
#define MAIL_USE_OAUTH2 0x80
/// CRAM-MD5
#define MAIL_USE_CRAM_MD5 0x100
/// Mail sending protocol
class MailSink : public MailProtocol
{
public:
/// Connection setup/shutdown
virtual bool Open
(
/// The transport layer to use
LSocketI *S,
/// The host to connect to
const char *RemoteHost,
/// The local domain
const char *LocalDomain,
/// The sink username (or NULL)
const char *UserName,
/// The sink password (or NULL)
const char *Password,
/// The port to connect with or 0 for default.
int Port,
/// Options: Use any of #MAIL_SSL, #MAIL_USE_STARTTLS, #MAIL_SECURE_AUTH, #MAIL_USE_PLAIN, #MAIL_USE_LOGIN etc or'd together.
int Flags
) = 0;
/// Close the connection
virtual bool Close() = 0;
// Commands available while connected
/// Write the email's contents into the LStringPipe returned from
/// SendStart and then call SendEnd to finish the transaction
virtual LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = NULL) = 0;
/// Finishes the mail send
virtual bool SendEnd(LStringPipe *Sink) = 0;
};
struct ImapMailFlags
{
union
{
struct
{
uint8_t ImapAnswered : 1;
uint8_t ImapDeleted : 1;
uint8_t ImapDraft : 1;
uint8_t ImapFlagged : 1;
uint8_t ImapRecent : 1;
uint8_t ImapSeen : 1;
uint8_t ImapExpunged :1;
};
uint16 All = 0;
};
ImapMailFlags(const char *init = NULL)
{
if (init)
Set(init);
}
LString Get()
{
char s[256] = "";
int ch = 0;
if (ImapAnswered) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\answered ");
if (ImapDeleted) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\deleted ");
if (ImapDraft) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\draft ");
if (ImapFlagged) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\flagged ");
if (ImapRecent) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\recent ");
if (ImapSeen) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\seen ");
if (ch == 0)
return LString();
LAssert(ch < sizeof(s));
s[--ch] = 0;
return s;
}
void Set(const char *s)
{
All = 0;
if (!s) s = "";
while (*s)
{
if (*s == '/' || *s == '\\')
{
while (*s == '/' || *s == '\\')
s++;
const char *e = s;
while (*e && isalpha(*e))
e++;
if (!_strnicmp(s, "answered", e-s))
ImapAnswered = true;
else if (!_strnicmp(s, "deleted", e-s))
ImapDeleted = true;
else if (!_strnicmp(s, "draft", e-s))
ImapDraft = true;
else if (!_strnicmp(s, "flagged", e-s))
ImapFlagged = true;
else if (!_strnicmp(s, "recent", e-s))
ImapRecent = true;
else if (!_strnicmp(s, "seen", e-s))
ImapSeen = true;
s = e;
}
else s++;
}
}
ImapMailFlags &operator =(const ImapMailFlags &f)
{
All = f.All;
return *this;
}
bool operator ==(ImapMailFlags &f) const
{
return All == f.All;
}
bool operator !=(ImapMailFlags &f) const
{
return All != f.All;
}
};
/// A bulk mail handling class
class MailTransaction
{
public:
/// The index of the mail in the folder
int Index;
/// \sa #MAIL_POSTED_TO_GUI, #MAIL_EXPLICIT
int Flags;
// bool Delete;
bool Status;
bool Oversize;
/// The mail protocol handler writes the email to this stream
LStreamI *Stream;
/// Flags used on the IMAP protocolf
ImapMailFlags Imap;
/// The user app can use this for whatever
void *UserData;
MailTransaction();
~MailTransaction();
};
/// Return code from MailSrcCallback
enum MailSrcStatus
{
/// Download the whole email
DownloadAll,
/// Download just the top part
DownloadTop,
/// Skip this email
DownloadNone,
/// About the whole receive
DownloadAbort
};
/// The callback function used by MailSource::Receive
typedef MailSrcStatus (*MailSrcCallback)
(
/// The currently executing transaction
MailTransaction *Trans,
/// The size of the email about to be downloaded
uint64 Size,
/// If DownloadTop is returned, you can set the number of lines to retreive here
int *LinesToDownload,
/// The data cookie passed into MailSource::Receive
void *Data
);
/// The callback function used by MailSource::Receive
typedef bool (*MailReceivedCallback)
(
/// The currently executing transaction
MailTransaction *Trans,
/// The data cookie passed into MailSource::Receive
void *Data
);
/// Collection of callbacks called during mail receive. You should zero this
/// entire object before using it. Because if someone adds new callbacks after
/// you write the calling code you wouldn't want to leave some callbacks un-
/// initialized. A NULL callback is ignored.
struct MailCallbacks
{
/// The callback data
void *CallbackData;
/// Called before receiving mail
MailSrcCallback OnSrc;
/// Called after mail received
MailReceivedCallback OnReceive;
};
/// A generic mail source object
class MailSource : public MailProtocol
{
public:
/// Opens a connection to the server
virtual bool Open
(
/// The transport socket
LSocketI *S,
/// The hostname or IP of the server
const char *RemoteHost,
/// The port on the host to connect to
int Port,
/// The username for authentication
const char *User,
/// The password for authentication
const char *Password,
/// [Optional] Persistant storage of settings
LDom *SettingStore,
/// [Optional] Flags: #MAIL_SOURCE_STARTTLS, #MAIL_SOURCE_AUTH, #MAIL_SOURCE_USE_PLAIN, #MAIL_SOURCE_USE_LOGIN
int Flags = 0) = 0;
/// Closes the connection
virtual bool Close() = 0;
/// Returns the number of messages available on the server
virtual ssize_t GetMessages() = 0;
/// Receives a list of messages from the server.
virtual bool Receive
(
/// An array of messages to receive. The MailTransaction objects contains the index of the message to receive
/// and various status values returned after the operation.
LArray &Trans,
/// An optional set of callback functions.
MailCallbacks *Callbacks = 0
) = 0;
/// Deletes a message on the server
virtual bool Delete(int Message) = 0;
/// Gets the size of the message on the server
virtual int Sizeof(int Message) = 0;
/// Gets the size of all the messages on the server
virtual bool GetSizes(LArray &Sizes) { return false; }
/// Gets the unique identifier of the message
virtual bool GetUid(int Message, char *Id, int IdLen) = 0;
/// Gets the unique identifiers of a list of messages
virtual bool GetUidList(LString::Array &Id) = 0;
/// Gets the headers associated with a given message
virtual LString GetHeaders(int Message) = 0;
/// Sets the proxy server. e.g. HTTP mail.
virtual void SetProxy(char *Server, int Port) {}
};
/////////////////////////////////////////////////////////////////////
// Mail IO implementations
/// SMTP implementation
class MailSmtp : public MailSink
{
protected:
bool ReadReply(const char *Str, LStringPipe *Pipe = NULL, MailProtocolError *Err = NULL);
bool WriteText(const char *Str);
public:
MailSmtp();
~MailSmtp();
bool Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0);
bool Close();
bool SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err = NULL);
LStringPipe *SendData(MailProtocolError *Err = NULL);
LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = NULL);
bool SendEnd(LStringPipe *Sink);
// bool Send(MailMessage *Msg, bool Mime = false);
};
class MailSendFolder : public MailSink
{
class MailPostFolderPrivate *d;
public:
MailSendFolder(char *Path);
~MailSendFolder();
bool Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0);
bool Close();
LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = NULL);
bool SendEnd(LStringPipe *Sink);
};
class MailPop3 : public MailSource
{
protected:
bool ReadReply();
LString ReadMultiLineReply();
int GetInt();
bool MailIsEnd(LString &s);
bool ListCmd(const char *Cmd, LHashTbl, bool> &Results);
const char *End;
const char *Marker;
int Messages;
public:
MailPop3();
~MailPop3();
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override;
// Commands available while connected
ssize_t GetMessages() override;
bool Receive(LArray &Trans, MailCallbacks *Callbacks = NULL) override;
bool Delete(int Message) override;
int Sizeof(int Message) override;
bool GetSizes(LArray &Sizes) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
};
class MailReceiveFolder : public MailSource
{
protected:
class MailReceiveFolderPrivate *d;
public:
MailReceiveFolder(char *Path);
~MailReceiveFolder();
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override;
// Commands available while connected
ssize_t GetMessages() override;
bool Receive(LArray &Trans, MailCallbacks *Callbacks = NULL) override;
bool Delete(int Message) override;
int Sizeof(int Message) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
};
class MailPhp : public MailSource
{
protected:
class MailPhpPrivate *d;
bool Get(LSocketI *S, char *Uri, LStream &Out, bool ChopDot);
public:
MailPhp();
~MailPhp();
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override;
// Commands available while connected
ssize_t GetMessages() override;
bool Receive(LArray &Trans, MailCallbacks *Callbacks = NULL) override;
bool Delete(int Message) override;
int Sizeof(int Message) override;
bool GetSizes(LArray &Sizes) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
void SetProxy(char *Server, int Port) override;
};
class MailImapFolder
{
friend class MailIMap;
friend struct ImapThreadPrivate;
char Sep;
char *Path;
public:
bool NoSelect;
bool NoInferiors;
bool Marked;
int Exists;
int Recent;
int Deleted;
// int UnseenIndex;
MailImapFolder();
virtual ~MailImapFolder();
char *GetPath();
void SetPath(const char *s);
char *GetName();
void SetName(const char *s);
char GetSep() { return Sep; }
void operator =(LHashTbl,int> &v);
};
class MailIMap : public MailSource
{
protected:
class MailIMapPrivate *d;
char Buf[2048];
List Uid;
LStringPipe ReadBuf;
List Dialog;
void ClearDialog();
void ClearUid();
bool FillUidList();
bool WriteBuf(bool ObsurePass = false, const char *Buffer = NULL, bool Continuation = false);
bool ReadResponse(int Cmd = -1, bool Plus = false);
bool Read(LStreamI *Out = NULL, int Timeout = -1);
bool ReadLine();
bool IsResponse(const char *Buf, int Cmd, bool &Ok);
void CommandFinished();
public:
typedef LHashTbl,LString> StrMap;
struct StrRange
{
ssize_t Start, End;
void Set(ssize_t s, ssize_t e)
{
Start = s;
End = e;
}
ssize_t Len() { return End - Start; }
};
// Typedefs
struct Untagged
{
LString Cmd;
LString Param;
int Id;
};
/// This callback is used to notify the application using this object of IMAP fetch responses.
/// \returns true if the application wants to continue reading and has taken ownership of the strings in "Parts".
typedef bool (*FetchCallback)
(
/// The IMAP object
class MailIMap *Imap,
/// The message sequence number
uint32_t Msg,
/// The fetch parts (which the callee needs to own if returning true)
StrMap &Parts,
/// The user data passed to the Fetch function
void *UserData
);
// Object
MailIMap();
~MailIMap();
// Mutex
bool Lock(const char *file, int line);
bool LockWithTimeout(int Timeout, const char *file, int line);
void Unlock();
// General
char GetFolderSep();
char *EncodePath(const char *Path);
char *GetCurrentPath();
bool GetExpungeOnExit();
void SetExpungeOnExit(bool b);
bool ServerOption(char *Opt);
bool IsOnline();
const char *GetWebLoginUri();
void SetParentWindow(LViewI *wnd);
void SetCancel(LCancel *Cancel);
ssize_t ParseImapResponse(char *Buffer, ssize_t BufferLen, LArray &Ranges, int Names);
// Connection
bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override;
bool Close() override; // Non-threadsafe soft close (normal operation)
bool GetCapabilities(LArray &s);
// Commands available while connected
bool Receive(LArray &Trans, MailCallbacks *Callbacks = NULL) override;
ssize_t GetMessages() override;
bool Delete(int Message) override;
bool Delete(bool ByUid, const char *Seq);
int Sizeof(int Message) override;
bool GetSizes(LArray &Sizes) override;
bool GetUid(int Message, char *Id, int IdLen) override;
bool GetUidList(LString::Array &Id) override;
LString GetHeaders(int Message) override;
char *SequenceToString(LArray *Seq);
// Imap specific commands
/// This method wraps the imap FETCH command
/// \returns the number of records accepted by the callback fn
int Fetch
(
/// True if 'Seq' is a UID, otherwise it's a sequence
bool ByUid,
/// The sequence number or UID
const char *Seq,
/// The parts to retrieve
const char *Parts,
/// Data is returned to the caller via this callback function
FetchCallback Callback,
/// A user defined param to pass back to the 'Callback' function.
void *UserData,
/// [Optional] The raw data received will be written to this stream if provided, else NULL.
LStreamI *RawCopy = NULL,
/// [Optional] The rough size of the fetch... used to pre-allocate a buffer to receive data.
int64 SizeHint = -1,
/// [Optional] An error object
LError *Error = NULL
);
/// Appends a message to the specified folder
bool Append
(
/// The folder to write to
const char *Folder,
/// [Optional] Flags for the message
ImapMailFlags *Flags,
/// The rfc822 body of the message
const char *Msg,
/// [Out] The UID of the message appended (if known, can be empty if not known)
LString &NewUid
);
bool GetFolders(LArray &Folders);
bool SelectFolder(const char *Path, StrMap *Values = NULL);
char *GetSelectedFolder();
int GetMessages(const char *Path);
bool CreateFolder(MailImapFolder *f);
bool DeleteFolder(const char *Path);
bool RenameFolder(const char *From, const char *To);
bool SetFolderFlags(MailImapFolder *f);
/// Expunges (final delete) any deleted messages the current folder.
bool ExpungeFolder();
// Uid methods
bool CopyByUid(LArray &InUids, const char *DestFolder);
bool SetFlagsByUid(LArray &Uids, const char *Flags);
/// Idle processing...
/// \returns true if something happened
bool StartIdle();
// bool OnIdle(int Timeout, LArray &Resp);
bool OnIdle(int Timeout, LString::Array &Resp);
bool FinishIdle();
bool Poll(int *Recent = NULL, LArray *New = NULL);
bool Status(char *Path, int *Recent);
bool Search(bool Uids, LArray &SeqNumbers, const char *Filter);
+ #if 0
// Utility
static bool Http(LSocketI *S,
LAutoString *OutHeaders,
LAutoString *OutBody,
int *StatusCode,
const char *InMethod,
const char *InUri,
const char *InHeaders,
const char *InBody);
+ #endif
};
#endif
diff --git a/include/lgi/common/Store3.h b/include/lgi/common/Store3.h
--- a/include/lgi/common/Store3.h
+++ b/include/lgi/common/Store3.h
@@ -1,733 +1,734 @@
/// \file
/// \author Matthew Allen, fret@memecode.com
#ifndef _MAIL_STORE_H_
#define _MAIL_STORE_H_
#include
#include "Mail.h"
#include "Store3Defs.h"
#undef GetObject
/*
Handling of attachments in the Store3 API
-----------------------------------------
Given the mail object ptr:
LDataI *m;
Query that the mail for it's root mime segment:
LDataI *Seg = dynamic_cast(m->GetObj(FIELD_MIME_SEG));
Query a seg for it's children:
auto Children = Seg->GetList(FIELD_MIME_SEG);
auto FirstChild = Children->First();
Access segment's charset and mimetype:
char *Charset = Seg->GetStr(FIELD_CHARSET);
char *MimeType = Seg->GetStr(FIELD_MIME_TYPE);
Get/Set the segment for it's content:
GAutoStreamI Data = Seg->GetStream(_FL); // get
Seg->SetStream(new MyStream(Data)); // set
Delete an mime segment:
Seg->Delete();
Add a new segment somewhere in the tree, including reparenting
it to another segments or mail object, even if the target parent it
not attached yet:
NewSeg->Save(ParentSeg);
*/
#include "LgiInterfaces.h"
#include "lgi/common/Mime.h"
#include "lgi/common/OptionsFile.h"
#include "lgi/common/Variant.h"
class LDataI;
class LDataFolderI;
class LDataStoreI;
class LDataPropI;
typedef LAutoPtr LAutoStreamI;
LString::Array ParseIdList(const char *In);
extern const char *Store3ItemTypeToMime(Store3ItemTypes type);
/// A storage event
/// a = StoreId
/// b = (void*)UserParam
/// \sa LDataEventsI::Post
#define M_STORAGE_EVENT (M_USER+0x500)
/// The storage class has this property (positive properties are owned by the app
#define FIELD_IS_ONLINE -100
#define FIELD_PROFILE_IMAP_LISTING -101
#define FIELD_PROFILE_IMAP_SELECT -102
#define LDATA_INT32_PROP(name, id) \
int32 Get##name() { return GetObject() ? (int32)GetObject()->GetInt(id) : OnError(_FL); } \
bool Set##name(int32 val) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); }
#define LDATA_INT64_PROP(name, id) \
int64 Get##name() { return GetObject() ? GetObject()->GetInt(id) : OnError(_FL); } \
bool Set##name(int64 val) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); }
#define LDATA_ENUM_PROP(name, id, type) \
type Get##name() { return (type) (GetObject() ? GetObject()->GetInt(id) : OnError(_FL)); } \
bool Set##name(type val) { return GetObject() ? GetObject()->SetInt(id, (int)val) >= Store3Delayed : OnError(_FL); }
#define LDATA_STR_PROP(name, id) \
const char *Get##name() { auto o = GetObject(); return o ? o->GetStr(id) : (const char*)OnError(_FL); } \
bool Set##name(const char *val) { return GetObject() ? GetObject()->SetStr(id, val) >= Store3Delayed : OnError(_FL); }
#define LDATA_DATE_PROP(name, id) \
const LDateTime *Get##name() { return (GetObject() ? GetObject()->GetDate(id) : (LDateTime*)OnError(_FL)); } \
bool Set##name(const LDateTime *val) { return GetObject() ? GetObject()->SetDate(id, val) >= Store3Delayed : OnError(_FL); }
#define LDATA_INT_TYPE_PROP(type, name, id, defaultVal) \
type Get##name() { return (type) (GetObject() ? GetObject()->GetInt(id) : OnError(_FL)); } \
bool Set##name(type val = defaultVal) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); }
/// This class is an interface to a collection of objects (NOT thread-safe).
typedef std::function LIteratorProgressFn;
template
class LDataIterator
{
public:
virtual ~LDataIterator() {}
/// \returns an empty object of the right type.
virtual T Create(LDataStoreI *Store) = 0;
/// \returns the first object (NOT thread-safe)
virtual T First() = 0;
/// \returns the first object (NOT thread-safe)
virtual T Next() = 0;
/// \returns the number of items in the collection
virtual size_t Length() = 0;
/// \returns the 'nth' item in the collection
virtual T operator [](size_t idx) = 0;
/// \returns the index of the given item in the collection
virtual ssize_t IndexOf(T n, bool NoAssert = false) = 0;
/// Deletes an item
/// \returns true on success
virtual bool Delete(T ptr) = 0;
/// Inserts an item at 'idx' or the end if not supplied.
/// \returns true on success
virtual bool Insert(T ptr, ssize_t idx = -1, bool NoAssert = false) = 0;
/// Clears list, but doesn't delete objects.
/// \returns true on success
virtual bool Empty() = 0;
/// Deletes all the objects from memory
/// \returns true on success
virtual bool DeleteObjects() = 0;
/// Gets the current loading/loaded state.
virtual Store3State GetState() = 0;
/// Sets the progress function
virtual void SetProgressFn(LIteratorProgressFn cb) = 0;
};
typedef LDataIterator *LDataIt;
#define EmptyVirtual(t) LAssert(0); return t
#define Store3CopyDecl bool CopyProps(LDataPropI &p) override
#define Store3CopyImpl(Cls) bool Cls::CopyProps(LDataPropI &p)
/// A generic interface for getting / setting properties.
class LDataPropI : virtual public LDom
{
LDataPropI &operator =(LDataPropI &p) = delete;
public:
virtual ~LDataPropI() {}
/// Copy all the values from 'p' over to this object
virtual bool CopyProps(LDataPropI &p) { return false; }
/// Gets a string property
virtual const char *GetStr(int id) { EmptyVirtual(NULL); }
/// Sets a string property, it will make a copy of the string, so you
/// still retain ownership of the string you're passing in.
virtual Store3Status SetStr(int id, const char *str) { EmptyVirtual(Store3Error); }
/// Gets an integer property.
virtual int64 GetInt(int id) { EmptyVirtual(false); }
/// Sets an integer property.
virtual Store3Status SetInt(int id, int64 i) { EmptyVirtual(Store3Error); }
/// Gets a date property
virtual const LDateTime *GetDate(int id) { EmptyVirtual(NULL); }
/// Sets a date property
virtual Store3Status SetDate(int id, const LDateTime *i) { EmptyVirtual(Store3Error); }
/// Gets a variant
virtual const LVariant *GetVar(int id) { EmptyVirtual(NULL); }
/// Sets a variant property
virtual Store3Status SetVar(int id, LVariant *i) { EmptyVirtual(Store3Error); }
/// Gets a sub object pointer
virtual LDataPropI *GetObj(int id) { EmptyVirtual(NULL); }
/// Sets a sub object pointer
virtual Store3Status SetObj(int id, LDataPropI *i) { EmptyVirtual(Store3Error); }
/// Gets an iterator interface to a list of sub-objects.
virtual LDataIt GetList(int id) { EmptyVirtual(NULL); }
/// Set the mime segments
virtual Store3Status SetRfc822(LStreamI *Rfc822Msg)
{
LAssert(!"Pretty sure you should be implementing this");
return Store3Error;
}
};
#pragma warning(default:4263)
class LDataUserI
{
friend class LDataI;
LDataI *Object;
public:
LString SetterRef;
LDataUserI();
virtual ~LDataUserI();
LDataI *GetObject();
virtual bool SetObject
(
/// The client side object to link with this object.
LDataI *o,
/// In the special case that 'Object' is being deleted, and is an
/// orphaned objects, SetObject must not attempt to delete 'Object'
/// a second time. This flag allows for that case.
bool InDestuctor,
/// The file name of the caller
const char *File,
/// The line number of the caller
int Line
);
};
/// This class is an interface between the UI and the back end for things
/// like email, contacts, calendar events, groups and filters
class LDataI : virtual public LDataPropI
{
friend class LDataUserI;
LDataI &operator =(LDataI &p) = delete;
public:
LDataUserI *UserData;
LDataI() { UserData = NULL; }
virtual ~LDataI()
{
if (UserData)
UserData->Object = NULL;
}
/// Returns the type of object
/// \sa MAGIC_MAIL and it's like
virtual uint32_t Type() = 0;
/// \return true if the object has been written to disk. By default the object
/// starts life in memory only.
virtual bool IsOnDisk() = 0;
/// \return true if the object is owned by some other object...
virtual bool IsOrphan() = 0;
/// \returns size of object on disk
virtual uint64 Size() = 0;
/// Saves the object to disk. If this function fails the object
/// is deleted, so if it returns false, stop using the ptr you
/// have to it.
/// \returns true if successful.
virtual Store3Status Save(LDataI *Parent = NULL) = 0;
/// Delete the on disk representation of the object. This will cause LDataEventsI::OnDelete
/// to be called after which this object will be freed from heap memory automatically. So
/// Once you call this method assume the object pointed at is gone.
virtual Store3Status Delete(bool ToTrash = true) = 0;
/// Gets the storage that this object belongs to.
virtual LDataStoreI *GetStore() = 0;
/// \returns a stream to access the data stored at this node. The caller
/// is responsible to free the stream when finished with it.
/// For Type == MAGIC_ATTACHMENT: the decoded body of the MIME segment.
/// For Type == MAGIC_MAIL: is an RFC822 encoded version of the email.
/// For other objects the stream is not defined.
virtual LAutoStreamI GetStream(const char *file, int line) = 0;
/// Sets the stream, which is used during the next call to LDataI::Save, which
/// also deletes the object when it's used. The caller loses ownership of the
/// object passed into this function.
virtual bool SetStream(LAutoStreamI stream) { return false; }
/// Parses the headers of the object and updates all the metadata fields
- virtual bool ParseHeaders() { return false; }
+ virtual bool ParseHeaders();
+ virtual bool ParseAddresses(const char *Str, int CC);
};
/// An interface to a folder structure
class LDataFolderI : virtual public LDataI
{
LDataFolderI &operator =(LDataFolderI &p) = delete;
public:
virtual ~LDataFolderI() {}
/// \returns an iterator for the sub-folders.
virtual LDataIterator &SubFolders() = 0;
/// \returns an iterator for the child objects
virtual LDataIterator &Children() = 0;
/// \returns an iterator for the fields this folder defines
virtual LDataIterator &Fields() = 0;
/// Deletes all child objects from disk and memory.
/// \return true on success;
virtual Store3Status DeleteAllChildren() { return Store3Error; }
/// Frees all the memory used by children objects without deleting from disk
virtual Store3Status FreeChildren() { return Store3Error; }
/// Called when the user selects the folder in the UI
virtual void OnSelect(bool s) {}
/// Called when the user selects a relevant context menu command
virtual void OnCommand(const char *Name) {}
};
#pragma warning(error:4263)
/// Event callback interface. Calls to these methods may be in a worker
/// thread, so make appropriate locking or pass the event off to the GUI
/// thread via a message.
class LDataEventsI
{
public:
virtual ~LDataEventsI() {}
/// This allows the caller to pass source:line info for debugging
/// It should be called prior to one of the following functions and
/// expires immediately after the function call.
virtual void SetContext(const char *file, int line) {}
/// Posts something to the GUI thread
/// \sa M_STORAGE_EVENT
virtual void Post(LDataStoreI *store, void *Param) {}
/// \returns the system path
virtual bool GetSystemPath(int Folder, LVariant &Path) { return false; }
/// \returns the options object
virtual LOptionsFile *GetOptions(bool Create = false) { return 0; }
/// A new item is available
virtual void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) = 0;
/// When an item is deleted
virtual bool OnDelete(LDataFolderI *parent, LArray &items) = 0;
/// When an item is moved to a new folder
virtual bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) = 0;
/// When an item changes
virtual bool OnChange(LArray &items, int FieldHint) = 0;
/// Notifcation of property change
virtual void OnPropChange(LDataStoreI *Store, int Prop, LVariantType Type) {}
/// Get the logging stream
virtual LStreamI *GetLogger(LDataStoreI *store) { return 0; }
/// Search for a object by type and name
virtual bool Match(LDataStoreI *store, LDataPropI *Addr, int ObjectType, LArray &Matches) { return 0; }
};
/// The virtual mail storage interface from which all mail stores inherit from.
///
/// The data store should implement LDataPropI::GetInt and handle these properties:
/// - FIELD_STATUS, acceptable returns value are:
/// * 0 - mail store is in error.
/// * 1 - mail store is ready to use / ok.
/// * 2 - mail store requires upgrading to use.
/// These are codified in the enum LDataStoreI::DataStoreStatus
/// - FIELD_READONLY, return values are:
/// * false - mail store is read/write
/// * true - mail store is read only
/// - FIELD_VERSION, existing return values are:
/// * 3 - a 'mail3' Scribe sqlite database.
/// * 10 - the Scribe IMAP implementation.
/// If you are create a new mail store use, values above 10 for your implementation
/// and optionally register them with Memecode for inclusion here.
/// - FIELD_IS_ONLINE, optionally returned if mail store is online or not.
/// - FIELD_ACCOUNT_ID, optionally return if the mail store is associated with an account.
///
/// The data store may optionally implement LDataPropI::SetInt to handle this property:
/// - FIELD_IS_ONLINE, acceptable values are:
/// * false - take the mail store offline.
/// * true - go online.
/// This is currently only implemented on the IMAP mail store.
class LDataStoreI : virtual public LDataPropI
{
public:
static LHashTbl,LDataStoreI*> Map;
int Id;
class LDsTransaction
{
protected:
LDataStoreI *Store;
public:
LDsTransaction(LDataStoreI *s)
{
Store = s;
}
virtual ~LDsTransaction() {}
};
typedef LAutoPtr StoreTrans;
LDataStoreI()
{
#ifdef HAIKU
// Could be in any thread... certainly not the LApp thread. More likely to be the
// main window thread.
#else
LAssert(LAppInst->InThread());
#endif
while (Map.Find(Id = LRand(1000)))
;
Map.Add(Id, this);
}
virtual ~LDataStoreI()
{
#ifndef HAIKU
LAssert(LAppInst->InThread());
#endif
if (!Map.Delete(Id))
LAssert(!"Delete failed.");
}
/// \returns size of object on disk
virtual uint64 Size() = 0;
/// Create a new data object that isn't written to disk yet
virtual LDataI *Create(int Type) = 0;
/// Get the root folder object
virtual LDataFolderI *GetRoot(bool create = false) = 0;
/// Move objects into a different folder.
///
/// Success:
/// 'Items' are owned by 'NewFolder', and not any previous folder.
/// Any LDataEventsI interface owned by the data store has it's 'OnMove' method called.
///
/// Failure:
/// 'Item' is owned by it's previous folder.
///
/// \return true on success.
virtual Store3Status Move
(
/// The folder to move the object to
LDataFolderI *NewFolder,
/// The object to move
LArray &Items
) = 0;
/// Deletes items, which results in either the items being moved to the local trash folder
/// or the items being completely deleted if there is no local trash. The items should all
/// be in the same folder and of the same type.
///
/// Success:
/// 'Items' are either owned by the local trash and not any previous folder, and
/// LDataEventsI::OnMove is called.
/// -or-
/// 'Items' are completely deleted and removed from it's parent and
/// LDataEventsI::OnDelete is called, after which the objects are freed.
///
/// Failure:
/// 'Items' are owned by it's previous folder.
///
/// \return true on success.
virtual Store3Status Delete
(
/// The object to delete
LArray &Items,
/// Send to the trash or not...
bool ToTrash
) = 0;
/// Changes items, which results in either the items properties being adjusted.
///
/// Success:
/// The items properties are changed, and the LDataEventsI::OnChange callback
/// is made.
///
/// Failure:
/// Items are not changed. No callback is made.
///
/// \return true on success.
virtual Store3Status Change
(
/// The object to change
LArray &Items,
/// The property to change...
int PropId,
/// The value to assign
/// (GV_INT32/64 -> SetInt, GV_DATETIME -> SetDateTime, GV_STRING -> SetStr)
LVariant &Value,
/// Optional operator for action
LOperator Operator
) = 0;
/// Compact the mail store
virtual void Compact
(
/// The parent window of the UI
LViewI *Parent,
/// The store should pass information up to the UI via setting various parameters from Store3UiFields
LDataPropI *Props,
/// The callback to get status, could be called by a worker thread...
std::function OnStatus
) = 0;
/// Upgrades the mail store to the current version for this build. You should call this in response
/// to getting Store3UpgradeRequired back from this->GetInt(FIELD_STATUS).
virtual void Upgrade
(
/// The parent window of the UI
LViewI *Parent,
/// The store should pass information up to the UI via setting various parameters from Store3UiFields
LDataPropI *Props,
/// The callback to get status, could be called by a worker thread...
std::function OnStatus
) { if (OnStatus) OnStatus(false); }
/// Tries to repair the database.
virtual void Repair
(
/// The parent window of the UI
LViewI *Parent,
/// The store should pass information up to the UI via setting various parameters from Store3UiFields
LDataPropI *Props,
/// The callback to get status, could be called by a worker thread...
std::function OnStatus
) { if (OnStatus) OnStatus(false); }
/// Set the sub-format
virtual bool SetFormat
(
/// The parent window of the UI
LViewI *Parent,
/// The store should pass information up to the UI via setting various parameters from Store3UiFields
LDataPropI *Props
) { return false; }
/// Called when event posted
virtual void OnEvent(void *Param) = 0;
/// Called when the application is not receiving messages.
/// \returns false to wait for more messages.
virtual bool OnIdle() = 0;
/// Gets the events interface
virtual LDataEventsI *GetEvents() = 0;
/// Start a scoped transaction
virtual StoreTrans StartTransaction() { return StoreTrans(0); }
};
/// Open a mail3 folder
/// \return a valid ptr or NULL on failure
extern LDataStoreI *OpenMail3
(
/// The file to open
const char *Mail3Folder,
/// Event interface,
LDataEventsI *Callback,
/// true if you want to create a new mail3 file.
bool Create = false
);
/// Open am imap store
/// \return a valid ptr or NULL on failure
extern LDataStoreI *OpenImap
(
/// The host name of the IMAP server
char *Host,
/// The port to connect to, or <= 0 means use default
int Port,
/// The user name of the account to connect to
char *User,
/// [Optional] The password of the user
char *Pass,
/// Various flags that control the type of connection made:
/// \sa #MAIL_SSL, #MAIL_SECURE_AUTH
int ConnectFlags,
/// Callback interface for various events...
LDataEventsI *Callback,
/// This allows the IMAP client to request SSL support from the
/// parent applications.
LCapabilityClient *caps,
/// Pointers to the progress info bars, or NULL if not needed.
MailProtocolProgress *prog[2],
/// The logging stream.
LStream *Log,
/// The identifier for the account
int AccoundId,
/// An interface into the persistant storage area.
LAutoPtr store
);
#ifdef WIN32
/// Open a MAPI store
/// \return a valid ptr or NULL on failure
extern LDataStoreI *OpenMapiStore
(
/// The MAPI profile name
const char *Profile,
/// The username to login as
const char *Username,
/// Their password
const char *Password,
/// The account ID
uint64 AccountId,
/// Event interface,
LDataEventsI *Callback
);
#endif
//////////////////////////////////////////////////////////////////////////////
// Common implementation of interfaces
template
class DNullIterator : public LDataIterator
{
public:
T First() { return 0; }
T Next() { return 0; }
int Length() { return 0; }
T operator [](int idx) { return 0; }
bool Delete(T ptr) { return 0; }
bool Insert(T ptr, int idx = -1, bool NoAssert = false) { return 0; }
bool DeleteObjects() { return 0; }
bool Empty() { return false; }
int IndexOf(T n, bool NoAssert = false) { return -1; }
};
template
class DIterator : public LDataIterator
{
int Cur;
public:
LArray a;
Store3State State;
LIteratorProgressFn Prog;
DIterator()
{
Cur = -1;
State = Store3Unloaded;
}
Store3State GetState() { return State; }
void SetProgressFn(LIteratorProgressFn cb)
{
Prog = cb;
}
void Swap(DIterator &di)
{
LSwap(Cur, di.Cur);
LSwap(State, di.State);
a.Swap(di.a);
}
TPub *Create(LDataStoreI *Store)
{
LAssert(State == Store3Loaded);
return new TPriv(dynamic_cast(Store));
}
TPub *First()
{
LAssert(State == Store3Loaded);
Cur = 0;
return (int)a.Length() > Cur ? a[Cur] : 0;
}
TPub *Next()
{
LAssert(State == Store3Loaded);
Cur++;
return (int)a.Length() > Cur ? a[Cur] : 0;
}
size_t Length()
{
return a.Length();
}
TPub *operator [](size_t idx)
{
LAssert(State == Store3Loaded);
return a[idx];
}
bool Delete(TPub *pub_ptr)
{
LAssert(State == Store3Loaded);
TPriv *priv_ptr = dynamic_cast(pub_ptr);
if (!priv_ptr)
{
LAssert(!"Not the right type of object.");
return false;
}
ssize_t i = a.IndexOf(priv_ptr);
if (i < 0)
return false;
a.DeleteAt(i, true);
return true;
}
bool Insert(TPub *pub_ptr, ssize_t idx = -1, bool NoAssert = false)
{
if (!NoAssert)
LAssert(State == Store3Loaded);
TPriv *priv_ptr = dynamic_cast(pub_ptr);
if (!priv_ptr)
{
LAssert(!"Not the right type of object.");
return false;
}
return a.AddAt(idx < 0 ? a.Length() : idx, priv_ptr);
}
bool Empty()
{
LAssert(State == Store3Loaded);
a.Length(0);
return true;
}
bool DeleteObjects()
{
a.DeleteObjects();
return true;
}
ssize_t IndexOf(TPub *pub_ptr, bool NoAssert = false)
{
if (!NoAssert)
LAssert(State == Store3Loaded);
TPriv *priv_ptr = dynamic_cast(pub_ptr);
if (!priv_ptr)
{
LAssert(!"Not the right type of object.");
return -1;
}
return a.IndexOf(priv_ptr);
}
};
#endif
diff --git a/include/lgi/common/Store3Defs.h b/include/lgi/common/Store3Defs.h
--- a/include/lgi/common/Store3Defs.h
+++ b/include/lgi/common/Store3Defs.h
@@ -1,458 +1,459 @@
#ifndef _STORE3_DEFS_H_
#define _STORE3_DEFS_H_
//////////////////////////////////////////////////////////////////////////////////////////////////////
// CORE DEFINITIONS (relevant to any implementation)
//////////////////////////////////////////////////////////////////////////////////////////////////////
/// Load state
///
/// This is used by both objects AND iterators
enum Store3State
{
/// The object is not loaded at all..
Store3Unloaded,
/// The object is currently loading (in some worker thread or something)
/// Best to not try and access anything like data fields.
Store3Loading,
/// The mail object has only it's headers loaded, and not the body.
Store3Headers,
/// The object is fully loaded and can be accessed normally.
Store3Loaded,
};
inline const char *toString(Store3State s)
{
#define _(s) case s: return #s;
switch (s)
{
_(Store3Unloaded)
_(Store3Loading)
_(Store3Headers)
_(Store3Loaded)
}
#undef _
LAssert(0);
return "#invalidStore3State";
}
/// Folder system type
enum Store3SystemFolder
{
Store3SystemNone,
Store3SystemInbox,
Store3SystemTrash,
Store3SystemOutbox,
Store3SystemSent,
Store3SystemCalendar,
Store3SystemContacts,
Store3SystemSpam,
};
inline const char *toString(Store3SystemFolder s)
{
#define _(s) case s: return #s;
switch (s)
{
_(Store3SystemNone)
_(Store3SystemInbox)
_(Store3SystemTrash)
_(Store3SystemOutbox)
_(Store3SystemSent)
_(Store3SystemCalendar)
_(Store3SystemContacts)
_(Store3SystemSpam)
}
#undef _
LAssert(0);
return "#invalidStore3SystemFolder";
}
/// This defines the possible outcomes of calling a function.
enum Store3Status
{
// Open mail store specific:
//--------------------------------------------------------------------------------
/// The store file is missing
Store3Missing = -1,
/// A format upgrade is required to open the store.
Store3UpgradeRequired = -2,
/// Function not implemented.
Store3NotImpl = -3,
/// Missing permissions for operation.
Store3NoPermissions = -4,
// General:
//--------------------------------------------------------------------------------
/// The method failed and no action was taken.
Store3Error = 0,
/// The method succeeded but the action was not completed immediately, notification
/// of the actions completion will come later via the callback interface.
Store3Delayed = 1,
/// The method succeeded and the action has been already completed.
Store3Success = 2,
};
/// Possible parts of UI
enum Store3UiFields
{
Store3UiCurrentPos, // [Set] sets the current progress value
Store3UiMaxPos, // [Set] sets the maximum progress value
Store3UiStatus, // [Set] set a status/progress string
Store3UiError, // [Set] set an error string
Store3UiInteractive, // [Get] returns a bool if the user is expecting interaction
Store3UiCancel, // [Get] returns a bool indicating if the user has cancelled the operation
Store3UiNewFormat, // [Get] returns a integer/enum describing the new format to use
};
enum Store3Backend
{
Store3Sqlite,
Store3Imap,
Store3Mapi,
Store3Webdav,
};
//////////////////////////////////////////////////////////////////////////////////////////////////////
// MAIL/CALENDAR CLIENT RELATED DEFINITIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////
enum EmailFlags
{
// Mail flags
MAIL_SENT = (1 << 0),
MAIL_RECEIVED = (1 << 1),
MAIL_CREATED = (1 << 2),
MAIL_FORWARDED = (1 << 3),
MAIL_REPLIED = (1 << 4),
MAIL_ATTACHMENTS = (1 << 5),
MAIL_READ = (1 << 6),
// #define MAIL_MARK (1 << 7), // Deprecated
MAIL_READY_TO_SEND = (1 << 8), // If this flag is set then the user
// wants to send the mail on the next
// run. When the user just saves a new
// message in the outbox this isn't set
// and isn't sent until they go in and
// say "send". At which point this flag
// is set and the message sent
MAIL_READ_RECEIPT = (1 << 9),
MAIL_IGNORE = (1 << 10),
MAIL_FIXED_WIDTH_FONT = (1 << 11),
MAIL_BOUNCED = (1 << 12), // The bounce source mail
MAIL_BOUNCE = (1 << 13), // The outgoing copy of a bounced mail
MAIL_SHOW_IMAGES = (1 << 14), // User selected to show images in HTML
MAIL_NEW = (1 << 18), // Mail is new, cleared after the OnNew event happens
MAIL_STORED_FLAT = (1 << 19), // Message is signed and/or encrypted and needs
// to be stored in such a way as the RFC822 image
// is not damaged.
// Bayesian filter flags:
MAIL_BAYES_HAM = (1 << 16), // Bayesian classified originally as ham
MAIL_BAYES_SPAM = (1 << 17), // Bayesian classified originally as spam
MAIL_HAM_DB = (1 << 20), // In Bayesian ham word database
MAIL_SPAM_DB = (1 << 21), // In Bayesian spam word database
};
extern LString EmailFlagsToStr(int flags);
enum EmailAddressType
{
MAIL_ADDR_TO = 0,
MAIL_ADDR_CC = 1,
MAIL_ADDR_BCC = 2,
MAIL_ADDR_FROM = 3,
};
enum EmailPriority
{
// FIELD_PRIORITY is equivilent to the header field: X-Priority
// 1 - High
// ...
// 5 - Low
MAIL_PRIORITY_HIGH = 1,
MAIL_PRIORITY_NORMAL = 3,
MAIL_PRIORITY_LOW = 5,
};
enum CalendarReminderType
{
CalEmail,
CalPopup,
CalScriptCallback,
CalMaxType,
};
enum CalendarReminderUnits
{
CalMinutes,
CalHours,
CalDays,
CalWeeks,
CalMaxUnit,
};
enum CalendarShowTimeAs
{
CalFree = 0,
CalTentative = 1,
CalBusy = 2,
CalOut = 3
};
enum CalendarType
{
CalEvent,
CalTodo,
CalJournal,
CalRequest,
CalReply,
};
enum CalendarPrivacyType
{
CalDefaultPriv = 0,
CalPublic,
CalPrivate
};
// To convert to string use 'Store3ItemTypeName'
enum Store3ItemTypes
{
MAGIC_NONE = 0x00000000,
MAGIC_BASE = 0xAAFF0000,
MAGIC_MAIL = (MAGIC_BASE+1), // Mail item
MAGIC_CONTACT = (MAGIC_BASE+2), // Contact item
MAGIC_FOLDER_OLD = (MAGIC_BASE+3), // Old Store1 folder of items
MAGIC_MAILBOX = (MAGIC_BASE+4), // Root of mail tree (nothing-abstract)
MAGIC_ATTACHMENT = (MAGIC_BASE+5), // Mail attachment
MAGIC_ANY = (MAGIC_BASE+6), // Trash folder type (accepts any object)
MAGIC_FILTER = (MAGIC_BASE+7), // Used to match messages against
MAGIC_FOLDER = (MAGIC_BASE+8), // Folder v2
MAGIC_CONDITION = (MAGIC_BASE+9), // Filter condition
MAGIC_ACTION = (MAGIC_BASE+10), // Filter action
MAGIC_CALENDAR = (MAGIC_BASE+11), // Calendar event
MAGIC_ATTENDEE = (MAGIC_BASE+12), // Event attendee
MAGIC_GROUP = (MAGIC_BASE+13), // Group of contacts
MAGIC_MAX = (MAGIC_BASE+14) // One past the end
};
extern const char *Store3ItemTypeName(Store3ItemTypes t);
// When setting this via LDataI::SetInt the return value is:
// - true if you need to mark the object dirty so that it gets saved
// - false if the flags is stored elsewhere and you don't have to save.
enum Store3Fields
{
FIELD_NULL = 0,
FIELD_FLAGS = 1, // (EmailFlags) The message flags
FIELD_TO = 2, // (DIterator) List of recipients
FIELD_CC = 3, // (EmailAddressType)
FIELD_FROM = 4, // (Store3Addr*) The From address
FIELD_REPLY = 5, // (Store3Addr*) The Reply-To address
FIELD_SUBJECT = 6, // (char*) Subject of the message
FIELD_TEXT = 7, // (char*) Textual body
FIELD_MESSAGE_ID = 8, // (char*) The message ID
FIELD_DATE_RECEIVED = 9, // (LDateTime*) Received date
FIELD_INTERNET_HEADER = 10, // (char*) The internet headers
// Contact fields
FIELD_FIRST_NAME = 11, // (char*) First name
FIELD_LAST_NAME = 12, // (char*) Last/surname
FIELD_EMAIL = 13, // (char*) The default email address
// \sa 'FIELD_ALT_EMAIL'
FIELD_HOME_STREET = 14, // (char*)
FIELD_HOME_SUBURB = 15, // (char*)
FIELD_HOME_POSTCODE = 16, // (char*)
FIELD_HOME_STATE = 17, // (char*)
FIELD_HOME_COUNTRY = 18, // (char*)
FIELD_WORK_PHONE = 19, // (char*) This was already defined
FIELD_HOME_PHONE = 20, // (char*)
FIELD_HOME_MOBILE = 21, // (char*)
FIELD_HOME_IM = 22, // (char*)
FIELD_HOME_FAX = 23, // (char*)
FIELD_HOME_WEBPAGE = 24, // (char*)
FIELD_NICK = 25, // (char*)
FIELD_SPOUSE = 26, // (char*)
FIELD_NOTE = 27, // (char*)
FIELD_PLUGIN_ASSOC = 28, // (char*)
// More fields
FIELD_SIZE = 29, // (uint64)
FIELD_DATE_SENT = 30, // (LDateTime*)
FIELD_COLUMN = 31, // (uint64)
// FIELD_BCC = 32, // Deprecated
FIELD_MIME_TYPE = 33, // (char*) The MIME type
FIELD_PRIORITY = 34, // (EmailPriority)
FIELD_FOLDER_OPEN = 35, // (bool) True if the folder is expanded to show child folders
// FIELD_CODE_PAGE = 36, // Deprecated
FIELD_COLOUR = 37, // (uint32) Rgba32
FIELD_ALTERNATE_HTML = 38, // (char*) The HTML part of the message
FIELD_CONTENT_ID = 39, // (char*) An attachment content ID
// Filter fields
FIELD_FILTER_NAME = 40, // (char*)
// FIELD_ACT_TYPE = 46, // Deprecated
// FIELD_ACT_ARG = 47, // Deprecated
// FIELD_DIGEST_INDEX = 48, // Deprecated
FIELD_COMBINE_OP = 49,
FIELD_FILTER_INDEX = 50,
FIELD_FILTER_SCRIPT = 52,
FIELD_STOP_FILTERING = 54,
FIELD_FILTER_INCOMING = 55,
FIELD_FILTER_OUTGOING = 56,
FIELD_FILTER_CONDITIONS_XML = 57,
FIELD_FILTER_ACTIONS_XML = 58,
FIELD_FILTER_INTERNAL = 59,
// Calendar fields
// FIELD_CAL_START_LOCAL = 60, // **deprecated**
// FIELD_CAL_END_LOCAL = 61, // **deprecated**
FIELD_CAL_SUBJECT = 62, // (char*) event title
FIELD_CAL_LOCATION = 63, // (char*) location of event
FIELD_CAL_ALL_DAY = 64, // (bool) All Day setting
// FIELD_CAL_REMINDER_ACTION = 65, // **deprecated** (CalendarReminderType) The reminder type
// FIELD_CAL_REMINDER_UNIT = 66, // **deprecated** (CalendarReminderUnits) The unit of time
FIELD_CAL_SHOW_TIME_AS = 67, // (CalendarShowTimeAs) Busy/Free etc
FIELD_CAL_RECUR_FREQ = 68, // (CalRecurFreq) Base time unit of recurring: e.g. Day/Week/Month/Year.
FIELD_CAL_RECUR_INTERVAL = 69, // (int) Number of FIELD_CAL_RECUR_FREQ units of time between recurring events.
FIELD_CAL_RECUR_FILTER_DAYS = 70 , // (int) Bitmask of days: Bit 0 = Sunday, Bit 1 = Monday etc
FIELD_CAL_RECUR_FILTER_MONTHS = 71, // (int) Bitmask of months: Bit 0 = January, Bit 1 = Feburary etc
FIELD_CAL_RECUR_FILTER_YEARS = 72, // (char*) Comma separated list of years to filter on.
FIELD_CAL_NOTES = 73, // (char*) Textual notes
FIELD_CAL_TYPE = 74, // (CalendarType) The type of event
FIELD_CAL_COMPLETED = 75, // (int) 0 -> 100%
FIELD_CAL_START_UTC = 76, // (LDateTime*) The start time and date
FIELD_CAL_END_UTC = 77, // (LDateTime*) The end time and date
FIELD_CAL_RECUR_FILTER_POS = 78, // (char*) Comma separated list of integers defining positions in the month to filter on.
FIELD_CAL_RECUR_END_DATE = 79, // (LDateTime*) The end of recurence if FIELD_CAL_RECUR_END_TYPE == CalEndOnDate.
FIELD_CAL_RECUR_END_COUNT = 80, // (int) Times to recur if FIELD_CAL_RECUR_END_TYPE == CalEndOnCount.
FIELD_CAL_RECUR_END_TYPE = 81, // (CalRecurEndType) Which ending to use... needs an enum
FIELD_CAL_RECUR = 82, // (int) true if the event recurs.
FIELD_CAL_TIMEZONE = 83, // (char*) The timezone as text
FIELD_CAL_PRIVACY = 84, // (CalendarPrivacyType) The privacy setting
// Attendee fields
FIELD_ATTENDEE_JSON = 85,
// FIELD_ATTENDEE_EMAIL = 86,
// FIELD_ATTENDEE_ATTENDENCE = 87,
// FIELD_ATTENDEE_NOTE = 88,
// FIELD_ATTENDEE_RESPONSE = 89,
// 2nd lot of contact fields
FIELD_WORK_STREET = 90, // (char*)
FIELD_WORK_SUBURB = 91, // (char*)
FIELD_WORK_POSTCODE = 92, // (char*)
FIELD_WORK_STATE = 93, // (char*)
FIELD_WORK_COUNTRY = 94, // (char*)
// #define FIELD_WORK_PHONE is previously defined
FIELD_WORK_MOBILE = 95, // (char*)
FIELD_WORK_IM = 96, // (char*)
FIELD_WORK_FAX = 97, // (char*)
FIELD_WORK_WEBPAGE = 98, // (char*)
FIELD_COMPANY = 99, // (char*)
/* Deprecated
FIELD_CONTACT_CUST_FLD1 = 100, // (char*)
FIELD_CONTACT_CUST_VAL1 = 101, // (char*)
FIELD_CONTACT_CUST_FLD2 = 102, // (char*)
FIELD_CONTACT_CUST_VAL2 = 103, // (char*)
FIELD_CONTACT_CUST_FLD3 = 104, // (char*)
FIELD_CONTACT_CUST_VAL3 = 105, // (char*)
FIELD_CONTACT_CUST_FLD4 = 106, // (char*)
FIELD_CONTACT_CUST_VAL4 = 107, // (char*)
*/
FIELD_CONTACT_IMAGE = 108, // (LSurface*)
FIELD_CONTACT_JSON = 109, // (char*)
// Misc additional fields
FIELD_LABEL = 110, // (char*) Mail label
FIELD_CHARSET = 111, // (char*) A character set
FIELD_ALT_EMAIL = 112, // (char*) Comma separated list of alternative,
// non-default email addresses. The default addr
// is stored under 'FIELD_EMAIL'
FIELD_UID = 113, // (char*)
FIELD_TITLE = 114, // (char*)
FIELD_TIMEZONE = 115, // (char*)
FIELD_REFERENCES = 116, // (char*)
FIELD_SERVER_UID = 117, // (int64) Server identifier
FIELD_FOLDER_PERM_READ = 118, // (int64)
FIELD_FOLDER_PERM_WRITE = 119, // (int64)
FIELD_GROUP_NAME = 120, // (char*) The name of a contact group
FIELD_HTML_CHARSET = 121, // (char*) The character set of the HTML body part
FIELD_POSITION = 122, // (char*) Role in organisation
FIELD_GROUP_LIST = 123, // (char*)
FIELD_FOLDER_THREAD = 124, // (int64)
FIELD_ACCOUNT_ID = 125, // (int64) The ID of an account
FIELD_FROM_CONTACT_NAME = 126, // (char*) Not used at the Store3 level
FIELD_FWD_MSG_ID = 127, // (char*) Mail::FwdMsgId
FIELD_FOLDER_TYPE = 128, // (Store3ItemTypes)
FIELD_FOLDER_NAME = 129, // (char*) Name of the folder
FIELD_UNREAD = 130, // (int64) Count of unread items
FIELD_SORT = 131, // (int64) Sort setting
FIELD_STATUS = 132, // (Store3Status)
FIELD_VERSION = 133, // (int64)
FIELD_ID = 134, // (int64)
FIELD_READONLY = 135, // (bool)
FIELD_NAME = 136, // (char*)
FIELD_WIDTH = 137, // (int64)
// #define FIELD_ATTACHMENT_CONTENT 139 - Deprecated
// #define FIELD_ATTACHMENTS 140 - Deprecated, use FIELD_MIME_SEG instead
FIELD_CACHE_FILENAME = 141, // (char*) IMAP backend: the filename
FIELD_CACHE_FLAGS = 142, // (char*) IMAP backend: IMAP flags
FIELD_DONT_SHOW_PREVIEW = 143, // (bool)
FIELD_STORE_PASSWORD = 144, // (char*)
FIELD_LOADED = 145, // (Store3State)
FIELD_DEBUG = 146, // (char*)
FIELD_MIME_SEG = 147, // (LDataIt)
FIELD_STORE_TYPE = 148, // (Store3Backend)
FIELD_BOUNCE_MSG_ID = 149, // (char*)
FIELD_FOLDER_INDEX = 150, // (int64)
FIELD_FORMAT = 151, // (int64)
FIELD_SYSTEM_FOLDER = 152, // (Store3SystemFolder)
FIELD_ERROR = 153, // (char*) An error message
FIELD_TYPE = 154, // (char*) The type of the object
FIELD_ATTACHMENTS_DATA = 155, // Meta field for specifying attachment data contents
FIELD_ATTACHMENTS_NAME = 156, // Meta field for specifying attachment file names
FIELD_MEMBER_OF_GROUP = 157, // Meta field for specifying membership of a content group
FIELD_TEMP_PATH = 158, // (char*) A temporary path to store files...
FIELD_HTML_RELATED = 159, // Array of related attachments for the HTML content.
// Pass this to an email's SetObj member to add a
// related attachment.
FIELD_CAL_REMINDERS = 160, // Individual reminders as CSV, fields are:
// Number, CalendarReminderUnits, CalendarReminderType, Param
//
// CalEmail: param option email address to send to as well as the guests
// CalPopup: not used
// CalScriptCallback: not impl (but will be the script function and args)
//
FIELD_CAL_LAST_CHECK = 161, // (LDateTime) Ts the calendar event was last checked for reminders
FIELD_DATE_MODIFIED = 162, // (LDateTime) Ts of modification
FIELD_INBOX = 163, // (LDataFolderI*) Inbox for mail store
FIELD_OUTBOX = 164, // (LDataFolderI*) Outbox for mail store
FIELD_SENT = 165, // (LDataFolderI*) Sent folder for mail store
FIELD_TRASH = 166, // (LDataFolderI*) Trash folder for mail store
FIELD_IMAP_SEQ = 167, // (uint32_t) IMAP sequence number
FIELD_CAL_STATUS = 168, // (char*) Status of the vCal event.
FIELD_STORE_STATUS = 169, // (ScribeAccountletStatusIcon) Status (icon) of a LDataStoreI
FIELD_RECEIVED_DOMAIN = 170, // (char*) First "Received:" header domain. (See also SdReceivedDomain)
FIELD_FOLDER_ITEMS = 171, // (int64) Number of items in a folder..
+ FIELD_PARENT = 172, // (LDataFolderI*) Parent of store3 item
FIELD_MAX,
};
#endif
diff --git a/include/lgi/common/StringClass.h b/include/lgi/common/StringClass.h
--- a/include/lgi/common/StringClass.h
+++ b/include/lgi/common/StringClass.h
@@ -1,1373 +1,1379 @@
/*
* A mis-guided attempt to make a string class look and feel like a python string.
*
* Author: Matthew Allen
* Email: fret@memecode.com
* Created: 16 Sept 2014
*/
#pragma once
#include
#include
#include
#if defined(_MSC_VER) || defined(__GTK_H__)
// This fixes compile errors in VS2008/Gtk
#undef _SIGN_DEFINED
#undef abs
#endif
#include
#if defined(_MSC_VER) && _MSC_VER < 1800/*_MSC_VER_VS2013*/
#include
#define PRId64 "I64i"
#else
#define __STDC_FORMAT_MACROS 1
#include
#include
#ifndef PRId64
#warning "PRId64 not defined."
#define PRId64 "Ld"
#endif
#endif
#include "LgiOsDefs.h"
#include "lgi/common/Unicode.h"
#include "lgi/common/Array.h"
#ifndef IsDigit
#define IsDigit(ch) ((ch) >= '0' && (ch) <= '9')
#endif
LgiExtern int LPrintf(class LString &Str, const char *Format, va_list &Arg);
#ifdef LGI_UNIT_TESTS
LgiExtern size_t LString_RefStrCount;
#endif
/// A pythonic string class.
class LString
{
protected:
/// This structure holds the string's data itself and is shared
/// between one or more LString instances.
struct RefStr
{
/// A reference count
int32 Refs;
/// The bytes in 'Str' not including the NULL terminator
size_t Len;
/// The first byte of the string. Further bytes are allocated
/// off the end of the structure using malloc. This must always
/// be the last element in the struct.
char Str[1];
/// Ptr to the NULL char at the end.
char *End() { return Str + Len; }
} *Str;
inline void _strip(LString &ret, const char *set, bool left, bool right)
{
if (!Str)
return;
char *s = Str->Str;
char *e = s + Str->Len;
if (!set) set = " \t\r\n";
if (left)
{
while (s < e && strchr(set, *s))
s++;
}
if (right)
{
while (e > s && strchr(set, e[-1]))
e--;
}
if (e > s)
ret.Set(s, e - s);
}
public:
/// A copyable array of strings
class Array : public LArray
{
public:
Array(size_t PreAlloc = 0) : LArray(PreAlloc)
{
// This allows the parent array to return an empty
// string without asserting if the caller requests an
// out of range index
warnResize = false;
}
Array(const Array &a)
{
*this = (Array&)a;
}
Array &operator =(const Array &a)
{
*((LArray*)this) = a;
fixed = a.fixed;
warnResize = a.warnResize;
return *this;
}
Array &operator +=(const Array &a)
{
SetFixedLength(false);
Add(a);
SetFixedLength(true);
return *this;
}
Array &operator +=(const LArray &a)
{
SetFixedLength(false);
Add(a);
SetFixedLength(true);
return *this;
}
};
/// Empty constructor
LString()
{
Str = NULL;
}
// This odd looking constructor allows the object to be used as the value type
// in a GHashTable, where the initialiser is '0', an integer.
LString(int i)
{
Str = NULL;
}
#ifndef _MSC_VER
// This odd looking constructor allows the object to be used as the value type
// in a GHashTable, where the initialiser is '0', an integer.
LString(long int i)
{
Str = NULL;
}
#endif
/// String constructor
LString(const char *str, ptrdiff_t bytes)
{
Str = NULL;
Set(str, bytes);
}
/// const char* constructor
LString(const char *str)
{
Str = NULL;
Set(str);
}
/// const char16* constructor
LString(const wchar_t *str, ptrdiff_t wchars = -1)
{
Str = NULL;
SetW(str, wchars);
}
/// Utf32 constructor
LString(const uint32_t *utf32, ptrdiff_t wchars = -1)
{
Str = NULL;
if (sizeof(*utf32) == sizeof(char16))
{
// 1:1 mapping
SetW((char16*)utf32, wchars);
}
else if (sizeof(char16) == 2) // Ie windows:
{
// Convert UTF32 to utf-8
if (!utf32)
return;
// Measure size:
ptrdiff_t bytes = 0;
uint8_t utf8[6];
for (ptrdiff_t i=0;
(wchars >= 0 && i < wchars) ||
(wchars < 0 && utf32[i]);
i++)
{
uint8_t *out = utf8;
ssize_t outBufSize = sizeof(utf8);
if (!LgiUtf32To8(utf32[i], out, outBufSize))
break;
bytes += out - utf8;
}
// Create memory buffer:
if (!Length(bytes))
return;
// Convert string:
uint8_t *p = (uint8_t*) Str->Str;
auto end = p + Str->Len;
for (ptrdiff_t i=0;
(wchars >= 0 && i < wchars) ||
(wchars < 0 && utf32[i]);
i++)
{
ssize_t outBufSize = end - p;
if (!LgiUtf32To8(utf32[i], p, outBufSize))
break;
}
assert((char*)p == Str->End());
*p = 0; // NULL terminate string
}
else assert(!"No valid mapping for UTF32 to char16?");
}
/*
#if defined(_WIN32) || defined(MAC)
/// const uint32* constructor
LString(const uint32_t *str, ptrdiff_t chars = -1)
{
Str = NULL;
if (chars < 0)
chars = Strlen(str);
ptrdiff_t utf_len = 0;
const uint32_t *end = str + chars;
const uint32_t *c = str;
while (c < end)
{
uint8_t utf[6], *u = utf;
ssize_t len = sizeof(utf);
if (!LgiUtf32To8(*c++, u, len))
break;
utf_len += u - utf;
}
if (Length((uint32_t)utf_len))
{
c = str;
uint8_t *u = (uint8_t*)Str->Str;
ssize_t len = Str->Len;
while (c < end)
{
if (!LgiUtf32To8(*c++, u, len))
break;
}
*u++ = 0;
}
}
#endif
*/
/// LString constructor
LString(const LString &s)
{
Str = s.Str;
if (Str)
Str->Refs++;
}
// Move constructor
LString(LString&& s)
{
Str = s.Str;
s.Str = NULL;
}
~LString()
{
Empty();
}
/// Removes a reference to the string and deletes if needed
void Empty()
{
if (!Str) return;
Str->Refs--;
if (Str->Refs < 0)
{
assert(!"Invalid refs");
}
if (Str->Refs == 0)
{
free(Str);
#ifdef LGI_UNIT_TESTS
LString_RefStrCount--;
#endif
}
Str = NULL;
}
/// Returns the pointer to the string data
char *Get() const
{
return Str ? Str->Str : NULL;
}
/// Sets the string to a new value
bool Set
(
/// Can be a pointer to string data or NULL to create an empty buffer (requires valid length)
const char *str,
/// Byte length of input string or -1 to copy till the NULL terminator.
ptrdiff_t bytes = -1
)
{
Empty();
if (bytes < 0)
{
if (str)
bytes = strlen(str);
else
return false;
}
Str = (RefStr*)malloc(sizeof(RefStr) + bytes);
if (!Str)
return false;
Str->Refs = 1;
Str->Len = (uint32_t)bytes;
#ifdef LGI_UNIT_TESTS
LString_RefStrCount++;
#endif
if (str)
memcpy(Str->Str, str, bytes);
Str->Str[bytes] = 0;
return true;
}
/// Sets the string to a new value
bool SetW
(
/// Can be a pointer to string data or NULL to create an empty buffer (requires valid length)
const wchar_t *str,
/// Number of 'char16' values in the input string or -1 to copy till the NULL terminator.
ptrdiff_t wchars = -1
)
{
size_t Sz = WideToUtf8Len(str, wchars);
if (Length(Sz))
{
#ifdef _MSC_VER
const uint16 *i = (const uint16*) str;
ssize_t InLen = wchars >= 0 ? wchars << 1 : 0x7fffffff;
assert(sizeof(*i) == sizeof(*str));
uint8_t *o = (uint8_t*)Str->Str;
ssize_t OutLen = Str->Len;
for (uint32_t ch; ch = LgiUtf16To32(i, InLen); )
{
if (!LgiUtf32To8(ch, o, OutLen))
{
*o = 0;
break;
}
}
#else
uint8_t *o = (uint8_t*)Str->Str;
ssize_t OutLen = Str->Len;
if (wchars >= 0)
{
const wchar_t *end = str + wchars;
for (const wchar_t *ch = str; ch < end; ch++)
{
if (!LgiUtf32To8(*ch, o, OutLen))
{
*o = 0;
break;
}
}
}
else
{
for (const wchar_t *ch = str; *ch; ch++)
{
if (!LgiUtf32To8(*ch, o, OutLen))
{
*o = 0;
break;
}
}
}
#endif
*o = 0;
}
return true;
}
/// Equality operator (case sensitive)
bool operator ==(const LString &s)
{
const char *a = Get();
const char *b = s.Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !strcmp(a, b);
}
bool operator !=(const LString &s)
{
return !(*this == s);
}
// Equality function (default: case insensitive, as the operator== is case sensitive)
bool Equals(const char *b, bool CaseInsensitive = true) const
{
const char *a = Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b));
}
// Equality function (default: case insensitive, as the operator== is case sensitive)
bool Equals(const LString &s, bool CaseInsensitive = true) const
{
const char *a = Get();
const char *b = s.Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !(CaseInsensitive ? _stricmp(a, b) : strcmp(a, b));
}
/// Assignment operator to copy one string to another
LString &operator =(const LString &s)
{
if (this != &s)
{
Empty();
Str = s.Str;
if (Str)
Str->Refs++;
}
return *this;
}
/// Equality with a C string (case sensitive)
bool operator ==(const char *b)
{
const char *a = Get();
if (!a && !b)
return true;
if (!a || !b)
return false;
return !strcmp(a, b);
}
bool operator !=(const char *b)
{
return !(*this == b);
}
/// Assignment operators
LString &operator =(const char *s)
{
if (Str == NULL ||
s < Str->Str ||
s > Str->End())
{
Empty();
Set(s);
}
else if (s != Str->Str)
{
// Special case for setting it to part of itself
// If you try and set a string to the start, it's a NOP
ptrdiff_t Off = s - Str->Str;
memmove(Str->Str, s, Str->Len - Off + 1);
Str->Len -= (uint32_t)Off;
}
return *this;
}
LString &operator =(const wchar_t *s)
{
SetW(s);
return *this;
}
LString &operator =(int val)
{
char n[32];
sprintf_s(n, sizeof(n), "%i", val);
Set(n);
return *this;
}
LString &operator =(int64 val)
{
char n[32];
sprintf_s(n, sizeof(n), "%" PRId64, (int64_t)val);
Set(n);
return *this;
}
/// Cast to C string operator
operator char *() const
{
return Str && Str->Len > 0 ? Str->Str : NULL;
}
int operator -(const LString &s) const
{
return Stricmp(Get(), s.Get());
}
/// Concatenation operator
LString operator +(const LString &s)
{
LString Ret;
size_t Len = Length() + s.Length();
if (Ret.Set(NULL, Len))
{
char *p = Ret.Get();
if (p)
{
if (Str)
{
memcpy(p, Str->Str, Str->Len);
p += Str->Len;
}
if (s.Str)
{
memcpy(p, s.Str->Str, s.Str->Len);
p += s.Str->Len;
}
*p++ = 0;
}
}
return Ret;
}
/// Concatenation / assignment operator
LString &operator +=(const LString &s)
{
ssize_t Len = Length() + s.Length();
ssize_t Alloc = sizeof(RefStr) + Len;
RefStr *rs = (RefStr*)malloc(Alloc);
if (rs)
{
rs->Refs = 1;
rs->Len = Len;
#ifdef LGI_UNIT_TESTS
LString_RefStrCount++;
#endif
char *p = rs->Str;
if (Str)
{
memcpy(p, Str->Str, Str->Len);
p += Str->Len;
}
if (s.Str)
{
memcpy(p, s.Str->Str, s.Str->Len);
p += s.Str->Len;
}
*p++ = 0;
assert(p - (char*)rs <= Alloc);
Empty();
Str = rs;
}
return *this;
}
LString operator *(ssize_t mul)
{
LString s;
if (Str)
{
s.Length(Str->Len * mul);
char *out = s.Get();
for (ssize_t i=0; iLen)
memcpy(out, Str->Str, Str->Len);
*out = 0;
}
return s;
}
/// Gets the length in bytes
size_t Length() const
{
return Str ? Str->Len : 0;
}
size_t Length(ssize_t NewLen)
{
if (NewLen < 0)
{
LAssert(!"No negative string len.");
Empty();
}
else if (Str)
{
if (NewLen <= (ssize_t)Str->Len)
{
Str->Len = NewLen;
Str->Str[NewLen] = 0;
}
else
{
RefStr *n = (RefStr*)malloc(sizeof(RefStr) + NewLen);
if (n)
{
n->Len = NewLen;
n->Refs = 1;
memcpy(n->Str, Str->Str, Str->Len);
n->Str[Str->Len] = 0; // NULL terminate...
Empty(); // Deref the old string...
Str = n;
}
else return 0;
}
}
else
{
Str = (RefStr*)malloc(sizeof(RefStr) + NewLen);
if (Str)
{
Str->Len = NewLen;
Str->Refs = 1;
Str->Str[0] = 0; // NULL terminate...
}
else return 0;
}
return Str ? Str->Len : 0;
}
/// Splits the string into parts using a separator
Array Split(const char *Sep, int Count = -1, bool CaseSen = false)
{
Array a;
if (Str && Sep)
{
const char *s = Str->Str, *Prev = s;
const char *end = s + Str->Len;
size_t SepLen = strlen(Sep);
if (s[Str->Len] == 0)
{
while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep)))
{
if (s > Prev)
a.New().Set(Prev, s - Prev);
s += SepLen;
Prev = s;
if (Count > 0 && a.Length() >= (uint32_t)Count)
break;
}
if (Prev < end)
a.New().Set(Prev, end - Prev);
a.SetFixedLength();
}
else assert(!"String not NULL terminated.");
}
return a;
}
/// Splits the string into parts using a separator
Array RSplit(const char *Sep, int Count = -1, bool CaseSen = false)
{
Array a;
if (Str && Sep)
{
const char *s = Get();
size_t SepLen = strlen(Sep);
LArray seps;
while ((s = CaseSen ? strstr(s, Sep) : Stristr(s, Sep)))
{
seps.Add(s);
s += SepLen;
}
ssize_t i, Last = seps.Length() - 1;
LString p;
for (i=Last; i>=0; i--)
{
const char *part = seps[i] + SepLen;
if (i == Last)
p.Set(part);
else
p.Set(part, seps[i+1]-part);
a.AddAt(0, p);
if (Count > 0 && a.Length() >= (uint32_t)Count)
break;
}
const char *End = seps[i > 0 ? i : 0];
p.Set(Get(), End - Get());
a.AddAt(0, p);
}
a.SetFixedLength(true, false);
return a;
}
/// Splits the string into parts using delimiter chars
Array SplitDelimit(const char *Delimiters = NULL, int Count = -1, bool GroupDelimiters = true) const
{
Array a;
if (Str)
{
const char *delim = Delimiters ? Delimiters : " \t\r\n";
const char *s = Get(), *end = s + Length();
while (s < end)
{
// Skip over non-delimiters
const char *e = s;
while (e < end && !strchr(delim, *e))
e++;
if (e > s || !GroupDelimiters)
a.New().Set(s, e - s);
s = e;
if (*s) s++;
if (GroupDelimiters)
{
// Skip any delimiters
while (s < end && strchr(delim, *s))
s++;
}
// Create the string
if (Count > 0 && a.Length() >= (uint32_t)Count)
break;
}
if
(
s < end ||
(
!GroupDelimiters &&
s > Get() &&
strchr(delim, s[-1])
)
)
a.New().Set(s);
}
a.SetFixedLength(true, false);
return a;
}
/// Joins an array of strings using a separator
LString Join(const LArray &a)
{
LString ret;
if (a.Length() == 0)
return ret;
char *Sep = Get();
size_t SepLen = Sep ? strlen(Sep) : 0;
size_t Bytes = SepLen * (a.Length() - 1);
LArray ALen;
for (unsigned i=0; iStr;
LArray Matches;
while ((Match = (CaseSen ? strstr(Match, Old) : Stristr(Match, Old))))
{
Matches.Add(Match);
if (Count >= 0 && (int)Matches.Length() >= Count)
break;
Match += OldLen;
}
size_t NewSize = Str->Len + (Matches.Length() * (NewLen - OldLen));
s.Length((uint32_t)NewSize);
char *Out = s.Get();
char *In = Str->Str;
// For each match...
for (unsigned i=0; iEnd();
if (In < End)
{
ptrdiff_t Bytes = End - In;
memcpy(Out, In, Bytes);
Out += Bytes;
}
assert(Out - s.Get() == NewSize); // Check we got the size right...
*Out = 0; // Null terminate
}
else
{
s = *this;
}
return s;
}
/// Convert string to double
double Float()
{
return Str ? atof(Str->Str) : NAN;
}
/// Convert to integer
int64 Int(int Base = 10, int64 Default = -1)
{
if (!Str)
return Default;
if
(
Str->Len > 2 &&
Str->Str[0] == '0' &&
(
Str->Str[1] == 'x' ||
Str->Str[1] == 'X'
)
)
{
return Atoi(Str->Str + 2, 16);
}
return Atoi(Str->Str, Base);
}
/// Checks if the string is a number
bool IsNumeric()
{
if (!Str)
return false;
for (char *s = Str->Str; *s; s++)
{
if (!IsDigit(*s) && !strchr("e-+.", *s))
return false;
}
return true;
}
/// Check for non whitespace
bool IsEmpty()
{
if (!Str)
return true;
for (char *s = Str->Str; *s; s++)
{
if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
return false;
}
return true;
}
+
+ /// Check string is UTF8
+ bool IsUtf8()
+ {
+ return Str ? LIsUtf8(Str->Str, Str->Len) : true;
+ }
/// Reverses all the characters in the string
LString Reverse()
{
LString s;
if (Length() > 0)
{
s = Str->Str;
for (auto *a = s.Get(), *b = s.Get() + s.Length() - 1; a < b; a++, b--)
{
char t = *a;
*a = *b;
*b = t;
}
}
return s;
}
/// Find a sub-string
ssize_t Find(const char *needle, ssize_t start = 0, ssize_t end = -1)
{
if (!needle) return -1;
char *c = Get();
if (!c) return -1;
char *pos = c + start;
while (c < pos)
{
if (!*c)
return -1;
c++;
}
char *found = (end > 0) ? Strnstr(c, needle, end - start) : strstr(c, needle);
return (found) ? found - Get() : -1;
}
/// Reverse find a string (starting from the end)
ssize_t RFind(const char *needle, int start = 0, ssize_t end = -1)
{
if (!needle) return -1;
char *c = Get();
if (!c) return -1;
char *pos = c + start;
while (c < pos)
{
if (!*c)
return -1;
c++;
}
char *found, *prev = NULL;
size_t str_len = strlen(needle);
while
((
found =
(
(end > 0) ?
Strnstr(c, needle, end - start) :
strstr(c, needle)
)
))
{
prev = found;
c = found + str_len;
}
return (prev) ? prev - Get() : -1;
}
/// Returns a copy of the string with all the characters converted to lower case
LString Lower()
{
LString s;
if (Str && s.Set(Str->Str, Str->Len))
Strlwr(s.Get());
return s;
}
/// Returns a copy of the string with all the characters converted to upper case
LString Upper()
{
LString s;
if (Str && s.Set(Str->Str, Str->Len))
Strupr(s.Get());
return s;
}
void Swap(LString &s)
{
LSwap(Str, s.Str);
}
/// Gets the character at 'index'
int operator() (ssize_t index) const
{
if (!Str)
return 0;
char *c = Str->Str;
if (index < 0)
{
size_t idx = Str->Len + index;
return c[idx];
}
else if (index < (int)Str->Len)
{
return c[index];
}
return 0;
}
/// Gets the string between at 'start' and 'end' (not including the end'th character)
LString operator() (ptrdiff_t start, ptrdiff_t end)
{
LString s;
if (Str)
{
ptrdiff_t start_idx = start < 0 ? Str->Len + start + 1 : start;
if (start_idx >= 0 && (uint32_t)start_idx < Str->Len)
{
ptrdiff_t end_idx = end < 0 ? Str->Len + end + 1 : end;
if (end_idx >= start_idx && (uint32_t)end_idx <= Str->Len)
s.Set(Str->Str + start_idx, end_idx - start_idx);
}
}
return s;
}
/// Strip off any leading and trailing characters from 'set' (or whitespace if NULL)
LString Strip(const char *set = NULL)
{
LString ret;
_strip(ret, set, true, true);
return ret;
}
/// Strip off any leading characters from 'set' (or whitespace if NULL)
LString LStrip(const char *set = NULL)
{
LString ret;
_strip(ret, set, true, false);
return ret;
}
/// Strip off any trailing characters from 'set' (or whitespace if NULL)
LString RStrip(const char *set = NULL)
{
LString ret;
_strip(ret, set, false, true);
return ret;
}
/// Prints a formatted string to this object
int Printf(const char *Fmt, ...)
{
Empty();
va_list Arg;
va_start(Arg, Fmt);
int Bytes = Printf(Arg, Fmt);
va_end(Arg);
return Bytes;
}
// Formats a string and returns it
static LString Fmt(const char *Fmt, ...)
{
LString s;
va_list Arg;
va_start(Arg, Fmt);
s.Printf(Arg, Fmt);
va_end(Arg);
return s;
}
/// Prints a varargs string
int Printf(va_list &Arg, const char *Fmt)
{
Empty();
return LPrintf(*this, Fmt, Arg);
}
static LString Escape(const char *In, ssize_t Len = -1, const char *Chars = "\r\n\b\\\'\"", char hexMode = 'x')
{
LString s;
if (In && Chars)
{
char Buf[256];
int Ch = 0;
if (Len < 0)
Len = strlen(In);
while (Len-- > 0)
{
if (Ch > sizeof(Buf)-16)
{
// Buffer full, add substring to 's'
Buf[Ch] = 0;
s += Buf;
Ch = 0;
}
if (strchr(Chars, *In))
{
Buf[Ch++] = '\\';
switch (*In)
{
#undef EscChar
#define EscChar(from, to) \
case from: Buf[Ch++] = to; break
EscChar('\n', 'n');
EscChar('\r', 'r');
EscChar('\\', '\\');
EscChar('\b', 'b');
EscChar('\a', 'a');
EscChar('\t', 't');
EscChar('\v', 'v');
EscChar('\'', '\'');
EscChar('\"', '\"');
EscChar('&', '&');
EscChar('?', '?');
EscChar(' ', ' ');
#undef EscChar
default:
if (hexMode == 'u')
Ch += sprintf_s(Buf+Ch, sizeof(Buf)-Ch, "u%04x", *In);
else
Ch += sprintf_s(Buf+Ch, sizeof(Buf)-Ch, "x%02x", *In);
break;
}
}
else Buf[Ch++] = *In;
In++;
}
if (Ch > 0)
{
Buf[Ch] = 0;
s += Buf;
}
}
return s;
}
LString Escape()
{
return LString::Escape(Get(), Length());
}
template
static LString UnEscape(const T *In, ssize_t Len = -1)
{
if (!In)
return LString();
LString s;
if (Len < 0)
// As memory allocation/copying around data is far slower then
// just scanning the string for size... don't try and chunk the
// processing.
Len = Strlen(In);
if (!s.Length(Len))
return LString();
auto *Out = s.Get();
auto *End = In + Len;
auto DoHex = [&](int chars)
{
int buf = 0;
for (int i=0; i= '0' && *In <= '9')
buf |= *In - '0';
else if (*In >= 'a' && *In <= 'f')
buf |= *In - 'a' + 10;
else if (*In >= 'A' && *In <= 'F')
buf |= *In - 'A' + 10;
else
return;
In++;
}
*Out++ = buf;
In--;
};
while (In < End)
{
if (*In == '\\')
{
In++;
switch (*In)
{
case 'n':
case 'N':
*Out++ = '\n';
break;
case 'r':
case 'R':
*Out++ = '\r';
break;
case 'b':
case 'B':
*Out++ = '\b';
break;
case 't':
case 'T':
*Out++ = '\t';
break;
default:
*Out++ = *In;
break;
case 0:
break;
case 'x':
case 'X':
In++; // consume the 'x'
DoHex(2);
break;
case 'u':
case 'U':
In++; // consume the 'u'
DoHex(4);
break;
}
if (*In)
In++;
else
break;
}
else
*Out++ = *In++;
}
// Trim excess size off string
s.Length(Out - s.Get());
return s;
}
static LString UnEscape(LString s) { return UnEscape(s.Get(), s.Length()); }
#if defined(__GTK_H__)
#elif defined(MAC) // && __COREFOUNDATION_CFBASE__
LString(const CFStringRef r)
{
Str = NULL;
*this = r;
}
LString &operator =(CFStringRef r)
{
if (r)
{
CFIndex length = CFStringGetLength(r);
CFRange range = CFRangeMake(0, length);
CFIndex usedBufLen = 0;
CFIndex slen = CFStringGetBytes(r,
range,
kCFStringEncodingUTF8,
'?',
false,
NULL,
0,
&usedBufLen);
if (Set(NULL, usedBufLen))
{
slen = CFStringGetBytes( r,
range,
kCFStringEncodingUTF8,
'?',
false,
(UInt8*)Str->Str,
Str->Len,
&usedBufLen);
Str->Str[usedBufLen] = 0; // NULL terminate
}
}
return *this;
}
CFStringRef CreateStringRef()
{
char *s = Get();
if (!s)
return NULL;
return CFStringCreateWithCString(kCFAllocatorDefault, s, kCFStringEncodingUTF8);
}
#ifdef __OBJC__
NSString *NsStr()
{
if (Str)
return [[NSString alloc] initWithBytes:Str->Str length:Str->Len encoding:NSUTF8StringEncoding];
return nil;
}
bool operator=(NSString *const s)
{
*this = [s UTF8String];
return !IsEmpty();
}
LString(NSString *const s)
{
Str = NULL;
*this = [s UTF8String];
}
#endif
#endif
};
diff --git a/include/lgi/common/Unicode.h b/include/lgi/common/Unicode.h
--- a/include/lgi/common/Unicode.h
+++ b/include/lgi/common/Unicode.h
@@ -1,889 +1,893 @@
//
// Unicode.h
//
// Created by Matthew Allen on 1/08/15.
//
#ifndef _LgiUnicode_h
#define _LgiUnicode_h
#include "LgiInc.h"
#ifdef MAC
#define REG
#else
#define REG register
#endif
typedef unsigned char uint8_t;
typedef signed char int8;
typedef signed short int16;
typedef unsigned short uint16;
#if !HAIKU32
typedef signed int int32;
#endif
typedef unsigned int uint32_t;
#ifdef _MSC_VER
typedef signed __int64 int64;
typedef unsigned __int64 uint64;
#ifdef _WIN64
typedef signed __int64 ssize_t;
#else
typedef signed int ssize_t;
#endif
#elif defined(LINUX)
typedef int64_t int64;
typedef uint64_t uint64;
#elif !defined(HAIKU)
typedef signed long long int64;
typedef unsigned long long uint64;
#endif
// Various unicode code points of interest
#define UNICODE_ZERO_WIDTH_SPACE 0x200B
#define UNICODE_ZERO_WIDTH_JOINER 0x200d
// Defines for decoding UTF8
#define IsUtf8_1Byte(c) ( ((uint8_t)(c) & 0x80) == 0x00 )
#define IsUtf8_2Byte(c) ( ((uint8_t)(c) & 0xe0) == 0xc0 )
#define IsUtf8_3Byte(c) ( ((uint8_t)(c) & 0xf0) == 0xe0 )
#define IsUtf8_4Byte(c) ( ((uint8_t)(c) & 0xf8) == 0xf0 )
#define IsUtf8_Lead(c) ( ((uint8_t)(c) & 0xc0) == 0xc0 )
#define IsUtf8_Trail(c) ( ((uint8_t)(c) & 0xc0) == 0x80 )
// Stand-alone functions
/// Convert a single utf-8 char to utf-32 or returns -1 on error.
inline int32 LgiUtf8To32(uint8_t *&i, ssize_t &Len)
{
int32 Out = 0;
#define InvalidUtf() { Len--; i++; return -1; }
if (Len > 0)
{
if (!*i)
{
Len = 0;
return 0;
}
if (IsUtf8_1Byte(*i))
{
// 1 byte UTF-8
Len--;
return *i++;
}
else if (IsUtf8_2Byte(*i))
{
// 2 byte UTF-8
if (Len > 1)
{
Out = ((int)(*i++ & 0x1f)) << 6;
Len--;
if (IsUtf8_Trail(*i))
{
Out |= *i++ & 0x3f;
Len--;
}
else InvalidUtf()
}
}
else if (IsUtf8_3Byte(*i))
{
// 3 byte UTF-8
if (Len > 2)
{
Out = ((int)(*i++ & 0x0f)) << 12;
Len--;
if (IsUtf8_Trail(*i))
{
Out |= ((int)(*i++ & 0x3f)) << 6;
Len--;
if (IsUtf8_Trail(*i))
{
Out |= *i++ & 0x3f;
Len--;
}
else InvalidUtf()
}
else InvalidUtf()
}
}
else if (IsUtf8_4Byte(*i))
{
// 4 byte UTF-8
if (Len > 3)
{
Out = ((int)(*i++ & 0x07)) << 18;
Len--;
if (IsUtf8_Trail(*i))
{
Out |= ((int)(*i++ & 0x3f)) << 12;
Len--;
if (IsUtf8_Trail(*i))
{
Out |= ((int)(*i++ & 0x3f)) << 6;
Len--;
if (IsUtf8_Trail(*i))
{
Out |= *i++ & 0x3f;
Len--;
}
else InvalidUtf()
}
else InvalidUtf()
}
else InvalidUtf()
}
}
else InvalidUtf()
}
return Out;
}
/// Convert a single utf-32 char to utf-8
inline bool LgiUtf32To8(uint32_t u32, uint8_t *&outBuf, ssize_t &outBufSize, bool warn = true)
{
if ((u32 & ~0x7f) == 0)
{
if (outBufSize > 0)
{
*outBuf++ = u32;
outBufSize--;
return true;
}
}
else if ((u32 & ~0x7ff) == 0)
{
if (outBufSize > 1)
{
*outBuf++ = 0xc0 | (u32 >> 6);
*outBuf++ = 0x80 | (u32 & 0x3f);
outBufSize -= 2;
return true;
}
}
else if ((u32 & 0xffff0000) == 0)
{
if (outBufSize > 2)
{
*outBuf++ = 0xe0 | (u32 >> 12);
*outBuf++ = 0x80 | ((u32 & 0x0fc0) >> 6);
*outBuf++ = 0x80 | (u32 & 0x3f);
outBufSize -= 3;
return true;
}
}
else
{
if (outBufSize > 3)
{
*outBuf++ = 0xf0 | (u32 >> 18);
*outBuf++ = 0x80 | ((u32 & 0x3f000) >> 12);
*outBuf++ = 0x80 | ((u32 & 0xfc0) >> 6);
*outBuf++ = 0x80 | (u32 & 0x3f);
outBufSize -= 4;
return true;
}
}
if (warn)
LAssert(!"Buffer size too small");
return false;
}
// Defined for decoding UTF16
#define IsUtf16_Lead(c) ( ((uint16)(c) & 0xfc00) == 0xD800 )
#define IsUtf16_Trail(c) ( ((uint16)(c) & 0xfc00) == 0xDc00 )
/// Convert a single utf-16 char to utf-32
inline uint32_t LgiUtf16To32(const uint16_t *&i, ssize_t &Bytes)
{
if (Bytes > 1)
{
if (!*i)
{
Bytes = 0;
return 0;
}
int n = *i & 0xfc00;
if (n == 0xd800 || n == 0xdc00)
{
// 2 word UTF
if (Bytes > 3)
{
Bytes -= sizeof(uint16)<<1;
int w = (*i & 0x3c0) >> 6;
int zy = *i++ & 0x3f;
return ((w + 1) << 16) | (zy << 10) | (*i++ & 0x3ff);
}
}
// 1 word UTF
Bytes -= sizeof(uint16);
return *i++;
}
return 0;
}
/// Convert a single utf-32 char to utf-16
inline bool LgiUtf32To16(uint32_t c, uint16_t *&i, ssize_t &Len)
{
if (c >= 0x10000)
{
// 2 word UTF
if (Len < 4)
return false;
int w = c - 0x10000;
*i++ = 0xd800 + (w >> 10);
*i++ = 0xdc00 + (c & 0x3ff);
Len -= sizeof(*i) << 1;
}
else
{
if (Len < 2)
return false;
if (c > 0xD7FF && c < 0xE000)
return false;
// 1 word UTF
*i++ = c;
Len -= sizeof(*i);
return true;
}
return false;
}
/// Seeks the pointer 'Ptr' to the next utf-8 character
template
inline bool LgiNextUtf8(T *&p)
{
T *old = p;
if (IsUtf8_Lead(*p))
{
p++;
while (IsUtf8_Trail(*p))
p++;
}
else p++;
return p > old;
}
/// Seeks the pointer 'Ptr' to the previous utf-8 character
template
inline void LgiPrevUtf8(T *&p)
{
p--;
while (IsUtf8_Trail(*p))
p--;
}
/// Pointer to utf-8 string
class LgiClass LUtf8Ptr
{
protected:
uint8_t *Ptr;
public:
static bool Warn;
LUtf8Ptr(const void *p = NULL);
/// Assign a new pointer to the string
LUtf8Ptr &operator =(char *s) { Ptr = (uint8_t*)s; return *this; }
/// Assign a new pointer to the string
LUtf8Ptr &operator =(uint8_t *s) { Ptr = s; return *this; }
/// \returns the current character in the string or -1 on error.
operator int32();
/// Change the character at the point, the pointer will advance to the end
/// of the character written.
LUtf8Ptr &operator =(uint32_t ch);
/// Seeks forward
LUtf8Ptr &operator ++();
LUtf8Ptr &operator ++(const int i);
LUtf8Ptr &operator +=(const ssize_t n);
/// Seeks 1 character backward
LUtf8Ptr &operator --();
LUtf8Ptr &operator --(const int i);
LUtf8Ptr &operator -=(const ssize_t n);
// Comparison
bool operator <(const LUtf8Ptr &p) { return Ptr < p.Ptr; }
bool operator <=(const LUtf8Ptr &p) { return Ptr <= p.Ptr; }
bool operator >(const LUtf8Ptr &p) { return Ptr > p.Ptr; }
bool operator >=(const LUtf8Ptr &p) { return Ptr >= p.Ptr; }
bool operator ==(const LUtf8Ptr &p) { return Ptr == p.Ptr; }
bool operator !=(const LUtf8Ptr &p) { return Ptr != p.Ptr; }
ptrdiff_t operator -(const LUtf8Ptr &p) { return Ptr - p.Ptr; }
/// Gets the bytes between the cur pointer and the end of the buffer or string.
int GetBytes();
/// Gets the characters between the cur pointer and the end of the buffer or string.
int GetChars();
/// Encodes a utf-8 char at the current location and moves the pointer along
void Add(wchar_t c);
/// Returns the current pointer.
uint8_t *GetPtr() { return Ptr; }
};
/// Unicode string class. Allows traversing a utf-8 strings.
class LgiClass LUtf8Str : public LUtf8Ptr
{
// Complete buffer
uint8_t *Start;
uint8_t *End;
LUtf8Ptr Cur;
bool Own;
void Empty();
public:
/// Constructor
LUtf8Str
(
/// The string pointer to start with
char *utf,
/// The number of bytes containing characters, or -1 if NULL terminated.
int bytes = -1,
/// Copy the string first
bool Copy = false
);
/// Constructor
LUtf8Str
(
/// The string pointer to start with. A utf-8 copy of the string will be created.
wchar_t *wide,
/// The number of wide chars, or -1 if NULL terminated.
int chars = -1
);
~LUtf8Str();
/// Assign a new pointer to the string
LUtf8Str &operator =(char *s);
/// Allocates a block of memory containing the wide representation of the string.
wchar_t *ToWide();
/// \returns true if the class seems to be valid.
bool Valid();
/// \returns true if at the start
bool IsStart();
/// \returns true if at the end
bool IsEnd();
};
// Converts character to lower case
template
T Tolower(T ch)
{
if (ch >= 'A' && ch <= 'Z')
return ch - 'A' + 'a';
return ch;
}
template
T *Strlwr(T *Str)
{
if (!Str)
return NULL;
for (T *s = Str; *s; s++)
{
if (*s >= 'A' && *s <= 'Z')
*s = *s - 'A' + 'a';
}
return Str;
}
// Converts character to upper case
template
T Toupper(T ch)
{
if (ch >= 'a' && ch <= 'z')
return ch - 'a' + 'A';
return ch;
}
template
T *Strupr(T *Str)
{
if (!Str)
return NULL;
for (T *s = Str; *s; s++)
{
if (*s >= 'a' && *s <= 'z')
*s = *s - 'a' + 'A';
}
return Str;
}
// Finds the length of the string in characters
template
ssize_t Strlen(const T *str)
{
if (!str)
return 0;
REG const T *s = str;
while (*s)
s++;
return s - str;
}
// Templated version of NewStr/NewStrW
// Duplicates a string in heap memory.
template
T *Strdup(const T *s, ssize_t len = -1)
{
if (!s) return NULL;
if (len < 0) len = Strlen(s);
T *n = new T[len+1];
if (!n) return NULL;
memcpy(n, s, sizeof(T) * len);
n[len] = 0;
return n;
}
// Compares two strings, case sensitive
template
int Strcmp(const T *str_a, const T *str_b)
{
if (!str_a || !str_b)
return -1;
REG const T *a = str_a;
REG const T *b = str_b;
while (true)
{
if (!*a || !*b || *a != *b)
return *a - *b;
a++;
b++;
}
return 0;
}
// Compares the first 'len' chars of two strings, case sensitive
template
int Strncmp(const T *str_a, const T *str_b, ssize_t len)
{
if (!str_a || !str_b)
return -1;
REG const T *a = str_a;
REG const T *b = str_b;
REG const T *end = a + len;
while (a < end)
{
if (!*a || !*b || *a != *b)
return *a - *b;
a++;
b++;
}
return 0;
}
// Compares two strings, case insensitive
template
int Stricmp(const T *str_a, const T *str_b)
{
if (!str_a || !str_b)
return -1;
REG const T *a = str_a;
REG const T *b = str_b;
REG T ach, bch;
while (true)
{
ach = Tolower(*a);
bch = Tolower(*b);
if (!ach || !bch || ach != bch)
return ach - bch;
a++;
b++;
}
return 0;
}
// Compares the first 'len' chars of two strings, case insensitive
template
int Strnicmp(const T *str_a, const T *str_b, ssize_t len)
{
if (!str_a || !str_b || len == 0)
return -1;
REG const T *a = str_a;
REG const T *b = str_b;
REG const T *end = a + len;
REG T ach, bch;
while (a < end)
{
ach = Tolower(*a);
bch = Tolower(*b);
if (!ach || !bch || ach != bch)
return ach - bch;
a++;
b++;
}
return 0;
}
/// Copies a string
template
T *Strcpy(T *dst, ssize_t dst_len, const I *src)
{
if (!dst || !src || dst_len == 0)
return NULL;
REG T *d = dst;
REG T *end = d + dst_len - 1; // leave 1 char for NULL terminator
REG const I *s = src;
while (d < end && *s)
{
*d++ = *s++;
}
*d = 0; // NULL terminate
return dst;
}
/// Finds the first instance of a character in the string
template
T *Strchr(T *str, int ch)
{
if (!str)
return NULL;
for (REG T *s = str; *s; s++)
{
if (*s == ch)
return s;
}
return NULL;
}
/// Finds the first instance of a character in the string
template
T *Strnchr(T *str, int ch, size_t len)
{
if (!str || len == 0)
return NULL;
REG T *e = str + len;
for (REG T *s = str; s < e; s++)
{
if (*s == ch)
return s;
}
return NULL;
}
/// Finds the last instance of a character in the string
template
T *Strrchr(T *str, int ch)
{
if (!str)
return NULL;
T *last = NULL;
for (REG T *s = str; *s; s++)
{
if (*s == ch)
last = s;
}
return last;
}
/// Appends a string to another
template
T *Strcat(T *dst, int dst_len, const T *postfix)
{
if (!dst || !postfix || dst_len < 1)
return NULL;
// Find the end of the string to append to
while (*dst)
{
dst++;
dst_len--;
}
// Reuse string copy at this point
Strcpy(dst, dst_len, postfix);
// Return the start of the complete string
return dst;
}
/// Searches the string 'Data' for the 'Value' in a case insensitive manner
template
T *Stristr(const T *Data, const T *Value)
{
if (!Data || !Value)
return NULL;
const T v = Tolower(*Value);
while (*Data)
{
if (Tolower(*Data) == v)
{
int i;
for (i=1; Data[i] && Tolower(Data[i]) == Tolower(Value[i]); i++)
;
if (Value[i] == 0)
return (T*)Data;
}
Data++;
}
return NULL;
}
/// Searches the string 'Data' for the 'Value' in a case insensitive manner
template
T *Strstr(const T *Data, const T *Value)
{
if (!Data || !Value)
return NULL;
const T v = *Value;
while (*Data)
{
if (*Data == v)
{
int i;
for (i=1; Data[i] && Data[i] == Value[i]; i++)
;
if (Value[i] == 0)
return (T*)Data;
}
Data++;
}
return NULL;
}
/// Searches the string 'Data' for the 'Value' in a case insensitive manner
template
T *Strnstr(const T *Data, const T *Value, ssize_t DataLen)
{
if (!Data || !Value)
return NULL;
const T v = *Value;
ptrdiff_t ValLen = Strlen(Value);
if (ValLen > DataLen)
return NULL;
while (*Data && DataLen >= ValLen)
{
if (*Data == v)
{
int i;
for (i=1; Data[i] && Data[i] == Value[i]; i++)
;
if (Value[i] == 0)
return (T*)Data;
}
Data++;
DataLen--;
}
return NULL;
}
/// Searches the string 'Data' for the 'Value' in a case insensitive manner
template
T *Strnistr(const T *Data, const T *Value, ptrdiff_t DataLen)
{
if (!Data || !Value)
return NULL;
const T v = Tolower(*Value);
ptrdiff_t ValLen = Strlen(Value);
if (ValLen > DataLen)
return NULL;
while (*Data && DataLen >= ValLen)
{
if (Tolower(*Data) == v)
{
int i;
for (i=1; Data[i] && Tolower(Data[i]) == Tolower(Value[i]); i++)
;
if (Value[i] == 0)
return (T*)Data;
}
Data++;
DataLen--;
}
return NULL;
}
/// Converts a string to int64 (base 10)
template
int64 Atoi(const T *s, int Base = 10, int64 DefaultValue = -1)
{
if (!s)
return DefaultValue;
bool Minus = false;
if (*s == '-')
{
Minus = true;
s++;
}
else if (*s == '+')
s++;
int64 v = 0;
const T *Start = s;
if (Base <= 10)
{
while (*s >= '0' && *s <= '9')
{
int d = *s - '0';
v *= Base;
v += d;
s++;
}
}
else
{
if (*s == '0' && Tolower(s[1]) == 'x')
s += 2;
int ValidChars = Base > 10 ? Base - 10 : 0;
while (*s)
{
int d;
if (*s >= '0' && *s <= '9')
d = *s - '0';
else if (*s >= 'a' && *s <= 'a' + ValidChars)
d = *s - 'a' + 10;
else if (*s >= 'A' && *s <= 'A' + ValidChars)
d = *s - 'A' + 10;
else
break;
v *= Base;
v += d;
s++;
}
}
if (s == Start)
return DefaultValue;
return Minus ? -v : v;
}
/// Works out the UTF8 length of a wide char string
inline size_t WideToUtf8Len(const wchar_t *s, ssize_t wchars = -1)
{
if (!s) return 0;
size_t Out = 0;
uint8_t Buf[6];
#ifdef _MSC_VER
const uint16 *i = (const uint16*) s;
ssize_t Len = wchars >= 0 ? wchars << 1 : 0x7fffffff;
for (uint32_t ch; ch = LgiUtf16To32(i, Len); )
{
uint8_t *b = Buf;
ssize_t len = sizeof(Buf);
if (!LgiUtf32To8(ch, b, len))
break;
Out += sizeof(Buf) - len;
}
#else
const wchar_t *end = wchars < 0 ? NULL : s + wchars;
for (uint32_t ch = 0;
(
wchars < 0
||
s < end
)
&&
(ch = *s);
s++)
{
uint8_t *b = Buf;
ssize_t len = sizeof(Buf);
if (!LgiUtf32To8(ch, b, len))
break;
Out += sizeof(Buf) - len;
}
#endif
return Out;
}
/// Converts a utf-8 string into a wide character string
/// \ingroup Text
LgiFunc wchar_t *Utf8ToWide
(
/// Input string
const char *In,
/// [Optional] Size of 'In' in 'chars' or -1 for NULL terminated
ssize_t InLen = -1
);
/// Converts a wide character string into a utf-8 string
/// \ingroup Text
LgiFunc char *WideToUtf8
(
/// Input string
const wchar_t *In,
/// [Optional] Number of wchar_t's in the input or -1 for NULL terminated
ptrdiff_t InLen = -1
);
+/// Return true if the string is valid utf-8
+/// \ingroup Text
+LgiClass bool LIsUtf8(const char *s, ssize_t len = -1);
+
#endif
diff --git a/include/lgi/common/Variant.h b/include/lgi/common/Variant.h
--- a/include/lgi/common/Variant.h
+++ b/include/lgi/common/Variant.h
@@ -1,473 +1,474 @@
/**
\file
\author Matthew Allen
\brief Variant class.\n
Copyright (C), Matthew Allen
*/
#ifndef __LVariant_H__
#define __LVariant_H__
#undef Bool
#include "lgi/common/DateTime.h"
#include "lgi/common/Containers.h"
#include "lgi/common/HashTable.h"
#include "lgi/common/LgiString.h"
class LCompiledCode;
#if !defined(_MSC_VER) && !defined(LINUX) && (!HAIKU64)
// For all Mac and Haiku32
#define LVARIANT_SIZET 1
#define LVARIANT_SSIZET 1
#endif
/// The different types the varient can be.
/// \sa LVariant::TypeToString to convert to string.
enum LVariantType
{
// Main types
/// Null type (0)
GV_NULL,
/// 32-bit integer (1)
GV_INT32,
/// 64-bit integer (2)
GV_INT64,
/// true or false boolean. (3)
GV_BOOL,
/// C++ double (4)
GV_DOUBLE,
/// Null terminated string value (5)
GV_STRING,
/// Block of binary data (6)
GV_BINARY,
/// List of LVariant (7)
GV_LIST,
/// Pointer to LDom object (8)
GV_DOM,
/// DOM reference, ie. a variable in a DOM object (9)
GV_DOMREF,
/// Untyped pointer (10)
GV_VOID_PTR,
/// LDateTime class. (11)
GV_DATETIME,
/// Hash table class, containing pointers to LVariants (12)
GV_HASHTABLE,
// Scripting language operator (13)
GV_OPERATOR,
// Custom scripting lang type (14)
GV_CUSTOM,
// Wide string (15)
GV_WSTRING,
// LSurface ptr (16)
GV_LSURFACE,
/// Pointer to LView (17)
GV_LVIEW,
/// Pointer to LMouse (18)
GV_LMOUSE,
/// Pointer to LKey (19)
GV_LKEY,
/// Pointer to LStream (20)
GV_STREAM,
/// The maximum value for the variant type. (21)
/// (This is used by the scripting engine to refer to a LVariant itself)
GV_MAX,
};
/// Language operators
enum LOperator
{
OpNull,
OpAssign,
OpPlus,
OpUnaryPlus,
OpMinus,
OpUnaryMinus,
OpMul,
OpDiv,
OpMod,
OpLessThan,
OpLessThanEqual,
OpGreaterThan,
OpGreaterThanEqual,
OpEquals,
OpNotEquals,
OpPlusEquals,
OpMinusEquals,
OpMulEquals,
OpDivEquals,
OpPostInc,
OpPostDec,
OpPreInc,
OpPreDec,
OpAnd,
OpOr,
OpNot,
};
class LgiClass LCustomType : public LDom
{
protected:
struct CustomField : public LDom
{
ssize_t Offset;
ssize_t Bytes;
ssize_t ArrayLen;
LVariantType Type;
LString Name;
LCustomType *Nested;
const char *GetClass() override { return "LCustomType.CustomField"; }
ssize_t Sizeof();
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
};
public:
struct Method : public LDom
{
LString Name;
LArray Params;
size_t Address = -1;
int FrameSize = -1;
const char *GetClass() override { return "LCustomType.Method"; }
};
protected:
// Global vars
int Pack;
size_t Size;
LString Name;
// Fields
LArray Flds;
LHashTbl, int> FldMap;
// Methods
LArray Methods;
LHashTbl, Method*> MethodMap;
// Private methods
ssize_t PadSize();
public:
LCustomType(const char *name, int pack = 1);
LCustomType(const char16 *name, int pack = 1);
~LCustomType();
const char *GetClass() override { return "LCustomType"; }
size_t Sizeof();
const char *GetName() { return Name; }
ssize_t Members() { return Flds.Length(); }
int AddressOf(const char *Field);
int IndexOf(const char *Field);
bool DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen = 1);
bool DefineField(const char *Name, LCustomType *Type, int ArrayLen = 1);
Method *DefineMethod(const char *Name, LArray &Params, size_t Address);
Method *GetMethod(const char *Name);
// Field access. You can't use the LDom interface to get/set member variables because
// there is no provision for the 'This' pointer.
bool Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex = 0);
bool Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex = 0);
// Dom access. However the DOM can be used to access information about the type itself.
// Which doesn't need a 'This' pointer.
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LScriptArguments &Args) override;
};
/// A class that can be different types
class LgiClass LVariant
{
public:
typedef LHashTbl,LVariant*> LHash;
/// The type of the variant
LVariantType Type;
/// The value of the variant
union
{
/// Valid when Type == #GV_INT32
int Int;
/// Valid when Type == #GV_BOOL
bool Bool;
/// Valid when Type == #GV_INT64
int64 Int64;
/// Valid when Type == #GV_DOUBLE
double Dbl;
/// Valid when Type == #GV_STRING
char *String;
/// Valid when Type == #GV_WSTRING
char16 *WString;
/// Valid when Type == #GV_DOM
LDom *Dom;
/// Valid when Type is #GV_VOID_PTR, #GV_LVIEW, #GV_LMOUSE or #GV_LKEY
void *Ptr;
/// Valid when Type == #GV_BINARY
struct _Binary
{
ssize_t Length;
void *Data;
} Binary;
/// Valid when Type == #GV_LIST
List *Lst;
/// Valid when Type == #GV_HASHTABLE
LHash *Hash;
/// Valid when Type == #GV_DATETIME
LDateTime *Date;
/// Valid when Type == #GV_CUSTOM
struct _Custom
{
LCustomType *Dom;
uint8_t *Data;
bool operator == (_Custom &c)
{
return Dom == c.Dom &&
Data == c.Data;
}
} Custom;
/// Valid when Type == #GV_DOMREF
struct _DomRef
{
/// The pointer to the dom object
LDom *Dom;
/// The name of the variable to set/get in the dom object
char *Name;
} DomRef;
/// Valid when Type == #GV_OPERATOR
LOperator Op;
/// Valid when Type == #GV_LSURFACE
struct
{
class LSurface *Ptr;
bool Own;
LSurface *Release()
{
auto p = Ptr;
Ptr = NULL;
Own = false;
return p;
}
} Surface;
/// Valid when Type == #GV_STREAM
struct
{
class LStreamI *Ptr;
bool Own;
LStreamI *Release()
{
auto p = Ptr;
Ptr = NULL;
Own = false;
return p;
}
} Stream;
/// Valid when Type == #GV_LVIEW
class LView *View;
/// Valid when Type == #GV_LMOUSE
class LMouse *Mouse;
/// Valid when Type == #GV_LKEY
class LKey *Key;
} Value;
/// Constructor to null
LVariant();
/// Constructor for integers
LVariant(int32_t i);
LVariant(uint32_t i);
LVariant(int64_t i);
LVariant(uint64_t i);
#if LVARIANT_SIZET
LVariant(size_t i);
#endif
#if LVARIANT_SSIZET
LVariant(ssize_t i);
#endif
/// Constructor for double
LVariant(double i);
/// Constructor for string
LVariant(const char *s);
/// Constructor for wide string
LVariant(const char16 *s);
/// Constructor for ptr
LVariant(void *p);
/// Constructor for DOM ptr
LVariant(LDom *p);
/// Constructor for DOM variable reference
LVariant(LDom *p, char *name);
/// Constructor for date
LVariant(const LDateTime *d);
/// Constructor for variant
LVariant(LVariant const &v);
/// Constructor for operator
LVariant(LOperator Op);
/// Destructor
~LVariant();
/// Assign bool value
LVariant &operator =(bool i);
/// Assign an integer value
LVariant &operator =(int32_t i);
LVariant &operator =(uint32_t i);
LVariant &operator =(int64_t i);
LVariant &operator =(uint64_t i);
#if LVARIANT_SIZET
LVariant &operator =(size_t i);
#endif
#if LVARIANT_SSIZET
LVariant &operator =(ssize_t i);
#endif
/// Assign double value
LVariant &operator =(double i);
/// Assign string value (makes a copy)
LVariant &operator =(const char *s);
/// Assign a wide string value (makes a copy)
LVariant &operator =(const char16 *s);
/// Assign another variant value
LVariant &operator =(LVariant const &i);
/// Assign value to a void ptr
LVariant &operator =(void *p);
/// Assign value to DOM ptr
LVariant &operator =(LDom *p);
/// Assign value to be a date/time
LVariant &operator =(const LDateTime *d);
LVariant &operator =(class LView *p);
LVariant &operator =(class LMouse *p);
LVariant &operator =(class LKey *k);
LVariant &operator =(class LStream *s);
bool operator ==(LVariant &v);
bool operator !=(LVariant &v) { return !(*this == v); }
/// Sets the value to a DOM variable reference
bool SetDomRef(LDom *obj, char *name);
/// Sets the value to a copy of block of binary data
bool SetBinary(ssize_t Len, void *Data, bool Own = false);
/// Sets the value to a copy of the list
List *SetList(List *Lst = NULL);
/// Sets the value to a hashtable
bool SetHashTable(LHash *Table = NULL, bool Copy = true);
/// Set the value to a surface
bool SetSurface(class LSurface *Ptr, bool Own);
/// Set the value to a stream
bool SetStream(class LStreamI *Ptr, bool Own);
/// Returns the string if valid (will convert a GV_WSTRING to utf)
char *Str();
/// Returns the value as an LString
LString LStr();
/// Returns a wide string if valid (will convert a GV_STRING to wide)
char16 *WStr();
/// Returns the string, releasing ownership of the memory to caller and
/// changing this LVariant to GV_NULL.
char *ReleaseStr();
/// Returns the wide string, releasing ownership of the memory to caller and
/// changing this LVariant to GV_NULL.
char16 *ReleaseWStr();
/// Sets the variant to a heap string and takes ownership of it
bool OwnStr(char *s);
/// Sets the variant to a wide heap string and takes ownership of it
bool OwnStr(char16 *s);
/// Sets the variant to NULL
void Empty();
/// Returns the byte length of the data
int64 Length();
/// True if currently a int
bool IsInt();
/// True if currently a bool
bool IsBool();
/// True if currently a double
bool IsDouble();
/// True if currently a string
bool IsString();
/// True if currently a binary block
bool IsBinary();
/// True if currently null
bool IsNull();
/// Changes the variant's type, maintaining the value where possible. If
/// no conversion is available then nothing happens.
LVariant &Cast(LVariantType NewType);
/// Casts the value to int, from whatever source type. The
/// LVariant type does not change after calling this.
int32 CastInt32() const;
/// Casts the value to a 64 bit int, from whatever source type. The
/// LVariant type does not change after calling this.
int64 CastInt64() const;
/// Casts the value to double, from whatever source type. The
/// LVariant type does not change after calling this.
double CastDouble() const;
/// Cast to a string from whatever source type, the LVariant will
/// take the type GV_STRING after calling this. This is because
/// returning a static string is not thread safe.
char *CastString();
/// Casts to a DOM ptr
LDom *CastDom() const;
/// Casts to a boolean. You probably DON'T want to use this function. The
/// behavior for strings -> bool is such that if the string is value it
/// always evaluates to true, and false if it's not a valid string. Commonly
/// what you want is to evaluate whether the string is zero or non-zero in
/// which cast you should use "CastInt32() != 0" instead.
bool CastBool() const;
/// Returns the pointer if available.
void *CastVoidPtr() const;
/// Returns a LView
LView *CastView() const { return Type == GV_LVIEW ? Value.View : NULL; }
/// List insert
bool Add(LVariant *v, int Where = -1);
/// Converts the varient type to a string
static const char *TypeToString(LVariantType t);
/// Converts an operator to a string
static const char *OperatorToString(LOperator op);
/// Converts the value to a string description include type.
LString ToString();
};
// General collection of arguments and a return value
class LgiClass LScriptArguments : public LArray
{
friend class LScriptEngine;
friend class LVirtualMachine;
friend class LVirtualMachinePriv;
friend struct ExecuteFunctionState;
LVirtualMachineI *Vm = NULL;
class LStream *Console = NULL;
LVariant *LocalReturn = NULL; // Owned by this instance
LVariant *Return = NULL;
const char *File = NULL;
int Line = 0;
LString ExceptionMsg;
ssize_t Address;
public:
static LStream NullConsole;
LScriptArguments(LVirtualMachineI *vm, LVariant *ret = NULL, LStream *console = NULL, ssize_t address = -1);
~LScriptArguments();
LVirtualMachineI *GetVm() { return Vm; }
void SetVm(LVirtualMachineI *vm) { Vm = vm; }
LVariant *GetReturn() { return Return; } // Must never be NULL.
LStream *GetConsole() { return Console; }
bool HasException() { return File != NULL || ExceptionMsg.Get() || Line != 0; }
bool Throw(const char *File, int Line, const char *Msg, ...);
// Accessor shortcuts
const char *StringAt(size_t i);
int32_t Int32At(size_t i, int32_t Default = 0);
int64_t Int64At(size_t i, int64_t Default = 0);
double DoubleAt(size_t i, double Default = 0);
+ LDom *DomAt(size_t i);
};
#endif
diff --git a/lvc/src/VcFolder.cpp b/lvc/src/VcFolder.cpp
--- a/lvc/src/VcFolder.cpp
+++ b/lvc/src/VcFolder.cpp
@@ -1,4991 +1,4991 @@
#include "Lvc.h"
#include "lgi/common/Combo.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/Json.h"
#include "lgi/common/ProgressDlg.h"
#include "resdefs.h"
#ifndef CALL_MEMBER_FN
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
#endif
#define MAX_AUTO_RESIZE_ITEMS 2000
#define PROFILE_FN 0
#if PROFILE_FN
#define PROF(s) Prof.Add(s)
#else
#define PROF(s)
#endif
class TmpFile : public LFile
{
int Status;
LString Hint;
public:
TmpFile(const char *hint = NULL)
{
Status = 0;
if (hint)
Hint = hint;
else
Hint = "_lvc";
}
LFile &Create()
{
LFile::Path p(LSP_TEMP);
p += Hint;
do
{
char s[256];
sprintf_s(s, sizeof(s), "../%s%i.tmp", Hint.Get(), LRand());
p += s;
}
while (p.Exists());
Status = LFile::Open(p.GetFull(), O_READWRITE);
return *this;
}
};
bool TerminalAt(LString Path)
{
#if defined(MAC)
const char *Locations[] = {
"/System/Applications/Utilities/Terminal.app",
"/Applications/Utilities/Terminal.app",
NULL
};
for (size_t i=0; Locations[i]; i++)
{
if (LFileExists(Locations[i]))
{
LString term;
term.Printf("%s/Contents/MacOS/Terminal", Locations[i]);
return LExecute(term, Path);
}
}
#elif defined(WINDOWS)
TCHAR w[MAX_PATH_LEN];
auto r = GetWindowsDirectory(w, CountOf(w));
if (r > 0)
{
LFile::Path p = LString(w);
p += "system32\\cmd.exe";
FileDev->SetCurrentFolder(Path);
return LExecute(p);
}
#elif defined(LINUX)
LExecute("gnome-terminal", NULL, Path);
#endif
return false;
}
int Ver2Int(LString v)
{
auto p = v.Split(".");
int i = 0;
for (auto s : p)
{
auto Int = s.Int();
if (Int < 256)
{
i <<= 8;
i |= (uint8_t)Int;
}
else
{
LAssert(0);
return 0;
}
}
return i;
}
int ToolVersion[VcMax] = {0};
#define DEBUG_READER_THREAD 0
#if DEBUG_READER_THREAD
#define LOG_READER(...) printf(__VA_ARGS__)
#else
#define LOG_READER(...)
#endif
ReaderThread::ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out) : LThread("ReaderThread")
{
Vcs = vcs;
Process = p;
Out = out;
Result = -1;
FilterCount = 0;
// We don't start this thread immediately... because the number of threads is scaled to the system
// resources, particularly CPU cores.
}
ReaderThread::~ReaderThread()
{
Out = NULL;
while (!IsExited())
LSleep(1);
}
const char *HgFilter = "We\'re removing Mercurial support";
const char *CvsKill = "No such file or directory";
int ReaderThread::OnLine(char *s, ssize_t len)
{
switch (Vcs)
{
case VcHg:
{
if (strnistr(s, HgFilter, len))
FilterCount = 4;
if (FilterCount > 0)
{
FilterCount--;
return 0;
}
else if (LString(s, len).Strip().Equals("remote:"))
{
return 0;
}
break;
}
case VcCvs:
{
if (strnistr(s, CvsKill, len))
return -1;
break;
}
default:
break;
}
return 1;
}
bool ReaderThread::OnData(char *Buf, ssize_t &r)
{
LOG_READER("OnData %i\n", (int)r);
#if 1
char *Start = Buf;
for (char *c = Buf; c < Buf + r;)
{
bool nl = *c == '\n';
c++;
if (nl)
{
int Result = OnLine(Start, c - Start);
if (Result < 0)
{
// Kill process and exit thread.
Process->Kill();
return false;
}
if (Result == 0)
{
ssize_t LineLen = c - Start;
ssize_t NextLine = c - Buf;
ssize_t Remain = r - NextLine;
if (Remain > 0)
memmove(Start, Buf + NextLine, Remain);
r -= LineLen;
c = Start;
}
else Start = c;
}
}
#endif
Out->Write(Buf, r);
return true;
}
int ReaderThread::Main()
{
bool b = Process->Start(true, false);
if (!b)
{
LString s("Process->Start failed.\n");
Out->Write(s.Get(), s.Length(), ErrSubProcessFailed);
return ErrSubProcessFailed;
}
char Buf[1024];
ssize_t r;
LOG_READER("%s:%i - starting reader loop, pid=%i\n", _FL, Process->Handle());
while (Process->IsRunning())
{
if (Out)
{
LOG_READER("%s:%i - starting read.\n", _FL);
r = Process->Read(Buf, sizeof(Buf));
LOG_READER("%s:%i - read=%i.\n", _FL, (int)r);
if (r > 0)
{
if (!OnData(Buf, r))
return -1;
}
}
else
{
Process->Kill();
return -1;
break;
}
}
LOG_READER("%s:%i - process loop done.\n", _FL);
if (Out)
{
while ((r = Process->Read(Buf, sizeof(Buf))) > 0)
OnData(Buf, r);
}
LOG_READER("%s:%i - loop done.\n", _FL);
Result = (int) Process->GetExitValue();
#if _DEBUG
if (Result)
printf("%s:%i - Process err: %i 0x%x\n", _FL, Result, Result);
#endif
return Result;
}
/////////////////////////////////////////////////////////////////////////////////////////////
int VcFolder::CmdMaxThreads = 0;
int VcFolder::CmdActiveThreads = 0;
void VcFolder::Init(AppPriv *priv)
{
if (!CmdMaxThreads)
CmdMaxThreads = LAppInst->GetCpuCount();
d = priv;
Expanded(false);
Insert(Tmp = new LTreeItem);
Tmp->SetText("Loading...");
LAssert(d != NULL);
}
VcFolder::VcFolder(AppPriv *priv, const char *uri)
{
Init(priv);
Uri.Set(uri);
GetType();
}
VcFolder::VcFolder(AppPriv *priv, LXmlTag *t)
{
Init(priv);
Serialize(t, false);
}
VcFolder::~VcFolder()
{
if (d->CurFolder == this)
d->CurFolder = NULL;
Log.DeleteObjects();
}
VersionCtrl VcFolder::GetType()
{
if (Type == VcNone)
Type = d->DetectVcs(this);
return Type;
}
bool VcFolder::IsLocal()
{
return Uri.IsProtocol("file");
}
const char *VcFolder::LocalPath()
{
if (!Uri.IsProtocol("file") || Uri.sPath.IsEmpty())
{
LAssert(!"Shouldn't call this if not a file path.");
return NULL;
}
auto c = Uri.sPath.Get();
#ifdef WINDOWS
if (*c == '/')
c++;
#endif
return c;
}
const char *VcFolder::GetText(int Col)
{
switch (Col)
{
case 0:
{
if (Uri.IsFile())
Cache = LocalPath();
else
Cache.Printf("%s%s", Uri.sHost.Get(), Uri.sPath.Get());
if (Cmds.Length())
Cache += " (...)";
return Cache;
}
case 1:
{
CountCache.Printf("%i/%i", Unpulled, Unpushed);
CountCache = CountCache.Replace("-1", "--");
return CountCache;
}
}
return NULL;
}
bool VcFolder::Serialize(LXmlTag *t, bool Write)
{
if (Write)
t->SetContent(Uri.ToString());
else
{
LString s = t->GetContent();
bool isUri = s.Find("://") >= 0;
if (isUri)
Uri.Set(s);
else
Uri.SetFile(s);
}
return true;
}
LXmlTag *VcFolder::Save()
{
LXmlTag *t = new LXmlTag(OPT_Folder);
if (t)
Serialize(t, true);
return t;
}
const char *VcFolder::GetVcName()
{
if (!VcCmd)
VcCmd = d->GetVcName(GetType());
return VcCmd;
}
char VcFolder::GetPathSep()
{
if (Uri.IsFile())
return DIR_CHAR;
return '/'; // FIXME: Assumption is that the remote system is unix based.
}
bool VcFolder::RunCmd(const char *Args, LoggingType Logging, std::function Callback)
{
Result Ret;
Ret.Code = -1;
const char *Exe = GetVcName();
if (!Exe || CmdErrors > 2)
return false;
if (Uri.IsFile())
{
new ProcessCallback(Exe,
Args,
LocalPath(),
Logging == LogNone ? d->Log : NULL,
GetTree()->GetWindow(),
Callback);
}
else
{
LAssert(!"Impl me.");
return false;
}
return true;
}
#if HAS_LIBSSH
SshConnection::LoggingType Convert(LoggingType t)
{
switch (t)
{
case LogNormal:
case LogSilo:
return SshConnection::LogInfo;
case LogDebug:
return SshConnection::LogDebug;
}
return SshConnection::LogNone;
}
#endif
bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging)
{
const char *Exe = GetVcName();
if (!Exe)
return false;
if (CmdErrors > 2)
return false;
if (Uri.IsFile())
{
if (d->Log && Logging != LogSilo)
d->Log->Print("%s %s\n", Exe, Args);
LAutoPtr Process(new LSubProcess(Exe, Args));
if (!Process)
return false;
Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath.Get() : LocalPath());
#if 0//def MAC
// Mac GUI apps don't share the terminal path, so this overrides that and make it work
auto Path = LGetPath();
if (Path.Length())
{
LString Tmp = LString(LGI_PATH_SEPARATOR).Join(Path);
printf("Tmp='%s'\n", Tmp.Get());
Process->SetEnvironment("PATH", Tmp);
}
#endif
LString::Array Ctx;
Ctx.SetFixedLength(false);
Ctx.Add(LocalPath());
Ctx.Add(Exe);
Ctx.Add(Args);
LAutoPtr c(new Cmd(Ctx, Logging, d->Log));
if (!c)
return false;
c->PostOp = Parser;
c->Params.Reset(Params);
c->Rd.Reset(new ReaderThread(GetType(), Process, c));
Cmds.Add(c.Release());
}
else
{
#if HAS_LIBSSH
auto c = d->GetConnection(Uri.ToString());
if (!c)
return false;
if (!c->Command(this, Exe, Args, Parser, Params, Convert(Logging)))
return false;
#endif
}
Update();
return true;
}
int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data)
{
VcCommit *A = dynamic_cast(a);
VcCommit *B = dynamic_cast(b);
if ((A != NULL) ^ (B != NULL))
{
// This handles keeping the "working folder" list item at the top
return (A != NULL) - (B != NULL);
}
// Sort the by date from most recent to least
return -A->GetTs().Compare(&B->GetTs());
}
void VcFolder::AddGitName(LString Hash, LString Name)
{
if (!Hash || !Name)
{
LAssert(!"Param error");
return;
}
LString Existing = GitNames.Find(Hash);
if (Existing)
GitNames.Add(Hash, Existing + "," + Name);
else
GitNames.Add(Hash, Name);
}
LString VcFolder::GetGitNames(LString Hash)
{
LString Short = Hash(0, 11);
return GitNames.Find(Short);
}
bool VcFolder::ParseBranches(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
LString::Array a = s.SplitDelimit("\r\n");
for (auto &l: a)
{
LString::Array c;
char *s = l.Get();
while (*s && IsWhite(*s))
s++;
bool IsCur = *s == '*';
if (IsCur)
s++;
while (*s && IsWhite(*s))
s++;
if (*s == '(')
{
s++;
auto e = strchr(s, ')');
if (e)
{
c.New().Set(s, e - s);
e++;
c += LString(e).SplitDelimit(" \t");
}
}
else
{
c = LString(s).SplitDelimit(" \t");
}
if (c.Length() < 1)
{
d->Log->Print("%s:%i - Too few parts in line '%s'\n", _FL, l.Get());
continue;
}
if (IsCur)
SetCurrentBranch(c[0]);
AddGitName(c[1], c[0]);
Branches.Add(c[0], new VcBranch(c[0], c[1]));
}
break;
}
case VcHg:
{
auto a = s.SplitDelimit("\r\n");
for (auto b: a)
{
if (b.Find("inactive") > 0)
continue;
auto name = b(0, 28).Strip();
auto refs = b(28, -1).SplitDelimit()[0].SplitDelimit(":");
auto branch = Branches.Find(name);
if (branch)
branch->Hash = refs.Last();
else
Branches.Add(name, new VcBranch(name, refs.Last()));
}
if (Params && Params->Str.Equals("CountToTip"))
CountToTip();
break;
}
default:
{
break;
}
}
IsBranches = Result ? StatusError : StatusNone;
OnBranchesChange();
return false;
}
void VcFolder::GetRemoteUrl(std::function Callback)
{
LAutoPtr p(new ParseParams);
p->Callback = Callback;
switch (GetType())
{
case VcGit:
{
StartCmd("config --get remote.origin.url", NULL, p.Release());
break;
}
case VcSvn:
{
StartCmd("info --show-item=url", NULL, p.Release());
break;
}
case VcHg:
{
StartCmd("paths default", NULL, p.Release());
break;
}
default:
break;
}
}
void VcFolder::SelectCommit(LWindow *Parent, LString Commit, LString Path)
{
bool requireFullMatch = true;
if (GetType() == VcGit)
requireFullMatch = false;
// This function find the given commit and selects it such that the diffs are displayed in the file list
VcCommit *ExistingMatch = NULL;
for (auto c: Log)
{
char *rev = c->GetRev();
bool match = requireFullMatch ? Commit.Equals(rev) : Strstr(rev, Commit.Get()) != NULL;
if (match)
{
ExistingMatch = c;
break;
}
}
FileToSelect = Path;
if (ExistingMatch)
{
ExistingMatch->Select(true);
}
else
{
// If the commit isn't there, it's likely that the log item limit was reached before the commit was
// found. In which case we should go get just that commit and add it:
d->Files->Empty();
// Diff just that ref:
LString a;
switch (GetType())
{
case VcGit:
{
a.Printf("diff %s~ %s", Commit.Get(), Commit.Get());
StartCmd(a, &VcFolder::ParseSelectCommit);
break;
}
case VcHg:
{
a.Printf("log -p -r %s", Commit.Get());
StartCmd(a, &VcFolder::ParseSelectCommit);
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
// if (Parent) LgiMsg(Parent, "The commit '%s' wasn't found", AppName, MB_OK, Commit.Get());
}
}
bool VcFolder::ParseSelectCommit(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
case VcSvn:
case VcCvs:
{
ParseDiff(Result, s, Params);
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
return false;
}
void VcFolder::OnBranchesChange()
{
auto *w = d->Tree->GetWindow();
if (!w || !LTreeItem::Select())
return;
if (Branches.Length())
{
// Set the colours up
LString Default;
for (auto b: Branches)
{
if (!stricmp(b.key, "default") ||
!stricmp(b.key, "trunk"))
Default = b.key;
/*
else
printf("Other=%s\n", b.key);
*/
}
int Idx = 1;
for (auto b: Branches)
{
if (!b.value->Colour.IsValid())
{
if (Default && !stricmp(b.key, Default))
b.value->Colour = GetPaletteColour(0);
else
b.value->Colour = GetPaletteColour(Idx++);
}
}
}
UpdateBranchUi();
}
void VcFolder::DefaultFields()
{
if (Fields.Length() == 0)
{
switch (GetType())
{
case VcHg:
{
Fields.Add(LGraph);
Fields.Add(LIndex);
Fields.Add(LRevision);
Fields.Add(LBranch);
Fields.Add(LAuthor);
Fields.Add(LTime);
Fields.Add(LMessageTxt);
break;
}
case VcGit:
{
Fields.Add(LGraph);
Fields.Add(LRevision);
Fields.Add(LBranch);
Fields.Add(LAuthor);
Fields.Add(LTime);
Fields.Add(LMessageTxt);
break;
}
default:
{
Fields.Add(LGraph);
Fields.Add(LRevision);
Fields.Add(LAuthor);
Fields.Add(LTime);
Fields.Add(LMessageTxt);
break;
}
}
}
}
int VcFolder::IndexOfCommitField(CommitField fld)
{
- return Fields.IndexOf(fld);
+ return (int)Fields.IndexOf(fld);
}
void VcFolder::UpdateColumns(LList *lst)
{
if (!lst)
lst = d->Commits;
lst->EmptyColumns();
for (auto c: Fields)
{
switch (c)
{
case LGraph: lst->AddColumn("---", 60); break;
case LIndex: lst->AddColumn("Index", 60); break;
case LBranch: lst->AddColumn("Branch", 60); break;
case LRevision: lst->AddColumn("Revision", 60); break;
case LAuthor: lst->AddColumn("Author", 240); break;
case LTime: lst->AddColumn("Date", 130); break;
case LMessageTxt: lst->AddColumn("Message", 700); break;
default: LAssert(0); break;
}
}
}
void VcFolder::FilterCurrentFiles()
{
LArray All;
d->Files->GetAll(All);
// Update the display property
for (auto i: All)
{
auto fn = i->GetText(COL_FILENAME);
bool vis = !d->FileFilter || Stristr(fn, d->FileFilter.Get());
i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone);
// LgiTrace("Filter '%s' by '%s' = %i\n", fn, d->FileFilter.Get(), vis);
}
d->Files->Sort(0);
d->Files->UpdateAllItems();
d->Files->ResizeColumnsToContent();
}
void VcFolder::Select(bool b)
{
#if PROFILE_FN
LProfile Prof("Select");
#endif
if (!b)
{
auto *w = d->Tree->GetWindow();
w->SetCtrlName(IDC_BRANCH, NULL);
}
PROF("Parent.Select");
LTreeItem::Select(b);
if (b)
{
if (Uri.IsFile() && !LDirExists(LocalPath()))
return;
PROF("DefaultFields");
DefaultFields();
PROF("Type Change");
if (GetType() != d->PrevType)
{
d->PrevType = GetType();
UpdateColumns();
}
PROF("UpdateCommitList");
if ((Log.Length() == 0 || CommitListDirty) && !IsLogging)
{
switch (GetType())
{
case VcGit:
{
LVariant Limit;
d->Opts.GetValue("git-limit", Limit);
LString cmd = "rev-list --all --header --timestamp --author-date-order", s;
if (Limit.CastInt32() > 0)
{
s.Printf(" -n %i", Limit.CastInt32());
cmd += s;
}
IsLogging = StartCmd(cmd, &VcFolder::ParseRevList);
break;
}
case VcSvn:
{
LVariant Limit;
d->Opts.GetValue("svn-limit", Limit);
if (CommitListDirty)
{
IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log"));
break;
}
LString s;
if (Limit.CastInt32() > 0)
s.Printf("log --limit %i", Limit.CastInt32());
else
s = "log";
IsLogging = StartCmd(s, &VcFolder::ParseLog);
break;
}
case VcHg:
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
StartCmd("resolve -l", &VcFolder::ParseResolveList);
break;
}
case VcPending:
{
break;
}
default:
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
break;
}
}
CommitListDirty = false;
}
PROF("GetBranches");
if (GetBranches())
OnBranchesChange();
if (d->CurFolder != this)
{
PROF("RemoveAll");
d->CurFolder = this;
d->Commits->RemoveAll();
}
PROF("Uncommit");
if (!Uncommit)
Uncommit.Reset(new UncommitedItem(d));
d->Commits->Insert(Uncommit, 0);
PROF("Log Loop");
int64 CurRev = Atoi(CurrentCommit.Get());
List Ls;
for (auto l: Log)
{
if (CurrentCommit &&
l->GetRev())
{
switch (GetType())
{
case VcSvn:
{
int64 LogRev = Atoi(l->GetRev());
if (CurRev >= 0 && CurRev >= LogRev)
{
CurRev = -1;
l->SetCurrent(true);
}
else
{
l->SetCurrent(false);
}
break;
}
default:
l->SetCurrent(!_stricmp(CurrentCommit, l->GetRev()));
break;
}
}
LList *CurOwner = l->GetList();
if (!CurOwner)
Ls.Insert(l);
}
PROF("Ls Ins");
d->Commits->Insert(Ls);
if (d->Resort >= 0)
{
PROF("Resort");
d->Commits->Sort(LstCmp, d->Resort);
d->Resort = -1;
}
PROF("ColSizing");
if (d->Commits->Length() > MAX_AUTO_RESIZE_ITEMS)
{
int i = 0;
if (GetType() == VcHg && d->Commits->GetColumns() >= 7)
{
d->Commits->ColumnAt(i++)->Width(60); // LGraph
d->Commits->ColumnAt(i++)->Width(40); // LIndex
d->Commits->ColumnAt(i++)->Width(100); // LRevision
d->Commits->ColumnAt(i++)->Width(60); // LBranch
d->Commits->ColumnAt(i++)->Width(240); // LAuthor
d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp
d->Commits->ColumnAt(i++)->Width(400); // LMessage
}
else if (d->Commits->GetColumns() >= 5)
{
d->Commits->ColumnAt(i++)->Width(40); // LGraph
d->Commits->ColumnAt(i++)->Width(270); // LRevision
d->Commits->ColumnAt(i++)->Width(240); // LAuthor
d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp
d->Commits->ColumnAt(i++)->Width(400); // LMessage
}
}
else d->Commits->ResizeColumnsToContent();
PROF("UpdateAll");
d->Commits->UpdateAllItems();
PROF("GetCur");
GetCurrentRevision();
}
}
int CommitRevCmp(VcCommit **a, VcCommit **b)
{
int64 arev = Atoi((*a)->GetRev());
int64 brev = Atoi((*b)->GetRev());
int64 diff = (int64)brev - arev;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
int CommitIndexCmp(VcCommit **a, VcCommit **b)
{
auto ai = (*a)->GetIndex();
auto bi = (*b)->GetIndex();
auto diff = (int64)bi - ai;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
int CommitDateCmp(VcCommit **a, VcCommit **b)
{
LTimeStamp ats, bts;
(*a)->GetTs().Get(ats);
(*b)->GetTs().Get(bts);
int64 diff = (int64)bts.Get() - ats.Get();
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
void VcFolder::GetCurrentRevision(ParseParams *Params)
{
if (CurrentCommit || IsIdent != StatusNone)
return;
switch (GetType())
{
case VcGit:
if (StartCmd("rev-parse HEAD", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcSvn:
if (StartCmd("info", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcHg:
if (StartCmd("id -i -n", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcCvs:
break;
default:
break;
}
}
bool VcFolder::GetBranches(ParseParams *Params)
{
if (Branches.Length() > 0 || IsBranches != StatusNone)
return true;
switch (GetType())
{
case VcGit:
if (StartCmd("-P branch -v", &VcFolder::ParseBranches, Params))
IsBranches = StatusActive;
break;
case VcSvn:
Branches.Add("trunk", new VcBranch("trunk"));
OnBranchesChange();
break;
case VcHg:
{
if (StartCmd("branches", &VcFolder::ParseBranches, Params))
IsBranches = StatusActive;
auto p = new ParseParams;
p->Callback = [this](auto code, auto str)
{
SetCurrentBranch(str.Strip());
};
StartCmd("branch", NULL, p);
break;
}
case VcCvs:
break;
default:
break;
}
return false;
}
bool VcFolder::ParseRevList(int Result, LString s, ParseParams *Params)
{
Log.DeleteObjects();
int Errors = 0;
switch (GetType())
{
case VcGit:
{
LString::Array Commits;
Commits.SetFixedLength(false);
// Split on the NULL chars...
char *c = s.Get();
char *e = c + s.Length();
while (c < e)
{
char *nul = c;
while (nul < e && *nul) nul++;
if (nul <= c) break;
Commits.New().Set(c, nul-c);
if (nul >= e) break;
c = nul + 1;
}
for (auto Commit: Commits)
{
LAutoPtr Rev(new VcCommit(d, this));
if (Rev->GitParse(Commit, true))
{
Log.Add(Rev.Release());
}
else
{
// LAssert(!"Parse failed.");
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get());
Errors++;
}
}
LinkParents();
break;
}
default:
LAssert(!"Impl me.");
break;
}
IsLogging = false;
return Errors == 0;
}
LString VcFolder::GetFilePart(const char *uri)
{
LUri u(uri);
LString File = u.IsFile() ?
u.DecodeStr(u.LocalPath()) :
u.sPath(Uri.sPath.Length(), -1).LStrip("/");
return File;
}
void VcFolder::ClearLog()
{
Uncommit.Reset();
Log.DeleteObjects();
}
void VcFolder::LogFilter(const char *Filter)
{
if (!Filter)
{
LAssert(!"No filter.");
return;
}
switch (GetType())
{
case VcGit:
{
// See if 'Filter' is a commit id?
LString args;
args.Printf("-P show %s", Filter);
ParseParams *params = new ParseParams;
params->Callback = [this, Filter=LString(Filter)](auto code, auto str)
{
ClearLog();
if (code == 0 && str.Find(Filter) >= 0)
{
// Found the commit...
d->Commits->Empty();
CurrentCommit.Empty();
ParseLog(code, str, NULL);
d->Commits->Insert(Log);
}
else
{
// Not a commit ref...?
LString args;
args.Printf("log --grep \"%s\"", Filter.Get());
IsLogging = StartCmd(args, &VcFolder::ParseLog);
}
};
StartCmd(args, NULL, params);
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
}
void VcFolder::LogFile(const char *uri)
{
LString Args;
if (IsLogging)
{
d->Log->Print("%s:%i - already logging.\n", _FL);
return;
}
const char *Page = "";
switch (GetType())
{
case VcGit:
Page = "-P ";
// fall through
case VcSvn:
case VcHg:
{
FileToSelect = GetFilePart(uri);
if (IsLocal() && !LFileExists(FileToSelect))
{
LFile::Path Abs(LocalPath());
Abs += FileToSelect;
if (Abs.Exists())
FileToSelect = Abs;
}
ParseParams *Params = new ParseParams(uri);
Args.Printf("%slog \"%s\"", Page, FileToSelect.Get());
IsLogging = StartCmd(Args, &VcFolder::ParseLog, Params, LogNormal);
break;
}
default:
NoImplementation(_FL);
break;
}
}
VcLeaf *VcFolder::FindLeaf(const char *Path, bool OpenTree)
{
VcLeaf *r = NULL;
if (OpenTree)
DoExpand();
for (auto n = GetChild(); !r && n; n = n->GetNext())
{
auto l = dynamic_cast(n);
if (l)
r = l->FindLeaf(Path, OpenTree);
}
return r;
}
bool VcFolder::ParseLog(int Result, LString s, ParseParams *Params)
{
int Skipped = 0, Errors = 0;
bool LoggingFile = Params ? Params->Str != NULL : false;
VcLeaf *File = LoggingFile ? FindLeaf(Params->Str, true) : NULL; // This may be NULL even if we are logging a file...
LArray *Out, BrowseLog;
if (File)
Out = &File->Log;
else if (LoggingFile)
Out = &BrowseLog;
else
Out = &Log;
LHashTbl, VcCommit*> Map;
for (auto pc: *Out)
Map.Add(pc->GetRev(), pc);
if (File)
{
for (auto Leaf = File; Leaf; Leaf = dynamic_cast(Leaf->GetParent()))
Leaf->OnExpand(true);
File->Select(true);
File->ScrollTo();
}
switch (GetType())
{
case VcGit:
{
LString::Array c;
c.SetFixedLength(false);
char *prev = s.Get();
#if 0
LFile::Path outPath("~/code/dump.txt");
LFile out(outPath.Absolute(), O_WRITE);
out.Write(s);
#endif
if (!s)
{
OnCmdError(s, "No output from command.");
return false;
}
char *i = s.Get();
while (*i)
{
if (!strnicmp(i, "commit ", 7))
{
if (i > prev)
{
c.New().Set(prev, i - prev);
// LgiTrace("commit=%i\n", (int)(i - prev));
}
prev = i;
}
while (*i)
{
if (*i++ == '\n')
break;
}
}
if (prev && i > prev)
{
// Last one...
c.New().Set(prev, i - prev);
}
for (auto txt: c)
{
LAutoPtr Rev(new VcCommit(d, this));
if (Rev->GitParse(txt, false))
{
if (!Map.Find(Rev->GetRev()))
Out->Add(Rev.Release());
else
Skipped++;
}
else
{
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, txt.Get());
Errors++;
}
}
Out->Sort(CommitDateCmp);
break;
}
case VcSvn:
{
LString::Array c = s.Split("------------------------------------------------------------------------");
for (unsigned i=0; i Rev(new VcCommit(d, this));
LString Raw = c[i].Strip();
if (Rev->SvnParse(Raw))
{
if (File || !Map.Find(Rev->GetRev()))
Out->Add(Rev.Release());
else
Skipped++;
}
else if (Raw)
{
OnCmdError(Raw, "ParseLog Failed");
Errors++;
}
}
Out->Sort(CommitRevCmp);
break;
}
case VcHg:
{
LString::Array c = s.Split("\n\n");
LHashTbl, VcCommit*> Idx;
for (auto &Commit: c)
{
LAutoPtr Rev(new VcCommit(d, this));
if (Rev->HgParse(Commit))
{
auto Existing = File ? NULL : Map.Find(Rev->GetRev());
if (!Existing)
Out->Add(Existing = Rev.Release());
if (Existing->GetIndex() >= 0)
Idx.Add(Existing->GetIndex(), Existing);
}
}
if (!File)
{
// Patch all the trivial parents...
for (auto c: Log)
{
if (c->GetParents()->Length() > 0)
continue;
auto CIdx = c->GetIndex();
if (CIdx <= 0)
continue;
auto Par = Idx.Find(CIdx - 1);
if (Par)
c->GetParents()->Add(Par->GetRev());
}
}
Out->Sort(CommitIndexCmp);
if (!File)
LinkParents();
d->Resort = 1;
break;
}
case VcCvs:
{
if (Result)
{
OnCmdError(s, "Cvs command failed.");
break;
}
LHashTbl, VcCommit*> Map;
LString::Array c = s.Split("=============================================================================");
for (auto &Commit: c)
{
if (Commit.Strip().Length())
{
LString Head, File;
LString::Array Versions = Commit.Split("----------------------------");
LString::Array Lines = Versions[0].SplitDelimit("\r\n");
for (auto &Line: Lines)
{
LString::Array p = Line.Split(":", 1);
if (p.Length() == 2)
{
// LgiTrace("Line: %s\n", Line->Get());
LString Var = p[0].Strip().Lower();
LString Val = p[1].Strip();
if (Var.Equals("branch"))
{
if (Val.Length())
Branches.Add(Val, new VcBranch(Val));
}
else if (Var.Equals("head"))
{
Head = Val;
}
else if (Var.Equals("rcs file"))
{
LString::Array f = Val.SplitDelimit(",");
File = f.First();
}
}
}
// LgiTrace("%s\n", Commit->Get());
for (unsigned i=1; i= 3)
{
LString Ver = Lines[0].Split(" ").Last();
LString::Array a = Lines[1].SplitDelimit(";");
LString Date = a[0].Split(":", 1).Last().Strip();
LString Author = a[1].Split(":", 1).Last().Strip();
LString Id = a[2].Split(":", 1).Last().Strip();
LString Msg = Lines[2];
LDateTime Dt;
if (Dt.Parse(Date))
{
LTimeStamp Ts;
if (Dt.Get(Ts))
{
VcCommit *Cc = Map.Find(Ts.Get());
if (!Cc)
{
Map.Add(Ts.Get(), Cc = new VcCommit(d, this));
Out->Add(Cc);
Cc->CvsParse(Dt, Author, Msg);
}
Cc->Files.Add(File.Get());
}
else LAssert(!"NO ts for date.");
}
else LAssert(!"Date parsing failed.");
}
}
}
}
break;
}
default:
LAssert(!"Impl me.");
break;
}
if (File)
{
File->ShowLog();
}
else if (LoggingFile)
{
if (auto ui = new BrowseUi(BrowseUi::TLog, d, this, Params->Str))
ui->ParseLog(BrowseLog, s);
}
// LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors);
IsLogging = false;
return !Result;
}
void VcFolder::LinkParents()
{
#if PROFILE_FN
LProfile Prof("LinkParents");
#endif
LHashTbl,VcCommit*> Map;
// Index all the commits
int i = 0;
for (auto c:Log)
{
c->Idx = i++;
c->NodeIdx = -1;
Map.Add(c->GetRev(), c);
}
// Create all the edges...
PROF("Create edges.");
for (auto c:Log)
{
auto *Par = c->GetParents();
for (auto &pRev : *Par)
{
auto *p = Map.Find(pRev);
if (p)
new VcEdge(p, c);
#if 0
else
return;
#endif
}
}
// Map the edges to positions
PROF("Map edges.");
typedef LArray EdgeArr;
LArray Active;
for (auto c:Log)
{
for (unsigned i=0; c->NodeIdx<0 && iParent == c)
{
c->NodeIdx = i;
break;
}
}
}
// Add starting edges to active set
for (auto e:c->Edges)
{
if (e->Child == c)
{
if (c->NodeIdx < 0)
c->NodeIdx = (int)Active.Length();
e->Idx = c->NodeIdx;
c->Pos.Add(e, e->Idx);
Active[e->Idx].Add(e);
}
}
// Now for all active edges... assign positions
for (unsigned i=0; iLength(); n++)
{
LAssert(Active.PtrCheck(Edges));
VcEdge *e = (*Edges)[n];
if (c == e->Child || c == e->Parent)
{
LAssert(c->NodeIdx >= 0);
c->Pos.Add(e, c->NodeIdx);
}
else
{
// May need to untangle edges with different parents here
bool Diff = false;
for (auto edge: *Edges)
{
if (edge != e &&
edge->Child != c &&
edge->Parent != e->Parent)
{
Diff = true;
break;
}
}
if (Diff)
{
int NewIndex = -1;
// Look through existing indexes for a parent match
for (unsigned ii=0; iiParent?
bool Match = true;
for (auto ee: Active[ii])
{
if (ee->Parent != e->Parent)
{
Match = false;
break;
}
}
if (Match)
NewIndex = ii;
}
if (NewIndex < 0)
// Create new index for this parent
NewIndex = (int)Active.Length();
Edges->Delete(e);
auto &NewEdges = Active[NewIndex];
NewEdges.Add(e);
Edges = &Active[i]; // The 'Add' above can invalidate the object 'Edges' refers to
e->Idx = NewIndex;
c->Pos.Add(e, NewIndex);
n--;
}
else
{
LAssert(e->Idx == i);
c->Pos.Add(e, i);
}
}
}
}
// Process terminating edges
for (auto e: c->Edges)
{
if (e->Parent == c)
{
if (e->Idx < 0)
{
// This happens with out of order commits..?
continue;
}
int i = e->Idx;
if (c->NodeIdx < 0)
c->NodeIdx = i;
if (Active[i].HasItem(e))
Active[i].Delete(e);
else
LgiTrace("%s:%i - Warning: Active doesn't have 'e'.\n", _FL);
}
}
// Collapse any empty active columns
for (unsigned i=0; iIdx > 0);
edge->Idx--;
c->Pos.Add(edge, edge->Idx);
}
}
i--;
}
}
}
}
void VcFolder::UpdateBranchUi()
{
auto w = d->Wnd();
DropDownBtn *dd;
if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd))
{
LString::Array a;
for (auto b: Branches)
a.Add(b.key);
dd->SetList(IDC_BRANCH, a);
}
LViewI *b;
if (Branches.Length() > 0 &&
w->GetViewById(IDC_BRANCH, b))
{
if (CurrentBranch)
{
b->Name(CurrentBranch);
}
else
{
auto it = Branches.begin();
if (it != Branches.end())
b->Name((*it).key);
}
}
LCombo *Cbo;
if (w->GetViewById(IDC_BRANCHES, Cbo))
{
Cbo->Empty();
int64 select = -1;
for (auto b: Branches)
{
if (CurrentBranch && CurrentBranch == b.key)
select = Cbo->Length();
Cbo->Insert(b.key);
}
if (select >= 0)
Cbo->Value(select);
Cbo->SendNotify(LNotifyTableLayoutRefresh);
// LgiTrace("%s:%i - Branches len=%i->%i\n", _FL, (int)Branches.Length(), (int)Cbo->Length());
}
}
VcFile *AppPriv::FindFile(const char *Path)
{
if (!Path)
return NULL;
LArray files;
if (Files->GetAll(files))
{
LString p = Path;
p = p.Replace(DIR_STR, "/");
for (auto f : files)
{
auto Fn = f->GetFileName();
if (p.Equals(Fn))
return f;
}
}
return NULL;
}
VcFile *VcFolder::FindFile(const char *Path)
{
return d->FindFile(Path);
}
void VcFolder::NoImplementation(const char* file, int line)
{
LString s;
s.Printf("%s, uri=%s, type=%s (%s:%i)",
LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE),
Uri.ToString().Get(),
toString(GetType()),
file, line);
OnCmdError(LString(), s);
}
void VcFolder::OnCmdError(LString Output, const char *Msg)
{
if (!CmdErrors)
{
if (Output.Length())
d->Log->Write(Output, Output.Length());
auto vc_name = GetVcName();
if (vc_name)
{
LString::Array a = GetProgramsInPath(GetVcName());
d->Log->Print("'%s' executables in the path:\n", GetVcName());
for (auto Bin : a)
d->Log->Print(" %s\n", Bin.Get());
}
else if (Msg)
{
d->Log->Print("%s\n", Msg);
}
}
CmdErrors++;
d->Tabs->Value(1);
GetCss(true)->Color(LColour::Red);
Update();
}
void VcFolder::ClearError()
{
GetCss(true)->Color(LCss::ColorInherit);
}
bool VcFolder::ParseInfo(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
{
auto p = s.Strip().SplitDelimit();
CurrentCommit = p[0].Strip(" \t\r\n+");
if (p.Length() > 1)
CurrentCommitIdx = p[1].Int();
else
CurrentCommitIdx = -1;
if (Params && Params->Str.Equals("CountToTip"))
CountToTip();
break;
}
case VcSvn:
{
if (s.Find("client is too old") >= 0)
{
OnCmdError(s, "Client too old");
break;
}
LString::Array c = s.Split("\n");
for (unsigned i=0; iStr.Equals("Branch"))
SetCurrentBranch(NewRev);
else
CurrentCommit = NewRev;
}
NewRev.Empty();
IsUpdate = false;
return true;
}
bool VcFolder::ParseWorking(int Result, LString s, ParseParams *Params)
{
IsListingWorking = false;
switch (GetType())
{
case VcSvn:
case VcHg:
{
ParseParams Local;
if (!Params) Params = &Local;
Params->IsWorking = true;
ParseStatus(Result, s, Params);
break;
}
case VcCvs:
{
bool Untracked = d->IsMenuChecked(IDM_UNTRACKED);
if (Untracked)
{
auto Lines = s.SplitDelimit("\n");
for (auto Ln: Lines)
{
auto p = Ln.SplitDelimit(" \t", 1);
if (p.Length() > 1)
{
auto f = new VcFile(d, this, LString(), true);
f->SetText(p[0], COL_STATE);
f->SetText(p[1], COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
}
}
// else fall thru
}
default:
{
ParseDiffs(s, LString(), true);
break;
}
}
FilterCurrentFiles();
d->Files->ResizeColumnsToContent();
if (GetType() == VcSvn)
{
Unpushed = d->Files->Length() > 0 ? 1 : 0;
Update();
}
return false;
}
void VcFolder::DiffRange(const char *FromRev, const char *ToRev)
{
if (!FromRev || !ToRev)
return;
switch (GetType())
{
case VcSvn:
{
ParseParams *p = new ParseParams;
p->IsWorking = false;
p->Str = LString(FromRev) + ":" + ToRev;
LString a;
a.Printf("diff -r%s:%s", FromRev, ToRev);
StartCmd(a, &VcFolder::ParseDiff, p);
break;
}
case VcGit:
{
ParseParams *p = new ParseParams;
p->IsWorking = false;
p->Str = LString(FromRev) + ":" + ToRev;
LString a;
a.Printf("-P diff %s..%s", FromRev, ToRev);
StartCmd(a, &VcFolder::ParseDiff, p);
break;
}
case VcCvs:
case VcHg:
default:
LAssert(!"Impl me.");
break;
}
}
bool VcFolder::ParseDiff(int Result, LString s, ParseParams *Params)
{
if (Params)
ParseDiffs(s, Params->Str, Params->IsWorking);
else
ParseDiffs(s, LString(), true);
return false;
}
void VcFolder::Diff(VcFile *file)
{
auto Fn = file->GetFileName();
if (!Fn ||
!Stricmp(Fn, ".") ||
!Stricmp(Fn, ".."))
return;
const char *Prefix = "";
switch (GetType())
{
case VcGit:
Prefix = "-P ";
// fall through
case VcHg:
{
LString a;
auto rev = file->GetRevision();
if (rev)
a.Printf("%sdiff %s \"%s\"", Prefix, rev, Fn);
else
a.Printf("%sdiff \"%s\"", Prefix, Fn);
StartCmd(a, &VcFolder::ParseDiff);
break;
}
case VcSvn:
{
LString a;
if (file->GetRevision())
a.Printf("diff -r %s \"%s\"", file->GetRevision(), Fn);
else
a.Printf("diff \"%s\"", Fn);
StartCmd(a, &VcFolder::ParseDiff);
break;
}
case VcCvs:
break;
default:
LAssert(!"Impl me.");
break;
}
}
void VcFolder::InsertFiles(List &files)
{
d->Files->Insert(files);
if (FileToSelect)
{
LListItem *scroll = NULL;
for (auto f: files)
{
// Convert to an absolute path:
bool match = false;
auto relPath = f->GetText(COL_FILENAME);
if (IsLocal())
{
LFile::Path p(LocalPath());
p += relPath;
match = p.GetFull().Equals(FileToSelect);
}
else
{
match = !Stricmp(FileToSelect.Get(), relPath);
}
f->Select(match);
if (match)
scroll = f;
}
if (scroll)
scroll->ScrollTo();
}
}
bool VcFolder::ParseDiffs(LString s, LString Rev, bool IsWorking)
{
LAssert(IsWorking || Rev.Get() != NULL);
switch (GetType())
{
case VcGit:
{
List Files;
LString::Array a = s.Split("\n");
LString Diff;
VcFile *f = NULL;
for (unsigned i=0; iSetDiff(Diff);
Diff.Empty();
auto Bits = a[i].SplitDelimit();
LString Fn, State = "M";
if (Bits[1].Equals("--cc"))
{
Fn = Bits.Last();
State = "C";
}
else
Fn = Bits.Last()(2,-1);
// LgiTrace("%s\n", a[i].Get());
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(State, COL_STATE);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
f->GetStatus();
Files.Insert(f);
}
else if (!_strnicmp(Ln, "new file", 8))
{
if (f)
f->SetText("A", COL_STATE);
}
else if (!_strnicmp(Ln, "deleted file", 12))
{
if (f)
f->SetText("D", COL_STATE);
}
else if (!_strnicmp(Ln, "index", 5) ||
!_strnicmp(Ln, "commit", 6) ||
!_strnicmp(Ln, "Author:", 7) ||
!_strnicmp(Ln, "Date:", 5) ||
!_strnicmp(Ln, "+++", 3) ||
!_strnicmp(Ln, "---", 3))
{
// Ignore
}
else
{
if (Diff) Diff += "\n";
Diff += a[i];
}
}
if (f && Diff)
{
f->SetDiff(Diff);
Diff.Empty();
}
InsertFiles(Files);
break;
}
case VcHg:
{
LString Sep("\n");
LString::Array a = s.Split(Sep);
LString::Array Diffs;
VcFile *f = NULL;
List Files;
LProgressDlg Prog(GetTree(), 1000);
Prog.SetDescription("Reading diff lines...");
Prog.SetRange(a.Length());
// Prog.SetYieldTime(300);
for (unsigned i=0; iSetDiff(Sep.Join(Diffs));
Diffs.Empty();
auto MainParts = a[i].Split(" -r ");
auto FileParts = MainParts.Last().Split(" ",1);
LString Fn = FileParts.Last();
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
// f->SetText(Status, COL_STATE);
Files.Insert(f);
}
else if (!_strnicmp(Ln, "index", 5) ||
!_strnicmp(Ln, "commit", 6) ||
!_strnicmp(Ln, "Author:", 7) ||
!_strnicmp(Ln, "Date:", 5) ||
!_strnicmp(Ln, "+++", 3) ||
!_strnicmp(Ln, "---", 3))
{
// Ignore
}
else
{
Diffs.Add(a[i]);
}
Prog.Value(i);
if (Prog.IsCancelled())
break;
}
if (f && Diffs.Length())
{
f->SetDiff(Sep.Join(Diffs));
Diffs.Empty();
}
InsertFiles(Files);
break;
}
case VcSvn:
{
List Files;
LString::Array a = s.Replace("\r").Split("\n");
LString Diff;
VcFile *f = NULL;
bool InPreamble = false;
bool InDiff = false;
for (unsigned i=0; iSetDiff(Diff);
f->Select(false);
}
Diff.Empty();
InDiff = false;
InPreamble = false;
LString Fn = a[i].Split(":", 1).Last().Strip();
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
f->SetText("M", COL_STATE);
f->GetStatus();
Files.Insert(f);
}
else if (!_strnicmp(Ln, "------", 6))
{
InPreamble = !InPreamble;
}
else if (!_strnicmp(Ln, "======", 6))
{
InPreamble = false;
InDiff = true;
}
else if (InDiff)
{
if (!strncmp(Ln, "--- ", 4) ||
!strncmp(Ln, "+++ ", 4))
{
}
else
{
if (Diff) Diff += "\n";
Diff += a[i];
}
}
}
InsertFiles(Files);
if (f && Diff)
{
f->SetDiff(Diff);
Diff.Empty();
}
break;
}
case VcCvs:
{
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
FilterCurrentFiles();
return true;
}
bool VcFolder::ParseFiles(int Result, LString s, ParseParams *Params)
{
d->ClearFiles();
ParseDiffs(s, Params->Str, false);
IsFilesCmd = false;
FilterCurrentFiles();
return false;
}
#if HAS_LIBSSH
void VcFolder::OnSshCmd(SshParams *p)
{
if (!p || !p->f)
{
LAssert(!"Param error.");
return;
}
LString s = p->Output;
int Result = p->ExitCode;
if (Result == ErrSubProcessFailed)
{
CmdErrors++;
}
else if (p->Parser)
{
bool Reselect = CALL_MEMBER_FN(*this, p->Parser)(Result, s, p->Params);
if (Reselect)
{
if (LTreeItem::Select())
Select(true);
}
}
if (p->Params &&
p->Params->Callback)
{
p->Params->Callback(Result, s);
}
}
#endif
void VcFolder::OnPulse()
{
bool Reselect = false, CmdsChanged = false;
static bool Processing = false;
if (!Processing)
{
Processing = true; // Lock out processing, if it puts up a dialog or something...
// bad things happen if we try and re-process something.
// printf("Cmds.Len=%i\n", (int)Cmds.Length());
for (unsigned i=0; iRd->GetState());
if (c->Rd->GetState() == LThread::THREAD_INIT)
{
if (CmdActiveThreads < CmdMaxThreads)
{
c->Rd->Run();
CmdActiveThreads++;
// printf("CmdActiveThreads++ = %i\n", CmdActiveThreads);
}
// else printf("Too many active threads.\n");
}
else if (c->Rd->IsExited())
{
CmdActiveThreads--;
// printf("CmdActiveThreads-- = %i\n", CmdActiveThreads);
LString s = c->GetBuf();
int Result = c->Rd->ExitCode();
if (Result == ErrSubProcessFailed)
{
if (!CmdErrors)
d->Log->Print("Error: Can't run '%s'\n", GetVcName());
CmdErrors++;
}
else if (c->PostOp)
{
if (s.Length() == 18 &&
s.Equals("GSUBPROCESS_ERROR\n"))
{
OnCmdError(s, "Sub process failed.");
}
else
{
Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params);
}
}
if (c->Params &&
c->Params->Callback)
{
c->Params->Callback(Result, s);
}
Cmds.DeleteAt(i--, true);
delete c;
CmdsChanged = true;
}
// else printf("Not exited.\n");
}
Processing = false;
}
if (Reselect)
{
if (LTreeItem::Select())
Select(true);
}
if (CmdsChanged)
{
Update();
}
if (CmdErrors)
{
d->Tabs->Value(1);
CmdErrors = false;
}
}
void VcFolder::OnRemove()
{
LXmlTag *t = d->Opts.LockTag(OPT_Folders, _FL);
if (t)
{
Uncommit.Reset();
if (LTreeItem::Select())
{
d->Files->Empty();
d->Commits->RemoveAll();
}
bool Found = false;
auto u = Uri.ToString();
for (auto c: t->Children)
{
if (!c->IsTag(OPT_Folder))
printf("%s:%i - Wrong tag: %s, %s\n", _FL, c->GetTag(), OPT_Folder);
else if (!c->GetContent())
printf("%s:%i - No content.\n", _FL);
else
{
auto Content = c->GetContent();
if (!_stricmp(Content, u))
{
c->RemoveTag();
delete c;
Found = true;
break;
}
}
}
LAssert(Found);
d->Opts.Unlock();
}
}
void VcFolder::Empty()
{
Type = VcNone;
IsCommit = false;
IsLogging = false;
IsUpdate = false;
IsFilesCmd = false;
CommitListDirty = false;
IsUpdatingCounts = false;
IsBranches = StatusNone;
IsIdent = StatusNone;
Unpushed = Unpulled = -1;
CmdErrors = 0;
CurrentCommitIdx = -1;
CurrentCommit.Empty();
RepoUrl.Empty();
VcCmd.Empty();
Uncommit.Reset();
Log.DeleteObjects();
d->Commits->Empty();
d->Files->Empty();
if (!Uri.IsFile())
GetCss(true)->Color(LColour::Blue);
}
void VcFolder::OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu s;
s.AppendItem("Browse To", IDM_BROWSE_FOLDER, Uri.IsFile());
s.AppendItem(
#ifdef WINDOWS
"Command Prompt At",
#else
"Terminal At",
#endif
IDM_TERMINAL, Uri.IsFile());
s.AppendItem("Clean", IDM_CLEAN);
s.AppendSeparator();
s.AppendItem("Pull", IDM_PULL);
s.AppendItem("Status", IDM_STATUS);
s.AppendItem("Push", IDM_PUSH);
s.AppendItem("Update Subs", IDM_UPDATE_SUBS, GetType() == VcGit);
s.AppendSeparator();
s.AppendItem("Remove", IDM_REMOVE);
s.AppendItem("Remote URL", IDM_REMOTE_URL);
if (!Uri.IsFile())
{
s.AppendSeparator();
s.AppendItem("Edit Location", IDM_EDIT);
}
int Cmd = s.Float(GetTree(), m);
switch (Cmd)
{
case IDM_BROWSE_FOLDER:
{
LBrowseToFile(LocalPath());
break;
}
case IDM_TERMINAL:
{
TerminalAt(LocalPath());
break;
}
case IDM_CLEAN:
{
Clean();
break;
}
case IDM_PULL:
{
Pull();
break;
}
case IDM_STATUS:
{
FolderStatus();
break;
}
case IDM_PUSH:
{
Push();
break;
}
case IDM_UPDATE_SUBS:
{
UpdateSubs();
break;
}
case IDM_REMOVE:
{
OnRemove();
delete this;
break;
}
case IDM_EDIT:
{
auto Dlg = new LInput(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location");
Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId)
{
if (ctrlId)
{
Uri.Set(Dlg->GetStr());
Empty();
Select(true);
}
delete dlg;
});
break;
}
case IDM_REMOTE_URL:
{
GetRemoteUrl([this](auto code, auto str)
{
LString Url = str.Strip();
if (Url)
{
auto a = new LAlert(GetTree(), "Remote Url", Url, "Copy", "Ok");
a->DoModal([this, Url](auto dlg, auto code)
{
if (code == 1)
{
LClipBoard c(GetTree());
c.Text(Url);
}
delete dlg;
});
}
});
break;
}
default:
break;
}
}
}
LString &VcFolder::GetCurrentBranch()
{
return CurrentBranch;
}
void VcFolder::SetCurrentBranch(LString name)
{
if (CurrentBranch != name)
{
CurrentBranch = name;
UpdateBranchUi();
}
}
void VcFolder::Checkout(const char *Rev, bool isBranch)
{
if (!Rev || IsUpdate)
return;
LString Args;
LAutoPtr params(new ParseParams(isBranch ? "Branch" : "Rev"));
NewRev = Rev;
switch (GetType())
{
case VcGit:
Args.Printf("checkout %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseCheckout, params.Release(), LogNormal);
break;
case VcSvn:
Args.Printf("up -r %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseCheckout, params.Release(), LogNormal);
break;
case VcHg:
Args.Printf("update -r %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseCheckout, params.Release(), LogNormal);
break;
default:
{
NoImplementation(_FL);
break;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
int FolderCompare(LTreeItem *a, LTreeItem *b, NativeInt UserData)
{
VcLeaf *A = dynamic_cast(a);
VcLeaf *B = dynamic_cast(b);
if (!A || !B)
return 0;
return A->Compare(B);
}
struct SshFindEntry
{
LString Flags, Name, User, Group;
uint64_t Size;
LDateTime Modified, Access;
SshFindEntry &operator =(const LString &s)
{
auto p = s.SplitDelimit("/");
if (p.Length() == 7)
{
Flags = p[0];
Group = p[1];
User = p[2];
Access.Set((uint64_t) p[3].Int());
Modified.Set((uint64_t) p[4].Int());
Size = p[5].Int();
Name = p[6];
}
return *this;
}
bool IsDir() { return Flags(0) == 'd'; }
bool IsHidden() { return Name(0) == '.'; }
const char *GetName() { return Name; }
static int Compare(SshFindEntry *a, SshFindEntry *b) { return Stricmp(a->Name.Get(), b->Name.Get()); }
};
bool VcFolder::ParseRemoteFind(int Result, LString s, ParseParams *Params)
{
if (!Params || !s)
return false;
auto Parent = Params->Leaf ? static_cast(Params->Leaf) : static_cast(this);
LUri u(Params->Str);
auto Lines = s.SplitDelimit("\r\n");
LArray Entries;
for (size_t i=1; iStr, Dir.GetName(), true);
}
}
else if (!Dir.IsHidden())
{
char *Ext = LGetExtension(Dir.GetName());
if (!Ext) continue;
if (!stricmp(Ext, "c") ||
!stricmp(Ext, "cpp") ||
!stricmp(Ext, "h"))
{
LUri Path = u;
Path += Dir.GetName();
new VcLeaf(this, Parent, Params->Str, Dir.GetName(), false);
}
}
}
return false;
}
void VcFolder::ReadDir(LTreeItem *Parent, const char *ReadUri)
{
LUri u(ReadUri);
if (u.IsFile())
{
// Read child items
LDirectory Dir;
for (int b = Dir.First(u.LocalPath()); b; b = Dir.Next())
{
auto name = Dir.GetName();
if (Dir.IsHidden())
continue;
LUri Path = u;
Path += name;
new VcLeaf(this, Parent, u.ToString(), name, Dir.IsDir());
}
}
#if HAS_LIBSSH
else
{
auto c = d->GetConnection(ReadUri);
if (!c)
return;
LString Path = u.sPath(Uri.sPath.Length(), -1).LStrip("/");
LString Args;
Args.Printf("\"%s\" -maxdepth 1 -printf \"%%M/%%g/%%u/%%A@/%%T@/%%s/%%P\n\"", Path ? Path.Get() : ".");
auto *Params = new ParseParams(ReadUri);
Params->Leaf = dynamic_cast(Parent);
c->Command(this, "find", Args, &VcFolder::ParseRemoteFind, Params, SshConnection::LogNone);
return;
}
#endif
Parent->Sort(FolderCompare);
}
void VcFolder::OnVcsType(LString errorMsg)
{
if (!d)
{
LAssert(!"No priv instance");
return;
}
#if HAS_LIBSSH
auto c = d->GetConnection(Uri.ToString(), false);
if (c)
{
auto NewType = c->Types.Find(Uri.sPath);
if (NewType && NewType != Type)
{
if (NewType == VcError)
{
OnCmdError(LString(), errorMsg);
}
else
{
Type = NewType;
ClearError();
Update();
if (LTreeItem::Select())
Select(true);
for (auto &e: OnVcsTypeEvents)
e();
OnVcsTypeEvents.Empty();
}
}
}
#endif
}
void VcFolder::DoExpand()
{
if (Tmp)
{
Tmp->Remove();
DeleteObj(Tmp);
ReadDir(this, Uri.ToString());
}
}
void VcFolder::OnExpand(bool b)
{
if (b)
DoExpand();
}
void VcFolder::ListCommit(VcCommit *c)
{
if (!IsFilesCmd)
{
LString Args;
switch (GetType())
{
case VcGit:
Args.Printf("-P show %s^..%s", c->GetRev(), c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
case VcSvn:
Args.Printf("log --verbose --diff -r %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
case VcCvs:
{
d->ClearFiles();
for (unsigned i=0; iFiles.Length(); i++)
{
VcFile *f = new VcFile(d, this, c->GetRev(), false);
if (f)
{
f->SetText(c->Files[i], COL_FILENAME);
d->Files->Insert(f);
}
}
FilterCurrentFiles();
break;
}
case VcHg:
{
Args.Printf("diff --change %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
}
default:
LAssert(!"Impl me.");
break;
}
if (IsFilesCmd)
d->ClearFiles();
}
}
LString ConvertUPlus(LString s)
{
LArray c;
LUtf8Ptr p(s);
int32 ch;
while ((ch = p))
{
if (ch == '{')
{
auto n = p.GetPtr();
if (n[1] == 'U' &&
n[2] == '+')
{
// Convert unicode code point
p += 3;
ch = (int32)htoi(p.GetPtr());
c.Add(ch);
while ((ch = p) != '}')
p++;
}
else c.Add(ch);
}
else c.Add(ch);
p++;
}
c.Add(0);
#ifdef LINUX
return LString((char16*)c.AddressOf());
#else
return LString(c.AddressOf());
#endif
}
bool VcFolder::ParseStatus(int Result, LString s, ParseParams *Params)
{
bool ShowUntracked = d->Wnd()->GetCtrlValue(IDC_UNTRACKED) != 0;
bool IsWorking = Params ? Params->IsWorking : false;
List Ins;
switch (GetType())
{
case VcCvs:
{
LHashTbl,VcFile*> Map;
for (auto i: *d->Files)
{
VcFile *f = dynamic_cast(i);
if (f)
Map.Add(f->GetText(COL_FILENAME), f);
}
#if 0
LFile Tmp("C:\\tmp\\output.txt", O_WRITE);
Tmp.Write(s);
Tmp.Close();
#endif
LString::Array a = s.Split("===================================================================");
for (auto i : a)
{
LString::Array Lines = i.SplitDelimit("\r\n");
if (Lines.Length() == 0)
continue;
LString f = Lines[0].Strip();
if (f.Find("File:") == 0)
{
LString::Array Parts = f.SplitDelimit("\t");
LString File = Parts[0].Split(": ").Last().Strip();
LString Status = Parts[1].Split(": ").Last();
LString WorkingRev;
for (auto l : Lines)
{
LString::Array p = l.Strip().Split(":", 1);
if (p.Length() > 1 &&
p[0].Strip().Equals("Working revision"))
{
WorkingRev = p[1].Strip();
}
}
VcFile *f = Map.Find(File);
if (!f)
{
if ((f = new VcFile(d, this, WorkingRev, IsWorking)))
Ins.Insert(f);
}
if (f)
{
f->SetText(Status, COL_STATE);
f->SetText(File, COL_FILENAME);
f->Update();
}
}
else if (f(0) == '?' &&
ShowUntracked)
{
LString File = f(2, -1);
VcFile *f = Map.Find(File);
if (!f)
{
if ((f = new VcFile(d, this, LString(), IsWorking)))
Ins.Insert(f);
}
if (f)
{
f->SetText("?", COL_STATE);
f->SetText(File, COL_FILENAME);
f->Update();
}
}
}
for (auto i: *d->Files)
{
VcFile *f = dynamic_cast(i);
if (f)
{
if (f->GetStatus() == VcFile::SUnknown)
f->SetStatus(VcFile::SUntracked);
}
}
break;
}
case VcGit:
{
auto Lines = s.SplitDelimit("\r\n");
int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1;
for (auto Ln : Lines)
{
auto Type = Ln(0);
if (Ln.Lower().Find("error:") >= 0)
{
}
else if (Ln.Find("usage: git") >= 0)
{
// It's probably complaining about the --porcelain=2 parameter
OnCmdError(s, "Args error");
}
else if (Type != '?')
{
VcFile *f = NULL;
if (Fmt == 2)
{
LString::Array p = Ln.SplitDelimit(" ", 8);
if (p.Length() < 7)
d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get());
else
{
auto path = p[6];
f = new VcFile(d, this, path, IsWorking);
auto state = p[1].Strip(".");
auto pos = p[1].Find(state);
d->Log->Print("%s state='%s' pos=%i\n", path.Get(), state.Get(), (int)pos);
f->SetText(state, COL_STATE);
f->SetText(p.Last(), COL_FILENAME);
f->SetStaged(pos == 0);
}
}
else if (Fmt == 1)
{
LString::Array p = Ln.SplitDelimit(" ");
f = new VcFile(d, this, LString(), IsWorking);
f->SetText(p[0], COL_STATE);
f->SetText(p.Last(), COL_FILENAME);
}
if (f)
Ins.Insert(f);
}
else if (ShowUntracked)
{
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
case VcHg:
case VcSvn:
{
if (s.Find("failed to import") >= 0)
{
OnCmdError(s, "Tool error.");
return false;
}
LString::Array Lines = s.SplitDelimit("\r\n");
for (auto Ln : Lines)
{
char Type = Ln(0);
if (Ln.Lower().Find("error:") >= 0)
{
}
else if (Ln.Find("client is too old") >= 0)
{
OnCmdError(s, "Client too old.");
return false;
}
else if (Strchr(" \t", Type) ||
Ln.Find("Summary of conflicts") >= 0)
{
// Ignore
}
else if (Type != '?')
{
LString::Array p = Ln.SplitDelimit(" ", 1);
if (p.Length() == 2)
{
LString File;
if (GetType() == VcSvn)
File = ConvertUPlus(p.Last());
else
File = p.Last();
if (GetType() == VcSvn &&
File.Find("+ ") == 0)
{
File = File(5, -1);
}
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText(p[0], COL_STATE);
f->SetText(File.Replace("\\","/"), COL_FILENAME);
f->GetStatus();
Ins.Insert(f);
}
else LAssert(!"What happen?");
}
else if (ShowUntracked)
{
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
if ((Unpushed = Ins.Length() > 0))
{
if (CmdErrors == 0)
GetCss(true)->Color(LColour(255, 128, 0));
}
else if (Unpulled == 0)
{
GetCss(true)->Color(LCss::ColorInherit);
}
Update();
if (LTreeItem::Select())
{
d->Files->Insert(Ins);
FilterCurrentFiles();
}
else
{
Ins.DeleteObjects();
}
if (Params && Params->Leaf)
Params->Leaf->AfterBrowse();
return false; // Don't refresh list
}
// Clone/checkout any sub-repositries.
bool VcFolder::UpdateSubs()
{
LString Arg;
switch (GetType())
{
default:
case VcSvn:
case VcHg:
case VcCvs:
return false;
case VcGit:
Arg = "submodule update --init --recursive";
break;
}
return StartCmd(Arg, &VcFolder::ParseUpdateSubs, NULL, LogNormal);
}
bool VcFolder::ParseUpdateSubs(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
default:
case VcSvn:
case VcHg:
case VcCvs:
return false;
case VcGit:
break;
}
return false;
}
void VcFolder::FolderStatus(const char *uri, VcLeaf *Notify)
{
LUri Uri(uri);
if (Uri.IsFile() && Uri.sPath)
{
LFile::Path p(Uri.sPath(1,-1));
if (!p.IsFolder())
{
LAssert(!"Needs to be a folder.");
return;
}
}
if (LTreeItem::Select())
d->ClearFiles();
LString Arg;
switch (GetType())
{
case VcSvn:
case VcHg:
Arg = "status";
break;
case VcCvs:
Arg = "status -l";
break;
case VcGit:
if (!ToolVersion[VcGit])
LAssert(!"Where is the version?");
// What version did =2 become available? It's definitely not in v2.5.4
// Not in v2.7.4 either...
if (ToolVersion[VcGit] >= Ver2Int("2.8.0"))
Arg = "-P status --porcelain=2";
else
Arg = "-P status --porcelain";
break;
default:
return;
}
ParseParams *p = new ParseParams;
if (uri && Notify)
{
p->AltInitPath = uri;
p->Leaf = Notify;
}
else
{
p->IsWorking = true;
}
StartCmd(Arg, &VcFolder::ParseStatus, p);
switch (GetType())
{
case VcHg:
CountToTip();
break;
default:
break;
}
}
void VcFolder::CountToTip()
{
// if (Path.Equals("C:\\Users\\matthew\\Code\\Lgi\\trunk"))
{
// LgiTrace("%s: CountToTip, br=%s, idx=%i\n", Path.Get(), CurrentBranch.Get(), (int)CurrentCommitIdx);
if (!CurrentBranch)
GetBranches(new ParseParams("CountToTip"));
else if (CurrentCommitIdx < 0)
GetCurrentRevision(new ParseParams("CountToTip"));
else
{
LString Arg;
Arg.Printf("id -n -r %s", CurrentBranch.Get());
StartCmd(Arg, &VcFolder::ParseCountToTip);
}
}
}
bool VcFolder::ParseCountToTip(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
if (CurrentCommitIdx >= 0)
{
auto p = s.Strip();
auto idx = p.Int();
if (idx >= CurrentCommitIdx)
{
Unpulled = (int) (idx - CurrentCommitIdx);
Update();
}
}
break;
default:
break;
}
return false;
}
void VcFolder::ListWorkingFolder()
{
if (IsListingWorking)
return;
d->ClearFiles();
bool Untracked = d->IsMenuChecked(IDM_UNTRACKED);
LString Arg;
switch (GetType())
{
case VcPending:
OnVcsTypeEvents.Add([this]()
{
ListWorkingFolder();
});
break;
case VcCvs:
if (Untracked)
Arg = "-qn update";
else
Arg = "-q diff --brief";
break;
case VcSvn:
Arg = "status";
break;
case VcGit:
#if 1
Arg = "-P status -vv";
#else
Arg = "-P diff --diff-filter=CMRTU --cached";
#endif
break;
case VcHg:
Arg = "status -mard";
break;
default:
return;
}
IsListingWorking = StartCmd(Arg, &VcFolder::ParseWorking);
}
void VcFolder::GitAdd()
{
if (!PostAdd)
return;
LString Args;
if (PostAdd->Files.Length() == 0)
{
LString m(PostAdd->Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -m \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal);
PostAdd.Reset();
}
else
{
char NativeSep[] = {GetPathSep(), 0};
LString Last = PostAdd->Files.Last();
Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", NativeSep).Get());
PostAdd->Files.PopLast();
StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal);
}
}
bool VcFolder::ParseGitAdd(int Result, LString s, ParseParams *Params)
{
if (Result)
{
OnCmdError(s, "add failed.");
}
else
{
GitAdd();
}
return false;
}
bool VcFolder::ParseCommit(int Result, LString s, ParseParams *Params)
{
if (LTreeItem::Select())
Select(true);
CommitListDirty = Result == 0;
CurrentCommit.Empty();
IsCommit = false;
if (Result)
{
switch (GetType())
{
case VcGit:
{
if (s.Find("Please tell me who you are") >= 0)
{
auto i = new LInput(GetTree(), "", "Git user name:", AppName);
i->DoModal([this, i](auto dlg, auto ctrlId)
{
if (ctrlId)
{
LString Args;
Args.Printf("config --global user.name \"%s\"", i->GetStr().Get());
StartCmd(Args);
auto inp = new LInput(GetTree(), "", "Git user email:", AppName);
i->DoModal([this, inp](auto dlg, auto ctrlId)
{
if (ctrlId)
{
LString Args;
Args.Printf("config --global user.email \"%s\"", inp->GetStr().Get());
StartCmd(Args);
}
delete dlg;
});
}
delete dlg;
});
}
break;
}
default:
break;
}
return false;
}
if (Result == 0 && LTreeItem::Select())
{
d->ClearFiles();
auto *w = d->Diff ? d->Diff->GetWindow() : NULL;
if (w)
w->SetCtrlName(IDC_MSG, NULL);
}
switch (GetType())
{
case VcGit:
{
Unpushed++;
CommitListDirty = true;
Update();
if (Params && Params->Str.Find("Push") >= 0)
Push();
break;
}
case VcSvn:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
GetCss(true)->Color(LColour::Green);
}
break;
}
case VcHg:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
if (Params && Params->Str.Find("Push") >= 0)
Push();
else
GetCss(true)->Color(LColour::Green);
}
break;
}
case VcCvs:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
GetCss(true)->Color(LColour::Green);
}
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
return true;
}
void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush)
{
LArray Add;
bool Partial = false;
for (auto fp: *d->Files)
{
VcFile *f = dynamic_cast(fp);
if (f)
{
int c = f->Checked();
if (c > 0)
Add.Add(f);
else
Partial = true;
}
}
if (CurrentBranch && Branch &&
!CurrentBranch.Equals(Branch))
{
int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO);
if (Response != IDYES)
return;
LJson j;
j.Set("Command", "commit");
j.Set("Msg", Msg);
j.Set("AndPush", (int64_t)AndPush);
StartBranch(Branch, j.GetJson());
return;
}
if (!IsCommit)
{
LString Args;
ParseParams *Param = AndPush ? new ParseParams("Push") : NULL;
switch (GetType())
{
case VcGit:
{
if (Add.Length() == 0)
{
break;
}
else if (Partial)
{
if (PostAdd.Reset(new GitCommit))
{
PostAdd->Files.SetFixedLength(false);
for (auto f : Add)
PostAdd->Files.Add(f->GetFileName());
PostAdd->Msg = Msg;
PostAdd->Branch = Branch;
PostAdd->Param = Param;
GitAdd();
}
}
else
{
LString m(Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -am \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
}
break;
}
case VcSvn:
{
LString::Array a;
a.New().Printf("commit -m \"%s\"", Msg);
for (auto pf: Add)
{
LString s = pf->GetFileName();
if (s.Find(" ") >= 0)
a.New().Printf("\"%s\"", s.Get());
else
a.New() = s;
}
Args = LString(" ").Join(a);
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
if (d->Tabs && IsCommit)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
break;
}
case VcHg:
{
LString::Array a;
LString CommitMsg = Msg;
TmpFile Tmp;
if (CommitMsg.Find("\n") >= 0)
{
Tmp.Create().Write(Msg);
a.New().Printf("commit -l \"%s\"", Tmp.GetName());
}
else
{
a.New().Printf("commit -m \"%s\"", Msg);
}
if (Partial)
{
for (auto pf: Add)
{
LString s = pf->GetFileName();
if (s.Find(" ") >= 0)
a.New().Printf("\"%s\"", s.Get());
else
a.New() = s;
}
}
Args = LString(" ").Join(a);
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
if (d->Tabs && IsCommit)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
break;
}
case VcCvs:
{
LString a;
a.Printf("commit -m \"%s\"", Msg);
IsCommit = StartCmd(a, &VcFolder::ParseCommit, NULL, LogNormal);
break;
}
default:
{
OnCmdError(LString(), "No commit impl for type.");
break;
}
}
}
}
bool VcFolder::ParseStartBranch(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
{
if (Result == 0 && Params && Params->Str)
{
LJson j(Params->Str);
auto cmd = j.Get("Command");
if (cmd.Equals("commit"))
{
auto Msg = j.Get("Msg");
auto AndPush = j.Get("AndPush").Int();
if (Msg)
{
Commit(Msg, NULL, AndPush > 0);
}
}
}
break;
}
default:
{
OnCmdError(LString(), "No commit impl for type.");
break;
}
}
return true;
}
void VcFolder::StartBranch(const char *BranchName, const char *OnCreated)
{
if (!BranchName)
return;
switch (GetType())
{
case VcHg:
{
LString a;
a.Printf("branch \"%s\"", BranchName);
StartCmd(a, &VcFolder::ParseStartBranch, OnCreated ? new ParseParams(OnCreated) : NULL);
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
}
void VcFolder::Push(bool NewBranchOk)
{
LString Args;
bool Working = false;
switch (GetType())
{
case VcHg:
{
auto args = NewBranchOk ? "push --new-branch" : "push";
Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal);
break;
}
case VcGit:
{
LString args;
if (NewBranchOk)
{
if (CurrentBranch)
{
args.Printf("push --set-upstream origin %s", CurrentBranch.Get());
}
else
{
OnCmdError(LString(), "Don't have the current branch?");
return;
}
}
else
{
args = "push";
}
Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal);
break;
}
case VcSvn:
{
// Nothing to do here.. the commit pushed the data already
break;
}
default:
{
OnCmdError(LString(), "No push impl for type.");
break;
}
}
if (d->Tabs && Working)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
}
bool VcFolder::ParsePush(int Result, LString s, ParseParams *Params)
{
bool Status = false;
if (Result)
{
bool needsNewBranchPerm = false;
switch (GetType())
{
case VcHg:
{
needsNewBranchPerm = s.Find("push creates new remote branches") >= 0;
break;
}
case VcGit:
{
needsNewBranchPerm = s.Find("The current branch") >= 0 &&
s.Find("has no upstream branch") >= 0;
break;
}
}
if (needsNewBranchPerm &&
LgiMsg(GetTree(), LLoadString(IDS_CREATE_NEW_BRANCH), AppName, MB_YESNO) == IDYES)
{
Push(true);
return false;
}
OnCmdError(s, "Push failed.");
}
else
{
switch (GetType())
{
case VcGit:
break;
case VcSvn:
break;
default:
break;
}
Unpushed = 0;
GetCss(true)->Color(LColour::Green);
Update();
Status = true;
}
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
return Status; // no reselect
}
void VcFolder::Pull(int AndUpdate, LoggingType Logging)
{
bool Status = false;
if (AndUpdate < 0)
AndUpdate = GetTree()->GetWindow()->GetCtrlValue(IDC_UPDATE) != 0;
switch (GetType())
{
case VcNone:
return;
case VcHg:
Status = StartCmd(AndUpdate ? "pull -u" : "pull", &VcFolder::ParsePull, NULL, Logging);
break;
case VcGit:
Status = StartCmd(AndUpdate ? "pull" : "fetch", &VcFolder::ParsePull, NULL, Logging);
break;
case VcSvn:
Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging);
break;
case VcPending:
OnVcsTypeEvents.New() = [this, AndUpdate, Logging]()
{
Pull(AndUpdate, Logging);
};
break;
default:
NoImplementation(_FL);
break;
}
if (d->Tabs && Status)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
}
bool VcFolder::ParsePull(int Result, LString s, ParseParams *Params)
{
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (Result)
{
OnCmdError(s, "Pull failed.");
return false;
}
else ClearError();
switch (GetType())
{
case VcGit:
{
// Git does a merge by default, so the current commit changes...
CurrentCommit.Empty();
break;
}
case VcHg:
{
CurrentCommit.Empty();
auto Lines = s.SplitDelimit("\n");
bool HasUpdates = false;
for (auto Ln: Lines)
{
if (Ln.Find("files updated") < 0)
continue;
auto Parts = Ln.Split(",");
for (auto p: Parts)
{
auto n = p.Strip().Split(" ", 1);
if (n.Length() == 2)
{
if (n[0].Int() > 0)
HasUpdates = true;
}
}
}
if (HasUpdates)
GetCss(true)->Color(LColour::Green);
else
GetCss(true)->Color(LCss::ColorInherit);
break;
}
case VcSvn:
{
// Svn also does a merge by default and can update our current position...
CurrentCommit.Empty();
LString::Array a = s.SplitDelimit("\r\n");
for (auto &Ln: a)
{
if (Ln.Find("At revision") >= 0)
{
LString::Array p = Ln.SplitDelimit(" .");
CurrentCommit = p.Last();
break;
}
else if (Ln.Find("svn cleanup") >= 0)
{
OnCmdError(s, "Needs cleanup");
break;
}
}
if (Params && Params->Str.Equals("log"))
{
LVariant Limit;
d->Opts.GetValue("svn-limit", Limit);
LString Args;
if (Limit.CastInt32() > 0)
Args.Printf("log --limit %i", Limit.CastInt32());
else
Args = "log";
IsLogging = StartCmd(Args, &VcFolder::ParseLog);
return false;
}
break;
}
default:
break;
}
CommitListDirty = true;
return true; // Yes - reselect and update
}
void VcFolder::MergeToLocal(LString Rev)
{
switch (GetType())
{
case VcGit:
{
LString Args;
Args.Printf("merge -m \"Merge with %s\" %s", Rev.Get(), Rev.Get());
StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal);
break;
}
case VcHg:
{
LString Args;
Args.Printf("merge -r %s", Rev.Get());
StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal);
break;
}
default:
LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
bool VcFolder::ParseMerge(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
if (Result == 0)
CommitListDirty = true;
else
OnCmdError(s, LLoadString(IDS_ERR_MERGE_FAILED));
break;
default:
LAssert(!"Impl me.");
break;
}
return true;
}
void VcFolder::Refresh()
{
CommitListDirty = true;
CurrentCommit.Empty();
GitNames.Empty();
Branches.DeleteObjects();
if (Uncommit && Uncommit->LListItem::Select())
Uncommit->Select(true);
Select(true);
}
void VcFolder::Clean()
{
switch (GetType())
{
case VcSvn:
StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal);
break;
default:
LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
bool VcFolder::ParseClean(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcSvn:
if (Result == 0)
GetCss(true)->Color(LCss::ColorInherit);
break;
default:
LAssert(!"Impl me.");
break;
}
return false;
}
LColour VcFolder::BranchColour(const char *Name)
{
if (!Name)
return GetPaletteColour(0);
auto b = Branches.Find(Name);
if (!b) // Must be a new one?
{
int i = 1;
for (auto b: Branches)
{
auto &v = b.value;
if (!v->Colour.IsValid())
{
if (v->Default)
v->Colour = GetPaletteColour(0);
else
v->Colour = GetPaletteColour(i++);
}
}
Branches.Add(Name, b = new VcBranch(Name));
b->Colour = GetPaletteColour((int)Branches.Length());
}
return b ? b->Colour : GetPaletteColour(0);
}
void VcFolder::CurrentRev(std::function Callback)
{
LString Cmd;
Cmd.Printf("id -i");
RunCmd(Cmd, LogNormal, [Callback](auto r)
{
if (r.Code == 0)
Callback(r.Out.Strip());
});
}
bool VcFolder::RenameBranch(LString NewName, LArray &Revs)
{
switch (GetType())
{
case VcHg:
{
// Update to the ancestor of the commits
LHashTbl,int> Refs(0, -1);
for (auto c: Revs)
{
for (auto p:*c->GetParents())
if (Refs.Find(p) < 0)
Refs.Add(p, 0);
if (Refs.Find(c->GetRev()) >= 0)
Refs.Add(c->GetRev(), 1);
}
LString::Array Ans;
for (auto i:Refs)
{
if (i.value == 0)
Ans.Add(i.key);
}
LArray Ancestors = d->GetRevs(Ans);
if (Ans.Length() != 1)
{
// We should only have one ancestor
LString s, m;
s.Printf("Wrong number of ancestors: " LPrintfInt64 ".\n", Ans.Length());
for (auto i: Ancestors)
{
m.Printf("\t%s\n", i->GetRev());
s += m;
}
LgiMsg(GetTree(), s, AppName, MB_OK);
break;
}
LArray Top;
for (auto c:Revs)
{
for (auto p:*c->GetParents())
if (Refs.Find(p) == 0)
Top.Add(c);
}
if (Top.Length() != 1)
{
d->Log->Print("Error: Can't find top most commit. (%s:%i)\n", _FL);
return false;
}
// Create the new branch...
auto First = Ancestors.First();
LString Cmd;
Cmd.Printf("update -r " LPrintfInt64, First->GetIndex());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
Cmd.Printf("branch \"%s\"", NewName.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
// Commit it to get a revision point to rebase to
Cmd.Printf("commit -m \"Branch: %s\"", NewName.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
CurrentRev([this, &Cmd, NewName, &Top](auto BranchNode)
{
// Rebase the old tree to this point
Cmd.Printf("rebase -s %s -d %s", Top.First()->GetRev(), BranchNode.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
CommitListDirty = true;
d->Log->Print("Finished rename.\n", _FL);
});
});
});
});
});
break;
}
default:
{
LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
return true;
}
bool VcFolder::ParseAddFile(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcCvs:
{
if (Result)
{
d->Tabs->Value(1);
OnCmdError(s, LLoadString(IDS_ERR_ADD_FAILED));
}
else ClearError();
break;
}
default:
break;
}
return false;
}
bool VcFolder::AddFile(const char *Path, bool AsBinary)
{
if (!Path)
return false;
switch (GetType())
{
case VcCvs:
{
auto p = LString(Path).RSplit(DIR_STR, 1);
ParseParams *params = NULL;
if (p.Length() >= 2)
{
if ((params = new ParseParams))
params->AltInitPath = p[0];
}
LString a;
a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", p.Length() > 1 ? p.Last().Get() : Path);
return StartCmd(a, &VcFolder::ParseAddFile, params);
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
return false;
}
bool VcFolder::ParseRevert(int Result, LString s, ParseParams *Params)
{
if (GetType() == VcSvn)
{
if (s.Find("Skipped ") >= 0)
Result = 1; // Stupid svn... *sigh*
}
if (Result)
{
OnCmdError(s, LLoadString(IDS_ERR_REVERT_FAILED));
}
ListWorkingFolder();
return false;
}
bool VcFolder::Revert(LString::Array &Uris, const char *Revision)
{
if (Uris.Length() == 0)
return false;
switch (GetType())
{
case VcGit:
{
LStringPipe cmd, paths;
LAutoPtr params;
if (Revision)
{
cmd.Print("checkout %s", Revision);
}
else
{
// Unstage the file...
cmd.Print("reset");
}
for (auto u: Uris)
{
auto Path = GetFilePart(u);
paths.Print(" \"%s\"", Path.Get());
}
auto p = paths.NewLStr();
cmd.Write(p);
if (!Revision)
{
if (params.Reset(new ParseParams))
{
params->Callback = [this, p](auto code, auto str)
{
LString c;
c.Printf("checkout %s", p.Get());
StartCmd(c, &VcFolder::ParseRevert);
};
}
}
return StartCmd(cmd.NewLStr(), &VcFolder::ParseRevert, params.Release());
break;
}
case VcHg:
case VcSvn:
{
LStringPipe p;
if (Revision)
p.Print("up -r %s", Revision);
else
p.Print("revert");
for (auto u: Uris)
{
auto Path = GetFilePart(u);
p.Print(" \"%s\"", Path.Get());
}
auto a = p.NewLStr();
return StartCmd(a, &VcFolder::ParseRevert);
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
return false;
}
bool VcFolder::ParseResolveList(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
{
auto lines = s.Replace("\r").Split("\n");
for (auto &ln: lines)
{
auto p = ln.Split(" ", 1);
if (p.Length() == 2)
{
if (p[0].Equals("U"))
{
auto f = new VcFile(d, this, LString(), true);
f->SetText(p[0], COL_STATE);
f->SetText(p[1], COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
}
}
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
return true;
}
bool VcFolder::ParseResolve(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
break;
}
case VcHg:
{
d->Log->Print("Resolve: %s\n", s.Get());
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
return true;
}
bool VcFolder::Resolve(const char *Path, LvcResolve Type)
{
if (!Path)
return false;
switch (GetType())
{
case VcGit:
{
LString a;
auto local = GetFilePart(Path);
LAutoPtr params(new ParseParams(Path));
switch (Type)
{
case ResolveIncoming:
a.Printf("checkout --theirs \"%s\"", local.Get());
break;
case ResolveLocal:
a.Printf("checkout --ours \"%s\"", local.Get());
break;
case ResolveMark:
a.Printf("add \"%s\"", local.Get());
break;
default:
OnCmdError(Path, "No resolve type implemented.");
return false;
}
if (Type == ResolveIncoming ||
Type == ResolveLocal)
{
// Add the file after the resolution:
params->Callback = [this, local](auto code, auto str)
{
LString a;
a.Printf("add \"%s\"", local.Get());
StartCmd(a, &VcFolder::ParseAddFile);
Refresh();
};
}
return StartCmd(a, &VcFolder::ParseResolve, params.Release());
}
case VcHg:
{
LString a;
auto local = GetFilePart(Path);
switch (Type)
{
case ResolveMark:
a.Printf("resolve -m \"%s\"", local.Get());
break;
case ResolveUnmark:
a.Printf("resolve -u \"%s\"", local.Get());
break;
case ResolveLocal:
a.Printf("resolve -t internal:local \"%s\"", local.Get());
break;
case ResolveIncoming:
a.Printf("resolve -t internal:other \"%s\"", local.Get());
break;
default:
break;
}
if (a)
return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path));
break;
}
case VcSvn:
case VcCvs:
default:
{
NoImplementation(_FL);
break;
}
}
return false;
}
bool BlameLine::Parse(VersionCtrl type, LArray &out, LString in)
{
auto lines = in.SplitDelimit("\n", -1, false);
switch (type)
{
case VcGit:
{
for (auto &ln: lines)
{
auto s = ln.Get();
auto open = ln.Find("(");
auto close = ln.Find(")", open);
if (open > 0 && close > open)
{
auto eRef = ln(0, open-1);
auto fields = ln(open + 1, close);
auto parts = fields.SplitDelimit();
auto &o = out.New();
o.ref = eRef;
o.line = parts.Last();
parts.PopLast();
LString::Array name;
LDateTime dt;
for (auto p: parts)
{
auto first = p(0);
if (IsDigit(first))
{
if (p.Find("-") > 0)
dt.SetDate(p);
else if (p.Find(":") > 0)
dt.SetTime(p);
}
else if (first == '+')
dt.SetTimeZone((int)p.Int(), false);
else
name.Add(p);
}
o.user = LString(" ").Join(name);
o.date = dt.Get();
o.src = ln(close + 1, -1);
}
else if (ln.Length() > 0)
{
int asd=0;
}
}
break;
}
case VcHg:
{
for (auto &ln: lines)
{
auto s = ln.Get();
auto eUser = strchr(s, ' ');
if (!eUser)
continue;
auto eRef = strchr(eUser, ':');
if (!eRef)
continue;
auto &o = out.New();
o.user.Set(s, eUser++ - s);
o.ref.Set(eUser, eRef - eUser);
o.src = eRef + 1;
}
break;
}
/*
case VcSvn:
{
break;
}
*/
default:
{
LAssert(0);
return false;
}
}
return true;
}
bool VcFolder::ParseBlame(int Result, LString s, ParseParams *Params)
{
if (!Params)
{
LAssert(!"Need the path in the params.");
return false;
}
LArray lines;
if (BlameLine::Parse(GetType(), lines, s))
{
if (auto ui = new BrowseUi(BrowseUi::TBlame, d, this, Params->Str))
ui->ParseBlame(lines, s);
}
else NoImplementation(_FL);
return false;
}
bool VcFolder::Blame(const char *Path)
{
if (!Path)
return false;
auto file = GetFilePart(Path);
LAutoPtr Params(new ParseParams(file));
LUri u(Path);
switch (GetType())
{
case VcGit:
{
LString a;
a.Printf("-P blame \"%s\"", file.Get());
return StartCmd(a, &VcFolder::ParseBlame, Params.Release());
break;
}
case VcHg:
{
LString a;
a.Printf("annotate -un \"%s\"", file.Get());
return StartCmd(a, &VcFolder::ParseBlame, Params.Release());
break;
}
case VcSvn:
{
LString a;
a.Printf("blame \"%s\"", file.Get());
return StartCmd(a, &VcFolder::ParseBlame, Params.Release());
break;
}
default:
{
NoImplementation(_FL);
break;
}
}
return true;
}
bool VcFolder::SaveFileAs(const char *Path, const char *Revision)
{
if (!Path || !Revision)
return false;
return true;
}
bool VcFolder::ParseSaveAs(int Result, LString s, ParseParams *Params)
{
return false;
}
bool VcFolder::ParseCounts(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
Unpushed = (int) s.Strip().Split("\n").Length();
break;
}
case VcSvn:
{
int64 ServerRev = 0;
bool HasUpdate = false;
LString::Array c = s.Split("\n");
for (unsigned i=0; i 1 && a[0].Equals("Status"))
ServerRev = a.Last().Int();
else if (a[0].Equals("*"))
HasUpdate = true;
}
if (ServerRev > 0 && HasUpdate)
{
int64 CurRev = CurrentCommit.Int();
Unpulled = (int) (ServerRev - CurRev);
}
else Unpulled = 0;
Update();
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
IsUpdatingCounts = false;
Update();
return false; // No re-select
}
void VcFolder::SetEol(const char *Path, int Type)
{
if (!Path) return;
switch (Type)
{
case IDM_EOL_LF:
{
ConvertEol(Path, false);
break;
}
case IDM_EOL_CRLF:
{
ConvertEol(Path, true);
break;
}
case IDM_EOL_AUTO:
{
#ifdef WINDOWS
ConvertEol(Path, true);
#else
ConvertEol(Path, false);
#endif
break;
}
}
}
void VcFolder::UncommitedItem::Select(bool b)
{
LListItem::Select(b);
if (b)
{
LTreeItem *i = d->Tree->Selection();
VcFolder *f = dynamic_cast(i);
if (f)
f->ListWorkingFolder();
if (d->Msg)
{
d->Msg->Name(NULL);
auto *w = d->Msg->GetWindow();
if (w)
{
w->SetCtrlEnabled(IDC_COMMIT, true);
w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true);
}
}
}
}
void VcFolder::UncommitedItem::OnPaint(LItem::ItemPaintCtx &Ctx)
{
LFont *f = GetList()->GetFont();
f->Transparent(false);
f->Colour(Ctx.Fore, Ctx.Back);
LDisplayString ds(f, "(working folder)");
ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx);
}
//////////////////////////////////////////////////////////////////////////////////////////
VcLeaf::VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder)
{
Parent = parent;
d = Parent->GetPriv();
LAssert(uri.Find("://") >= 0); // Is URI
Uri.Set(uri);
LAssert(Uri);
Leaf = leaf;
Folder = folder;
Item->Insert(this);
if (Folder)
{
Insert(Tmp = new LTreeItem);
Tmp->SetText("Loading...");
}
}
VcLeaf::~VcLeaf()
{
for (auto l: Log)
{
if (!l->GetList())
delete l;
}
}
LString VcLeaf::Full()
{
LUri u = Uri;
u += Leaf;
return u.ToString();
}
void VcLeaf::OnBrowse()
{
auto full = Full();
LList *Files = d->Files;
Files->Empty();
LDirectory Dir;
for (int b = Dir.First(full); b; b = Dir.Next())
{
if (Dir.IsDir())
continue;
VcFile *f = new VcFile(d, Parent, LString(), true);
if (f)
{
f->SetUri(LString("file://") + full);
f->SetText(Dir.GetName(), COL_FILENAME);
Files->Insert(f);
}
}
Files->ResizeColumnsToContent();
if (Folder)
Parent->FolderStatus(full, this);
}
void VcLeaf::AfterBrowse()
{
}
VcLeaf *VcLeaf::FindLeaf(const char *Path, bool OpenTree)
{
if (!Stricmp(Path, Full().Get()))
return this;
if (OpenTree)
DoExpand();
VcLeaf *r = NULL;
for (auto n = GetChild(); !r && n; n = n->GetNext())
{
auto l = dynamic_cast(n);
if (l)
r = l->FindLeaf(Path, OpenTree);
}
return r;
}
void VcLeaf::DoExpand()
{
if (Tmp)
{
Tmp->Remove();
DeleteObj(Tmp);
Parent->ReadDir(this, Full());
}
}
void VcLeaf::OnExpand(bool b)
{
if (b)
DoExpand();
}
const char *VcLeaf::GetText(int Col)
{
if (Col == 0)
return Leaf;
return NULL;
}
int VcLeaf::GetImage(int Flags)
{
return Folder ? IcoFolder : IcoFile;
}
int VcLeaf::Compare(VcLeaf *b)
{
// Sort folders to the top...
if (Folder ^ b->Folder)
return (int)b->Folder - (int)Folder;
// Then alphabetical
return Stricmp(Leaf.Get(), b->Leaf.Get());
}
bool VcLeaf::Select()
{
return LTreeItem::Select();
}
void VcLeaf::Select(bool b)
{
LTreeItem::Select(b);
if (b)
{
d->Commits->RemoveAll();
OnBrowse();
ShowLog();
}
}
void VcLeaf::ShowLog()
{
if (!Log.Length())
return;
d->Commits->RemoveAll();
Parent->DefaultFields();
Parent->UpdateColumns();
for (auto i: Log)
// We make a copy of the commit here so that the LList owns the copied object,
// and this object still owns 'i'.
d->Commits->Insert(new VcCommit(*i), -1, false);
d->Commits->UpdateAllItems();
d->Commits->ResizeColumnsToContent();
}
void VcLeaf::OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu s;
s.AppendItem("Log", IDM_LOG);
s.AppendItem("Blame", IDM_BLAME, !Folder);
s.AppendSeparator();
s.AppendItem("Browse To", IDM_BROWSE_FOLDER);
s.AppendItem("Terminal At", IDM_TERMINAL);
int Cmd = s.Float(GetTree(), m - _ScrollPos());
switch (Cmd)
{
case IDM_LOG:
{
Parent->LogFile(Full());
break;
}
case IDM_BLAME:
{
Parent->Blame(Full());
break;
}
case IDM_BROWSE_FOLDER:
{
LBrowseToFile(Full());
break;
}
case IDM_TERMINAL:
{
TerminalAt(Full());
break;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////
ProcessCallback::ProcessCallback(LString exe,
LString args,
LString localPath,
LTextLog *log,
LView *view,
std::function callback) :
Log(log),
View(view),
Callback(callback),
LThread("ProcessCallback.Thread"),
LSubProcess(exe, args)
{
SetInitFolder(localPath);
if (Log)
Log->Print("%s %s\n", exe.Get(), args.Get());
Run();
}
int ProcessCallback::Main()
{
if (!Start())
{
Ret.Out.Printf("Process failed with %i", GetErrorCode());
Callback(Ret);
}
else
{
while (IsRunning())
{
auto Rd = Read();
if (Rd.Length())
{
Ret.Out += Rd;
if (Log)
Log->Write(Rd.Get(), Rd.Length());
}
}
auto Rd = Read();
if (Rd.Length())
{
Ret.Out += Rd;
if (Log)
Log->Write(Rd.Get(), Rd.Length());
}
Ret.Code = GetExitValue();
}
View->PostEvent(M_HANDLE_CALLBACK, (LMessage::Param)this);
return 0;
}
void ProcessCallback::OnComplete() // Called in the GUI thread...
{
Callback(Ret);
}
diff --git a/src/common/Coding/Instructions.h b/src/common/Coding/Instructions.h
--- a/src/common/Coding/Instructions.h
+++ b/src/common/Coding/Instructions.h
@@ -1,2081 +1,2081 @@
/*
This file is included in both the LVirtualMachinePriv::Execute and LVirtualMachinePriv::Decompile
That way the "parsing" of instructions is the same.
During decompile the define VM_DECOMP is active.
During execution the define VM_EXECUTE is active.
*/
#ifdef VM_EXECUTE
#define Resolve() &Scope[c.r->Scope][c.r->Index]; c.r++
#define LResolveRef(nm) LVariant *nm =
#else
#define Resolve() c.r++
#define LResolveRef(nm)
// LVarRef *
#endif
default:
{
#if VM_DECOMP
if (Log)
Log->Print("\t%p Unknown instruction %i (0x%x)\n",
CurrentScriptAddress - 1,
c.u8[-1], c.u8[-1]);
#endif
OnException(_FL, CurrentScriptAddress, "Unknown instruction");
SetScriptError;
break;
}
case INop:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Nop\n", CurrentScriptAddress - 1);
#endif
break;
}
case ICast:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Cast %s",
CurrentScriptAddress - 1,
c.r[0].GetStr());
#endif
LResolveRef(Var) Resolve();
uint8_t Type = *c.u8++;
#if VM_DECOMP
if (Log)
Log->Print(" to %s\n", LVariant::TypeToString((LVariantType)Type));
#endif
#if VM_EXECUTE
switch (Type)
{
case GV_INT32:
{
*Var = Var->CastInt32();
break;
}
case GV_STRING:
{
*Var = Var->CastString();
break;
}
case GV_DOM:
{
*Var = Var->CastDom();
break;
}
case GV_DOUBLE:
{
*Var = Var->CastDouble();
break;
}
case GV_INT64:
{
*Var = Var->CastInt32();
break;
}
case GV_BOOL:
{
*Var = Var->CastBool();
break;
}
default:
{
LString s;
s.Printf("%s ICast warning: unknown type %i/%s\n",
Code->AddrToSourceRef(CurrentScriptAddress),
Var->Type,
LVariant::TypeToString(Var->Type));
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
#endif
break;
}
case IAssign:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Assign %s <- %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
CheckParam(Dst != Src);
*Dst = *Src;
#endif
break;
}
case IJump:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Jump by %i (to 0x%x)\n",
CurrentScriptAddress - 1,
c.i32[0],
CurrentScriptAddress + 4 + c.i32[0]);
#endif
#ifdef VM_EXECUTE
int32 Jmp = *
#endif
c.i32++;
#ifdef VM_EXECUTE
CheckParam(Jmp != 0);
c.u8 += Jmp;
#endif
break;
}
case IJumpZero:
{
#if VM_DECOMP
if (Log)
Log->Print("%p JumpZ(%s) by 0x%x\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.i32[1]);
#endif
LResolveRef(Exp) Resolve();
#ifdef VM_EXECUTE
int32 Jmp = *
#endif
c.i32++;
#ifdef VM_EXECUTE
CheckParam(Jmp != 0);
if (!Exp->CastInt32())
c.u8 += Jmp;
#endif
break;
}
// case IUnaryPlus:
case IUnaryMinus:
{
#if VM_DECOMP
if (Log)
Log->Print("%p UnaryMinus %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr());
#endif
LResolveRef(Var) Resolve();
#ifdef VM_EXECUTE
switch (Var->Type)
{
case GV_DOUBLE:
*Var = -Var->CastDouble();
break;
case GV_INT64:
*Var = -Var->CastInt64();
break;
default:
*Var = -Var->CastInt32();
break;
}
#endif
break;
}
case IPlus:
case IPlusEquals:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Plus %s += %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
if (Dst->Str())
{
size_t dlen = strlen(Dst->Str());
char *ss;
LVariant SrcTmp;
switch (Src->Type)
{
case GV_NULL:
ss = (char*)"(null)";
break;
case GV_STRING:
ss = Src->Str();
break;
default:
SrcTmp = *Src;
ss = SrcTmp.CastString();
break;
}
if (ss)
{
size_t slen = strlen(ss);
char *s = new char[slen + dlen + 1];
if (s)
{
memcpy(s, Dst->Value.String, dlen);
memcpy(s + dlen, ss, slen);
s[dlen + slen] = 0;
DeleteArray(Dst->Value.String);
Dst->Value.String = s;
}
}
}
else switch (DecidePrecision(Dst->Type, Src->Type))
{
case GV_DOUBLE:
*Dst = Dst->CastDouble() + Src->CastDouble();
break;
case GV_INT64:
*Dst = Dst->CastInt64() + Src->CastInt64();
break;
default:
*Dst = Dst->CastInt32() + Src->CastInt32();
break;
}
#endif
break;
}
case IMinus:
case IMinusEquals:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Minus %s -= %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
switch (DecidePrecision(Dst->Type, Src->Type))
{
case GV_DOUBLE:
*Dst = Dst->CastDouble() - Src->CastDouble();
break;
case GV_INT64:
*Dst = Dst->CastInt64() - Src->CastInt64();
break;
default:
*Dst = Dst->CastInt32() - Src->CastInt32();
break;
}
#endif
break;
}
case IMul:
case IMulEquals:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Mul %s *= %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
switch (DecidePrecision(Dst->Type, Src->Type))
{
case GV_DOUBLE:
*Dst = Dst->CastDouble() * Src->CastDouble();
break;
case GV_INT64:
*Dst = Dst->CastInt64() * Src->CastInt64();
break;
default:
*Dst = Dst->CastInt32() * Src->CastInt32();
break;
}
#endif
break;
}
case IDiv:
case IDivEquals:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Div %s /= %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
switch (DecidePrecision(Dst->Type, Src->Type))
{
case GV_DOUBLE:
*Dst = Dst->CastDouble() / Src->CastDouble();
break;
case GV_INT64:
*Dst = Dst->CastInt64() / Src->CastInt64();
break;
default:
*Dst = Dst->CastInt32() / Src->CastInt32();
break;
}
#endif
break;
}
case IMod:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Mod %s %%= %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
switch (DecidePrecision(Dst->Type, Src->Type))
{
case GV_DOUBLE:
*Dst = fmod(Dst->CastDouble(), Src->CastDouble());
break;
case GV_INT64:
*Dst = Dst->CastInt64() % Src->CastInt64();
break;
default:
*Dst = Dst->CastInt32() % Src->CastInt32();
break;
}
#endif
break;
}
case IPostInc:
case IPreInc:
{
#if VM_DECOMP
if (Log)
Log->Print("%p PostInc %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr());
#endif
LResolveRef(v) Resolve();
#ifdef VM_EXECUTE
switch (v->Type)
{
case GV_DOUBLE:
*v = v->Value.Dbl + 1;
break;
case GV_INT64:
*v = v->Value.Int64 + 1;
break;
default:
*v = v->CastInt32() + 1;
break;
}
#endif
break;
}
case IPostDec:
case IPreDec:
{
#if VM_DECOMP
if (Log)
Log->Print("%p PostDec %sn",
CurrentScriptAddress - 1,
c.r[0].GetStr());
#endif
LResolveRef(v) Resolve();
#ifdef VM_EXECUTE
switch (v->Type)
{
case GV_DOUBLE:
*v = v->Value.Dbl - 1;
break;
case GV_INT64:
*v = v->Value.Int64 - 1;
break;
default:
*v = v->CastInt32() - 1;
break;
}
#endif
break;
}
case IEquals:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s == %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = CompareVariants(Dst, Src) == 0;
#endif
break;
}
case INotEquals:
{
#if VM_DECOMP
if (Log)
Log->Print( "%p %s != %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = CompareVariants(Dst, Src) != 0;
#endif
break;
}
case ILessThan:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s < %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = CompareVariants(Dst, Src) < 0;
#endif
break;
}
case ILessThanEqual:
{
#if VM_DECOMP
if (Log)
Log->Print( "%p %s < %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = CompareVariants(Dst, Src) <= 0;
#endif
break;
}
case IGreaterThan:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s < %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = CompareVariants(Dst, Src) > 0;
#endif
break;
}
case IGreaterThanEqual:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s < %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = CompareVariants(Dst, Src) >= 0;
#endif
break;
}
case ICallMethod:
{
LFunc *Meth = *c.fn++;
if (!Meth)
{
Log->Print( "%s ICallMethod error: No method struct.\n",
Code->AddrToSourceRef(CurrentScriptAddress - sizeof(Meth)));
SetScriptError;
break;
}
#ifdef VM_DECOMP
if (Log)
{
Log->Print("%p Call: %s = %s(",
CurrentScriptAddress - sizeof(Meth) - 1,
c.r[0].GetStr(),
Meth->Method.Get());
}
#endif
LResolveRef(Ret) Resolve();
uint16 Args = *c.u16++;
#ifdef VM_EXECUTE
LScriptArguments Arg(Vm, Ret, NULL, CurrentScriptAddress);
#endif
for (int i=0; iPrint("%s%s", i?", ":"", c.r[0].GetStr());
#endif
#if VM_EXECUTE
Arg[i] = Resolve();
CheckParam(Arg[i] != NULL);
#else
c.r++;
#endif
}
#if VM_DECOMP
if (Log)
Log->Print(")\n");
#endif
#if VM_EXECUTE
LHostFunc *Hf = dynamic_cast(Meth);
if (Hf)
{
if (!(Hf->Context->*(Hf->Func))(Arg))
{
if (Log)
Log->Print( "%s ICallMethod error: Method '%s' failed.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
Meth->Method.Get());
SetScriptError;
}
}
else
{
// Fixme
if (!Meth->Call(NULL, Arg))
{
if (Log)
Log->Print( "%s ICallMethod error: Method '%s' failed.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
Meth->Method.Get());
SetScriptError;
}
}
ON_EXCEPTION(Arg);
#endif
break;
}
case ICallScript:
{
int32 FuncAddr = *c.i32++;
if (FuncAddr < 0 || (uint32_t)FuncAddr >= Code->ByteCode.Length())
{
if (Log)
Log->Print( "%s ICallScript error: Script function call invalid addr '%p'.\n",
Code->AddrToSourceRef(CurrentScriptAddress - sizeof(FuncAddr)),
FuncAddr);
SetScriptError;
break;
}
uint16 Frame = *c.u16++;
#if VM_DECOMP
if (Log)
Log->Print("%p CallScript: %s = %p(frame=%i)(",
CurrentScriptAddress - 5,
c.r[0].GetStr(),
FuncAddr,
Frame);
#endif
#ifdef VM_EXECUTE
// Set up stack for function call
int CurFrameSize = Frames.Last().CurrentFrameSize;
StackFrame &Sf = Frames.New();
Sf.CurrentFrameSize = Frame;
Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0;
Sf.ReturnValue = *c.r++;
if (Sf.ReturnValue.Scope == SCOPE_LOCAL)
Sf.ReturnValue.Index -= CurFrameSize;
uint16 Args = *c.u16++;
// Increase the local stack size
size_t LocalsBase = Locals.Length();
size_t LocalsPos = Scope[SCOPE_LOCAL] - Locals.AddressOf();
Locals.SetFixedLength(false);
Locals.Length(LocalsBase + Frame);
Locals.SetFixedLength();
Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsPos);
// Put the arguments of the function call into the local array
LArray Arg;
#else
LResolveRef(Ret) Resolve();
int Args = *c.u16++;
#endif
for (int i=0; iPrint("%s%s", i?",":"", c.r[0].GetStr());
#endif
#if VM_EXECUTE
Locals[LocalsBase+i] = *Resolve();
#else
c.r++;
#endif
}
#if VM_EXECUTE
// Set IP to start of function
Sf.ReturnIp = CurrentScriptAddress;
c.u8 = Base + FuncAddr;
Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsBase); // This can evaluation to NULL when there is NO locals.
#endif
#if VM_DECOMP
if (Log)
Log->Print(")\n");
#endif
break;
}
case IRet:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Ret %s\n", CurrentScriptAddress - 1, c.r[0].GetStr());
#endif
LResolveRef(ReturnValue) Resolve();
#ifdef VM_EXECUTE
if (Frames.Length() > 0)
{
StackFrame Sf = Frames[Frames.Length()-1];
LVarRef &Ret = Sf.ReturnValue;
LVariant *RetVar = &Scope[Ret.Scope][Ret.Index];
// LgiTrace("IRet to %i:%i\n", Ret.Scope, Ret.Index);
if (Ret.Scope == SCOPE_LOCAL)
LAssert(Locals.PtrCheck(RetVar));
*RetVar = *ReturnValue;
CheckParam(RetVar->Type == ReturnValue->Type);
Frames.Length(Frames.Length()-1);
Locals.SetFixedLength(false);
if (Locals.Length() >= Sf.CurrentFrameSize)
{
ssize_t Base = Locals.Length() - Sf.CurrentFrameSize;
/*
if (ArgsOutput)
{
if (Frames.Length() == 0)
{
for (unsigned i=0; iLength(); i++)
{
*(*ArgsOutput)[i] = Locals[Base+i];
}
}
}
*/
// LgiTrace("%s:%i Locals %i -> %i\n", _FL, Locals.Length(), Base);
Locals.Length(Base);
Scope[SCOPE_LOCAL] = &Locals[Sf.PrevFrameStart];
}
else
{
// LgiTrace("%s:%i - Locals %i -> %i\n", _FL, Locals.Length(), 0);
Locals.Length(0);
Scope[SCOPE_LOCAL] = NULL;
}
Locals.SetFixedLength();
c.u8 = Base + Sf.ReturnIp;
}
else
{
ExitScriptExecution;
}
#endif
break;
}
case IArrayGet:
{
#if VM_DECOMP
if (Log)
Log->Print( "%p ArrayGet %s = %s[%s]\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr(),
c.r[2].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Var) Resolve();
LResolveRef(Idx) Resolve();
#ifdef VM_EXECUTE
switch (Var->Type)
{
case GV_LIST:
{
CheckParam(Var->Value.Lst);
LVariant *t = Var->Value.Lst->ItemAt(Idx->CastInt32());
if (t)
{
if (Var == Dst)
{
if (Var->Value.Lst->Delete(t))
{
*Var = *t;
DeleteObj(t);
}
else CheckParam(!"List delete failed.");
}
else *Dst = *t;
}
else Dst->Empty();
break;
}
case GV_HASHTABLE:
{
CheckParam(Var->Value.Hash);
LVariant *t = (LVariant*)Var->Value.Hash->Find(Idx->CastString());
if (t) *Dst = *t;
else Dst->Empty();
break;
}
case GV_CUSTOM:
{
LCustomType *T = Var->Value.Custom.Dom;
size_t Sz = T->Sizeof();
int Index = Idx->CastInt32();
Dst->Type = GV_CUSTOM;
Dst->Value.Custom.Dom = T;
Dst->Value.Custom.Data = Var->Value.Custom.Data + (Sz * Index);
break;
}
case GV_STRING:
{
auto c = Var->Str();
auto i = Idx->CastInt64();
if (!c || i < 0)
break;
LUtf8Ptr p(c);
uint32_t ch;
do
{
ch = p;
if (i-- == 0)
{
*Dst = ch;
break;
}
p++;
}
while (ch);
break;
}
default:
{
LString s;
s.Printf("%s IArrayGet warning: Can't array deref variant type %i\n",
Code->AddrToSourceRef(CurrentScriptAddress),
Var->Type);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
#endif
break;
}
case IArraySet:
{
#if VM_DECOMP
if (Log)
Log->Print( "%p ArraySet %s[%s] = %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr(),
c.r[2].GetStr());
#endif
LResolveRef(Var) Resolve();
LResolveRef(Idx) Resolve();
LResolveRef(Val) Resolve();
#ifdef VM_EXECUTE
switch (Var->Type)
{
case GV_LIST:
{
CheckParam(Var->Value.Lst);
(*Var->Value.Lst).Insert(new LVariant(*Val), Idx->CastInt32());
break;
}
case GV_HASHTABLE:
{
CheckParam(Var->Value.Hash);
LVariant *Old = (LVariant*)Var->Value.Hash->Find(Idx->CastString());
DeleteObj(Old);
Var->Value.Hash->Add(Idx->CastString(), new LVariant(*Val));
break;
}
default:
{
LString s;
s.Printf("%s IArraySet warning: Can't dereference type '%s'\n",
Code->AddrToSourceRef(CurrentScriptAddress),
LVariant::TypeToString(Var->Type));
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
#endif
break;
}
case IAnd:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s && %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = (Dst->CastInt32() != 0) && (Src->CastInt32() != 0);
#endif
break;
}
case IOr:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s || %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Src) Resolve();
#ifdef VM_EXECUTE
*Dst = (Dst->CastInt32() != 0) || (Src->CastInt32() != 0);
#endif
break;
}
case INot:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s = !%s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[0].GetStr());
#endif
LResolveRef(Dst) Resolve();
#ifdef VM_EXECUTE
*Dst = !Dst->CastBool();
#endif
break;
}
case IDomGet:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s = %s->DomGet(%s, %s)\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr(),
c.r[2].GetStr(),
c.r[3].GetStr());
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Dom) Resolve();
LResolveRef(Name) Resolve();
LResolveRef(Arr) Resolve();
#ifdef VM_EXECUTE
// Return "NULL" in Dst on error
if (Dst != Dom)
Dst->Empty();
switch (Dom->Type)
{
case GV_DOM:
case GV_STREAM:
case GV_LSURFACE:
{
auto *dom = Dom->CastDom();
CheckParam(dom != NULL);
char *sName = Name->Str();
CheckParam(sName);
bool Ret = dom->GetVariant(sName, *Dst, CastArrayIndex(Arr));
if (!Ret)
{
Dst->Empty();
LString s;
s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
LVariant::TypeToString(Dom->Type),
sName);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
}
break;
}
case GV_DATETIME:
{
CheckParam(Dom->Value.Date != NULL);
char *sName = Name->Str();
CheckParam(sName);
bool Ret = Dom->Value.Date->GetVariant(sName, *Dst, CastArrayIndex(Arr));
if (!Ret)
{
Dst->Empty();
LString s;
s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
LVariant::TypeToString(Dom->Type),
sName);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
}
break;
}
case GV_CUSTOM:
{
LCustomType *Type = Dom->Value.Custom.Dom;
if (Type)
{
int Fld;
if (Name->Type == GV_INT32)
Fld = Name->Value.Int;
else
Fld = Type->IndexOf(Name->Str());
int Index = Arr ? Arr->CastInt32() : 0;
Type->Get(Fld, *Dst, Dom->Value.Custom.Data, Index);
}
break;
}
case GV_LIST:
{
CheckParam(Dom->Value.Lst);
char *sName = Name->Str();
CheckParam(sName);
LDomProperty p = LStringToDomProp(sName);
if (p == ObjLength)
(*Dst) = (int)Dom->Value.Lst->Length();
break;
}
case GV_HASHTABLE:
{
CheckParam(Dom->Value.Hash);
char *sName = Name->Str();
CheckParam(sName);
LDomProperty p = LStringToDomProp(sName);
if (p == ObjLength)
(*Dst) = (int)Dom->Value.Hash->Length();
break;
}
case GV_BINARY:
{
char *sName = Name->Str();
CheckParam(sName);
LDomProperty p = LStringToDomProp(sName);
if (p == ObjLength)
(*Dst) = Dom->Value.Binary.Length;
break;
}
case GV_INT32:
{
char *sName = Name->Str();
CheckParam(sName);
LDomProperty p = LStringToDomProp(sName);
switch (p)
{
case TypeString:
{
char s[32];
sprintf_s(s, sizeof(s), "%i", Dom->Value.Int);
*Dst = s;
break;
}
case TypeDouble:
{
*Dst = (double)Dom->Value.Int;
break;
}
default:
{
Dst->Empty();
LString s;
s.Printf("%s IDomGet warning: Unexpected int32 member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
break;
}
case GV_INT64:
{
char *sName = Name->Str();
CheckParam(sName);
LDomProperty p = LStringToDomProp(sName);
switch (p)
{
case TypeString:
{
char s[32];
sprintf_s(s, sizeof(s), LPrintfInt64, Dom->Value.Int64);
*Dst = s;
break;
}
case TypeDouble:
{
*Dst = (double)Dom->Value.Int64;
break;
}
default:
{
Dst->Empty();
LString s;
s.Printf("%s IDomGet warning: Unexpected int64 member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
break;
}
case GV_DOUBLE:
{
char *sName = Name->Str();
CheckParam(sName);
LDomProperty p = LStringToDomProp(sName);
switch (p)
{
case TypeString:
{
char s[32];
sprintf_s(s, sizeof(s), "%g", Dom->Value.Dbl);
*Dst = s;
break;
}
case TypeInt:
{
*Dst = (int64)Dom->Value.Dbl;
break;
}
default:
{
Dst->Empty();
LString s;
s.Printf("%s IDomGet warning: Unexpected double member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
break;
}
case GV_STRING:
{
char *sName = Name->Str();
CheckParam(sName);
LDomProperty p = LStringToDomProp(sName);
switch (p)
{
case ObjLength:
{
(*Dst) = (int)strlen(Dom->Str());
break;
}
case TypeInt:
{
(*Dst) = Dom->CastInt32();
break;
}
case TypeDouble:
{
(*Dst) = Dom->CastDouble();
break;
}
default:
{
Dst->Empty();
LString s;
s.Printf("%s IDomGet warning: Unexpected string member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
break;
}
case GV_NULL:
{
LString s;
s.Printf("%s IDomGet warning: Can't deref NULL object.\n",
Code->AddrToSourceRef(CurrentScriptAddress));
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
default:
{
LString s;
s.Printf("%s IDomGet warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n",
Code->AddrToSourceRef(CurrentScriptAddress),
LVariant::TypeToString(Dom->Type),
_FL,
CurrentScriptAddress);
if (Log)
Log->Write(s, s.Length());
if (LVirtualMachine::BreakOnWarning)
OnException(NULL, -1, CurrentScriptAddress, s);
Status = ScriptWarning;
break;
}
}
#endif
break;
}
case IDomSet:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s->DomSet(%s, %s) = %s\n",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr(),
c.r[2].GetStr(),
c.r[3].GetStr());
#endif
LResolveRef(Dom) Resolve();
LResolveRef(Name) Resolve();
LResolveRef(Arr) Resolve();
LResolveRef(Value) Resolve();
#ifdef VM_EXECUTE
char *sName = Name->Str();
if (!sName)
{
if (Log)
Log->Print("%s IDomSet error: No name string.\n",
Code->AddrToSourceRef(CurrentScriptAddress));
SetScriptError;
break;
}
switch (Dom->Type)
{
case GV_DOM:
// case GV_GFILE:
case GV_STREAM:
case GV_LSURFACE:
{
auto *dom = Dom->CastDom();
CheckParam(dom != NULL);
bool Ret = dom->SetVariant(sName, *Value, CastArrayIndex(Arr));
if (!Ret)
{
if (Log)
Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
LVariant::TypeToString(Dom->Type),
sName);
Status = ScriptWarning;
}
break;
}
case GV_DATETIME:
{
CheckParam(Dom->Value.Date != NULL);
bool Ret = Dom->Value.Date->SetVariant(sName, *Value, CastArrayIndex(Arr));
if (!Ret)
{
if (Log)
Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
LVariant::TypeToString(Dom->Type),
sName);
Status = ScriptWarning;
}
break;
}
case GV_CUSTOM:
{
LCustomType *Type = Dom->Value.Custom.Dom;
if (Type)
{
int Fld;
if (IsDigit(*sName))
Fld = atoi(sName);
else
Fld = Type->IndexOf(sName);
int Index = Arr ? Arr->CastInt32() : 0;
if (!Type->Set(Fld, *Value, Dom->Value.Custom.Data, Index) &&
Log)
{
Log->Print("%s IDomSet warning: Couldn't set '%s' on custom type.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
}
}
break;
}
case GV_STRING:
{
LDomProperty p = LStringToDomProp(sName);
switch (p)
{
case ObjLength:
{
char *s;
int DLen = Value->CastInt32();
if (DLen && (s = new char[DLen+1]))
{
size_t SLen = Dom->Str() ? strlen(Dom->Str()) : 0;
if (SLen)
memcpy(s, Dom->Str(), SLen);
memset(s+SLen, ' ', DLen-SLen);
s[DLen] = 0;
DeleteArray(Dom->Value.String);
Dom->Value.String = s;
}
else Dom->Empty();
break;
}
case TypeInt:
{
*Dom = Value->CastInt32();
Dom->Str();
break;
}
case TypeDouble:
{
*Dom = Value->CastDouble();
Dom->Str();
break;
}
default:
{
if (Log)
Log->Print("%s IDomSet warning: Unexpected string member %s.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
Status = ScriptWarning;
break;
}
}
break;
}
default:
{
if (Log)
Log->Print("%s IDomSet warning: Unexpected type %s.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
LVariant::TypeToString(Dom->Type));
Status = ScriptWarning;
break;
}
}
#endif
break;
}
case IDomCall:
{
#if VM_DECOMP
if (Log)
Log->Print("%p %s = %s->DomCall(%s, ",
CurrentScriptAddress - 1,
c.r[0].GetStr(),
c.r[1].GetStr(),
c.r[2].GetStr());
#else
LVarRef DstRef = *c.r;
#endif
LResolveRef(Dst) Resolve();
LResolveRef(Dom) Resolve();
LResolveRef(Name) Resolve();
#ifdef VM_EXECUTE
LResolveRef(Args) Resolve();
int ArgCount = Args->CastInt32();
char *sName = Name->Str();
CheckParam(sName)
if (Dom->Type == GV_CUSTOM)
{
#define DEBUG_CUSTOM_METHOD_CALL 1
auto t = Dom->Value.Custom.Dom;
CheckParam(t);
auto m = t->GetMethod(sName);
CheckParam(m);
CheckParam(m->Params.Length() == ArgCount);
// Set up new stack frame...
StackFrame &Sf = Frames.New();
Sf.CurrentFrameSize = m->FrameSize;
Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0;
Sf.ReturnValue = DstRef;
// Increase the local stack size
AddLocalSize(m->FrameSize + 1);
#if DEBUG_CUSTOM_METHOD_CALL
LgiTrace("CustomType.Call(%s) Args=%i, Frame=%i, Addr=%i, LocalsBase=%i ",
sName, ArgCount, m->FrameSize, m->Address, LocalsBase);
#endif
size_t i = LocalsBase;
Locals[i++] = *Dom; // this pointer...
#if DEBUG_CUSTOM_METHOD_CALL
LString s = Locals[i-1].ToString();
LgiTrace("This=%s, ", s.Get());
#endif
size_t end = i + ArgCount;
while (i < end)
{
Locals[i++] = *Resolve();
#if DEBUG_CUSTOM_METHOD_CALL
s = Locals[i-1].ToString();
LgiTrace("[%i]=%s, ", i-1, s.Get());
#endif
}
// Now adjust the local stack to point to the locals for the function
Scope[1] = Locals.Length() ? &Locals[LocalsBase] : NULL;
// Set IP to start of function
Sf.ReturnIp = CurrentScriptAddress;
c.u8 = Base + m->Address;
#if DEBUG_CUSTOM_METHOD_CALL
LgiTrace("\n");
#endif
break;
}
LScriptArguments Arg(Vm, Dst, NULL, CurrentScriptAddress);
Arg.Length(ArgCount);
for (int i=0; iType);
}
else switch (Dom->Type)
{
case GV_DOM:
case GV_STREAM:
case GV_LSURFACE:
{
auto *dom = Dom->CastDom();
CheckParam(dom);
- bool Ret = dom->CallMethod(sName, Arg);
+ auto Ret = dom->CallMethod(sName, Arg);
if (!Ret)
{
Dst->Empty();
if (Log)
Log->Print("%s IDomCall warning: %s(...) failed.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
Status = ScriptWarning;
}
break;
}
case GV_DATETIME:
{
CheckParam(Dom->Value.Date);
- bool Ret = Dom->Value.Date->CallMethod(sName, Dst, Arg);
+ auto Ret = Dom->Value.Date->CallMethod(sName, Dst, Arg);
if (!Ret)
{
Dst->Empty();
if (Log)
Log->Print("%s IDomCall warning: %s(...) failed.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
Status = ScriptWarning;
}
break;
}
case GV_LIST:
{
CheckParam(Dom->Value.Lst);
switch (p)
{
case ObjLength:
{
*Dst = (int64)Dom->Value.Lst->Length();
break;
}
case ContainerAdd:
{
if (Arg.Length() > 0 &&
Arg[0])
{
- int Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1;
+ auto Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1;
- LVariant *v = new LVariant;
+ auto v = new LVariant;
*v = *Arg[0];
Dom->Value.Lst->Insert(v, Index);
}
break;
}
case ContainerDelete:
{
for (unsigned i=0; iCastInt32();
- LVariant *Elem = Dom->Value.Lst->ItemAt(n);
+ auto n = Arg[i]->CastInt32();
+ auto Elem = Dom->Value.Lst->ItemAt(n);
if (Elem)
{
Dom->Value.Lst->Delete(Elem);
DeleteObj(Elem);
}
}
}
break;
}
case ContainerHasKey:
{
if (Arg.Length() > 0 && Arg[0])
{
- int Index = Arg[0]->CastInt32();
+ auto Index = Arg[0]->CastInt32();
*Dst = (bool) (Index >= 0 && Index < (int)Dom->Value.Lst->Length());
}
else
{
*Dst = false;
}
break;
}
case ContainerHasItem:
{
bool found = false;
if (Arg.Length() > 0 && Arg[0])
{
auto item = Arg[0];
for (auto v: *Dom->Value.Lst)
{
if (*v == *item)
{
found = true;
break;
}
}
}
*Dst = found;
break;
}
case ContainerSort:
{
- LVariant *Param = Arg.Length() > 0 ? Arg[0] : NULL;
+ auto Param = Arg.Length() > 0 ? Arg[0] : NULL;
Dom->Value.Lst->Sort(LVariantCmp, (NativeInt)Param);
break;
}
default:
{
Dst->Empty();
if (Log)
Log->Print( "%s IDomCall warning: Unexpected list member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
Status = ScriptWarning;
break;
}
}
break;
}
case GV_HASHTABLE:
{
CheckParam(Dom->Value.Hash);
switch (p)
{
case ObjLength:
{
*Dst = Dom->Value.Hash->Length();
break;
}
case ContainerAdd:
{
if (Arg.Length() == 2 &&
Arg[0] &&
Arg[1])
{
char *Key = Arg[1]->Str();
if (Key)
{
LVariant *v = new LVariant;
*v = *Arg[0];
Dom->Value.Hash->Add(Key, v);
}
}
break;
}
case ContainerDelete:
{
if (Arg.Length() == 1 &&
Arg[0])
{
char *Key = Arg[0]->Str();
if (Key)
{
LVariant *v = (LVariant*) Dom->Value.Hash->Find(Key);
if (v)
{
Dom->Value.Hash->Delete(Key);
delete v;
}
}
}
break;
}
case ContainerHasKey:
{
if (Arg.Length() > 0 && Arg[0])
{
char *Key = Arg[0]->Str();
*Dst = (bool) (Dom->Value.Hash->Find(Key) != NULL);
}
else
{
*Dst = false;
}
break;
}
case ContainerHasItem:
{
bool found = false;
if (Arg.Length() > 0 && Arg[0])
{
auto item = Arg[0];
for (auto p: *Dom->Value.Hash)
{
if (*p.value == *item)
{
found = true;
break;
}
}
}
*Dst = found;
break;
}
default:
{
Dst->Empty();
if (Log)
Log->Print("%s IDomCall warning: Unexpected hashtable member '%s'.\n",
Code->AddrToSourceRef(CurrentScriptAddress),
sName);
Status = ScriptWarning;
break;
}
}
break;
}
case GV_BINARY:
{
switch (p)
{
default:
break;
case ObjLength:
*Dst = Dom->Value.Binary.Length;
break;
}
break;
}
case GV_STRING:
{
if (Arg.Length() > 0 && !Arg[0])
{
Dst->Empty();
break;
}
switch (p)
{
case ObjLength:
{
char *s = Dom->Str();
*Dst = (int) (s ? strlen(s) : 0);
break;
}
case StrJoin:
{
switch (Arg[0]->Type)
{
case GV_LIST:
{
LStringPipe p(256);
List *Lst = Arg[0]->Value.Lst;
const char *Sep = Dom->CastString();
auto It = Lst->begin();
LVariant *v = *It;
if (v)
{
LVariant Tmp = *v;
p.Print("%s", Tmp.CastString());
while ((v = *(++It)))
{
Tmp = *v;
p.Print("%s%s", Sep, Tmp.CastString());
}
}
Dst->OwnStr(p.NewStr());
break;
}
default:
{
*Dst = *Arg[0];
Dst->CastString();
break;
}
}
break;
}
case StrSplit:
{
const char *Sep = Arg[0]->Str();
if (!Sep)
{
Dst->Empty();
break;
}
LVariant Tmp;
if (Dst == Dom)
{
Tmp = *Dom;
Dom = &Tmp;
}
Dst->SetList();
size_t SepLen = strlen(Sep);
int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1;
const char *c = Dom->CastString();
while (c && *c)
{
if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit)
break;
const char *next = strstr(c, Sep);
if (!next)
break;
LVariant *v = new LVariant;
v->OwnStr(NewStr(c, next - c));
Dst->Value.Lst->Insert(v);
c = next + SepLen;
}
if (c && *c)
{
LVariant *v = new LVariant;
v->OwnStr(NewStr(c));
Dst->Value.Lst->Insert(v);
}
break;
}
case StrSplitDelimit:
{
const char *Sep = Arg[0]->Str();
if (!Sep)
{
Dst->Empty();
break;
}
LVariant Tmp;
if (Dst == Dom)
{
Tmp = *Dom;
Dom = &Tmp;
}
Dst->SetList();
int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1;
const char *c = Dom->CastString();
while (c && *c)
{
if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit)
break;
const char *next = c;
while (*next && !strchr(Sep, *next))
next++;
LVariant *v = new LVariant;
v->OwnStr(NewStr(c, next - c));
Dst->Add(v);
for (c = next; *c && strchr(Sep, *c); c++)
;
}
if (c && *c)
Dst->Add(new LVariant(c));
break;
}
case StrFind:
{
const char *s = Dom->Str();
if (!s)
{
*Dst = -1;
break;
}
ssize_t sLen = Strlen(s);
auto sub = Arg[0]->Str();
auto start = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0;
auto end = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1;
if (start >= sLen)
{
*Dst = -1;
break;
}
char *sStart = (char*)s + start;
char *pos;
if (end >= 0)
pos = Strnstr(sStart, sub, end);
else
pos = Strstr(sStart, sub);
if (pos)
*Dst = (int64) (pos - s);
else
*Dst = -1;
break;
}
case StrRfind:
{
const char *s = Dom->Str();
if (!s)
{
*Dst = -1;
break;
}
ssize_t sLen = strlen(s);
auto sub = Arg[0]->Str();
auto start_idx = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0;
auto end_idx = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1;
if (start_idx >= sLen)
{
*Dst = -1;
break;
}
auto sublen = Strlen(sub);
auto cur = s + start_idx;
auto end = end_idx >= 0 ? cur + end_idx : NULL;
const char *pos = NULL;
while (true)
{
cur = (end)
?
Strnstr(cur, sub, end - cur)
:
Strstr(cur, sub);
if (cur)
{
pos = cur;
cur += sublen;
}
else break;
}
if (pos)
*Dst = (int64) (pos - s);
else
*Dst = -1;
break;
}
case StrLower:
{
if (Dst != Dom)
*Dst = Dom->CastString();
StrLwr(Dst->Str());
break;
}
case StrUpper:
{
if (Dst != Dom)
*Dst = Dom->CastString();
StrUpr(Dst->Str());
break;
}
case StrStrip:
{
auto s = Dom->Str();
if (s)
{
const char *Delimit = Arg.Length() > 0 ? Arg[0]->Str() : NULL;
if (!Delimit)
Delimit = LWhiteSpace;
auto start = s;
auto end = s + Strlen(s);
while (start < end && strchr(Delimit, *start))
start++;
while (end > start && strchr(Delimit, end[-1]))
end--;
Dst->OwnStr(NewStr(start, end - start));
}
else Dst->Empty();
break;
}
case StrSub:
{
auto s = Dom->Str();
if (s)
{
ssize_t Start = Arg.Length() > 0 ? Arg[0]->CastInt32() : 0;
ssize_t End = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1;
ssize_t Len = strlen(s);
if (End < 0 || End > Len)
End = Len;
if (Start < 0)
Start = 0;
if (Start <= End)
Dst->OwnStr(NewStr(s + Start, End - Start));
else
Dst->Empty();
}
else Dst->Empty();
break;
}
default:
{
Dst->Empty();
if (Log)
Log->Print("%p IDomCall warning: Unexpected string member %s (%s:%i).\n",
CurrentScriptAddress,
sName,
_FL);
Status = ScriptWarning;
break;
}
}
break;
}
default:
{
const char *Type = LVariant::TypeToString(Dom->Type);
char t[32];
if (!Type)
{
sprintf_s(t, sizeof(t), "UnknownType(%i)", Dom->Type);
Type = t;
}
Dst->Empty();
if (Log)
{
Log->Print("%s IDomCall warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n",
Code->AddrToSourceRef(CurrentScriptAddress),
Type,
_FL,
CurrentScriptAddress);
}
Status = ScriptWarning;
break;
}
}
ON_EXCEPTION(Arg);
#else
LVariant *Count = NULL;
switch (c.r->Scope)
{
case SCOPE_GLOBAL:
Count = &Code->Globals[c.r->Index];
c.r++;
break;
default:
OnException(_FL, CurrentScriptAddress, "Unsupported scope.");
return ScriptError;
}
int Args = Count->CastInt32();
for (int i=0; iPrint("%s%s", i ? ", " : "", c.r->GetStr());
#endif
c.r++;
}
#if VM_DECOMP
if (Log)
Log->Print(")\n");
#endif
#endif
break;
}
case IBreakPoint:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Debugger\n", CurrentScriptAddress-1);
#elif VM_EXECUTE
OnException(_FL, CurrentScriptAddress-1, "ShowDebugger");
return ScriptWarning;
#endif
break;
}
case IDebug:
{
#if VM_DECOMP
if (Log)
Log->Print("%p Debugger\n", CurrentScriptAddress-1);
#elif VM_EXECUTE
#ifdef WINDOWS
__debugbreak();
#elif defined MAC
__builtin_trap();
#elif defined LINUX
Gtk::raise(SIGINT);
#else
#warning "Not impl."
#endif
#endif
break;
}
#undef Resolve
#undef LResolveRef
\ No newline at end of file
diff --git a/src/common/Gdc2/Font/Charset.cpp b/src/common/Gdc2/Font/Charset.cpp
--- a/src/common/Gdc2/Font/Charset.cpp
+++ b/src/common/Gdc2/Font/Charset.cpp
@@ -1,1711 +1,1636 @@
#include
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/Font.h"
#include "lgi/common/Charset.h"
struct UnicodeMappings
{
int Unicode;
char Ascii;
}
MissingMaps[] =
{
{0x2019, '\''},
{0x201C, '\"'},
{0x201D, '\"'},
{0, 0}
};
typedef uint32_t iso2022jp_block[16];
iso2022jp_block *iso2022jp_map[128];
iso2022jp_block iso2022jp_blocks[] =
{
{0,0,0x10000000,0,0,0x53118c,0x800000,0x800000,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0xfffe0000,0xfffe03fb,0x3fb,0},
{0xffff0002,0xffffffff,0x2ffff,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0x33510000,0x80d0063,0,0,0,0,0,0,0x8,0x800,0,0,0xf0000,0,0x140000,0},
{0x6404098d,0x20301f81,0x40000,0xcc3,0xcc,0x20,0,0,0x40000,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0x3999900f,0x99999939,0x804,0,0,0x300c0003,0xc8c0,0x8000},
{0x60,0,0x5,0xa400,0,0,0,0,0,0,0,0,0,0,0,0},
{0x103fffef,0,0xfffffffe,0xffffffff,0x780fffff,0xfffffffe,0xffffffff,0x787fffff,0,0,0,0,0,0,0,0},
{0x43f36f8b,0x9b462442,0xe3e0e82c,0x400a0004,0xdb365f65,0x4497977,0xe3f0ecd7,0x8c56038,0x3403e602,0x35518000,0x7eabe0c8,0x98698200,0x2942a948,0x8060e803,0xad93441c,0x4568c03a},
{0x8656aa60,0x2403f7a,0x14618388,0x21741020,0x7022021,0x40bc3000,0x4462a624,0xa2060a8,0x85740217,0x9c840402,0x14157bfb,0x11e27f24,0x2efb665,0x20ff1f75,0x38403a70,0x676326c3},
{0x20924dd9,0xfc946b0,0x4850bc98,0xa03f8638,0x88162388,0x52323e09,0xe3a422aa,0xc72c00dd,0x26e1a166,0x8f0a840b,0x559e27eb,0x89bbc241,0x85400014,0x8496361,0x8ad07f0c,0x5cfff3e},
{0xa803ff1a,0x7b407a41,0x80024745,0x38eb0500,0x5d851,0x710c9934,0x1000397,0x24046366,0x5180d0,0x430ac000,0x30c89071,0x58000008,0xf7000e99,0x415f80,0x941000b0,0x62800018},
{0x9d00240,0x1568200,0x8015004,0x5101d10,0x1084c1,0x10504025,0x4d8a410f,0xa60d4009,0x914cab19,0x98121c0,0x3c485,0x80000652,0x80b04,0x9041d,0x905c4849,0x16900009},
{0x22200c65,0x24338412,0x47960c03,0x42250a04,0x90880028,0x4f084900,0xd3aa14a2,0x3e87d830,0x1f618604,0x41867ea4,0x5b3c390,0x211857a5,0x2a48241e,0x4a041128,0x161b0a40,0x88400d60},
{0x9502020a,0x10608221,0x4000243,0x80001444,0xc040000,0x70000000,0xc11a06,0xc00024a,0x401a00,0x40451404,0xbdb30029,0x52b0a78,0xbfa0bba9,0x8379407c,0xe81d12fc,0xc5694bf6},
{0x44aeff6,0xff022115,0x402bed63,0x242d033,0x131000,0x59ca1b02,0x20000a0,0x2c41a703,0x8ff24880,0x204,0x10055800,0x489200,0x20011894,0x34805004,0x684c3200,0x68be49ea},
{0x2e42184c,0x21c9a820,0x80b050b9,0xff7c001e,0x14e0849a,0x1e028c1,0xac49870e,0xdddb130f,0x89fbbe1a,0x51a2a2e0,0x32ca5502,0x928b3e46,0x438f1dbf,0x32186703,0x33c03028,0xa9230811},
{0x3a65c000,0x4028fe3,0x86252c4e,0xa1bf3d,0x8cd43a1a,0x317c06c9,0x950a00e0,0xedb018b,0x8c20e34b,0xf0101182,0xa7287d94,0x40fbc9ac,0x6534484,0x44445a90,0x13fc8,0xf5d40048},
{0xec577701,0x891dc442,0x49286b83,0xd2424109,0x59fe061d,0x3a221800,0x3b9fb7e4,0xc0eaf003,0x82021386,0xe4008980,0x10a1b200,0xcc44b80,0x8944d309,0x48341faf,0xc458259,0x450420a},
{0x10c8a040,0x44503140,0x1004004,0x5408280,0x442c0108,0x1a056a30,0x51420a6,0x645690cf,0x31000021,0xcbf09c18,0x63e2a120,0x1b5104c,0x9a83538c,0x3281b8b2,0xa84987a,0xc0233e7},
{0x9018d4cc,0x9070a1a1,0xe0048a1e,0x451c3d4,0x21c2439a,0x53104844,0x36400292,0xf3bd0241,0xe8f0ab09,0xa5d27dc0,0xd24bc242,0xd0afa43f,0x34a11aa0,0x3d88247,0x651bc452,0xc83ad294},
{0x40c8001c,0x33140e06,0xb21b614f,0xc0d00088,0xa898a02a,0x166ba1c5,0x85b42e50,0x604c08b,0x1e04f933,0xa251056e,0x76380400,0x73b8ec07,0x18324406,0xc8164081,0x63097c8a,0xaa042980},
{0xca9c1c24,0x27604e0e,0x83000990,0x81040046,0x10816011,0x908540d,0xcc0a000e,0xc000500,0xa0440430,0x6784008b,0x8a195288,0x8b18865e,0x41602e59,0x9cbe8c10,0x891c6861,0x89800},
{0x89a8100,0x41900018,0xe4a14007,0x640d0505,0xe4d310e,0xff0a4806,0x2aa81632,0xb852e,0xca841800,0x696c0e20,0x16000032,0x3905658,0x1a285120,0x11248000,0x432618e1,0xeaa5d52},
{0xae280fa0,0x4500fa7b,0x89406408,0xc044c880,0xb1419005,0x24c48424,0x603a1a34,0xc1949000,0x3a8246,0xc106180d,0x99100022,0x1511e050,0x824057,0x20a041a,0x8930004f,0x444ad813},
{0xed228a02,0x400510c0,0x1021000,0x31018808,0x2044600,0x708f000,0xa2008900,0x22020000,0x16100200,0x10400042,0x2605200,0x200052f4,0x82308510,0x42021100,0x80b54308,0x9a2070e1},
{0x8012040,0xfc653500,0xab0419c1,0x62140286,0x440087,0x2449085,0xa85405c,0x33803207,0xb8c00400,0xc0d0ce20,0x80c030,0xd250508,0x400a90,0x80c0200,0x40006505,0x41026421},
{0x268,0x847c0024,0xde200002,0x40498619,0x40000808,0x20010084,0x10108400,0x1c742cd,0xd52a7038,0x1d8f1968,0x3e12be50,0x81d92ef5,0x2412cec4,0x732e0828,0x4b3424ac,0xd41d020c},
{0x80002a02,0x8110097,0x114411c4,0x7d451786,0x64949d9,0x87914000,0xd8c4254c,0x491444ba,0xc8001b92,0x15800271,0xc000081,0xc200096a,0x40024800,0xba493021,0x1c802080,0x1008e2ac},
{0x341004,0x841400e1,0x20000020,0x10149800,0x4aa70c2,0x54208688,0x4130c62,0x20109180,0x2064082,0x54001c40,0xe4e90383,0x84802125,0x2000e433,0xe60944c0,0x81260a03,0x80112da},
{0x97906901,0xf8864001,0x81e24d,0xa6510a0e,0x81ec011a,0x8441c600,0xb62cadb8,0x8741a46f,0x4b028d54,0x2681161,0x2057bb60,0x43350a0,0xb7b4a8c0,0x1122402,0x20009ad3,0xc82271},
{0x809e2081,0xe1800c8a,0x8151b009,0x40281031,0x89a52a0e,0x620e69b6,0xd1444425,0x4d548085,0x1fb12c75,0x862dd807,0x4841d87c,0x226e414e,0x9e088200,0xed37f80c,0x75268c80,0x8149313},
{0xc8040e32,0x6ea6484e,0x66702c4a,0xba0126c0,0x185dd30c,0,0,0,0,0x5400000,0x81337020,0x3a54f81,0x641055ec,0x2344c318,0x341462,0x1a090a43},
{0x13a5187b,0xa8480102,0xc5440440,0xe2dd8106,0x2d481af0,0x416b626,0x6e405058,0x31128032,0xc0007e4,0x420a8208,0x803b4840,0x87134860,0x3428850d,0xe5290319,0x870a2345,0x5c1825a9},
{0xd9c577a6,0x3e85e00,0xa7000081,0x41c6cd54,0xa2042800,0x2b0ab860,0xda9e0020,0xe1a08ea,0x11c0427c,0x3768908,0x1058621,0x18a80000,0xc44846a0,0x20220d05,0x91485422,0x28978a01},
{0x87898,0x31221605,0x8804240,0x6a2fa4e,0x92110814,0x9b042002,0x6432e52,0x90105000,0x85ba0041,0x20203042,0x5a04f0b,0x40802708,0x1a930591,0x600df50,0x3021a202,0x4e800630},
{0x4c80cc4,0x8001a004,0xd4316000,0xa020880,0x281c00,0x418e18,0xca106ad0,0x4b00f210,0x1506274d,0x88900220,0x82a85a00,0x81504549,0x80002004,0x2c088804,0x508d1,0x4ac48001},
{0x62e020,0xa42008e,0x6a8c3055,0xe0a5090e,0x42c42906,0x80b34814,0xb330803e,0x731c0102,0x600d1494,0x9400c20,0xc040301a,0xc094a451,0x5c88dca,0xa40c96c2,0x34040001,0x11000c8},
{0xa9c9550d,0x1c5a2428,0x48370142,0x100f7a4d,0x452a32b4,0x9205317b,0x5c44b894,0x458a68d7,0x2ed15097,0x42081943,0x9d40d202,0x20979840,0x64d5409,0,0,0},
{0,0x84800000,0x4215542,0x17001c06,0x61107624,0xb9ddff87,0x5c0a659f,0x3c00245d,0x59adb0,0,0,0x9b28d0,0x2000422,0x44080108,0xac409804,0x90288d0a},
{0xe0018700,0x310400,0x82211794,0x10540019,0x21a2cb2,0x40039c02,0x88043d60,0x7900080c,0xba3c1628,0xcb088640,0x90807274,0x1e,0xd8000000,0x9c87e188,0x4124034,0x2791ae64},
{0xe6fbe86b,0x5366408f,0x537feea6,0xb5e4e32b,0x2869f,0x1228548,0x8004402,0x20a02116,0x2040004,0x52000,0x1547e00,0x1ac162c,0x10852a84,0x5308c14,0xb943fbc3,0x906000ca},
{0x40326000,0x80901200,0x4c810b30,0x40020054,0x1d6a0029,0x2802000,0x48000,0x150c2610,0x7018040,0xc24d94d,0x18502810,0x50205001,0x4d01000,0x2017080,0x21c30108,0x132},
{0x7190088,0x5600802,0x4c0e0012,0xf0a10405,0x2,0,0,0,0,0,0,0x800000,0x35a8e8d,0x5a0421bd,0x11703488,0x26},
{0x10000000,0x8804c502,0xf801b815,0x25ed147c,0x1bb0ed60,0x1bd70589,0x1a627af3,0xac50d0c,0x524ae5d1,0x63050490,0x52440354,0x16122b57,0x1101a872,0x182949,0x10080948,0x886c6000},
{0x58f916e,0x39903012,0x4930f840,0x1b8880,0,0x428500,0x98000058,0x7014ea04,0x611d1628,0x60005113,0xa71a24,0,0x3c00000,0x10187120,0xa9270172,0x89066004},
{0x20cc022,0x40810900,0x8ca0202d,0xe34,0,0x11012100,0xc11a8011,0x892ec4c,0x85000040,0x1806c7ac,0x512e03e,0x108000,0x80ce4008,0x2106d01,0x8568641,0x27011e},
{0x83d3750,0x4e05e032,0x48401c0,0x1400081,0,0,0,0x591aa0,0x882443c8,0xc8001d48,0x72030152,0x4049013,0x4008280,0xd148a10,0x2088056,0x2704a040},
{0x4c000000,0,0,0xa3200000,0xa0ae1902,0xdf002660,0x7b15f010,0x3ad08121,0x284180,0x48001003,0x8014cc00,0xc414cf,0x30202000,0x1,0,0},
{0,0,0,0,0,0,0,0,0xffffdf7a,0xefffffff,0x3fffffff,0,0,0,0,0x2}
};
class LgiIso2022Jp
{
public:
LgiIso2022Jp()
{
int n, o = 0, i = 0;
for (n=0; n<3; n++) iso2022jp_map[o++] = &iso2022jp_blocks[i++];
o += 13;
for (n=0; n<4; n++) iso2022jp_map[o++] = &iso2022jp_blocks[i++];;
o += 4;
iso2022jp_map[o++] = &iso2022jp_blocks[i++];;
o += 14;
for (n=0; n<41; n++) iso2022jp_map[o++] = &iso2022jp_blocks[i++];;
o += 47;
iso2022jp_map[o++] = &iso2022jp_blocks[i++];;
LAssert(o == 128);
}
bool CanEncode(char16 *s, ssize_t l)
{
if (s)
{
if (l < 0) l = StrlenW(s);
for (int i=0; i> 9];
if (!*block)
{
return false;
}
u &= 0x1ff;
if (
(
*block[u >> 5]
&
(1 << (u & 0x1f))
)
== 0
)
{
return false;
}
}
return true;
}
return false;
}
} Iso2022Jp;
/////////////////////////////////////////////////////////////////////////////////////
-bool LIsUtf8(const char *s, ssize_t len)
-{
- #define LenCheck(Need) \
- if (len >= 0 && (len - (s - Start)) < Need) \
- goto Utf8Error;
- #define TrailCheck() \
- if (!IsUtf8_Trail(*s)) \
- goto Utf8Error; \
- s++;
-
- if (!s || *s == 0)
- return true;
-
- const char *Start = s;
- while
- (
- (
- len < 0 ||
- ((s - Start) < len)
- )
- &&
- *s
- )
- {
- if (IsUtf8_1Byte(*s))
- {
- s++;
- }
- else if (IsUtf8_2Byte(*s))
- {
- s++;
- LenCheck(1);
- TrailCheck();
- }
- else if (IsUtf8_3Byte(*s))
- {
- s++;
- LenCheck(2);
- TrailCheck();
- TrailCheck();
- }
- else if (IsUtf8_4Byte(*s))
- {
- s++;
- LenCheck(3);
- TrailCheck();
- TrailCheck();
- TrailCheck();
- }
- else goto Utf8Error;
- }
-
- return true;
-
-Utf8Error:
- #if 1
- LgiTrace("%s:%i - Invalid utf @ offset=%i, bytes=", _FL, (int) (s - Start));
- auto end = len < 0 ? NULL : Start + len;
- for (auto i = 0; i < 16; i++)
- {
- if
- (
- (end && s >= end)
- ||
- *s == 0
- )
- break;
- LgiTrace("%02.2x,", (uint8_t)*s++);
- }
- LgiTrace("\n");
- #endif
- return false;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////
short _gdc_usascii_mapping[128] =
{
// 0x80 - 0x8f
0xc7, 0xfc, 0xe9, 0xe2, 0xe4, 0xe0, 0xe5, 0xe7, 0xea, 0xeb, 0xe8, 0xef, 0xee, 0xec, 0xc4, 0xc5,
// 0x90 - 0x9f
0xc9, 0xe6, 0xc6, 0xf4, 0xf6, 0xf2, 0xfb, 0xf9, 0xff, 0xd6, 0xdc, 0xa2, 0xa3, 0xa5, 0x20a7, 0x192,
// 0xa0 - 0xaf
0xe1, 0xed, 0xf3, 0xfa, 0xf1, 0xd1, 0xb2, 0xb0, 0xbf, 0x2310, 0xac, 0xbd, 0xbc, 0xa1, 0xab, 0xbb,
// 0xb0 - 0xbf
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
// 0xc0 - 0xcf
0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
// 0xd0 - 0xdf
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
// 0xe0 - 0xef
0x3b1, 0x3b2, 0x393, 0x3a0, 0x3a3, 0x3c3, 0x3bc, 0x3c4, 0x3a6, 0x398, 0x3a9, 0x3b4, 0x221e, 0xd8, 0x3b6, 0x2229,
// 0xf0 - 0xff
0x2261, 0xb1, 0x2265, 0x2264, 0x2320, 0x2321, 0xf7, 0x2248, 0xb0, 0x2022, 0x2219, 0x221a, 0x207f, 178, 0x25a0, 0x25a1
};
// This mapping just NUL's out the characters between 0x80 and 0x9f which aren't defined
// in the ISO spec. The rest of the characters map to themselves.
short _gdc_ISO_8859_identity_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
short _gdc_ISO_8859_2_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xa0, 0x104, 0x2d8, 0x141, 0xa4, 0x13d, 0x15a, 0xa7, 0xa8, 0x160, 0x15e, 0x164, 0x179, 0xad, 0x17d, 0x17b,
0xb0, 0x105, 0x2db, 0x142, 0xb4, 0x13e, 0x15b, 0x2c7, 0xb8, 0x161, 0x15f, 0x165, 0x17a, 0x2dd, 0x17e, 0x17c,
0x154, 0xc1, 0xc2, 0x102, 0xc4, 0x139, 0x106, 0xc7, 0x10c, 0xc9, 0x118, 0xcb, 0x11a, 0xcd, 0xce, 0x10e,
0x110, 0x143, 0x147, 0xd3, 0xd4, 0x150, 0xd6, 0xd7, 0x158, 0x16e, 0xda, 0x170, 0xdc, 0xdd, 0x162, 0xdf,
0x155, 0xe1, 0xe2, 0x103, 0xe4, 0x13a, 0x107, 0xe7, 0x10d, 0xe9, 0x119, 0xeb, 0x11b, 0xed, 0xee, 0x10f,
0x111, 0x144, 0x148, 0xf3, 0xf4, 0x151, 0xf6, 0xf7, 0x159, 0x16f, 0xfa, 0x171, 0xfc, 0xfd, 0x163, 0x2d9
};
short _gdc_ISO_8859_3_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xa0, 0x126, 0x2d8, 0xa3, 0xa4, 0, 0x124, 0xa7, 0xa8, 0x130, 0x15e, 0x11e, 0x134, 0xad, 0, 0x17b,
0xb0, 0x127, 0xb2, 0xb3, 0xb4, 0xb5, 0x125, 0xb7, 0xb8, 0x131, 0x15f, 0x11f, 0x135, 0xbd, 0, 0x17c,
0xc0, 0xc1, 0xc2, 0, 0xc4, 0x10a, 0x108, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0, 0xd1, 0xd2, 0xd3, 0xd4, 0x120, 0xd6, 0xd7, 0x11c, 0xd9, 0xda, 0xdb, 0xdc, 0x16c, 0x15c, 0xdf,
0xe0, 0xe1, 0xe2, 0, 0xe4, 0x10b, 0x109, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0, 0xf1, 0xf2, 0xf3, 0xf4, 0x121, 0xf6, 0xf7, 0x11d, 0xf9, 0xfa, 0xfb, 0xfc, 0x16d, 0x15d, 0x2d9
};
short _gdc_ISO_8859_4_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x0A0, 0x104, 0x138, 0x156, 0x0A4, 0x128, 0x13B, 0x0A7, 0x0A8, 0x160, 0x112, 0x122, 0x166, 0x0AD, 0x17D, 0x0AF,
0x0B0, 0x105, 0x2DB, 0x157, 0x0B4, 0x129, 0x13C, 0x2C7, 0x0B8, 0x161, 0x113, 0x123, 0x167, 0x14A, 0x17E, 0x14B,
0x100, 0x0C1, 0x0C2, 0x0C3, 0x0C4, 0x0C5, 0x0C6, 0x12E, 0x10C, 0x0C9, 0x118, 0x0CB, 0x116, 0x0CD, 0x0CE, 0x12A,
0x110, 0x145, 0x14C, 0x136, 0x0D4, 0x0D5, 0x0D6, 0x0D7, 0x0D8, 0x172, 0x0DA, 0x0DB, 0x0DC, 0x168, 0x16A, 0x0DF,
0x101, 0x0E1, 0x0E2, 0x0E3, 0x0E4, 0x0E5, 0x0E6, 0x12F, 0x10D, 0x0E9, 0x119, 0x0EB, 0x117, 0x0ED, 0x0EE, 0x12B,
0x111, 0x146, 0x14D, 0x137, 0x0F4, 0x0F5, 0x0F6, 0x0F7, 0x0F8, 0x173, 0x0FA, 0x0FB, 0x0FC, 0x169, 0x16B, 0x2D9
};
short _gdc_ISO_8859_5_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x0A0, 0x401, 0x402, 0x403, 0x404, 0x405, 0x406, 0x407, 0x408, 0x409, 0x40A, 0x40B, 0x40C, 0x0AD, 0x40E, 0x40F,
0x410, 0x411, 0x412, 0x413, 0x414, 0x415, 0x416, 0x417, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F,
0x420, 0x421, 0x422, 0x423, 0x424, 0x425, 0x426, 0x427, 0x428, 0x429, 0x42A, 0x42B, 0x42C, 0x42D, 0x42E, 0x42F,
0x430, 0x431, 0x432, 0x433, 0x434, 0x435, 0x436, 0x437, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F,
0x440, 0x441, 0x442, 0x443, 0x444, 0x445, 0x446, 0x447, 0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F,
0x2116, 0x451, 0x452, 0x453, 0x454, 0x455, 0x456, 0x457, 0x458, 0x459, 0x45A, 0x45B, 0x45C, 0x0A7, 0x45E, 0x45F
};
short _gdc_ISO_8859_6_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xA0, 0, 0, 0, 0xA4, 0, 0, 0, 0, 0, 0, 0, 0x60C, 0xAD, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x61B, 0, 0, 0, 0x61F,
0, 0x621, 0x622, 0x623, 0x624, 0x625, 0x626, 0x627, 0x628, 0x629, 0x62A, 0x62B, 0x62C, 0x62D, 0x62E, 0x62F,
0x630, 0x631, 0x632, 0x633, 0x634, 0x635, 0x636, 0x637, 0x638, 0x639, 0x63A, 0, 0, 0, 0, 0,
0x640, 0x641, 0x642, 0x643, 0x644, 0x645, 0x646, 0x647, 0x648, 0x649, 0x64A, 0x64B, 0x64C, 0x64D, 0x64E, 0x64F,
0x650, 0x651, 0x652, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
short _gdc_ISO_8859_7_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x0A0, 0x2BD, 0x2BC, 0x0A3, 0, 0, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0, 0x0AB, 0x0AC, 0x0AD, 0, 0x015,
0x0B0, 0x0B1, 0x0B2, 0x0B3, 0x384, 0x385, 0x386, 0x0B7, 0x388, 0x389, 0x38A, 0x0BB, 0x38C, 0x0BD, 0x38E, 0x38F,
0x390, 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, 0x399, 0x39A, 0x39B, 0x39C, 0x39D, 0x39E, 0x39F,
0x3A0, 0x3A1, 0, 0x3A3, 0x3A4, 0x3A5, 0x3A6, 0x3A7, 0x3A8, 0x3A9, 0x3AA, 0x3AB, 0x3AC, 0x3AD, 0x3AE, 0x3AF,
0x3B0, 0x3B1, 0x3B2, 0x3B3, 0x3B4, 0x3B5, 0x3B6, 0x3B7, 0x3B8, 0x3B9, 0x3BA, 0x3BB, 0x3BC, 0x3BD, 0x3BE, 0x3BF,
0x3C0, 0x3C1, 0x3C2, 0x3C3, 0x3C4, 0x3C5, 0x3C6, 0x3C7, 0x3C8, 0x3C9, 0x3CA, 0x3CB, 0x3CC, 0x3CD, 0x3CE, 0
};
short _gdc_ISO_8859_8_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x0A0, 0, 0x0A2, 0x0A3, 0x0A4, 0x0A5, 0x0A6, 0x0A7, 0x0A8, 0x0A9, 0x0D7, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x203E,
0x0B0, 0x0B1, 0x0B2, 0x0B3, 0x0B4, 0x0B5, 0x0B6, 0x0B7, 0x0B8, 0x0B9, 0x0F7, 0x0BB, 0x0BC, 0x0BD, 0x0BE, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2017,
0x5D0, 0x5D1, 0x5D2, 0x5D3, 0x5D4, 0x5D5, 0x5D6, 0x5D7, 0x5D8, 0x5D9, 0x5DA, 0x5DB, 0x5DC, 0x5DD, 0x5DE, 0x5DF,
0x5E0, 0x5E1, 0x5E2, 0x5E3, 0x5E4, 0x5E5, 0x5E6, 0x5E7, 0x5E8, 0x5E9, 0x5EA, 0, 0, 0, 0, 0
};
short _gdc_ISO_8859_9_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0x11E, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0x130, 0x15E, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0x11F, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0x131, 0x15F, 0xFF
};
short _gdc_ISO_8859_13_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xA0, 0x201D, 0xA2, 0xA3, 0xA4, 0x201E, 0xA6, 0xA7, 0xD8, 0xA9, 0x156, 0xAB, 0xAC, 0xAD, 0xAE, 0xC6,
0xB0, 0xB1, 0xB2, 0xB3, 0x201C, 0xB5, 0xB6, 0xB7, 0xF8, 0xB9, 0x157, 0xBB, 0xBC, 0xBD, 0xBE, 0xE6,
0x104, 0x12E, 0x100, 0x106, 0xC4, 0xC5, 0x118, 0x112, 0x10C, 0xC9, 0x179, 0x116, 0x122, 0x136, 0x12A, 0x13B,
0x160, 0x143, 0x145, 0xD3, 0x14C, 0xD5, 0xD6, 0xD7, 0x172, 0x141, 0x15A, 0x16A, 0xDC, 0x17B, 0x17D, 0xDF,
0x105, 0x12F, 0x101, 0x107, 0xE4, 0xE5, 0x119, 0x113, 0x10D, 0xE9, 0x17A, 0x117, 0x123, 0x137, 0x12B, 0x13C,
0x161, 0x144, 0x146, 0xF3, 0x14D, 0xF5, 0xF6, 0xF7, 0x173, 0x142, 0x15B, 0x16B, 0xFC, 0x17C, 0x17E, 0x2019
};
short _gdc_ISO_8859_15_mapping[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0xA0, 0xA1, 0xA2, 0xA3, 0x20AC, 0xA5, 0x160, 0xA7, 0x161, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0x17D, 0xB5, 0xB6, 0xB7, 0x17E, 0xB9, 0xBA, 0xBB, 0x152, 0x153, 0x178, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
short _gdc_win_874_mapping[128] =
{
0x20AC, 0, 0, 0, 0, 0x2026, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0, 0, 0, 0, 0, 0, 0, 0,
0xA0, 0xE01, 0xE02, 0xE03, 0xE04, 0xE05, 0xE06, 0xE07,
0xE08, 0xE09, 0xE0A, 0xE0B, 0xE0C, 0xE0D, 0xE0E, 0xE0F,
0xE10, 0xE11, 0xE12, 0xE13, 0xE14, 0xE15, 0xE16, 0xE17,
0xE18, 0xE19, 0xE1A, 0xE1B, 0xE1C, 0xE1D, 0xE1E, 0xE1F,
0xE20, 0xE21, 0xE22, 0xE23, 0xE24, 0xE25, 0xE26, 0xE27,
0xE28, 0xE29, 0xE2A, 0xE2B, 0xE2C, 0xE2D, 0xE2E, 0xE2F,
0xE30, 0xE31, 0xE32, 0xE33, 0xE34, 0xE35, 0xE36, 0xE37,
0xE38, 0xE39, 0xE3A, 0, 0, 0, 0, 0xE3F,
0xE40, 0xE41, 0xE42, 0xE43, 0xE44, 0xE45, 0xE46, 0xE47,
0xE48, 0xE49, 0xE4A, 0xE4B, 0xE4C, 0xE4D, 0xE4E, 0xE4F,
0xE50, 0xE51, 0xE52, 0xE53, 0xE54, 0xE55, 0xE56, 0xE57,
0xE58, 0xE59, 0xE5A, 0xE5B, 0, 0, 0, 0,
};
short _gdc_win_1250_mapping[128] =
{
0x20AC, 0, 0x201A, 0, 0x201E, 0x2026, 0x2020, 0x2021, 0, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A,
0xA0, 0x02C7, 0x02D8, 0x0141, 0xA4, 0x0104, 0xA6, 0xA7, 0xA8, 0xA9, 0x015E, 0xAB, 0xAC, 0xAD, 0xAE, 0x017B,
0xB0, 0xB1, 0x02DB, 0x0142, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0x0105, 0x015F, 0xBB, 0x013D, 0x02DD, 0x013E, 0x017C,
0x0154, 0xC1, 0xC2, 0x0102, 0xC4, 0x0139, 0x0106, 0xC7, 0x010C, 0xC9, 0x0118, 0xCB, 0x011A, 0xCD, 0xCE, 0x010E,
0x0110, 0x0143, 0x0147, 0xD3, 0xD4, 0x0150, 0xD6, 0xD7, 0x0158, 0x016E, 0xDA, 0x0170, 0xDC, 0xDD, 0x0162, 0xDF,
0x0155, 0xE1, 0xE2, 0x0103, 0xE4, 0x013A, 0x0107, 0xE7, 0x010D, 0xE9, 0x0119, 0xEB, 0x011B, 0xED, 0xEE, 0x010F,
0x0111, 0x0144, 0x0148, 0xF3, 0xF4, 0x0151, 0xF6, 0xF7, 0x0159, 0x016F, 0xFA, 0x0171, 0xFC, 0xFD, 0x0163, 0x02D9
};
short _gdc_win_1251_mapping[128] =
{
0x402, 0x403, 0x201A, 0x453, 0x201E, 0x2026, 0x2020, 0x2021,
0x20AC, 0x2030, 0x409, 0x2039, 0x40A, 0x40C, 0x40B, 0x40F,
0x452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0, 0x2122, 0x459, 0x203A, 0x45A, 0x45C, 0x45B, 0x45F,
0x0A0, 0x40E, 0x45E, 0x408, 0x0A4, 0x490, 0x0A6, 0x0A7,
0x401, 0x0A9, 0x404, 0x0AB, 0x0AC, 0x0AD, 0x0AE, 0x407,
0x0B0, 0x0B1, 0x406, 0x456, 0x491, 0x0B5, 0x0B6, 0x0B7,
0x451, 0x2116, 0x454, 0x0BB, 0x458, 0x405, 0x455, 0x457,
0x410, 0x411, 0x412, 0x413, 0x414, 0x415, 0x416, 0x417,
0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E, 0x41F,
0x420, 0x421, 0x422, 0x423, 0x424, 0x425, 0x426, 0x427,
0x428, 0x429, 0x42A, 0x42B, 0x42C, 0x42D, 0x42E, 0x42F,
0x430, 0x431, 0x432, 0x433, 0x434, 0x435, 0x436, 0x437,
0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E, 0x43F,
0x440, 0x441, 0x442, 0x443, 0x444, 0x445, 0x446, 0x447,
0x448, 0x449, 0x44A, 0x44B, 0x44C, 0x44D, 0x44E, 0x44F
};
short _gdc_win_1252_mapping[128] =
{
0x20AC, 0, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0, 0x017D, 0,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0, 0x017E, 0x0178,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
short _gdc_win_1253_mapping[128] =
{
0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021,
0, 0x2030, 0, 0x2039, 0, 0, 0, 0,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0, 0x2122, 0, 0x203A, 0, 0, 0, 0,
0xA0, 0x385, 0x386, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0, 0xAB, 0xAC, 0xAD, 0xAE, 0x2015,
0xB0, 0xB1, 0xB2, 0xB3, 0x384, 0xB5, 0xB6, 0xB7,
0x388, 0x389, 0x38A, 0xBB, 0x38C, 0xBD, 0x38E, 0x38F,
0x390, 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397,
0x398, 0x399, 0x39A, 0x39B, 0x39C, 0x39D, 0x39E, 0x39F,
0x3A0, 0x3A1, 0, 0x3A3, 0x3A4, 0x3A5, 0x3A6, 0x3A7,
0x3A8, 0x3A9, 0x3AA, 0x3AB, 0x3AC, 0x3AD, 0x3AE, 0x3AF,
0x3B0, 0x3B1, 0x3B2, 0x3B3, 0x3B4, 0x3B5, 0x3B6, 0x3B7,
0x3B8, 0x3B9, 0x3BA, 0x3BB, 0x3BC, 0x3BD, 0x3BE, 0x3BF,
0x3C0, 0x3C1, 0x3C2, 0x3C3, 0x3C4, 0x3C5, 0x3C6, 0x3C7,
0x3C8, 0x3C9, 0x3CA, 0x3CB, 0x3CC, 0x3CD, 0x3CE, 0
};
short _gdc_win_1254_mapping[128] =
{
0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021,
0x2C6, 0x2030, 0x160, 0x2039, 0x152, 0, 0, 0,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x2DC, 0x2122, 0x161, 0x203A, 0x153, 0, 0, 0x178,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0x11E, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7,
0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0x130, 0x15E, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0x11F, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0x131, 0x15F, 0xFF,
};
short _gdc_win_1255_mapping[128] =
{
0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021,
0x2C6, 0x2030, 0, 0x2039, 0, 0, 0, 0,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x2DC, 0x2122, 0, 0x203A, 0, 0, 0, 0,
0xA0, 0xA1, 0xA2, 0xA3, 0x20AA, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xD7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xB8, 0xB9, 0xF7, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0x5B0, 0x5B1, 0x5B2, 0x5B3, 0x5B4, 0x5B5, 0x5B6, 0x5B7,
0x5B8, 0x5B9, 0, 0x5BB, 0x5BC, 0x5BD, 0x5BE, 0x5BF,
0x5C0, 0x5C1, 0x5C2, 0x5C3, 0x5F0, 0x5F1, 0x5F2, 0x5F3,
0x5F4, 0, 0, 0, 0, 0, 0, 0,
0x5D0, 0x5D1, 0x5D2, 0x5D3, 0x5D4, 0x5D5, 0x5D6, 0x5D7,
0x5D8, 0x5D9, 0x5DA, 0x5DB, 0x5DC, 0x5DD, 0x5DE, 0x5DF,
0x5E0, 0x5E1, 0x5E2, 0x5E3, 0x5E4, 0x5E5, 0x5E6, 0x5E7,
0x5E8, 0x5E9, 0x5EA, 0, 0, 0x200E, 0x200F, 0,
};
short _gdc_win_1256_mapping[128] =
{
0x20AC, 0x67E, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021,
0x2C6, 0x2030, 0x679, 0x2039, 0x152, 0x686, 0x698, 0x688,
0x6AF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x6A9, 0x2122, 0x691, 0x203A, 0x153, 0x200C, 0x200D, 0x6BA,
0xA0, 0x60C, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0x6BE, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xB8, 0xB9, 0x61B, 0xBB, 0xBC, 0xBD, 0xBE, 0x61F,
0x6C1, 0x621, 0x622, 0x623, 0x624, 0x625, 0x626, 0x627,
0x628, 0x629, 0x62A, 0x62B, 0x62C, 0x62D, 0x62E, 0x62F,
0x630, 0x631, 0x632, 0x633, 0x634, 0x635, 0x636, 0xD7,
0x637, 0x638, 0x639, 0x63A, 0x640, 0x641, 0x642, 0x643,
0xE0, 0x644, 0xE2, 0x645, 0x646, 0x647, 0x648, 0xE7,
0xE8, 0xE9, 0xEA, 0xEB, 0x649, 0x64A, 0xEE, 0xEF,
0x64B, 0x64C, 0x64D, 0x64E, 0xF4, 0x64F, 0x650, 0xF7,
0x651, 0xF9, 0x652, 0xFB, 0xFC, 0x200E, 0x200F, 0x6D2,
};
short _gdc_win_1257_mapping[128] =
{
0x20AC, 0, 0x201A, 0, 0x201E, 0x2026, 0x2020, 0x2021,
0, 0x2030, 0, 0x2039, 0, 0xA8, 0x2C7, 0xB8,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0, 0x2122, 0, 0x203A, 0, 0xAF, 0x2DB, 0,
0xA0, 0, 0xA2, 0xA3, 0xA4, 0, 0xA6, 0xA7,
0xD8, 0xA9, 0x156, 0xAB, 0xAC, 0xAD, 0xAE, 0xC6,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xF8, 0xB9, 0x157, 0xBB, 0xBC, 0xBD, 0xBE, 0xE6,
0x104, 0x12E, 0x100, 0x106, 0xC4, 0xC5, 0x118, 0x112,
0x10C, 0xC9, 0x179, 0x116, 0x122, 0x136, 0x12A, 0x13B,
0x160, 0x143, 0x145, 0xD3, 0x14C, 0xD5, 0xD6, 0xD7,
0x172, 0x141, 0x15A, 0x16A, 0xDC, 0x17B, 0x17D, 0xDF,
0x105, 0x12F, 0x101, 0x107, 0xE4, 0xE5, 0x119, 0x113,
0x10D, 0xE9, 0x17A, 0x117, 0x123, 0x137, 0x12B, 0x13C,
0x161, 0x144, 0x146, 0xF3, 0x14D, 0xF5, 0xF6, 0xF7,
0x173, 0x142, 0x15B, 0x16B, 0xFC, 0x17C, 0x17E, 0x2D9,
};
short _gdc_win_1258_mapping[128] =
{
0x20AC, 0, 0x201A, 0x192, 0x201E, 0x2026, 0x2020, 0x2021,
0x2C6, 0x2030, 0, 0x2039, 0x152, 0, 0, 0,
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x2DC, 0x2122, 0, 0x203A, 0x153, 0, 0, 0x178,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0x102, 0xC4, 0xC5, 0xC6, 0xC7,
0xC8, 0xC9, 0xCA, 0xCB, 0x300, 0xCD, 0xCE, 0xCF,
0x110, 0xD1, 0x309, 0xD3, 0xD4, 0x1A0, 0xD6, 0xD7,
0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0x1AF, 0x303, 0xDF,
0xE0, 0xE1, 0xE2, 0x103, 0xE4, 0xE5, 0xE6, 0xE7,
0xE8, 0xE9, 0xEA, 0xEB, 0x301, 0xED, 0xEE, 0xEF,
0x111, 0xF1, 0x323, 0xF3, 0xF4, 0x1A1, 0xF6, 0xF7,
0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0x1B0, 0x20AB, 0xFF,
};
short _gdc_koi8r_mapping[128] =
{
0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590,
0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2219, 0x221A, 0x2248, 0x2264, 0x2265, 0xA0, 0x2321, 0xB0, 0xB2, 0xB7, 0xF7,
0x2550, 0x2551, 0x2552, 0x451, 0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x255C, 0x255D, 0x255E,
0x255F, 0x2560, 0x2561, 0x401, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x256B, 0x256C, 0xA9,
0x44E, 0x430, 0x431, 0x446, 0x434, 0x435, 0x444, 0x433, 0x445, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E,
0x43F, 0x44F, 0x440, 0x441, 0x442, 0x443, 0x436, 0x432, 0x44C, 0x44B, 0x437, 0x448, 0x44D, 0x449, 0x447, 0x44A,
0x42E, 0x410, 0x411, 0x426, 0x414, 0x415, 0x424, 0x413, 0x425, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E,
0x41F, 0x42F, 0x420, 0x421, 0x422, 0x423, 0x416, 0x412, 0x42C, 0x42B, 0x417, 0x428, 0x42D, 0x429, 0x427, 0x42A
};
short _gdc_koi8u_mapping[128] =
{
0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590,
0x2591, 0x2592, 0x2593, 0x2320, 0x25A0, 0x2019, 0x221A, 0x2248, 0x2264, 0x2265, 0xA0, 0x2321, 0xB0, 0xB2, 0xB7, 0xF7,
0x2550, 0x2551, 0x2552, 0x451, 0x454, 0x2554, 0x456, 0x457, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x491, 0x255D, 0x255E,
0x255F, 0x2560, 0x2561, 0x401, 0x404, 0x2563, 0x406, 0x407, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x490, 0x256C, 0xA9,
0x44E, 0x430, 0x431, 0x446, 0x434, 0x435, 0x444, 0x433, 0x445, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E,
0x43F, 0x44F, 0x440, 0x441, 0x442, 0x443, 0x436, 0x432, 0x44C, 0x44B, 0x437, 0x448, 0x44D, 0x449, 0x447, 0x44A,
0x42E, 0x410, 0x411, 0x426, 0x414, 0x415, 0x424, 0x413, 0x425, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E,
0x41F, 0x42F, 0x420, 0x421, 0x422, 0x423, 0x416, 0x412, 0x42C, 0x42B, 0x417, 0x428, 0x42D, 0x429, 0x427, 0x42A
};
short _gdc_koi8ru_mapping[128] =
{
0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590,
0x2591, 0x2592, 0x2593, 0x201C, 0x25A0, 0x2219, 0x201D, 0x2014, 0x2116, 0x2122, 0xA0, 0xBB, 0xAE, 0xAB, 0xB7, 0xA4,
0x2550, 0x2551, 0x2552, 0x451, 0x454, 0x2554, 0x456, 0x457, 0x2557, 0x2558, 0x2559, 0x255A, 0x255B, 0x491, 0x45E, 0x255E,
0x255F, 0x2560, 0x2561, 0x401, 0x404, 0x2563, 0x406, 0x407, 0x2566, 0x2567, 0x2568, 0x2569, 0x256A, 0x490, 0x40E, 0xA9,
0x44E, 0x430, 0x431, 0x446, 0x434, 0x435, 0x444, 0x433, 0x445, 0x438, 0x439, 0x43A, 0x43B, 0x43C, 0x43D, 0x43E,
0x43F, 0x44F, 0x440, 0x441, 0x442, 0x443, 0x436, 0x432, 0x44C, 0x44B, 0x437, 0x448, 0x44D, 0x449, 0x447, 0x44A,
0x42E, 0x410, 0x411, 0x426, 0x414, 0x415, 0x424, 0x413, 0x425, 0x418, 0x419, 0x41A, 0x41B, 0x41C, 0x41D, 0x41E,
0x41F, 0x42F, 0x420, 0x421, 0x422, 0x423, 0x416, 0x412, 0x42C, 0x42B, 0x417, 0x428, 0x42D, 0x429, 0x427, 0x42A,
};
#if defined WIN32
#define WinDef(d) d
// Map the windows codepages to their real names.
#define WINDOWS_1250 EASTEUROPE_CHARSET
#define WINDOWS_1252 ANSI_CHARSET
#define WINDOWS_1251 RUSSIAN_CHARSET
#define WINDOWS_1253 GREEK_CHARSET
#define WINDOWS_1254 TURKISH_CHARSET
#define WINDOWS_1255 HEBREW_CHARSET
#define WINDOWS_1256 ARABIC_CHARSET
#define WINDOWS_1257 BALTIC_CHARSET
#define WINDOWS_932 SHIFTJIS_CHARSET
#define WINDOWS_936 GB2313_CHARSET
#define WINDOWS_949 HANGEUL_CHARSET
#define WINDOWS_950 CHINESEBIG5_CHARSET
#else
#define WinDef(d) 0
#endif
LCharset::LCharset(const char *cp, const char *des, short *map, const char *alt)
{
Charset = cp;
Description = des;
UnicodeMap = map;
IconvName = 0;
AlternateNames = alt;
Type = CpNone;
if (cp)
{
if (stricmp(cp, "utf-8") == 0)
{
Type = CpUtf8;
}
else if (stricmp(cp, "utf-16") == 0)
{
Type = CpUtf16;
}
else if (stricmp(cp, "utf-32") == 0)
{
Type = CpUtf32;
}
else if (stricmp(cp, "ucs-2") == 0)
{
Type = CpUtf16;
IconvName = "UCS-2-INTERNAL";
}
else if (UnicodeMap)
{
Type = CpMapped;
}
#ifdef WIN32
else if (strnicmp(cp, "windows-", 8) == 0)
{
Type = CpWindowsDb;
}
#endif
else
{
Type = CpIconv;
}
}
}
bool LCharset::IsUnicode()
{
return (Type == CpUtf8) ||
(Type == CpUtf16) ||
(Type == CpUtf32);
}
const char *LCharset::GetIconvName()
{
return IconvName ? IconvName : Charset;
}
bool LCharset::IsAvailable()
{
if (Type != CpIconv)
return true;
#ifndef LGI_STATIC
LFontSystem *FontSys = LFontSystem::Inst();
if (FontSys)
return FontSys->HasIconv(true);
#endif // LGI_STATIC
return false;
}
static LCharset LgiCharsets[] = {
// Good 'ol ascii
LCharset("us-ascii", "ASCII", _gdc_usascii_mapping, "ascii-us,iso-ir-6,ANSI_X3.4-1986,ISO_646.irv,ASCII,ISO646-US,us,IBM367,cp367,csASCII"),
// Unicode (up here so they get found first)
LCharset("utf-8", "Utf-8"),
LCharset("utf-16", "Utf-16"),
LCharset("utf-32", "Utf-32"),
LCharset("ucs-2", "Ucs-2"),
// ISO (get prefered over windows charsets by being first in this list)
LCharset("iso-8859-1", "ISO 8859-1 (West Europe)", _gdc_ISO_8859_identity_mapping, "iso-ir-100,ISO_8859-1,latin1,l1,IBM819,CP819,csISOLatin1,iso8859-1"),
LCharset("iso-8859-2", "ISO 8859-2 (East Europe)", _gdc_ISO_8859_2_mapping, "iso-ir-101,ISO_8859-2,latin2,l2,csISOLatin2"),
LCharset("iso-8859-3", "ISO 8859-3 (Latin Script)", _gdc_ISO_8859_3_mapping, "iso-ir-109,ISO_8859-3,latin3,l3,csISOLatin3"),
LCharset("iso-8859-4", "ISO 8859-4 (Baltic)", _gdc_ISO_8859_4_mapping, "iso-ir-110,ISO_8859-4,latin4,l4,csISOLatin4"),
LCharset("iso-8859-5", "ISO 8859-5 (Russian)", _gdc_ISO_8859_5_mapping, "iso-ir-144,ISO_8859-5,cyrillic,csISOLatinCyrillic"),
LCharset("iso-8859-6", "ISO 8859-6 (Arabic)", _gdc_ISO_8859_6_mapping, "iso-ir-127,ISO_8859-6,ECMA-114,ASMO-708,arabic,csISOLatinArabic"),
LCharset("iso-8859-7", "ISO 8859-7 (Greek)", _gdc_ISO_8859_7_mapping, "iso-ir-126,ISO_8859-7,ELOT_928,ECMA-118,greek,greek8,csISOLatinGreek"),
LCharset("iso-8859-8", "ISO 8859-8 (Hebrew)", _gdc_ISO_8859_8_mapping, "iso-ir-138,ISO_8859-8,hebrew,csISOLatinHebrew,iso-8859-8-i"),
LCharset("iso-8859-9", "ISO 8859-9 (Turkish)", _gdc_ISO_8859_9_mapping, "iso-ir-148,ISO_8859-9,latin5,l5,csISOLatin5"),
LCharset("iso-8859-13", "ISO 8859-13 (Baltik)", _gdc_ISO_8859_13_mapping, "ISO_8859-9"),
LCharset("iso-8859-15", "ISO 8859-15 (Latic 9)", _gdc_ISO_8859_15_mapping, "ISO_8859-15"),
// Windows
LCharset("windows-874", "Windows 874 (Thai)", _gdc_win_874_mapping, "iso-8859-11,cp874"),
LCharset("windows-932", "Windows 932 (Japanese)"),
LCharset("windows-936", "Windows 936 (Chinese)"),
LCharset("windows-949", "Windows 949 (Korean)"),
LCharset("windows-950", "Windows 950 (Chinese)"),
LCharset("windows-1250", "Windows 1250 (Latin 2)", _gdc_win_1250_mapping, "x-cp1250,cp1250"),
LCharset("windows-1251", "Windows 1251 (Cyrillic)", _gdc_win_1251_mapping, "x-cp1251,cp1251"),
LCharset("windows-1252", "Windows 1252 (Latin 1)", _gdc_win_1252_mapping, "x-cp1252,cp1252"),
LCharset("windows-1253", "Windows 1253 (Greek)", _gdc_win_1253_mapping, "x-cp1253,cp1253"),
LCharset("windows-1254", "Windows 1254 (Turkish)", _gdc_win_1254_mapping, "x-cp1254,cp1254"),
LCharset("windows-1255", "Windows 1255 (Hebrew)", _gdc_win_1255_mapping, "x-cp1255,cp1255"),
LCharset("windows-1256", "Windows 1256 (Arabic)", _gdc_win_1256_mapping, "x-cp1256,cp1256"),
LCharset("windows-1257", "Windows 1257 (Baltic)", _gdc_win_1257_mapping, "x-cp1257,cp1257"),
LCharset("windows-1258", "Windows 1258 (Veitnam)", _gdc_win_1258_mapping, "x-cp1258,cp1258"),
// Russian
LCharset("koi8-r", "KOI8-R", _gdc_koi8r_mapping, "csKOI8R"),
LCharset("koi8-u", "KOI8-U", _gdc_koi8u_mapping, "csKOI8U"),
LCharset("koi8-ru", "KOI8-RU", _gdc_koi8ru_mapping, "csKOI8RU"),
LCharset("koi8-t", "KOI8-T (Tajik)"),
// Codepages
LCharset("cp850", "Cp850", 0, "IBM850,850,csPC850Multilingual"),
LCharset("cp862", "Cp862", 0, "IBM862,862,csPC862LatinHebrew"),
LCharset("cp866", "Cp866", 0, "IBM866,866,csIBM866"),
LCharset("cp1133", "Cp1133 (Laotian)"),
// Japanese
LCharset("euc-jp", "EUC-JP", 0, "csEUCPkdFmtJapanese"),
LCharset("shift_jis", "SHIFT_JIS", 0, "MS_Kanji,csShiftJIS"),
LCharset("cp932", "cp932", 0, 0),
LCharset("iso-2022-jp", "ISO-2022-JP", 0, "csISO2022JP"),
LCharset("iso-2022-jp-1", "ISO-2022-JP-1"),
LCharset("iso-2022-jp-2", "ISO-2022-JP-2", 0, "csISO2022JP2"),
// Chinese
LCharset("euc-cn", "EUC-CN (Chinese)"),
LCharset("hz-gb-2312", "HZ (Chinese)", 0, "hz"),
LCharset("gbk", "GBK (Chinese)", 0, "CP936,MS936,windows-936,x-gbk,gb2312,GB-2312,csGB2312,GB2312_CHARSET"),
LCharset("gb18030", "GB18030 (Chinese)"),
LCharset("euc-tw", "EUC-TW (Chinese)"),
LCharset("big5", "BIG5 (Chinese)", 0, "csBig5"),
LCharset("big5-hkscs", "BIG5-HKSCS (Chinese)"),
// LCharset("gb2312", "GB-2312 (Chinese)", 0, "GB-2312,csGB2312"),
LCharset("iso-2022-cn", "ISO-2022-CN (Chinese)"),
LCharset("iso-2022-cn-eXT","ISO-2022-CN-EXT (Chinese)"),
// Korean
LCharset("euc-kr", "EUC-KR", 0, "csEUCKR"),
LCharset("iso-2022-kr", "ISO-2022-KR", 0, "csISO2022KR"),
LCharset("johab", "JOHAB"),
LCharset("cp949", "CP949", 0, "ks_c_5601-1987,ks_c_5601"),
// Armenian
// LCharset("armscii-8", "ARMSCII-8 (Armenian)"),
// Georgian
LCharset("Georgian-Academy","Georgian-Academy"),
LCharset("Georgian-PS", " Georgian-PS"),
// Thai
LCharset("tis-620", "TIS-620 (Thai)"),
// Laotian
LCharset("mulelao-1", "MuleLao-1"),
// Vietnamese
LCharset("viscii", "VISCII (Vietnamese)", 0, "csVISCII"),
LCharset("tcvn", "TCVN (Vietnamese)"),
// EOF marker
LCharset()
};
static LCharsetSystem CharsetSystem;
LCharsetSystem *LCharsetSystem::Inst()
{
return &CharsetSystem;
}
LCharset *LGetCharsetInfo(const char *Cs)
{
return CharsetSystem.GetCsInfo(Cs);
}
/////////////////////////////////////////////////////////////////////////////
// Utf-16 conversion
int LCpToAnsi(char *cp)
{
int Ansi = 0;
if (cp &&
strnicmp(cp, "windows-", 8) == 0)
{
Ansi = atoi(cp+9);
}
return Ansi;
}
ssize_t LBufConvertCp(void *Out, const char *OutCp, ssize_t OutLen, const void *&In, const char *InCp, ssize_t &InLen)
{
int Status = 0;
if (Out && OutCp && In && InCp)
{
LCharset *InInfo = LGetCharsetInfo(InCp);
LCharset *OutInfo = LGetCharsetInfo(OutCp);
if (InInfo && OutInfo)
{
char *In8 = (char*)In;
uchar *Out8 = (uchar*)Out;
if (InLen < 0)
{
switch (InInfo->Type)
{
case CpMapped:
case CpUtf8:
case CpIconv:
InLen = (int)strlen((char*)In);
break;
case CpUtf16:
case CpWindowsDb:
InLen = StringLen((uint16*)In) << 1;
break;
case CpUtf32:
InLen = StringLen((uint32_t*)In) << 2;
break;
default:
LAssert(0);
return 0;
}
}
#ifdef WIN32
if (InInfo->Type == CpWindowsDb && OutInfo->Type == CpUtf16)
{
// mb -> unicode
char Cp[32];
sprintf_s(Cp, sizeof(Cp), ".%s", InInfo->Charset + 8);
setlocale(LC_ALL, Cp);
void *Start = Out;
while (OutLen >= sizeof(char16) &&
InLen > 0)
{
int s = mbtowc((char16*)Out, (char*)In, min(InLen, MB_CUR_MAX));
if (s > 0)
{
((char*&)In) += s;
InLen -= s;
((char16*&)Out)++;
OutLen -= sizeof(char16);
}
else break;
}
return (NativeInt)Out-(NativeInt)Start;
}
else if (InInfo->Type == CpUtf16 && OutInfo->Type == CpWindowsDb)
{
// unicode -> mb
char Cp[32];
sprintf_s(Cp, sizeof(Cp), ".%s", OutInfo->Charset + 8);
setlocale(LC_ALL, Cp);
void *Start = Out;
while (OutLen >= MB_CUR_MAX &&
InLen > sizeof(char16) )
{
#if 1
int s = 0;
errno_t err = wctomb_s(&s, (char*)Out, OutLen, ((char16*)In)[0]);
if (err || s == 0)
break;
#else
int s = wctomb((char*)Out, ((char16*)In)[0] );
if (s > 0)
#endif
{
((char16*&)In)++;
InLen -= sizeof(char16);
((char*&)Out) += s;
OutLen -= s;
}
}
return (NativeInt)Out-(NativeInt)Start;
}
else
#endif
if (InInfo->Type == CpIconv ||
OutInfo->Type == CpIconv)
{
#ifndef LGI_STATIC
LFontSystem *Fs = LFontSystem::Inst();
if (Fs)
return Fs->IconvConvert(OutInfo->GetIconvName(), (char*)Out, OutLen, InInfo->GetIconvName(), (const char*&)In, InLen);
#else
LAssert(!"No iconv in static build");
#endif
}
else
{
// Mapped or Utf conversion
uint32_t Utf32 = 0;
while (OutLen > 0 && InLen > 0)
{
char *RewindIn = In8;
ptrdiff_t RewindInLen = InLen;
// Convert input char to Utf-32
switch (InInfo->Type)
{
case CpMapped:
{
if (*In8)
{
uchar i = (uchar)*In8++;
InLen--;
if (i & 0x80)
{
Utf32 = InInfo->UnicodeMap[i - 0x80];
if (!Utf32) Utf32 = '?';
}
else
{
Utf32 = i;
}
}
else
{
Utf32 = 0;
InLen = 0;
}
break;
}
case CpUtf8:
{
Utf32 = LgiUtf8To32((uint8_t *&)In8, InLen);
break;
}
case CpUtf16:
{
Utf32 = LgiUtf16To32((const uint16_t *&)In8, InLen);
if (Utf32 == 0xfeff || Utf32 == 0xfffe)
continue;
break;
}
case CpUtf32:
{
Utf32 = *((uint32_t*&)In8)++;
InLen -= 4;
break;
}
default:
LAssert(0);
break;
}
if (!Utf32)
{
break;
}
// Convert Utf-32 into output format
switch (OutInfo->Type)
{
case CpMapped:
{
if (Utf32 & ~0x7f)
{
int n;
for (n=0; n<128; n++)
{
if (OutInfo->UnicodeMap[n] == Utf32)
{
*Out8++ = 0x80 + n;
break;
}
}
if (n >= 128)
{
for (n=0; MissingMaps[n].Unicode; n++)
{
if (MissingMaps[n].Unicode == Utf32)
{
*Out8++ = MissingMaps[n].Ascii;
break;
}
}
if (!MissingMaps[n].Unicode)
{
*Out8++ = '?';
}
}
}
else
{
*Out8++ = Utf32;
}
OutLen--;
break;
}
case CpUtf8:
{
// uchar *PrevOut8 = Out8;
if (!LgiUtf32To8(Utf32, (uint8_t*&) Out8, OutLen, false /* we do handle errors ok */ ))
{
// Not enough buffer to encode the character
In8 = RewindIn;
InLen = RewindInLen;
OutLen = 0;
}
break;
}
case CpUtf16:
{
LgiUtf32To16(Utf32, (uint16_t*&)Out8, OutLen);
break;
}
case CpUtf32:
{
*((uint32_t*&)Out8)++ = Utf32;
OutLen -= 4;
break;
}
default:
{
break;
}
}
}
In = (void*)In8;
Status = (int) (Out8 - (uchar*)Out);
}
}
else
{
// printf("%s:%i - LBufConvertCp failed '%s' -> '%s'.\n", __FILE__, __LINE__, InCp, OutCp);
}
}
return Status;
}
template
T *DupeString(T *s, ssize_t Len = -1)
{
if (!s)
return NULL;
if (Len < 0)
{
Len = 0;
while (s[Len])
Len++;
}
T *ns = new T[Len+1];
if (!ns)
return NULL;
memcpy(ns, s, sizeof(T) * Len);
ns[Len] = 0;
return ns;
}
LString LStrConvertCp(const char *OutCp, const void *In, const char *InCp, ssize_t InLen)
{
if (!OutCp || !In || !InCp)
return LString();
LCharset *InInfo = LGetCharsetInfo(InCp);
LCharset *OutInfo = LGetCharsetInfo(OutCp);
if (!InInfo || !OutInfo)
return LString();
if (InLen < 0)
{
switch (InInfo->Type)
{
case CpMapped:
case CpUtf8:
case CpIconv:
InLen = (int)strlen((char*)In);
break;
case CpUtf16:
case CpWindowsDb:
InLen = StringLen((uint16*)In) << 1;
break;
case CpUtf32:
InLen = StringLen((uint32_t*)In) << 2;
break;
default:
return LString();
}
}
switch (OutInfo->Type)
{
case CpMapped:
case CpUtf8:
case CpIconv:
break;
case CpUtf16:
case CpWindowsDb:
case CpUtf32:
default:
LAssert(!"LString doesn't >8bit char (yet).");
return LString();
}
if (!stricmp(InCp, OutCp))
return LString((char*)In, InLen);
LStringPipe b;
if (InInfo->Type == CpIconv ||
OutInfo->Type == CpIconv)
{
#ifndef LGI_STATIC
LFontSystem *Fs = LFontSystem::Inst();
if (Fs)
{
auto InCs = InInfo->GetIconvName();
auto OutCs = OutInfo->GetIconvName();
if (Fs->IconvConvert(OutCs, &b, InCs, (const char*&)In, InLen))
return b.NewLStr();
InCp = "iso-8859-1";
}
#else
LAssert(!"No inconv in static build");
#endif
}
char Buf[2 << 10];
while (InLen > 0)
{
ssize_t Bytes = LBufConvertCp(Buf, OutCp, sizeof(Buf), In, InCp, InLen);
if (Bytes > 0)
b.Write((uchar*)Buf, (int)Bytes);
else
break;
}
return b.NewLStr();
}
void *LNewConvertCp(const char *OutCharset, const void *In, const char *InCharset, ssize_t InLen)
{
if (!OutCharset || !In || !InCharset)
return NULL;
auto InInfo = LGetCharsetInfo(InCharset);
auto OutInfo = LGetCharsetInfo(OutCharset);
if (!InInfo || !OutInfo)
return NULL;
LMemQueue b;
if (InLen < 0)
{
switch (InInfo->Type)
{
case CpMapped:
case CpUtf8:
case CpIconv:
InLen = (int)strlen((char*)In);
break;
case CpUtf16:
case CpWindowsDb:
InLen = StringLen((uint16*)In) << 1;
break;
case CpUtf32:
InLen = StringLen((uint32_t*)In) << 2;
break;
default:
LAssert(0);
return NULL;
}
}
int NullSize;
switch (OutInfo->Type)
{
case CpMapped:
case CpUtf8:
case CpIconv:
NullSize = 1;
break;
case CpUtf16:
case CpWindowsDb:
NullSize = 2;
break;
case CpUtf32:
NullSize = 4;
break;
default:
LAssert(0);
return NULL;
}
if (!stricmp(InCharset, OutCharset))
{
if (InInfo->Type == CpUtf16)
return DupeString((uint16*)In, InLen/sizeof(uint16));
else if (InInfo->Type == CpUtf32)
return DupeString((uint32_t*)In, InLen/sizeof(uint32_t));
else
return NewStr((char*)In, InLen);
}
if (InInfo->Type == CpIconv ||
OutInfo->Type == CpIconv)
{
#ifndef LGI_STATIC
LFontSystem *Fs = LFontSystem::Inst();
if (Fs)
{
const char *InCs = InInfo->GetIconvName();
const char *OutCs = OutInfo->GetIconvName();
if (!Fs->IconvConvert(OutCs, &b, InCs, (const char*&)In, InLen))
{
InCharset = "iso-8859-1";
goto BufConvert;
}
}
#else
LAssert(!"No inconv in static build");
#endif
}
else
{
BufConvert:
char Buf[2 << 10];
while (InLen > 0)
{
ssize_t Bytes = LBufConvertCp(Buf, OutCharset, sizeof(Buf), In, InCharset, InLen);
if (Bytes > 0)
{
b.Write((uchar*)Buf, (int)Bytes);
}
else
{
break;
}
}
}
return b.GetSize() ? b.New(NullSize) : 0;
}
int LCharLen(const void *Str, const char *Cp, int Bytes)
{
if (Str && Cp)
{
LCharset *InInfo = LGetCharsetInfo(Cp);
if (InInfo)
{
switch (InInfo->Type)
{
default:
case CpMapped:
{
return (int)strlen((char*)Str);
}
case CpUtf8:
{
uchar *s = (uchar*)Str;
int Len = 0;
if (Bytes > 0)
{
uchar *e = s + Bytes;
while (*s && s < e)
{
LgiNextUtf8((char*&)s);
Len++;
}
}
else
{
while (*s)
{
LgiNextUtf8((char*&)s);
Len++;
}
}
return Len;
}
case CpUtf16:
{
return StringLen((uint16*)Str);
}
case CpUtf32:
{
return StringLen((uint32_t*)Str);
}
}
}
}
return 0;
}
bool LIsCpImplemented(char *Cp)
{
return LGetCharsetInfo(Cp) != 0;
}
const char *LAnsiToLgiCp(int AnsiCodePage)
{
if (AnsiCodePage < 0)
{
#ifdef WIN32
AnsiCodePage = GetACP();
#else
return "utf-8";
#endif
}
#define WinCp(i) case i: return "windows-" #i;
switch (AnsiCodePage)
{
WinCp(874)
WinCp(932)
WinCp(936)
WinCp(949)
WinCp(950)
WinCp(1250)
WinCp(1251)
WinCp(1252)
WinCp(1253)
WinCp(1254)
WinCp(1255)
WinCp(1256)
WinCp(1257)
WinCp(1258)
case 20127:
return "us-ascii";
case 28591:
return "iso-8859-1";
case 28592:
return "iso-8859-2";
case 28593:
return "iso-8859-3";
case 28594:
return "iso-8859-4";
case 28595:
return "iso-8859-5";
case 28596:
return "iso-8859-6";
case 28597:
return "iso-8859-7";
case 28598:
return "iso-8859-8";
case 28599:
return "iso-8859-9";
case 28600:
return "ISO-8859-10";
case 28605:
return "ISO-8859-15";
case 50220:
case 50221:
return "iso-2022-jp";
case 51932:
return "euc-jp";
case 51949:
return "euc-kr";
case 65001:
return "utf-8";
}
#undef WinCp
return 0;
}
char *LSeekUtf8(const char *Ptr, ssize_t D, char *Start)
{
uchar *p = (uchar*)Ptr;
if (p)
{
if (D >= 0)
{
for (int i=0; i(uchar*)Start; i++)
{
p--;
while (p>(uchar*)Start && IsUtf8_Trail(*p))
p--;
}
}
else
{
// You must pass a start point to move backwards in
// the utf-8 string, otherwise you can run off the
// beginning of the array.
LAssert(0);
}
}
return (char*)p;
}
bool LMatchCharset(short *Map, char16 *Utf, bool &Has8Bit)
{
if (Map && Utf)
{
// Test Charset because we have a map of all the chars in it...
char16 *c;
for (c = Utf; *c; c++)
{
if (*c > 0x7f)
{
// Check char
Has8Bit = true;
int i;
for (i=0; i<128; i++)
{
if (Map[i] == *c)
break;
}
if (i >= 128)
{
// Char not found
return false;
}
}
}
if (Has8Bit)
{
if (!*c)
{
return true;
}
}
}
return false;
}
const char *LUnicodeToCharset(const char *Utf8, ssize_t Len, LString::Array *Prefs)
{
const char *Status = "utf-8"; // The default..
LAutoWString Utf((char16*)LNewConvertCp(LGI_WideCharset, Utf8, "utf-8", Len));
if (Utf)
{
if (Prefs)
{
for (auto p: *Prefs)
{
LCharset *Cp = CharsetSystem.GetCsInfo(p);
if (Cp &&
stricmp(Cp->Charset, "us-ascii") != 0 &&
Cp->UnicodeMap)
{
bool Has8Bit = false;
if (LMatchCharset(Cp->UnicodeMap, Utf, Has8Bit))
{
return Cp->Charset;
}
if (!Has8Bit)
{
return "us-ascii";
}
}
}
}
for (LCharset *Cp = LgiCharsets + 1; Cp->Charset; Cp++)
{
if (Cp->UnicodeMap)
{
bool Has8Bit = false;
if (LMatchCharset(Cp->UnicodeMap, Utf, Has8Bit))
{
return Cp->Charset;
}
if (!Has8Bit)
{
return "us-ascii";
}
}
}
}
return Status;
}
LString LToNativeCp(const char *In, ssize_t InLen)
{
const char *Cp = LAnsiToLgiCp();
LString s;
#ifdef WIN32
LCharset *CpInfo = LGetCharsetInfo(Cp);
if (!CpInfo || CpInfo->Type == CpWindowsDb)
{
if (In)
{
// Double byte charset conversion, don't rely on iconv
// being around to do the conversion.
setlocale(LC_ALL, ".ACP");
if (InLen < 0)
InLen = strlen(In);
LAutoWString Wide(Utf8ToWide(In, InLen));
if (Wide)
{
size_t Converted;
auto Len = wcstombs_s(&Converted, NULL, 0, Wide, 0);
if (s.Length(Len))
{
wcstombs_s(&Converted, s.Get(), Len+1, Wide, Len+1);
s.Get()[Len] = 0;
}
}
}
}
#endif
if (!s)
s = LStrConvertCp(Cp, In, "utf-8", InLen);
return s;
}
LString LFromNativeCp(const char *In, ssize_t InLen)
{
const char *Cp = LAnsiToLgiCp();
LString s;
#ifdef WIN32
LCharset *CpInfo = LGetCharsetInfo(Cp);
if (!CpInfo || CpInfo->Type == CpWindowsDb)
{
if (In)
{
// Double byte charset conversion, don't rely on iconv
// being around to do the conversion.
setlocale(LC_ALL, ".ACP");
if (InLen < 0)
{
#ifdef __GNUC__
// FIXME
InLen = strlen(In);
#else
InLen = _mbstrlen(In);
#endif
}
else
{
// Work out how many chars 'InLen' bytes is
ssize_t Bytes = InLen;
const char *i = In;
int Chars = 0;
while (*i && Bytes > 0)
{
int n = mblen(i, MB_CUR_MAX);
if (n > 0)
{
Chars++;
Bytes -= n;
i += n;
}
else break;
}
InLen = Chars;
}
size_t Converted;
size_t Len = mbstowcs_s(&Converted, NULL, 0, In, 0);
if (Len)
{
LAutoWString Buf(new char16[Len+1]);
if (Buf)
{
mbstowcs_s(&Converted, Buf, Len, In, Len);
Buf[Len] = 0;
s = Buf;
}
}
}
}
#endif
if (!s)
s = LStrConvertCp("utf-8", In, Cp, InLen);
return s;
}
///////////////////////////////////////////////////////////////////////////
struct LCharsetSystemPriv
{
LCharset *Utf8;
LCharset *Utf16;
LHashTbl, LCharset*> Charsets;
LCharsetSystemPriv() : Charsets(512)
{
Utf8 = 0;
Utf16 = 0;
}
};
LCharsetSystem::LCharsetSystem()
{
char l[256];
// Charset setup, store all the charset pointers
// in a hash table for O(1) lookup.
d = new LCharsetSystemPriv;
LAssert(LgiCharsets->Charset != NULL);
for (LCharset *Cs = LgiCharsets; Cs->Charset; Cs++)
{
strcpy_s(l, sizeof(l), Cs->Charset);
#ifdef _MSC_VER
_strlwr_s(l, sizeof(l));
#else
strlwr(l);
#endif
if (!stricmp(l, "utf-8"))
d->Utf8 = Cs;
else if (!stricmp(l, "utf-16"))
d->Utf16 = Cs;
d->Charsets.Add(l, Cs);
auto a = LString(Cs->AlternateNames).SplitDelimit(",");
for (int n=0; nCharsets.Add(l, Cs);
}
}
}
LCharsetSystem::~LCharsetSystem()
{
DeleteObj(d);
}
LCharset *LCharsetSystem::GetCsInfo(const char *Cp)
{
if (Cp && d)
{
// Lookup the charset in the hash table
char l[256];
strcpy_s(l, sizeof(l), Cp);
#ifdef _MSC_VER
_strlwr_s(l, sizeof(l));
#else
strlwr(l);
#endif
if (!stricmp(l, "utf-8"))
return d->Utf8;
else if (!stricmp(l, "utf-16"))
return d->Utf16;
LCharset *Cs = (LCharset*) d->Charsets.Find(l);
if (Cs)
{
return Cs;
}
else
{
// printf("%s:%i - No charset '%s' in font sub system.\n", __FILE__, __LINE__, l);
// printf("Charsets=%i\n", Charsets->GetUsed());
}
}
return 0;
}
LCharset *LGetCsInfo(const char *Cs)
{
return CharsetSystem.GetCsInfo(Cs);
}
LCharset *LCharsetSystem::GetCsList()
{
return LgiCharsets;
}
LCharset *LGetCsList()
{
return LgiCharsets;
}
diff --git a/src/common/Lgi/Variant.cpp b/src/common/Lgi/Variant.cpp
--- a/src/common/Lgi/Variant.cpp
+++ b/src/common/Lgi/Variant.cpp
@@ -1,2444 +1,2449 @@
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/Variant.h"
const char *LVariant::TypeToString(LVariantType t)
{
switch (t)
{
case GV_NULL: return "NULL";
case GV_INT32: return "int32";
case GV_INT64: return "int64";
case GV_BOOL: return "bool";
case GV_DOUBLE: return "double";
case GV_STRING: return "String";
case GV_BINARY: return "Binary";
case GV_LIST: return "List";
case GV_DOM: return "Dom";
case GV_DOMREF: return "DomReference";
case GV_VOID_PTR: return "VoidPtr";
case GV_DATETIME: return "DateTime";
case GV_HASHTABLE: return "HashTable";
case GV_OPERATOR: return "Operator";
case GV_CUSTOM: return "Custom";
case GV_WSTRING: return "WString";
case GV_LVIEW: return "View";
case GV_STREAM: return "Stream";
case GV_LSURFACE: return "Surface";
case GV_LMOUSE: return "MouseEvent";
case GV_LKEY: return "KeyboardEvent";
default: return "Unknown";
}
return NULL;
}
const char *LVariant::OperatorToString(LOperator op)
{
switch (op)
{
case OpNull: return "OpNull";
case OpAssign: return "OpAssign";
case OpPlus: return "OpPlus";
case OpUnaryPlus: return "OpUnaryPlus";
case OpMinus: return "OpMinus";
case OpUnaryMinus: return "OpUnaryMinus";
case OpMul: return "OpMul";
case OpDiv: return "OpDiv";
case OpMod: return "OpMod";
case OpLessThan: return "OpLessThan";
case OpLessThanEqual: return "OpLessThanEqual";
case OpGreaterThan: return "OpGreaterThan";
case OpGreaterThanEqual: return "OpGreaterThanEqual";
case OpEquals: return "OpEquals";
case OpNotEquals: return "OpNotEquals";
case OpPlusEquals: return "OpPlusEquals";
case OpMinusEquals: return "OpMinusEquals";
case OpMulEquals: return "OpMulEquals";
case OpDivEquals: return "OpDivEquals";
case OpPostInc: return "OpPostInc";
case OpPostDec: return "OpPostDec";
case OpPreInc: return "OpPreInc";
case OpPreDec: return "OpPreDec";
case OpAnd: return "OpAnd";
case OpOr: return "OpOr";
case OpNot: return "OpNot";
}
return NULL;
}
LVariant::LVariant()
{
Type = GV_NULL;
ZeroObj(Value);
}
LVariant::LVariant(LVariant const &v)
{
Type = GV_NULL;
ZeroObj(Value);
*this = v;
}
#if LVARIANT_SIZET
LVariant::LVariant(size_t i)
{
Type = GV_NULL;
*this = i;
}
#endif
#if LVARIANT_SSIZET
LVariant::LVariant(ssize_t i)
{
Type = GV_NULL;
*this = i;
}
#endif
LVariant::LVariant(int32_t i)
{
Type = GV_INT32;
Value.Int = i;
}
LVariant::LVariant(uint32_t i)
{
Type = GV_INT32;
Value.Int = i;
}
LVariant::LVariant(int64_t i)
{
Type = GV_INT64;
Value.Int64 = i;
}
LVariant::LVariant(uint64_t i)
{
Type = GV_INT64;
Value.Int64 = i;
}
LVariant::LVariant(double i)
{
Type = GV_DOUBLE;
Value.Dbl = i;
}
LVariant::LVariant(const char *s)
{
Value.String = NewStr(s);
Type = Value.String ? GV_STRING : GV_NULL;
}
LVariant::LVariant(const char16 *s)
{
Value.WString = NewStrW(s);
Type = Value.WString ? GV_WSTRING : GV_NULL;
}
LVariant::LVariant(void *p)
{
Type = GV_NULL;
*this = p;
}
LVariant::LVariant(LDom *p)
{
Type = GV_NULL;
*this = p;
}
LVariant::LVariant(LDom *p, char *name)
{
Type = GV_NULL;
SetDomRef(p, name);
}
LVariant::LVariant(const LDateTime *d)
{
Type = GV_NULL;
*this = d;
}
LVariant::LVariant(LOperator Op)
{
Type = GV_OPERATOR;
Value.Op = Op;
}
LVariant::~LVariant()
{
Empty();
}
bool LVariant::operator ==(LVariant &v)
{
switch (Type)
{
default:
case GV_NULL:
return v.Type == Type;
case GV_INT32:
return Value.Int == v.CastInt32();
case GV_INT64:
return Value.Int64 == v.CastInt64();
case GV_BOOL:
return Value.Bool == v.CastBool();
case GV_DOUBLE:
return Value.Dbl == v.CastDouble();
case GV_STRING:
{
char *s = v.Str();
if (Value.String && s)
return !strcmp(Value.String, s);
break;
}
case GV_WSTRING:
{
char16 *w = v.WStr();
if (Value.WString && w)
return !StrcmpW(Value.WString, w);
break;
}
case GV_BINARY:
{
if (v.Type == Type &&
Value.Binary.Data == v.Value.Binary.Data &&
Value.Binary.Length == v.Value.Binary.Length)
{
return true;
}
break;
}
case GV_LIST:
{
if (!Value.Lst || !v.Value.Lst)
return false;
if (Value.Lst->Length() != v.Value.Lst->Length())
return false;
auto ValIt = Value.Lst->begin();
auto VIt = v.Value.Lst->begin();
LVariant *a, *b;
while ( (a = *ValIt) && (b = *VIt) )
{
if (!(*a == *b))
return false;
ValIt++;
VIt++;
}
return true;
}
case GV_DOMREF:
{
return Value.DomRef.Dom == v.Value.DomRef.Dom &&
Value.DomRef.Name != 0 &&
v.Value.DomRef.Name != 0 &&
!stricmp(Value.DomRef.Name, v.Value.DomRef.Name);
}
case GV_DATETIME:
{
if (Value.Date && v.Value.Date)
{
return Value.Date->Compare(v.Value.Date) == 0;
}
break;
}
case GV_DOM:
return Value.Dom == v.Value.Dom;
case GV_OPERATOR:
return Value.Op == v.Value.Op;
case GV_CUSTOM:
return Value.Custom == v.Value.Custom;
case GV_LSURFACE:
return Value.Surface.Ptr == v.Value.Surface.Ptr;
case GV_LVIEW:
return Value.View == v.Value.View;
/*
case GV_GFILE:
return Value.File.Ptr == v.Value.File.Ptr;
*/
case GV_STREAM:
return Value.Stream.Ptr == v.Value.Stream.Ptr;
case GV_LMOUSE:
return Value.Mouse == v.Value.Mouse;
case GV_LKEY:
return Value.Key == v.Value.Key;
case GV_VOID_PTR:
return Value.Ptr == v.Value.Ptr;
case GV_HASHTABLE:
{
LAssert(0);
break;
}
}
return false;
}
LVariant &LVariant::operator =(const LDateTime *d)
{
Empty();
if (d)
{
Type = GV_DATETIME;
Value.Date = new LDateTime;
if (Value.Date)
{
*Value.Date = *d;
// if (Dirty) *Dirty = true;
}
}
return *this;
}
LVariant &LVariant::operator =(bool i)
{
Empty();
Type = GV_BOOL;
Value.Bool = i;
// if (Dirty) *Dirty = true;
return *this;
}
#if LVARIANT_SIZET
LVariant &LVariant::operator =(size_t i)
{
Empty();
#if LGI_64BIT
Type = GV_INT64;
Value.Int64 = i;
#else
Type = GV_INT32;
Value.Int = i;
#endif
return *this;
}
#endif
#if LVARIANT_SSIZET
LVariant &LVariant::operator =(ssize_t i)
{
Empty();
#if LGI_64BIT
Type = GV_INT64;
Value.Int64 = i;
#else
Type = GV_INT32;
Value.Int = i;
#endif
return *this;
}
#endif
LVariant &LVariant::operator =(int32_t i)
{
Empty();
Type = GV_INT32;
Value.Int = i;
return *this;
}
LVariant &LVariant::operator =(uint32_t i)
{
Empty();
Type = GV_INT32;
Value.Int = i;
return *this;
}
LVariant &LVariant::operator =(int64_t i)
{
Empty();
Type = GV_INT64;
Value.Int64 = i;
return *this;
}
LVariant &LVariant::operator =(uint64_t i)
{
Empty();
Type = GV_INT64;
Value.Int64 = i;
return *this;
}
LVariant &LVariant::operator =(double i)
{
Empty();
Type = GV_DOUBLE;
Value.Dbl = i;
// if (Dirty) *Dirty = true;
return *this;
}
LVariant &LVariant::operator =(const char *s)
{
Empty();
if (s)
{
Type = GV_STRING;
Value.String = NewStr(s);
}
return *this;
}
LVariant &LVariant::operator =(const char16 *s)
{
Empty();
if (s)
{
Type = GV_WSTRING;
Value.WString = NewStrW(s);
}
// if (Dirty) *Dirty = true;
return *this;
}
LVariant &LVariant::operator =(void *p)
{
Empty();
if (p)
{
Type = GV_VOID_PTR;
Value.Ptr = p;
// if (Dirty) *Dirty = true;
}
return *this;
}
LVariant &LVariant::operator =(LDom *p)
{
Empty();
if (p)
{
Type = GV_DOM;
Value.Dom = p;
// if (Dirty) *Dirty = true;
}
return *this;
}
LVariant &LVariant::operator =(LView *p)
{
Empty();
if (p)
{
Type = GV_LVIEW;
Value.View = p;
// if (Dirty) *Dirty = true;
}
return *this;
}
LVariant &LVariant::operator =(LMouse *p)
{
Empty();
if (p)
{
Type = GV_LMOUSE;
Value.Mouse = p;
// if (Dirty) *Dirty = true;
}
return *this;
}
LVariant &LVariant::operator =(LKey *p)
{
Empty();
if (p)
{
Type = GV_LKEY;
Value.Key = p;
}
return *this;
}
LVariant &LVariant::operator =(LStream *s)
{
Empty();
if (s)
{
Type = GV_STREAM;
Value.Stream.Ptr = s;
Value.Stream.Own = false;
}
return *this;
}
LVariant &LVariant::operator =(LVariant const &i)
{
if (&i == this)
return *this;
Empty();
Type = i.Type;
switch (Type)
{
case GV_NULL:
{
break;
}
case GV_INT32:
{
Value.Int = i.Value.Int;
break;
}
case GV_BOOL:
{
Value.Bool = i.Value.Bool;
break;
}
case GV_INT64:
{
Value.Int64 = i.Value.Int64;
break;
}
case GV_DOUBLE:
{
Value.Dbl = i.Value.Dbl;
break;
}
case GV_STRING:
{
Value.String = NewStr(((LVariant&)i).Str());
break;
}
case GV_WSTRING:
{
Value.WString = NewStrW(i.Value.WString);
break;
}
case GV_BINARY:
{
SetBinary(i.Value.Binary.Length, i.Value.Binary.Data);
break;
}
case GV_LIST:
{
SetList(i.Value.Lst);
break;
}
case GV_DOM:
{
Value.Dom = i.Value.Dom;
break;
}
case GV_VOID_PTR:
case GV_LVIEW:
case GV_LMOUSE:
case GV_LKEY:
{
Value.Ptr = i.Value.Ptr;
break;
}
case GV_DATETIME:
{
if (i.Value.Date)
{
Value.Date = new LDateTime;
if (Value.Date)
{
*Value.Date = *i.Value.Date;
}
}
break;
}
case GV_HASHTABLE:
{
if ((Value.Hash = new LHash))
{
if (i.Value.Hash)
{
// const char *k;
// for (LVariant *var = i.Value.Hash->First(&k); var; var = i.Value.Hash->Next(&k))
for (auto it : *i.Value.Hash)
{
Value.Hash->Add(it.key, new LVariant(*it.value));
}
}
}
break;
}
case GV_CUSTOM:
{
Value.Custom.Data = i.Value.Custom.Data;
Value.Custom.Dom = i.Value.Custom.Dom;
break;
}
case GV_LSURFACE:
{
Value.Surface = i.Value.Surface;
if (Value.Surface.Own &&
Value.Surface.Ptr)
Value.Surface.Ptr->IncRef();
break;
}
/*
case GV_GFILE:
{
Value.File = i.Value.File;
if (Value.File.Own &&
Value.File.Ptr)
Value.File.Ptr->AddRef();
break;
}
*/
case GV_STREAM:
{
Value.Stream.Ptr = i.Value.Stream.Ptr;
Value.Stream.Own = false;
break;
}
default:
{
printf("%s:%i - Unknown variant type '%i'\n", _FL, Type);
LAssert(0);
break;
}
}
// if (Dirty) *Dirty = true;
return *this;
}
bool LVariant::SetDomRef(LDom *obj, char *name)
{
Empty();
Type = GV_DOMREF;
Value.DomRef.Dom = obj;
Value.DomRef.Name = NewStr(name);
return Value.DomRef.Name != 0;
}
bool LVariant::SetBinary(ssize_t Len, void *Data, bool Own)
{
bool Status = false;
Empty();
Type = GV_BINARY;
Value.Binary.Length = Len;
if (Own)
{
Value.Binary.Data = Data;
Status = true;
}
else
{
if ((Value.Binary.Data = new uchar[Value.Binary.Length]))
{
if (Data)
memcpy(Value.Binary.Data, Data, Value.Binary.Length);
else
memset(Value.Binary.Data, 0, Value.Binary.Length);
Status = true;
}
}
return Status;
}
List *LVariant::SetList(List *Lst)
{
Empty();
Type = GV_LIST;
if ((Value.Lst = new List) && Lst)
{
for (auto s: *Lst)
{
LVariant *New = new LVariant;
if (New)
{
*New = *s;
Value.Lst->Insert(New);
}
}
}
return Value.Lst;
}
bool LVariant::SetHashTable(LHash *Table, bool Copy)
{
Empty();
Type = GV_HASHTABLE;
if (Copy && Table)
{
if ((Value.Hash = new LHash))
{
// const char *k;
// for (LVariant *p = Table->First(&k); p; p = Table->Next(&k))
for (auto i : *Table)
{
Value.Hash->Add(i.key, i.value);
}
}
}
else
{
Value.Hash = Table ? Table : new LHash;
}
return Value.Hash != 0;
}
bool LVariant::SetSurface(class LSurface *Ptr, bool Own)
{
Empty();
if (!Ptr)
return false;
Type = GV_LSURFACE;
Value.Surface.Ptr = Ptr;
if ((Value.Surface.Own = Own))
Value.Surface.Ptr->IncRef();
return true;
}
bool LVariant::SetStream(class LStreamI *Ptr, bool Own)
{
Empty();
if (!Ptr)
return false;
Type = GV_STREAM;
Value.Stream.Ptr = Ptr;
Value.Stream.Own = Own;
return true;
}
bool LVariant::OwnStr(char *s)
{
Empty();
if (!s)
return false;
Type = GV_STRING;
Value.String = s;
return true;
}
bool LVariant::OwnStr(char16 *w)
{
Empty();
if (!w)
return false;
Type = GV_WSTRING;
Value.WString = w;
return true;
}
char *LVariant::ReleaseStr()
{
char *Ret = Str();
if (Ret)
{
Value.String = 0;
Type = GV_NULL;
}
return Ret;
}
LString LVariant::LStr()
{
return Str();
}
char *LVariant::Str()
{
if (Type == GV_STRING)
return Value.String;
if (Type == GV_WSTRING)
{
char *u = WideToUtf8(Value.WString);
DeleteArray(Value.WString);
Type = GV_STRING;
return Value.String = u;
}
return 0;
}
char16 *LVariant::ReleaseWStr()
{
char16 *Ret = WStr();
if (Ret)
{
Value.WString = 0;
Type = GV_NULL;
}
return Ret;
}
char16 *LVariant::WStr()
{
if (Type == GV_WSTRING)
return Value.WString;
if (Type == GV_STRING)
{
char16 *w = Utf8ToWide(Value.String);
DeleteArray(Value.String);
Type = GV_WSTRING;
return Value.WString = w;
}
return 0;
}
void LVariant::Empty()
{
switch (Type)
{
default:
break;
case GV_CUSTOM:
{
Value.Custom.Data = 0;
Value.Custom.Dom = 0;
break;
}
case GV_DOMREF:
{
DeleteArray(Value.DomRef.Name);
Value.DomRef.Dom = 0;
break;
}
case GV_STRING:
{
DeleteArray(Value.String);
break;
}
case GV_WSTRING:
{
DeleteArray(Value.WString);
break;
}
case GV_BINARY:
{
char *d = (char*) Value.Binary.Data;
DeleteArray(d);
Value.Binary.Data = 0;
break;
}
case GV_DATETIME:
{
DeleteObj(Value.Date);
break;
}
case GV_LIST:
{
if (Value.Lst)
{
Value.Lst->DeleteObjects();
DeleteObj(Value.Lst);
}
break;
}
case GV_HASHTABLE:
{
if (Value.Hash)
{
// for (LVariant *v = (LVariant*) Value.Hash->First(); v; v = (LVariant*) Value.Hash->Next())
for (auto i : *Value.Hash)
{
DeleteObj(i.value);
}
DeleteObj(Value.Hash);
}
break;
}
case GV_LSURFACE:
{
if (Value.Surface.Own &&
Value.Surface.Ptr)
{
Value.Surface.Ptr->DecRef();
Value.Surface.Ptr = NULL;
}
break;
}
/*
case GV_GFILE:
{
if (Value.File.Ptr &&
Value.File.Own)
{
Value.File.Ptr->DecRef();
Value.File.Ptr = NULL;
}
break;
}
*/
case GV_STREAM:
{
if (Value.Stream.Ptr)
{
if (Value.Stream.Own)
delete Value.Stream.Ptr;
Value.Stream.Ptr = NULL;
}
break;
}
}
Type = GV_NULL;
ZeroObj(Value);
}
int64 LVariant::Length()
{
switch (Type)
{
case GV_INT32:
return sizeof(Value.Int);
case GV_INT64:
return sizeof(Value.Int64);
case GV_BOOL:
return sizeof(Value.Bool);
case GV_DOUBLE:
return sizeof(Value.Dbl);
case GV_STRING:
return Value.String ? strlen(Value.String) : 0;
case GV_BINARY:
return Value.Binary.Length;
case GV_LIST:
{
int64 Sz = 0;
if (Value.Lst)
{
for (auto v : *Value.Lst)
Sz += v->Length();
}
return Sz;
}
case GV_DOM:
{
LVariant v;
if (Value.Dom)
Value.Dom->GetValue("length", v);
return v.CastInt32();
}
case GV_DOMREF:
break;
case GV_VOID_PTR:
return sizeof(Value.Ptr);
case GV_DATETIME:
return sizeof(*Value.Date);
case GV_HASHTABLE:
{
int64 Sz = 0;
if (Value.Hash)
{
// for (LVariant *v=Value.Hash->First(); v; v=Value.Hash->Next())
for (auto i : *Value.Hash)
Sz += i.value->Length();
}
return Sz;
}
case GV_OPERATOR:
return sizeof(Value.Op);
case GV_CUSTOM:
break;
case GV_WSTRING:
return Value.WString ? StrlenW(Value.WString) * sizeof(char16) : 0;
case GV_LSURFACE:
{
int64 Sz = 0;
if (Value.Surface.Ptr)
{
LRect r = Value.Surface.Ptr->Bounds();
int Bytes = Value.Surface.Ptr->GetBits() >> 3;
Sz = r.X() * r.Y() * Bytes;
}
return Sz;
}
case GV_LVIEW:
return sizeof(LView);
case GV_LMOUSE:
return sizeof(LMouse);
case GV_LKEY:
return sizeof(LKey);
case GV_STREAM:
return Value.Stream.Ptr->GetSize();
default: break;
}
return 0;
}
bool LVariant::IsInt()
{
return Type == GV_INT32 || Type == GV_INT64;
}
bool LVariant::IsBool()
{
return Type == GV_BOOL;
}
bool LVariant::IsDouble()
{
return Type == GV_DOUBLE;
}
bool LVariant::IsString()
{
return Type == GV_STRING;
}
bool LVariant::IsBinary()
{
return Type == GV_BINARY;
}
bool LVariant::IsNull()
{
return Type == GV_NULL;
}
#define IsList() (Type == GV_LIST && Value.Lst)
LVariant &LVariant::Cast(LVariantType NewType)
{
if (NewType != Type)
{
switch (NewType)
{
default:
{
// No conversion possible
break;
}
case GV_INT32:
{
*this = (int)CastInt32();
break;
}
case GV_INT64:
{
*this = (int64_t) CastInt64();
break;
}
case GV_BOOL:
{
if (Type == GV_DOUBLE)
{
*this = Value.Dbl != 0.0;
}
else
{
*this = CastInt32() != 0;
}
break;
}
case GV_DOUBLE:
{
*this = CastDouble();
break;
}
case GV_STRING:
{
*this = CastString();
break;
}
case GV_DATETIME:
{
switch (Type)
{
case GV_STRING:
{
// String -> LDateTime
LDateTime *Dt = new LDateTime;
if (Dt)
{
Dt->Set(Value.String);
Empty();
Value.Date = Dt;
Type = NewType;
}
break;
}
case GV_INT64:
{
// Int64 (system date) -> LDateTime
LDateTime *Dt = new LDateTime;
if (Dt)
{
Dt->Set((uint64_t)Value.Int64);
Empty();
Value.Date = Dt;
Type = NewType;
}
break;
}
default:
{
// No conversion available
break;
}
}
break;
}
}
}
return *this;
}
void *LVariant::CastVoidPtr() const
{
switch (Type)
{
default:
break;
case GV_STRING:
return Value.String;
case GV_BINARY:
return Value.Binary.Data;
case GV_LIST:
return Value.Lst;
case GV_DOM:
return Value.Dom;
case GV_DOMREF:
return Value.DomRef.Dom;
case GV_VOID_PTR:
return Value.Ptr;
case GV_DATETIME:
return Value.Date;
case GV_HASHTABLE:
return Value.Hash;
case GV_CUSTOM:
return Value.Custom.Data;
case GV_WSTRING:
return Value.WString;
case GV_LSURFACE:
return Value.Surface.Ptr;
case GV_LVIEW:
return Value.View;
case GV_LMOUSE:
return Value.Mouse;
case GV_LKEY:
return Value.Key;
}
return 0;
}
LDom *LVariant::CastDom() const
{
switch (Type)
{
default:
break;
case GV_DOM:
return Value.Dom;
case GV_DOMREF:
return Value.DomRef.Dom;
case GV_STREAM:
return dynamic_cast(Value.Stream.Ptr);
case GV_LSURFACE:
return Value.Surface.Ptr;
case GV_CUSTOM:
return Value.Custom.Dom;
}
return NULL;
}
bool LVariant::CastBool() const
{
switch (Type)
{
default:
LAssert(0);
break;
case GV_NULL:
return false;
case GV_INT32:
return Value.Int != 0;
case GV_INT64:
return Value.Int64 != 0;
case GV_BOOL:
return Value.Bool;
case GV_DOUBLE:
return Value.Dbl != 0.0;
case GV_BINARY:
return Value.Binary.Data != NULL;
case GV_LIST:
return Value.Lst != NULL;
case GV_DOM:
return Value.Dom != NULL;
case GV_DOMREF:
return Value.DomRef.Dom != NULL;
case GV_VOID_PTR:
return Value.Ptr != NULL;
case GV_LVIEW:
return Value.View != NULL;
case GV_LMOUSE:
return Value.Mouse != NULL;
case GV_LKEY:
return Value.Key != NULL;
case GV_DATETIME:
return Value.Date != NULL;
case GV_HASHTABLE:
return Value.Hash != NULL;
case GV_OPERATOR:
return Value.Op != OpNull;
case GV_CUSTOM:
return Value.Custom.Dom != 0 && Value.Custom.Data != 0;
/*
case GV_GFILE:
return Value.File.Ptr != NULL;
*/
case GV_STREAM:
return Value.Stream.Ptr != NULL;
// As far as I understand this is the behavour in Python, which I'm using for
// a reference to what the "correct" thing to do here is. Basically it's treating
// the string like a pointer instead of a value. If the pointer is valid the
// conversion to bool return true, and false if it's not a valid pointer. This
// means things like if (!StrinLVariant) evaluate correctly in the scripting engine
// but it means that if you want to evaluate the value of the varient you should
// use CastInt32 instead.
case GV_STRING:
return ValidStr(Value.String);
case GV_WSTRING:
return ValidStrW(Value.WString);
}
return false;
}
double LVariant::CastDouble() const
{
switch (Type)
{
default:
break;
case GV_BOOL:
return Value.Bool ? 1.0 : 0.0;
case GV_DOUBLE:
return Value.Dbl;
case GV_INT32:
return (double)Value.Int;
case GV_INT64:
return (double)Value.Int64;
case GV_STRING:
return Value.String ? atof(Value.String) : 0;
case GV_DOMREF:
{
static LVariant v;
if (Value.DomRef.Dom)
{
if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v))
{
return v.CastDouble();
}
}
break;
}
}
return 0;
}
int32 LVariant::CastInt32() const
{
switch (Type)
{
default:
break;
case GV_BOOL:
return (int32)Value.Bool;
case GV_DOUBLE:
return (int32)Value.Dbl;
case GV_INT32:
return Value.Int;
case GV_INT64:
return (int32)Value.Int64;
case GV_STRING:
if (!Value.String)
return 0;
if (IsAlpha(Value.String[0]))
return !Stricmp(Value.String, "true");
else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x')
return static_cast(Atoi(Value.String, 16));
return atoi(Value.String);
case GV_DOM:
return Value.Dom != 0;
case GV_DOMREF:
{
static LVariant v;
if (Value.DomRef.Dom)
{
if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v))
{
return v.CastInt32();
}
}
break;
}
case GV_LIST:
return Value.Lst != NULL;
case GV_HASHTABLE:
return Value.Hash != NULL;
case GV_LSURFACE:
return Value.Surface.Ptr != NULL;
case GV_LVIEW:
return Value.View != NULL;
case GV_LMOUSE:
return Value.Mouse != NULL;
case GV_LKEY:
return Value.Key != NULL;
case GV_STREAM:
return Value.Stream.Ptr != NULL;
}
return 0;
}
int64 LVariant::CastInt64() const
{
switch (Type)
{
default:
break;
case GV_BOOL:
return (int64)Value.Bool;
case GV_DOUBLE:
return (int64)Value.Dbl;
case GV_INT32:
return Value.Int;
case GV_INT64:
return Value.Int64;
case GV_STRING:
{
if (!Value.String)
return 0;
if (IsAlpha(Value.String[0]))
return !Stricmp(Value.String, "true");
else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x')
return Atoi(Value.String, 16);
return Atoi(Value.String);
}
case GV_DOMREF:
{
static LVariant v;
if (Value.DomRef.Dom)
{
if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v))
{
return v.CastInt64();
}
}
break;
}
}
return 0;
}
char *LVariant::CastString()
{
char i[40];
switch (Type)
{
case GV_LIST:
{
LStringPipe p(256);
List::I it = Value.Lst->begin();
bool First = true;
p.Print("{");
for (LVariant *v = *it; v; v = *++it)
{
if (v->Type == GV_STRING ||
v->Type == GV_WSTRING)
p.Print("%s\"%s\"", First ? "" : ", ", v->CastString());
else
p.Print("%s%s", First ? "" : ", ", v->CastString());
First = false;
}
p.Print("}");
OwnStr(p.NewStr());
return Str();
break;
}
case GV_HASHTABLE:
{
LStringPipe p(256);
p.Print("{");
bool First = true;
// const char *k;
// for (LVariant *v = Value.Hash->First(&k); v; v = Value.Hash->Next(&k))
for (auto i : *Value.Hash)
{
p.Print("%s%s = %s", First ? "" : ", ", i.key, i.value->CastString());
First = false;
}
p.Print("}");
OwnStr(p.NewStr());
return Str();
break;
}
case GV_DOMREF:
{
static LVariant v;
if (Value.DomRef.Dom)
{
if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v))
{
return v.CastString();
}
}
break;
}
case GV_INT32:
{
sprintf_s(i, sizeof(i), "%i", Value.Int);
*this = i;
return Str();
}
case GV_DOUBLE:
{
sprintf_s(i, sizeof(i), "%f", Value.Dbl);
*this = i;
return Str();
}
case GV_BOOL:
{
sprintf_s(i, sizeof(i), "%i", Value.Bool);
*this = i;
return Str();
}
case GV_INT64:
{
sprintf_s(i, sizeof(i), LPrintfInt64, Value.Int64);
*this = i;
return Str();
}
case GV_STRING:
case GV_WSTRING:
{
return Str();
}
case GV_DATETIME:
{
if (Value.Date)
{
char s[64];
Value.Date->Get(s, sizeof(s));
*this = s;
return Str();
}
break;
}
case GV_DOM:
{
sprintf_s(i, sizeof(i), "dom:%p", Value.Dom);
*this = i;
break;
}
default:
{
break;
}
}
return 0;
}
/////////////////////////////////////////////////////////////////////////////////
LDom *LDom::ResolveObject(const char *Var, LString &Name, LString &Array)
{
LDom *Object = this;
try
{
// Tokenize the string
LString::Array t;
for (auto *s = Var; s && *s; )
{
const char *e = s;
while (*e && *e != '.')
{
if (*e == '[')
{
e++;
while (*e && *e != ']')
{
if (*e == '\"' || *e == '\'')
{
char d = *e++;
while (*e && *e != d)
e++;
if (*e == d) e++;
}
else e++;
}
if (*e == ']')
e++;
}
else e++;
}
LString part = LString(s, e - s).Strip();
if (part.Length() > 0)
t.New() = part; // Store non-empty part
s = *e ? e + 1 : e;
}
// Process elements
for (int i=0; iGetVariant(Obj, v, Index))
{
if (v.Type == GV_LIST)
{
int N = atoi(Index);
LVariant *Element = v.Value.Lst->ItemAt(N);
if (Element && Element->Type == GV_DOM)
{
Object = Element->Value.Dom;
}
else
{
return NULL;
}
}
else if (v.Type == GV_DOM)
{
Object = v.Value.Dom;
}
else
{
return NULL;
}
}
else
{
return NULL;
}
}
else
{
if (Object->GetVariant(Obj, v) &&
v.Type == GV_DOM)
{
Object = v.Value.Dom;
}
else
{
return NULL;
}
}
}
}
}
catch (...)
{
LgiTrace("LDom::ResolveObject crashed: '%s'\n", Var);
return NULL;
}
return Object;
}
struct LDomPropMap
{
LHashTbl, LDomProperty> ToProp;
LHashTbl, const char *> ToString;
LDomPropMap()
{
#undef _
#define _(symbol, txt) Define(txt, symbol);
#include "lgi/common/DomFields.h"
#undef _
}
void Define(const char *s, LDomProperty p)
{
if (!s)
return;
#if defined(_DEBUG) // Check for duplicates.
auto existing_prop = ToProp.Find(s);
LAssert(existing_prop == ObjNone);
auto existing_str = ToString.Find(p);
LAssert(existing_str == NULL);
#endif
ToProp.Add(s, p);
ToString.Add(p, s);
}
} DomPropMap;
LDomProperty LStringToDomProp(const char *Str)
{
return DomPropMap.ToProp.Find(Str);
}
const char *LDomPropToString(LDomProperty Prop)
{
return DomPropMap.ToString.Find(Prop);
}
struct DomObjectMap
{
typedef LArray Members;
LHashTbl, Members*> maps;
};
static DomObjectMap ObjMap;
bool LDom::_AddMember(DomMemberType type, const char *name, const char *args)
{
auto *members = ObjMap.maps.Find(GetClass());
if (!members)
ObjMap.maps.Add(GetClass(), members = new DomObjectMap::Members);
auto &m = members->New();
m.Type = type;
m.Name.Printf("%s", GetClass(), name);
if (m.Type == DomMethod)
{
if (!args)
{
LAssert(!"no args specified");
return false;
}
m.Args = args;
}
return true;
}
bool LDom::_EnumMembers(const char *object, LArray &members)
{
auto *m = ObjMap.maps.Find(object);
if (!m)
return false;
members = *m;
return true;
}
bool LDom::GetValue(const char *Var, LVariant &Value)
{
if (!Var)
return false;
if (!_OnAccess(true))
{
LgiTrace("%s:%i - Locking error\n", _FL);
LAssert(0);
return false;
}
bool Status = false;
LString Name, Arr;
LDom *Object = ResolveObject(Var, Name, Arr);
if (Object)
{
if (Name.IsEmpty())
LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var);
else
Status = Object->GetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr.Get());
}
_OnAccess(false);
return Status;
}
bool LDom::SetValue(const char *Var, LVariant &Value)
{
bool Status = false;
if (Var)
{
// LMutex *Sem = dynamic_cast(this);
if (_OnAccess(true))
{
LString Name, Arr;
LDom *Object = ResolveObject(Var, Name, Arr);
if (Object)
{
if (Name.IsEmpty())
LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var);
else
Status = Object->SetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr.Get());
}
_OnAccess(false);
}
else
{
LgiTrace("%s:%i - Locking error\n", _FL);
LAssert(0);
}
}
return Status;
}
bool LVariant::Add(LVariant *v, int Where)
{
if (!v)
{
LAssert(!"No value to insert.");
return false;
}
if (Type == GV_NULL)
SetList();
if (Type != GV_LIST)
{
LAssert(!"Not a list variant");
return false;
}
return Value.Lst->Insert(v, Where);
}
LString LVariant::ToString()
{
LString s;
switch (Type)
{
case GV_NULL:
s = "NULL";
break;
case GV_INT32:
s.Printf("(int)%i", Value.Int);
break;
case GV_INT64:
s.Printf("(int64)" LPrintfInt64, Value.Int64);
break;
case GV_BOOL:
s.Printf("(bool)%s", Value.Bool ? "true" : "false");
break;
case GV_DOUBLE:
s.Printf("(double)%f", Value.Dbl);
break;
case GV_STRING:
s.Printf("(string)\"%s\"", Value.String);
break;
case GV_BINARY:
s.Printf("(binary[%i])%p", Value.Binary.Length, Value.Binary.Data);
break;
case GV_LIST:
s.Printf("(list[%i])%p", Value.Lst?Value.Lst->Length():0, Value.Lst);
break;
case GV_DOM:
s.Printf("(LDom)%p", Value.Dom);
break;
case GV_DOMREF:
s.Printf("(LDom)%p.%s", Value.DomRef.Dom, Value.DomRef.Name);
break;
case GV_VOID_PTR:
s.Printf("(void*)%p", Value.Ptr);
break;
case GV_DATETIME:
{
char dt[64];
Value.Date->Get(dt, sizeof(dt));
s.Printf("(LDateTime)%s", dt);
break;
}
case GV_HASHTABLE:
s.Printf("(LHashTbl)%p", Value.Hash);
break;
case GV_OPERATOR:
s.Printf("(LOperator)%s", OperatorToString(Value.Op));
break;
case GV_CUSTOM:
s.Printf("(LCustom.%s)%p", Value.Custom.Dom->GetName(), Value.Custom.Data);
break;
case GV_WSTRING:
s.Printf("(wchar_t*)\"%S\"", Value.WString);
break;
case GV_LSURFACE:
s.Printf("(LSurface)%p", Value.Surface.Ptr);
break;
case GV_LVIEW:
s.Printf("(LView)%p", Value.View);
break;
case GV_LMOUSE:
s.Printf("(LMouse)%p", Value.Mouse);
break;
case GV_LKEY:
s.Printf("(LKey)%p", Value.Key);
break;
case GV_STREAM:
s.Printf("(LStream)%p", Value.Stream.Ptr);
break;
default:
s = "(unknown)NULL";
break;
}
return s;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
LCustomType::LCustomType(const char *name, int pack) : FldMap(0, -1)
{
Name = name;
Pack = 1;
Size = 0;
}
LCustomType::LCustomType(const char16 *name, int pack) : FldMap(0, -1)
{
Name = name;
Pack = 1;
Size = 0;
}
LCustomType::~LCustomType()
{
Flds.DeleteObjects();
Methods.DeleteObjects();
}
size_t LCustomType::Sizeof()
{
return (size_t)PadSize();
}
ssize_t LCustomType::PadSize()
{
if (Pack > 1)
{
// Bump size to the pack boundary...
int Remain = Size % Pack;
if (Remain)
return Size + Pack - Remain;
}
return Size;
}
int LCustomType::IndexOf(const char *Field)
{
return FldMap.Find(Field);
}
int LCustomType::AddressOf(const char *Field)
{
if (!Field)
return -1;
for (unsigned i=0; iName, Field))
return (int)i;
}
return -1;
}
bool LCustomType::DefineField(const char *Name, LCustomType *Type, int ArrayLen)
{
if (ArrayLen < 1)
{
LAssert(!"Can't have zero size field.");
return false;
}
if (Name == NULL || Type == NULL)
{
LAssert(!"Invalid parameter.");
return false;
}
if (FldMap.Find(Name) >= 0)
{
LAssert(!"Field already exists.");
return false;
}
FldMap.Add(Name, (int)Flds.Length());
CustomField *Def;
Flds.Add(Def = new CustomField);
Size = PadSize();
Def->Offset = Size;
Def->Name = Name;
Def->Type = GV_CUSTOM;
Def->Bytes = Type->Sizeof();
Def->ArrayLen = ArrayLen;
Size += Def->Bytes * ArrayLen;
return true;
}
bool LCustomType::DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen)
{
if (ArrayLen < 1)
{
LAssert(!"Can't have zero size field.");
return false;
}
if (Name == NULL)
{
LAssert(!"No field name.");
return false;
}
if (FldMap.Find(Name) >= 0)
{
LAssert(!"Field already exists.");
return false;
}
FldMap.Add(Name, (int)Flds.Length());
CustomField *Def;
Flds.Add(Def = new CustomField);
Size = PadSize();
Def->Offset = Size;
Def->Name = Name;
Def->Type = Type;
Def->Bytes = Bytes;
Def->ArrayLen = ArrayLen;
Size += Bytes * ArrayLen;
return true;
}
LCustomType::Method *LCustomType::GetMethod(const char *Name)
{
return MethodMap.Find(Name);
}
LCustomType::Method *LCustomType::DefineMethod(const char *Name, LArray &Params, size_t Address)
{
Method *m = MethodMap.Find(Name);
if (m)
{
LAssert(!"Method already defined.");
return NULL;
}
Methods.Add(m = new Method);
m->Name = Name;
m->Params = Params;
m->Address = Address;
MethodMap.Add(Name, m);
return m;
}
bool LCustomType::CustomField::GetVariant(const char *Field, LVariant &Value, const char *Array)
{
LDomProperty p = LStringToDomProp(Field);
switch (p)
{
case ObjName: // Type: String
Value = Name;
break;
case ObjLength: // Type: Int32
Value = Bytes;
break;
default:
return false;
}
return true;
}
ssize_t LCustomType::CustomField::Sizeof()
{
switch (Type)
{
case GV_INT32:
return sizeof(int32_t);
case GV_INT64:
return sizeof(int64_t);
case GV_BOOL:
return sizeof(bool);
case GV_DOUBLE:
return sizeof(double);
case GV_STRING:
return sizeof(char);
case GV_DATETIME:
return sizeof(LDateTime);
case GV_HASHTABLE:
return sizeof(LVariant::LHash);
case GV_OPERATOR:
return sizeof(LOperator);
case GV_LMOUSE:
return sizeof(LMouse);
case GV_LKEY:
return sizeof(LKey);
case GV_CUSTOM:
return Nested->Sizeof();
default:
LAssert(!"Unknown type.");
break;
}
return 0;
}
bool LCustomType::Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex)
{
if (Index < 0 ||
Index >= Flds.Length() ||
!This)
{
LAssert(!"Invalid parameter error.");
return false;
}
CustomField *Def = Flds[Index];
if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen)
{
LAssert(!"Array out of bounds.");
return false;
}
uint8_t *Ptr = This + Def->Offset;
Out.Empty();
switch (Def->Type)
{
case GV_STRING:
{
int Len;
for (Len = 0; Ptr[Len] && Len < Def->ArrayLen-1; Len++)
;
Out.OwnStr(NewStr((char*)Ptr, Len));
break;
}
case GV_WSTRING:
{
char16 *p = (char16*)Ptr;
int Len;
for (Len = 0; p[Len] && Len < Def->ArrayLen-1; Len++)
;
Out.OwnStr(NewStrW(p, Len));
break;
}
case GV_INT32:
case GV_INT64:
{
switch (Def->Bytes)
{
case 1:
{
Out.Value.Int = Ptr[ArrayIndex];
Out.Type = GV_INT32;
break;
}
case 2:
{
Out.Value.Int = ((uint16*)Ptr)[ArrayIndex];
Out.Type = GV_INT32;
break;
}
case 4:
{
Out.Value.Int = ((uint32_t*)Ptr)[ArrayIndex];
Out.Type = GV_INT32;
break;
}
case 8:
{
Out.Value.Int64 = ((uint64*)Ptr)[ArrayIndex];
Out.Type = GV_INT64;
break;
}
default:
{
LAssert(!"Unknown integer size.");
return false;
}
}
break;
}
case GV_MAX:
{
Out = *((LVariant*)Ptr);
break;
}
default:
{
LAssert(!"Impl this type.");
return false;
}
}
return true;
}
bool LCustomType::Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex)
{
if (Index < 0 ||
Index >= Flds.Length() ||
!This)
{
LAssert(!"Invalid parameter error.");
return false;
}
CustomField *Def = Flds[Index];
uint8_t *Ptr = This + Def->Offset;
if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen)
{
LAssert(!"Array out of bounds.");
return false;
}
switch (Def->Type)
{
case GV_STRING:
{
char *s = In.Str();
if (!s)
{
*Ptr = 0;
break;
}
if (Def->Bytes == 1)
{
// Straight up string copy...
if (s)
strcpy_s((char*)Ptr, Def->ArrayLen, s);
else
*Ptr = 0;
}
else if (Def->Bytes == sizeof(char16))
{
// utf8 -> wide conversion...
const void *In = Ptr;
ssize_t Len = strlen(s);
ssize_t Ch = LBufConvertCp(Ptr, LGI_WideCharset, Def->ArrayLen-1, In, "utf-8", Len);
if (Ch >= 0)
{
// Null terminate
Ptr[Ch] = 0;
}
else
{
LAssert(!"LBufConvertCp failed.");
return false;
}
}
break;
}
case GV_WSTRING:
{
char16 *p = (char16*)Ptr;
char16 *w = In.WStr();
if (!w)
{
*p = 0;
break;
}
if (Def->Bytes == sizeof(char16))
{
// Straight string copy...
Strcpy(p, Def->ArrayLen, w);
}
else
{
// Conversion to utf-8
const void *In = Ptr;
ssize_t Len = StrlenW(w) * sizeof(char16);
ssize_t Ch = LBufConvertCp(Ptr, "utf-8", Def->ArrayLen-sizeof(char16),
In, LGI_WideCharset, Len);
if (Ch >= 0)
{
// Null terminate
p[Ch/sizeof(char16)] = 0;
}
else
{
LAssert(!"LBufConvertCp failed.");
return false;
}
}
break;
}
case GV_INT32:
case GV_INT64:
{
switch (Def->Bytes)
{
case 1:
{
Ptr[ArrayIndex] = In.CastInt32();
break;
}
case 2:
{
((uint16*)Ptr)[ArrayIndex] = In.CastInt32();
break;
}
case 4:
{
((uint32_t*)Ptr)[ArrayIndex] = In.CastInt32();
break;
}
case 8:
{
((uint64*)Ptr)[ArrayIndex] = In.CastInt64();
break;
}
default:
{
LAssert(!"Unknown integer size.");
return false;
}
}
break;
}
case GV_MAX:
{
*((LVariant*)Ptr) = In;
break;
}
default:
LAssert(!"Impl this type.");
break;
}
return true;
}
bool LCustomType::GetVariant(const char *Field, LVariant &Value, const char *Array)
{
LDomProperty p = LStringToDomProp(Field);
switch (p)
{
case ObjName: // Type: String
{
Value = Name;
return true;
}
case ObjType: // Type: String
{
Value = "LCustomType";
return true;
}
case ObjLength: // Type: Int32
{
Value = (int)Sizeof();
return true;
}
case ObjField: // Type: CustomField[]
{
if (Array)
{
int Index = atoi(Array);
if (Index >= 0 && Index < Flds.Length())
{
Value = (LDom*)&Flds[Index];
return true;
}
}
else
{
Value = (int)Flds.Length();
break;
}
break;
}
default:
break;
}
LAssert(0);
return false;
}
bool LCustomType::SetVariant(const char *Name, LVariant &Value, const char *Array)
{
LAssert(0);
return false;
}
bool LCustomType::CallMethod(const char *MethodName, LScriptArguments &Args)
{
if (!MethodName)
return false;
if (!_stricmp(MethodName, "New"))
{
Args.GetReturn()->Empty();
Args.GetReturn()->Type = GV_CUSTOM;
Args.GetReturn()->Value.Custom.Dom = this;
Args.GetReturn()->Value.Custom.Data = new uint8_t[Sizeof()];
return true;
}
if (!_stricmp(MethodName, "Delete")) // Type: (Object)
{
for (unsigned i=0; iType == GV_CUSTOM)
{
DeleteArray(v->Value.Custom.Data);
v->Empty();
}
}
return true;
}
LAssert(0);
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
LStream LScriptArguments::NullConsole;
LScriptArguments::LScriptArguments(LVirtualMachineI *vm, LVariant *ret, LStream *console, ssize_t address)
{
Vm = vm;
Address = address;
if (ret)
Return = ret;
else
Return = LocalReturn = new LVariant;
if (console)
Console = console;
else
Console = &NullConsole;
}
LScriptArguments::~LScriptArguments()
{
DeleteObjects();
DeleteObj(LocalReturn);
}
const char *LScriptArguments::StringAt(size_t i)
{
return IdxCheck(i) ? (*this)[i]->Str() : NULL;
}
int32_t LScriptArguments::Int32At(size_t i, int32_t Default)
{
return IdxCheck(i) ? (*this)[i]->CastInt32() : Default;
}
int64_t LScriptArguments::Int64At(size_t i, int64_t Default)
{
return IdxCheck(i) ? (*this)[i]->CastInt64() : Default;
}
double LScriptArguments::DoubleAt(size_t i, double Default)
{
return IdxCheck(i) ? (*this)[i]->CastDouble() : Default;
}
+LDom *LScriptArguments::DomAt(size_t i)
+{
+ return IdxCheck(i) ? (*this)[i]->CastDom() : NULL;
+}
+
bool LScriptArguments::Throw(const char *file, int line, const char *Msg, ...)
{
if (!Vm)
return false;
File = file;
Line = line;
va_list Arg;
va_start(Arg, Msg);
ExceptionMsg.Printf(Arg, Msg);
va_end(Arg);
Vm->OnException(File, Line, Address, ExceptionMsg);
return true;
}
diff --git a/src/common/Net/Mail.cpp b/src/common/Net/Mail.cpp
--- a/src/common/Net/Mail.cpp
+++ b/src/common/Net/Mail.cpp
@@ -1,2690 +1,2679 @@
/*hdr
** FILE: Mail.cpp
** AUTHOR: Matthew Allen
** DATE: 28/5/98
** DESCRIPTION: Mail app
**
** Copyright (C) 1998, Matthew Allen
** fret@memecode.com
*/
#include
#include
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/Mail.h"
#include "lgi/common/Base64.h"
#include "lgi/common/DateTime.h"
#include "lgi/common/DocView.h"
#include "lgi/common/Store3Defs.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/TextConvert.h"
#include "lgi/common/Mime.h"
#include "../Hash/md5/md5.h"
const char *sTextPlain = "text/plain";
const char *sTextHtml = "text/html";
const char *sTextXml = "text/xml";
const char *sApplicationInternetExplorer = "application/internet-explorer";
const char sMultipartMixed[] = "multipart/mixed";
const char sMultipartEncrypted[] = "multipart/encrypted";
const char sMultipartSigned[] = "multipart/signed";
const char sMultipartAlternative[] = "multipart/alternative";
const char sMultipartRelated[] = "multipart/related";
const char sAppOctetStream[] = "application/octet-stream";
//////////////////////////////////////////////////////////////////////////////////////////////////
LogEntry::LogEntry(LColour col)
{
c = col;
}
bool LogEntry::Add(const char *t, ssize_t len)
{
if (!t)
return false;
if (len < 0)
len = strlen(t);
/*
// Strip off any whitespace on the end of the line.
while (len > 0 && strchr(" \t\r\n", t[len-1]))
len--;
*/
LAutoWString w(Utf8ToWide(t, len));
if (!w)
return false;
size_t ch = StrlenW(w);
return Txt.Add(w, ch);
}
bool Base64Str(LString &s)
{
LString b64;
ssize_t Base64Len = BufferLen_BinTo64(s.Length());
if (!b64.Set(NULL, Base64Len))
return false;
#ifdef _DEBUG
ssize_t Ch =
#endif
ConvertBinaryToBase64(b64.Get(), b64.Length(), (uchar*)s.Get(), s.Length());
LAssert(Ch == b64.Length());
s = b64;
return true;
}
bool UnBase64Str(LString &s)
{
LString Bin;
ssize_t BinLen = BufferLen_64ToBin(s.Length());
if (!Bin.Set(NULL, BinLen))
return false;
ssize_t Ch = ConvertBase64ToBinary((uchar*)Bin.Get(), Bin.Length(), s.Get(), s.Length());
LAssert(Ch <= (int)Bin.Length());
s = Bin;
s.Get()[Ch] = 0;
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////
// returns the maximum length of the lines contained in the string
int MaxLineLen(char *Text)
{
if (!Text)
return false;
int Max = 0;
int i = 0;
for (char *c = Text; *c; c++)
{
if (*c == '\r')
{
// return
}
else if (*c == '\n')
{
// eol
Max = MAX(i, Max);
i = 0;
}
else
{
// normal char
i++;
}
}
return Max;
}
bool IsDotLined(char *Text)
{
if (Text)
{
for (char *l = Text; l && *l; )
{
if (l[0] == '.')
{
if (l[1] == '\n' || l[1] == 0)
{
return true;
}
}
l = strchr(l, '\n');
if (l) l++;
}
}
return false;
}
// Is s a valid non-whitespace string?
bool ValidNonWSStr(const char *s)
{
if (s && *s)
{
while (*s && strchr(" \r\t\n", *s))
{
s++;
}
if (*s)
{
return true;
}
}
return false;
}
-void TokeniseStrList(char *Str, List &Output, const char *Delim)
+void TokeniseStrList(const char *Str, LString::Array &Output, const char *Delim)
{
- if (Str && Delim)
+ if (!Str || !Delim)
+ return;
+
+ auto s = Str;
+ while (*s)
{
- char *s = Str;
- while (*s)
+ while (*s && strchr(LWhiteSpace, *s))
+ s++;
+
+ auto e = s;
+ for (; *e; e++)
{
- while (*s && strchr(LWhiteSpace, *s))
- s++;
-
- char *e = s;
- for (; *e; e++)
+ if (strchr("\'\"", *e))
{
- if (strchr("\'\"", *e))
- {
- // handle string constant
- char delim = *e++;
- e = strchr(e, delim);
- }
- else if (*e == '<')
- {
- e = strchr(e, '>');
- }
- else
- {
- while (*e && *e != '<' && !IsWhite(*e) && !strchr(Delim, *e))
- e++;
- }
-
- if (!e || !*e || strchr(Delim, *e))
- {
- break;
- }
+ // handle string constant
+ char delim = *e++;
+ e = strchr(e, delim);
+ }
+ else if (*e == '<')
+ {
+ e = strchr(e, '>');
+ }
+ else
+ {
+ while (*e && *e != '<' && !IsWhite(*e) && !strchr(Delim, *e))
+ e++;
}
- ssize_t Len = e ? e - s : strlen(s);
- if (Len > 0)
+ if (!e || !*e || strchr(Delim, *e))
{
- char *Temp = new char[Len+1];
- if (Temp)
- {
- memcpy(Temp, s, Len);
- Temp[Len] = 0;
- Output.Insert(Temp);
- }
+ break;
}
+ }
- if (e)
- {
- s = e;
- for (; *s && strchr(Delim, *s); s++);
- }
- else break;
+ ssize_t Len = e ? e - s : strlen(s);
+ if (Len > 0)
+ Output.New().Set(s, Len);
+
+ if (e)
+ {
+ s = e;
+ for (; *s && strchr(Delim, *s); s++)
+ ;
}
+ else break;
}
}
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
void DeNullText(char *in, ssize_t &len)
{
char *out = in;
char *end = in + len;
while (in < end)
{
if (*in)
- {
*out++ = *in;
- }
else
- {
len--;
- }
in++;
}
}
//////////////////////////////////////////////////////////////////////////////
typedef char CharPair[2];
static CharPair Pairs[] =
{
{'<', '>'},
{'(', ')'},
{'\'', '\''},
{'\"', '\"'},
{0, 0},
};
struct MailAddrPart
{
LAutoString Part;
bool Brackets;
bool ValidEmail;
LAutoString RemovePairs(char *Str, ssize_t Len, CharPair *Pairs)
{
char *s = Str;
if (Len < 0)
Len = strlen(s);
while (*s && strchr(LWhiteSpace, *s))
{
s++;
Len--;
}
if (!*s)
return LAutoString();
// Get the end of the string...
char *e = s;
if (Len < 0)
e += strlen(s);
else
e += Len;
// Seek back over any trailing whitespace
while (e > s && strchr(LWhiteSpace, e[-1]))
e--;
for (CharPair *p = Pairs; (*p)[0]; p++)
{
if ((*p)[0] == *s && (*p)[1] == e[-1])
{
s++;
e--;
if (s < e)
{
// reset search
p = Pairs - 1;
}
else break;
}
}
Len = e - s;
if (Len < 0)
return LAutoString();
return LAutoString(NewStr(s, Len));
}
MailAddrPart(char *s, ssize_t len)
{
ValidEmail = false;
Brackets = false;
if (s)
{
if (len < 0)
len = strlen(s);
while (strchr(LWhiteSpace, *s) && len > 0)
{
s++;
len--;
}
Brackets = *s == '<';
Part = RemovePairs(s, len, Pairs);
// ValidEmail = IsValidEmail(Part);
}
}
int Score()
{
if (!Part)
return 0;
return (ValidEmail ? 1 : 0) + (Brackets ? 1 : 0);
}
};
int PartCmp(LAutoPtr *a, LAutoPtr *b)
{
return (*b)->Score() - (*a)->Score();
}
bool IsAngleBrackets(LString &s)
{
if (s(0) == '<' && s(-1) == '>')
return true;
return false;
}
void DecodeAddrName(const char *Str, std::function Cb, const char *DefaultDomain)
{
if (!Str)
return;
LString s = Str;
LString non;
LString email;
LString::Array a;
auto startBracket = s.Find("<");
auto endBracket = s.Find(">", startBracket);
if (startBracket >= 0 && endBracket >= 0)
{
// Keep the angle brackets for the time being...
a.New() = s(0, startBracket) + s(++endBracket, -1);
a.New() = s(startBracket, endBracket);
}
else a.New() = s;
for (unsigned i=0; i");
}
else
{
non += a[i];
}
}
if (!email)
{
a = s.SplitDelimit("()");
non.Empty();
for (unsigned i=0; i 0)
{
const char *ChSet = " \t\r\n\'\"<>";
do
{
non = non.Strip(ChSet);
}
while (non.Length() > 0 && strchr(ChSet, non(0)));
}
Cb(non, email.Strip());
}
void DecodeAddrName(const char *Start, LAutoString &Name, LAutoString &Addr, const char *DefaultDomain)
{
DecodeAddrName(Start, [&Name, &Addr](LString n, LString a){
Name.Reset(NewStr(n));
Addr.Reset(NewStr(a));
}, DefaultDomain);
}
void DecodeAddrName(const char *Start, LString &Name, LString &Addr, const char *DefaultDomain)
{
DecodeAddrName(Start, [&Name, &Addr](LString n, LString a){
Name = n;
Addr = a;
}, DefaultDomain);
}
#if 0
struct LDecodeAddrNameTest
{
LDecodeAddrNameTest()
{
// Testing code
char *Input[] =
{
"\"Sound&Secure@speedytechnical.com\" ",
"\"@MM-Social Mailman List\" ",
"'Matthew Allen (fret)' ",
"Matthew Allen (fret) ",
"\"'Matthew Allen'\" ",
"Matthew Allen",
"fret@memecode.com",
"\"\" ",
" (fret@memecode.com)",
"Matthew Allen ",
"\"Matthew, Allen\" (fret@memecode.com)",
"Matt'hew Allen ",
"john.omalley ",
"Bankers' Association (ABA)",
"'Amy's Mum' ",
"\"Philip Doggett (JIRA)\" ",
"\"group name\"