Rethink portable releases:

* Remove complicated and unreadable NSIS stuff. No need to have a installer for a developer application. ZIP file is fine.
* On startup, if a portable_settings.txt exists in the application folder, heidisql.exe will read its contents into a unique registry key.
* In MainForm.OnDestroy, settings are written to that file again, and the reg key gets deleted.
* Avoid using Microsoft's .reg file syntax, as you need admin privileges to store and restore such files, even when using the appropriate Windows API functions.
This commit is contained in:
Ansgar Becker
2010-03-22 21:10:38 +00:00
parent 4724d660eb
commit ed5ec1e122
12 changed files with 119 additions and 2053 deletions

View File

@ -292,7 +292,7 @@ begin
i := 0;
NewName := 'Unnamed';
while MainReg.KeyExists(REGPATH + REGKEY_SESSIONS + NewName) do begin
while MainReg.KeyExists(RegPath + REGKEY_SESSIONS + NewName) do begin
inc(i);
NewName := 'Unnamed-' + IntToStr(i);
end;
@ -314,7 +314,7 @@ var
begin
if MessageDlg('Delete session "' + SelectedSession + '" ?', mtConfirmation, [mbYes, mbCancel], 0) = mrYes then
begin
SessionKey := REGPATH + REGKEY_SESSIONS + SelectedSession;
SessionKey := RegPath + REGKEY_SESSIONS + SelectedSession;
if MainReg.KeyExists(SessionKey) then
MainReg.DeleteKey(SessionKey);
FSessionNames.Delete(FSessionNames.IndexOf(SelectedSession));
@ -345,7 +345,7 @@ end;
procedure Tconnform.RefreshSessionList;
begin
// Refresh list of session names
MainReg.OpenKey(REGPATH + REGKEY_SESSIONS, True);
MainReg.OpenKey(RegPath + REGKEY_SESSIONS, True);
FSessionNames.BeginUpdate;
MainReg.GetKeyNames(FSessionNames);
FSessionNames.EndUpdate;
@ -398,7 +398,7 @@ begin
OpenRegistry;
SessionFocused := Assigned(Node);
if SessionFocused then begin
SessionExists := MainReg.KeyExists(REGPATH + REGKEY_SESSIONS + SelectedSession);
SessionExists := MainReg.KeyExists(RegPath + REGKEY_SESSIONS + SelectedSession);
if SessionExists then begin
OpenRegistry(SelectedSession);
FOrgNetType := GetRegValue(REGNAME_NETTYPE, DEFAULT_NETTYPE, SelectedSession);
@ -470,7 +470,7 @@ begin
lblCounterRight.Enabled := False;
if (not Assigned(ListSessions.FocusedNode))
or (not MainReg.KeyExists(REGPATH + REGKEY_SESSIONS + SelectedSession)) then
or (not MainReg.KeyExists(RegPath + REGKEY_SESSIONS + SelectedSession)) then
Exit;
DummyDate := StrToDateTime('2000-01-01');
@ -517,9 +517,9 @@ begin
MessageDLG('Session "'+NewText+'" already exists!', mtError, [mbCancel], 0);
NewText := SelectedSession;
end else begin
SessionKey := REGPATH + REGKEY_SESSIONS + SelectedSession;
SessionKey := RegPath + REGKEY_SESSIONS + SelectedSession;
if MainReg.KeyExists(SessionKey) then
MainReg.MoveKey(SessionKey, REGPATH + REGKEY_SESSIONS + NewText, true);
MainReg.MoveKey(SessionKey, RegPath + REGKEY_SESSIONS + NewText, true);
// Also fix internal session name in main form, which gets used to store e.g. "lastuseddb" later
if Mainform.SessionName = SelectedSession then begin
Mainform.SessionName := NewText;

View File

@ -21,7 +21,6 @@ const
// General things
APPNAME = 'HeidiSQL';
APPDOMAIN = 'http://www.heidisql.com/';
REGPATH = '\Software\' + APPNAME + '\';
REGKEY_SESSIONS = 'Servers\';
// Some unique char, used to separate e.g. selected columns in registry
DELIM = '|';

View File

@ -250,9 +250,11 @@ type
function KeyPressed(Code: Integer): Boolean;
function GeneratePassword(Len: Integer): String;
procedure InvalidateVT(VT: TVirtualStringTree; RefreshTag: Integer; ImmediateRepaint: Boolean);
procedure HandlePortableSettings(StartupMode: Boolean);
var
MainReg: TRegistry;
RegPath: String = '\Software\' + APPNAME + '\';
MutexHandle: THandle = 0;
dbgCounter: Integer = 0;
DecimalSeparatorSystemdefault: Char;
@ -2593,9 +2595,11 @@ procedure OpenRegistry(Session: String = '');
var
folder : String;
begin
if MainReg = nil then
if MainReg = nil then begin
MainReg := TRegistry.Create;
folder := REGPATH;
HandlePortableSettings(True);
end;
folder := RegPath;
if Session <> '' then
folder := folder + REGKEY_SESSIONS + Session;
if MainReg.CurrentPath <> folder then
@ -2619,7 +2623,7 @@ end;
Read a boolean preference value from registry
@param string Name of the value
@param boolean Default-value to return if valueName was not found
@param string Subkey of REGPATH where to search for the value
@param string Subkey of RegPath where to search for the value
}
function GetRegValue( valueName: String; defaultValue: Boolean; Session: String = '' ) : Boolean;
begin
@ -3456,6 +3460,108 @@ begin
end;
procedure HandlePortableSettings(StartupMode: Boolean);
var
Content, FileName, Name, Value, KeyPath: String;
Lines, Segments: TStringList;
i: Integer;
DataType: TRegDataType;
const
Chr10Replacement = '<}}}>';
Chr13Replacement = '<{{{>';
Delimiter = '<|||>';
procedure ReadKeyToContent(Path: String);
var
Names: TStringList;
i: Integer;
SubPath: String;
begin
MainReg.OpenKeyReadOnly(Path);
SubPath := Copy(Path, Length(RegPath)+1, MaxInt);
Names := TStringList.Create;
MainReg.GetValueNames(Names);
for i:=0 to Names.Count-1 do begin
DataType := MainReg.GetDataType(Names[i]);
Content := Content +
SubPath + Names[i] + Delimiter +
IntToStr(Integer(DataType)) + Delimiter;
case DataType of
rdString: begin
Value := MainReg.ReadString(Names[i]);
Value := StringReplace(Value, #13, Chr13Replacement, [rfReplaceAll]);
Value := StringReplace(Value, #10, Chr10Replacement, [rfReplaceAll]);
end;
rdInteger:
Value := IntToStr(MainReg.ReadInteger(Names[i]));
rdBinary, rdUnknown, rdExpandString:
MessageDlg(Names[i]+' has an unsupported data type.', mtError, [mbOK], 0);
end;
Content := Content + Value + CRLF;
end;
Names.Clear;
MainReg.GetKeyNames(Names);
for i:=0 to Names.Count-1 do
ReadKeyToContent(Path + Names[i] + '\');
Names.Free;
end;
begin
// Export registry keys and values into textfile, for portable reasons
FileName := ExtractFilePath(ParamStr(0)) + 'portable_settings.txt';
if not FileExists(FileName) then
Exit;
// Open the right key
if StartupMode then
RegPath := '\Software\' + APPNAME + ' Portable '+IntToStr(GetCurrentThreadId)+'\';
Screen.Cursor := crHourGlass;
try
if StartupMode then begin
Content := ReadTextfile(FileName);
Lines := Explode(CRLF, Content);
for i:=0 to Lines.Count-1 do begin
// Each line has 3 segments: reg path | data type | value
Segments := Explode(Delimiter, Lines[i]);
KeyPath := RegPath + ExtractFilePath(Segments[0]);
Name := ExtractFileName(Segments[0]);
DataType := TRegDataType(StrToInt(Segments[1]));
MainReg.OpenKey(KeyPath, True);
if MainReg.ValueExists(Name) then
Continue; // Don't touch value if already there
Value := '';
if Segments.Count >= 3 then
Value := Segments[2];
case DataType of
rdString: begin
Value := StringReplace(Value, Chr13Replacement, #13, [rfReplaceAll]);
Value := StringReplace(Value, Chr10Replacement, #10, [rfReplaceAll]);
MainReg.WriteString(Name, Value);
end;
rdInteger:
MainReg.WriteInteger(Name, MakeInt(Value));
rdBinary, rdUnknown, rdExpandString:
MessageDlg(Name+' has an unsupported data type.', mtError, [mbOK], 0);
end;
Segments.Free;
end;
Lines.Free;
end else begin
// Application closes: Recursively read values in keys and their subkeys into textfile
ReadKeyToContent(RegPath);
SaveUnicodeFile(FileName, Content);
MainReg.CloseKey;
MainReg.DeleteKey(RegPath);
end;
except
On E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
Screen.Cursor := crDefault;
end;
end.

View File

@ -1176,6 +1176,8 @@ begin
deletefile(filename+'bmp');
if MainReg <> nil then begin
MainReg.CloseKey;
// Export settings into textfile in portable mode.
HandlePortableSettings(False);
MainReg.Free;
end;
end;

View File

@ -186,7 +186,7 @@ begin
comboExportOutputTarget.Text := '';
// Add session names from registry
SessionNames := TStringList.Create;
MainReg.OpenKey(REGPATH + REGKEY_SESSIONS, True);
MainReg.OpenKey(RegPath + REGKEY_SESSIONS, True);
MainReg.GetKeyNames(SessionNames);
for i:=0 to SessionNames.Count-1 do begin
if SessionNames[i] <> Mainform.SessionName then