diff --git a/.gitignore b/.gitignore index 384e3ab..5a1650d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ *.xojo_uistate +*.debug.app +Update Test App/Builds - Kaju Update Test.xojo_project +DebugKaju Update Test.tar.gz +DebugKaju Admin.tar.gz +Kaju Admin App/Builds - Kaju Admin.xojo_project diff --git a/Client Harness/.Xojo Self-Updater Harness.xojo_uistate b/Client Harness/.Xojo Self-Updater Harness.xojo_uistate deleted file mode 100644 index a87845f..0000000 Binary files a/Client Harness/.Xojo Self-Updater Harness.xojo_uistate and /dev/null differ diff --git a/Client Harness/Window1.xojo_window b/Client Harness/Window1.xojo_window deleted file mode 100644 index 88841d5..0000000 --- a/Client Harness/Window1.xojo_window +++ /dev/null @@ -1,41 +0,0 @@ -#tag Window -Begin Window Window1 - BackColor = &cFFFFFF00 - Backdrop = 0 - CloseButton = True - Compatibility = "" - Composite = False - Frame = 0 - FullScreen = False - FullScreenButton= False - HasBackColor = False - Height = 400 - ImplicitInstance= True - LiveResize = True - MacProcID = 0 - MaxHeight = 32000 - MaximizeButton = True - MaxWidth = 32000 - MenuBar = 30872107 - MenuBarVisible = True - MinHeight = 64 - MinimizeButton = True - MinWidth = 64 - Placement = 0 - Resizeable = True - Title = "Untitled" - Visible = True - Width = 600 -End -#tag EndWindow - -#tag WindowCode - #tag Event - Sub Open() - - End Sub - #tag EndEvent - - -#tag EndWindowCode - diff --git a/Client Harness/Xojo Self-Updater Harness.xojo_project b/Client Harness/Xojo Self-Updater Harness.xojo_project deleted file mode 100644 index 97e186f..0000000 --- a/Client Harness/Xojo Self-Updater Harness.xojo_project +++ /dev/null @@ -1,39 +0,0 @@ -Type=Desktop -RBProjectVersion=2014.021 -MinIDEVersion=20070100 -Class=App;App.xojo_code;&h6760B355;&h0;false -Window=Window1;Window1.xojo_window;&h7D9E081E;&h0;false -MenuBar=MainMenuBar;MainMenuBar.xojo_menu;&h1D7122B;&h0;false -BuildSteps=Build Automation;Build Automation.xojo_code;&h4EEA07A4;&h0;false -DefaultWindow=Window1 -AppMenuBar=MainMenuBar -MajorVersion=1 -MinorVersion=0 -SubVersion=0 -NonRelease=0 -Release=0 -InfoVersion= -LongVersion= -ShortVersion= -WinCompanyName=MacTechnologies Consulting -WinInternalName= -WinProductName= -WinFileDescription= -AutoIncrementVersionInformation=False -BuildFlags=&h4900 -BuildLanguage=&h0 -DebugLanguage=&h0 -Region= -WindowsName=Xojo Self-Updater.exe -MacCarbonMachName=Xojo Self-Updater -LinuxX86Name=Xojo Self-Updater -MacCreator= -MDI=0 -MDICaption= -DefaultEncoding=&h0 -AppIcon=Xojo Self-Updater Harness.xojo_resources;&h0 -OSXBundleID=com.mactechnologies.xojoselfupdater -DebuggerCommandLine= -UseGDIPlus=False -UseBuildsFolder=True -IsWebProject=False diff --git a/External Images/Some_Image.png b/External Images/Some_Image.png new file mode 100644 index 0000000..5ca3db5 Binary files /dev/null and b/External Images/Some_Image.png differ diff --git a/Kaju Admin App/App.xojo_code b/Kaju Admin App/App.xojo_code new file mode 100644 index 0000000..6cc64ef --- /dev/null +++ b/Kaju Admin App/App.xojo_code @@ -0,0 +1,216 @@ +#tag Class +Protected Class App +Inherits Application + #tag Event + Sub NewDocument() + dim w as new WndAdmin + w.Show + End Sub + #tag EndEvent + + #tag Event + Sub Open() + return + + // + // Some test code below + // + + // + // Create the pref folder + // + + dim f as FolderItem = PrefFolder + + dim u as new Kaju.UpdateChecker( f ) + #pragma unused u + + dim r as double + + r = Kaju.VersionToDouble( "1.2a999" ) + r = Kaju.VersionToDouble( "1.2b3" ) + r = Kaju.VersionToDouble( "1.2" ) + r = Kaju.VersionToDouble( "1.3.999d777" ) + + dim vers as string = Kaju.AppVersionString + vers = Kaju.VersionStringFor( 1, 3, 44 ) + + 'u.TestUpdate( kTestJSON1 ) + 'u.TestUpdate( kTestJSON2 ) + 'u.TestUpdate( kTestJSON2 ) + + 'dim sh as new Kaju.ZipShell + 'dim zipFile as FolderItem = SpecialFolder.Desktop.Child( f.Name + ".zip" ) + 'sh.Compress( f, zipFile ) + ' + 'sh.Decompress( zipFile ) + ' + 'MsgBox sh.ContentsOf( zipFile ) + + + End Sub + #tag EndEvent + + #tag Event + Sub OpenDocument(item As FolderItem) + // + // See if this document is open already + // + + dim firstAdminWindow as WndAdmin + + dim lastIndex as integer = WindowCount - 1 + for i as integer = 0 to lastIndex + dim thisWnd as Window = Window( i ) + if thisWnd IsA WndAdmin then + + dim adminWnd as WndAdmin = WndAdmin( thisWnd ) + + if adminWnd.Document <> nil and adminWnd.Document.NativePath = item.NativePath then + adminWnd.Show + return + end if + + if firstAdminWindow is nil then + firstAdminWindow = adminWnd + end if + + end if + next + + // + // If we get here, it's not already open + // so see if the front window can be used + // + + if firstAdminWindow <> nil and firstAdminWindow.Document is nil and not firstAdminWindow.ContentsChanged then + // + // It's an empty window + // + firstAdminWindow.OpenDocument( item ) + + else + // + // Create a new window + // + dim w as new WndAdmin + w.OpenDocument( item ) + + end if + End Sub + #tag EndEvent + + + #tag MenuHandler + Function FileNew() As Boolean Handles FileNew.Action + NewDocument + + Return True + + End Function + #tag EndMenuHandler + + #tag MenuHandler + Function FileOpen() As Boolean Handles FileOpen.Action + dim dlg as new OpenDialog + dlg.MultiSelect = true + dlg.PromptText = "Choose a Kaju document:" + + dim f as FolderItem = dlg.ShowModal + + if f <> nil then + for i as integer = 1 to dlg.Count + OpenDocument( dlg.Item( i - 1 ) ) + next + end if + + Return True + + End Function + #tag EndMenuHandler + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + dim f as FolderItem = SpecialFolder.ApplicationData.Child( "Kaju Admin" ) + + if not f.Exists then + f.CreateAsFolder + end if + + return f + + End Get + #tag EndGetter + PrefFolder As FolderItem + #tag EndComputedProperty + + #tag Property, Flags = &h0 + UpdateInitiater As Kaju.UpdateInitiater + #tag EndProperty + + + #tag Constant, Name = kEditClear, Type = String, Dynamic = False, Default = \"&Delete", Scope = Public + #Tag Instance, Platform = Windows, Language = Default, Definition = \"&Delete" + #Tag Instance, Platform = Linux, Language = Default, Definition = \"&Delete" + #tag EndConstant + + #tag Constant, Name = kFileClose, Type = String, Dynamic = False, Default = \"Close &Window", Scope = Public + #tag EndConstant + + #tag Constant, Name = kFileCloseShortcut, Type = String, Dynamic = False, Default = \"", Scope = Public + #Tag Instance, Platform = Mac OS, Language = Default, Definition = \"Cmd+W" + #Tag Instance, Platform = Windows, Language = Default, Definition = \"Ctrl+W" + #Tag Instance, Platform = Linux, Language = Default, Definition = \"Ctrl+W" + #tag EndConstant + + #tag Constant, Name = kFileNew, Type = String, Dynamic = False, Default = \"&New Kaju Document", Scope = Public + #tag EndConstant + + #tag Constant, Name = kFileNewShortcut, Type = String, Dynamic = False, Default = \"", Scope = Public + #Tag Instance, Platform = Mac OS, Language = Default, Definition = \"Cmd+N" + #Tag Instance, Platform = Windows, Language = Default, Definition = \"Ctrl+N" + #Tag Instance, Platform = Linux, Language = Default, Definition = \"Ctrl+N" + #tag EndConstant + + #tag Constant, Name = kFileOpen, Type = String, Dynamic = False, Default = \"&Open Kaju Document...", Scope = Public + #tag EndConstant + + #tag Constant, Name = kFileOpenShortcut, Type = String, Dynamic = False, Default = \"", Scope = Public + #Tag Instance, Platform = Windows, Language = Default, Definition = \"Ctrl+O" + #Tag Instance, Platform = Mac OS, Language = Default, Definition = \"Cmd+O" + #Tag Instance, Platform = Linux, Language = Default, Definition = \"Ctrl+O" + #tag EndConstant + + #tag Constant, Name = kFileQuit, Type = String, Dynamic = False, Default = \"&Quit", Scope = Public + #Tag Instance, Platform = Windows, Language = Default, Definition = \"E&xit" + #tag EndConstant + + #tag Constant, Name = kFileQuitShortcut, Type = String, Dynamic = False, Default = \"", Scope = Public + #Tag Instance, Platform = Mac OS, Language = Default, Definition = \"Cmd+Q" + #Tag Instance, Platform = Linux, Language = Default, Definition = \"Ctrl+Q" + #Tag Instance, Platform = Windows, Language = Default, Definition = \"Ctrl+Q" + #tag EndConstant + + #tag Constant, Name = kFileSave, Type = String, Dynamic = False, Default = \"&Save", Scope = Public + #tag EndConstant + + #tag Constant, Name = kFileSaveAs, Type = String, Dynamic = False, Default = \"Sav&e As...", Scope = Public + #tag EndConstant + + #tag Constant, Name = kFileSaveShortcut, Type = String, Dynamic = False, Default = \"", Scope = Public + #Tag Instance, Platform = Mac OS, Language = Default, Definition = \"Cmd+S" + #Tag Instance, Platform = Windows, Language = Default, Definition = \"Ctrl+S" + #Tag Instance, Platform = Linux, Language = Default, Definition = \"Ctrl+S" + #tag EndConstant + + #tag Constant, Name = kTestJSON1, Type = String, Dynamic = False, Default = \"[\n\t{\n\t\"Version\" : \"1.0.3\"\x2C\n\t\"MacBinary\" : \n\t\t{\n\t\t\"URL\" : \"http://www.sample.com/macdownload\"\x2C\n\t\t\"Signature\" : \"123456\"\n\t\t}\n\t} \n]", Scope = Private + #tag EndConstant + + #tag Constant, Name = kTestJSON2, Type = String, Dynamic = False, Default = \"[\n\t{\n\t\"Version\" : \"1.0.3\"\x2C\n\t\"MacBinary\" : \n\t\t{\n\t\t\"URL\" : \"http://www.sample.com/macdownload\"\x2C\n\t\t\"Signature\" : \"123456\"\n\t\t}\x2C\n\t\"ReleaseNotes\" : \"Version 1.0.3

This is a really important update that you should have.\"\n\t} \x2C\n\t{\n\t\"Version\" : \"1.1b1\"\x2C\n\t\"MacBinary\" : \n\t\t{\n\t\t\"URL\" : \"http://www.sample.com/macdownload\"\x2C\n\t\t\"Signature\" : \"123456\"\n\t\t}\x2C\n\t\"InfoURL\" : \"http://www.sample.com/moreinfo\"\n\t}\n]", Scope = Private + #tag EndConstant + + +End Class +#tag EndClass diff --git a/Client Harness/Build Automation.xojo_code b/Kaju Admin App/Build Automation.xojo_code similarity index 100% rename from Client Harness/Build Automation.xojo_code rename to Kaju Admin App/Build Automation.xojo_code diff --git a/Kaju Admin App/Controls/CheckBoxChanger.xojo_code b/Kaju Admin App/Controls/CheckBoxChanger.xojo_code new file mode 100644 index 0000000..28043e5 --- /dev/null +++ b/Kaju Admin App/Controls/CheckBoxChanger.xojo_code @@ -0,0 +1,18 @@ +#tag Class +Protected Class CheckBoxChanger +Inherits CheckBox + #tag Event + Sub Action() + self.Window.ContentsChanged = true + RaiseEvent Action() + End Sub + #tag EndEvent + + + #tag Hook, Flags = &h0 + Event Action() + #tag EndHook + + +End Class +#tag EndClass diff --git a/Kaju Admin App/Controls/TextAreaChanger.xojo_code b/Kaju Admin App/Controls/TextAreaChanger.xojo_code new file mode 100644 index 0000000..8eda7a7 --- /dev/null +++ b/Kaju Admin App/Controls/TextAreaChanger.xojo_code @@ -0,0 +1,18 @@ +#tag Class +Protected Class TextAreaChanger +Inherits TextArea + #tag Event + Sub TextChange() + self.Window.ContentsChanged = true + RaiseEvent TextChange() + End Sub + #tag EndEvent + + + #tag Hook, Flags = &h0 + Event TextChange() + #tag EndHook + + +End Class +#tag EndClass diff --git a/Kaju Admin App/Controls/TextFieldChanger.xojo_code b/Kaju Admin App/Controls/TextFieldChanger.xojo_code new file mode 100644 index 0000000..688fb6b --- /dev/null +++ b/Kaju Admin App/Controls/TextFieldChanger.xojo_code @@ -0,0 +1,297 @@ +#tag Class +Protected Class TextFieldChanger +Inherits TextField + #tag Event + Sub TextChange() + self.Window.ContentsChanged = true + RaiseEvent TextChange() + End Sub + #tag EndEvent + + + #tag Hook, Flags = &h0 + Event TextChange() + #tag EndHook + + + #tag ViewBehavior + #tag ViewProperty + Name="AcceptTabs" + Visible=true + Group="Behavior" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Alignment" + Visible=true + Group="Behavior" + InitialValue="0" + Type="Integer" + EditorType="Enum" + #tag EnumValues + "0 - Default" + "1 - Left" + "2 - Center" + "3 - Right" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="AutoDeactivate" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="AutomaticallyCheckSpelling" + Visible=true + Group="Behavior" + InitialValue="False" + Type="boolean" + #tag EndViewProperty + #tag ViewProperty + Name="BackColor" + Visible=true + Group="Appearance" + InitialValue="&hFFFFFF" + Type="Color" + #tag EndViewProperty + #tag ViewProperty + Name="Bold" + Visible=true + Group="Font" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Border" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="CueText" + Visible=true + Group="Initial State" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="DataField" + Visible=true + Group="Database Binding" + Type="String" + EditorType="DataField" + #tag EndViewProperty + #tag ViewProperty + Name="DataSource" + Visible=true + Group="Database Binding" + Type="String" + EditorType="DataSource" + #tag EndViewProperty + #tag ViewProperty + Name="Enabled" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Format" + Visible=true + Group="Appearance" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Position" + InitialValue="22" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="HelpTag" + Visible=true + Group="Appearance" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Italic" + Visible=true + Group="Font" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="LimitText" + Visible=true + Group="Behavior" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="LockBottom" + Visible=true + Group="Position" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="LockLeft" + Visible=true + Group="Position" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="LockRight" + Visible=true + Group="Position" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="LockTop" + Visible=true + Group="Position" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Mask" + Visible=true + Group="Behavior" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Password" + Visible=true + Group="Appearance" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="ReadOnly" + Visible=true + Group="Behavior" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="TabIndex" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="TabPanelIndex" + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="TabStop" + Visible=true + Group="Position" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Text" + Visible=true + Group="Initial State" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="TextColor" + Visible=true + Group="Appearance" + InitialValue="&h000000" + Type="Color" + #tag EndViewProperty + #tag ViewProperty + Name="TextFont" + Visible=true + Group="Font" + InitialValue="System" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="TextSize" + Visible=true + Group="Font" + InitialValue="0" + Type="Single" + #tag EndViewProperty + #tag ViewProperty + Name="TextUnit" + Visible=true + Group="Font" + InitialValue="0" + Type="FontUnits" + EditorType="Enum" + #tag EnumValues + "0 - Default" + "1 - Pixel" + "2 - Point" + "3 - Inch" + "4 - Millimeter" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Underline" + Visible=true + Group="Font" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="UseFocusRing" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Position" + InitialValue="80" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Admin App/FileTypes1.xojo_filetypeset b/Kaju Admin App/FileTypes1.xojo_filetypeset new file mode 100644 index 0000000..54422e8 --- /dev/null +++ b/Kaju Admin App/FileTypes1.xojo_filetypeset @@ -0,0 +1,41 @@ +#tag FileTypeSet + #tag FileType + CodeName=KajuDocument + DocIcon=Kaju Admin.xojo_resources;&h0 + Extension=.kaju + Flags=&h5 + MacCreator= + MacType= + Name=KajuDocument + UTI=com.mactechnologies.kajudocument + #tag EndFileType + + #tag FileType + CodeName=TextHtml + Extension=.htm;.html + Flags=&h2 + MacCreator=???? + MacType=TEXT + Name=text/html + UTI=public.html + #tag EndFileType + + #tag FileType + CodeName=Any + Extension= + Flags=&h2 + MacCreator=???? + MacType=???? + Name=special/any + #tag EndFileType + + #tag FileType + CodeName=ApplicationZip + Extension=.zip + Flags=&h2 + MacCreator=SITx + MacType=ZIP + Name=application/zip + #tag EndFileType + +#tag EndFileTypeSet diff --git a/Kaju Admin App/Kaju Admin.xojo_project b/Kaju Admin App/Kaju Admin.xojo_project new file mode 100644 index 0000000..e1b514e --- /dev/null +++ b/Kaju Admin App/Kaju Admin.xojo_project @@ -0,0 +1,56 @@ +Type=Desktop +RBProjectVersion=2014.031 +MinIDEVersion=20070100 +Folder=Kaju Classes;../Kaju Classes;&h2570CF64;&h0;false +Class=App;App.xojo_code;&h7955649;&h0;false +MenuBar=MainMenuBar;MainMenuBar.xojo_menu;&h4AB8FF74;&h0;false +FileTypeSet=FileTypes1;FileTypes1.xojo_filetypeset;&h352FB9F5;&h0;false +Window=WndAdmin;WndAdmin.xojo_window;&h5D541828;&h0;false +BuildSteps=Build Automation;Build Automation.xojo_code;&h378B80A5;&h0;false +Module=Kaju;../Kaju Classes/Kaju.xojo_code;&h11400316;&h2570CF64;false +Window=KajuUpdateWindow;../Kaju Classes/KajuUpdateWindow.xojo_window;&h18AE3D9;&h2570CF64;false +Class=UpdateChecker;../Kaju Classes/Kaju/UpdateChecker.xojo_code;&h70CF0A50;&h11400316;false +Class=KajuException;../Kaju Classes/Kaju/KajuException.xojo_code;&h3047231F;&h11400316;false +Class=UpdateInformation;../Kaju Classes/Kaju/UpdateInformation.xojo_code;&h79AE1ADC;&h11400316;false +Class=BinaryInformation;../Kaju Classes/Kaju/BinaryInformation.xojo_code;&hE8D70B5;&h11400316;false +Class=ZipShell;../Kaju Classes/Kaju/ZipShell.xojo_code;&hCC15E9B;&h11400316;false +Class=Information;../Kaju Classes/Kaju/Information.xojo_code;&h6497A7C4;&h11400316;false +Class=UpdateInitiater;../Kaju Classes/Kaju/UpdateInitiater.xojo_code;&h4DBB48E5;&h11400316;false +Folder=Controls;Controls;&hE8B14E5;&h0;false +Folder=Other Classes;Other Classes;&h6AF5D169;&h0;false +Class=TextFieldChanger;Controls/TextFieldChanger.xojo_code;&h4D090F7E;&hE8B14E5;false +Class=TextAreaChanger;Controls/TextAreaChanger.xojo_code;&hDD8825B;&hE8B14E5;false +Class=CheckBoxChanger;Controls/CheckBoxChanger.xojo_code;&h5115CD40;&hE8B14E5;false +Class=FolderItemAlias;Other Classes/FolderItemAlias.xojo_code;&h53EF1AE5;&h6AF5D169;false +Class=HTTPSSocket;../Kaju Classes/Kaju/HTTPSSocket.xojo_code;&h5ACDFAEB;&h11400316;false +AppMenuBar=MainMenuBar +MajorVersion=1 +MinorVersion=0 +SubVersion=0 +NonRelease=0 +Release=3 +InfoVersion=Kaju Admin +LongVersion=v.1.0 +ShortVersion=1.0 +WinCompanyName=MacTechnologies Consulting +WinInternalName= +WinProductName= +WinFileDescription= +AutoIncrementVersionInformation=False +BuildFlags=&h4900 +BuildLanguage=&h0 +DebugLanguage=&h0 +Region= +WindowsName=Kaju Admin.exe +MacCarbonMachName=Kaju Admin +LinuxX86Name=Kaju Admin +MacCreator= +MDI=0 +MDICaption= +DefaultEncoding=&h0 +AppIcon=Kaju Admin.xojo_resources;&hC +OSXBundleID=com.mactechnologies.kajuadmin +DebuggerCommandLine= +UseGDIPlus=False +UseBuildsFolder=True +IsWebProject=False diff --git a/Kaju Admin App/Kaju Admin.xojo_resources b/Kaju Admin App/Kaju Admin.xojo_resources new file mode 100644 index 0000000..148a241 Binary files /dev/null and b/Kaju Admin App/Kaju Admin.xojo_resources differ diff --git a/Kaju Admin App/MainMenuBar.xojo_menu b/Kaju Admin App/MainMenuBar.xojo_menu new file mode 100644 index 0000000..68fdab9 --- /dev/null +++ b/Kaju Admin App/MainMenuBar.xojo_menu @@ -0,0 +1,156 @@ +#tag Menu +Begin Menu MainMenuBar + Begin MenuItem FileMenu + SpecialMenu = 0 + Text = "&File" + Index = -2147483648 + AutoEnable = True + Visible = True + Begin MenuItem FileNew + SpecialMenu = 0 + Text = "#App.kFileNew" + Index = -2147483648 + ShortcutKey = "#App.kFileNewShortcut" + Shortcut = "#App.kFileNewShortcut" + AutoEnable = True + Visible = True + End + Begin MenuItem FileOpen + SpecialMenu = 0 + Text = "#App.kFileOpen" + Index = -2147483648 + ShortcutKey = "#App.kFileOpenShortcut" + Shortcut = "#App.kFileOpenShortcut" + AutoEnable = True + Visible = True + End + Begin MenuItem FileClose + SpecialMenu = 0 + Text = "#App.kFileClose" + Index = -2147483648 + ShortcutKey = "#App.kFileCloseShortcut" + Shortcut = "Cmd+#App.kFileCloseShortcut" + MenuModifier = True + AutoEnable = True + Visible = True + End + Begin MenuItem UntitledSeparator0 + SpecialMenu = 0 + Text = "-" + Index = -2147483648 + AutoEnable = True + Visible = True + End + Begin MenuItem FileSave + SpecialMenu = 0 + Text = "#App.kFileSave" + Index = -2147483648 + ShortcutKey = "#App.kFileSaveShortcut" + Shortcut = "#App.kFileSaveShortcut" + AutoEnable = True + Visible = True + End + Begin MenuItem FileSaveAs + SpecialMenu = 0 + Text = "#App.kFileSaveAs" + Index = -2147483648 + AutoEnable = True + Visible = True + End + Begin MenuItem UntitledSeparator + SpecialMenu = 0 + Text = "-" + Index = -2147483648 + AutoEnable = True + Visible = True + End + Begin QuitMenuItem FileQuit + SpecialMenu = 0 + Text = "#App.kFileQuit" + Index = -2147483648 + ShortcutKey = "#App.kFileQuitShortcut" + Shortcut = "#App.kFileQuitShortcut" + AutoEnable = True + Visible = True + End + End + Begin MenuItem EditMenu + SpecialMenu = 0 + Text = "&Edit" + Index = -2147483648 + AutoEnable = True + Visible = True + Begin MenuItem EditUndo + SpecialMenu = 0 + Text = "&Undo" + Index = -2147483648 + ShortcutKey = "Z" + Shortcut = "Cmd+Z" + MenuModifier = True + AutoEnable = True + Visible = True + End + Begin MenuItem EditSeparator1 + SpecialMenu = 0 + Text = "-" + Index = -2147483648 + AutoEnable = True + Visible = True + End + Begin MenuItem EditCut + SpecialMenu = 0 + Text = "Cu&t" + Index = -2147483648 + ShortcutKey = "X" + Shortcut = "Cmd+X" + MenuModifier = True + AutoEnable = True + Visible = True + End + Begin MenuItem EditCopy + SpecialMenu = 0 + Text = "&Copy" + Index = -2147483648 + ShortcutKey = "C" + Shortcut = "Cmd+C" + MenuModifier = True + AutoEnable = True + Visible = True + End + Begin MenuItem EditPaste + SpecialMenu = 0 + Text = "&Paste" + Index = -2147483648 + ShortcutKey = "V" + Shortcut = "Cmd+V" + MenuModifier = True + AutoEnable = True + Visible = True + End + Begin MenuItem EditClear + SpecialMenu = 0 + Text = "#App.kEditClear" + Index = -2147483648 + AutoEnable = True + Visible = True + End + Begin MenuItem EditSeparator2 + SpecialMenu = 0 + Text = "-" + Index = -2147483648 + AutoEnable = True + Visible = True + End + Begin MenuItem EditSelectAll + SpecialMenu = 0 + Text = "Select &All" + Index = -2147483648 + ShortcutKey = "A" + Shortcut = "Cmd+A" + MenuModifier = True + AutoEnable = True + Visible = True + End + End +End +#tag EndMenu diff --git a/Kaju Admin App/Other Classes/FolderItemAlias.xojo_code b/Kaju Admin App/Other Classes/FolderItemAlias.xojo_code new file mode 100644 index 0000000..5abee10 --- /dev/null +++ b/Kaju Admin App/Other Classes/FolderItemAlias.xojo_code @@ -0,0 +1,113 @@ +#tag Class +Protected Class FolderItemAlias + #tag Method, Flags = &h0 + Sub Constructor(f As FolderItem) + zSaveInfo = f.GetSaveInfo( nil ) + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Operator_Convert() As FolderItem + return me.Resolve + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Operator_Convert(f As FolderItem) + if f IsA FolderItem then me.Constructor( f ) + + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function OriginalExists() As Boolean + dim f as FolderItem = me.Resolve + if f is nil then + return false + else + return f.Exists + end if + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Resolve() As FolderItem + if zSaveInfo.LenB = 0 then + return nil + else + dim f as new FolderItem( zSaveInfo ) + return f + end if + + End Function + #tag EndMethod + + + #tag Note, Name = Legal + This class was created by Kem Tekinay, MacTechnologies Consulting (ktekinay@mactechnologies.com). + It is copyright ©2012, all rights reserved. + + You may use this class AS IS at your own risk for any purpose. The author does not warrant its use + for any particular purpose and disavows any responsibility for bad design, poor execution, + or any other faults. + + The author does not actively support this class, although comments and recommendations + are welcome. + + You may modify code in this class as long as those modifications are clearly indicated + via comments in the source code. + + You may distribute this class, modified or unmodified, as long as any modifications + are clearly indicated, as noted above, and this copyright notice is not disturbed or removed. + + If you do make useful modifications, please let me know so I can include them in + future versions. + #tag EndNote + + + #tag Property, Flags = &h21 + Private zSaveInfo As String + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Admin App/WndAdmin.xojo_window b/Kaju Admin App/WndAdmin.xojo_window new file mode 100644 index 0000000..25d5db3 --- /dev/null +++ b/Kaju Admin App/WndAdmin.xojo_window @@ -0,0 +1,3468 @@ +#tag Window +Begin Window WndAdmin + BackColor = &cFFFFFF00 + Backdrop = 0 + CloseButton = True + Compatibility = "" + Composite = True + Frame = 0 + FullScreen = False + FullScreenButton= False + HasBackColor = False + Height = 680 + ImplicitInstance= True + LiveResize = True + MacProcID = 0 + MaxHeight = 32000 + MaximizeButton = True + MaxWidth = 32000 + MenuBar = 1253638004 + MenuBarVisible = True + MinHeight = 680 + MinimizeButton = True + MinWidth = 932 + Placement = 0 + Resizeable = True + Title = "Untitled" + Visible = True + Width = 932 + Begin Listbox lbVersions + AutoDeactivate = True + AutoHideScrollbars= True + Bold = False + Border = True + ColumnCount = 1 + ColumnsResizable= False + ColumnWidths = "" + DataField = "" + DataSource = "" + DefaultRowHeight= -1 + Enabled = True + EnableDrag = False + EnableDragReorder= False + GridLinesHorizontal= 0 + GridLinesVertical= 0 + HasHeading = False + HeadingIndex = -1 + Height = 614 + HelpTag = "" + Hierarchical = False + Index = -2147483648 + InitialParent = "" + InitialValue = "" + Italic = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + RequiresSelection= False + Scope = 2 + ScrollbarHorizontal= False + ScrollBarVertical= True + SelectionType = 0 + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 14 + Underline = False + UseFocusRing = True + Visible = True + Width = 203 + _ScrollOffset = 0 + _ScrollWidth = -1 + End + Begin PushButton btnNew + AutoDeactivate = True + Bold = True + ButtonStyle = "6" + Cancel = False + Caption = "+" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Scope = 2 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 640 + Underline = False + Visible = True + Width = 20 + End + Begin PushButton btnDelete + AutoDeactivate = True + Bold = True + ButtonStyle = "6" + Cancel = False + Caption = "-" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 39 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Scope = 2 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 640 + Underline = False + Visible = True + Width = 20 + End + Begin TextFieldChanger fldVersion + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "Version" + DataSource = "" + Enabled = False + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + Italic = False + Left = 363 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 52 + Underline = False + UseFocusRing = True + Visible = True + Width = 80 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 0 + InitialParent = "" + Italic = False + Left = 251 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 12 + TabPanelIndex = 0 + Text = "Version:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 53 + Transparent = False + Underline = False + Visible = True + Width = 100 + End + Begin TabPanel TabPanel1 + AutoDeactivate = True + Bold = False + Enabled = True + Height = 542 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 251 + LockBottom = True + LockedInPosition= True + LockLeft = True + LockRight = True + LockTop = True + Panels = "" + Scope = 2 + SmallTabs = False + TabDefinition = "Release Notes\rBinaries\rImage" + TabIndex = 14 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 86 + Underline = False + Value = 1 + Visible = True + Width = 653 + Begin TextAreaChanger fldReleaseNotes + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= True + BackColor = &cFFFFFF00 + Bold = False + Border = True + DataField = "ReleaseNotes" + DataSource = "" + Enabled = False + Format = "" + Height = 210 + HelpTag = "" + HideSelection = True + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LimitText = 0 + LineHeight = 0.0 + LineSpacing = 1.0 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Multiline = True + ReadOnly = False + Scope = 2 + ScrollbarHorizontal= False + ScrollbarVertical= True + Styled = False + TabIndex = 0 + TabPanelIndex = 1 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 160 + Underline = False + UseFocusRing = True + Visible = True + Width = 613 + End + Begin HTMLViewer hvReleaseNotesPreview + AutoDeactivate = True + Enabled = True + Height = 210 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Left = 271 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = False + Renderer = 0 + Scope = 2 + TabIndex = 1 + TabPanelIndex = 1 + TabStop = True + Top = 408 + Visible = True + Width = 613 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 4 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 2 + TabPanelIndex = 1 + Text = "Preview:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 383 + Transparent = False + Underline = False + Visible = True + Width = 100 + End + Begin CheckBoxChanger cbMacBinary + AutoDeactivate = True + Bold = False + Caption = "Mac" + DataField = "" + DataSource = "" + Enabled = False + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + State = 0 + TabIndex = 0 + TabPanelIndex = 2 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 191 + Underline = False + Value = False + Visible = True + Width = 100 + End + Begin TextFieldChanger fldMacBinaryURL + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 2 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 257 + Underline = False + UseFocusRing = True + Visible = False + Width = 470 + End + Begin Label lblMacBinaryHash + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 3 + TabPanelIndex = 2 + Text = "Hash:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 224 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin Label lblMacBinaryURL + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 4 + TabPanelIndex = 2 + Text = "URL:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 258 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 7 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 3 + TabPanelIndex = 1 + Text = "Release Notes (HTML):" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 138 + Transparent = False + Underline = False + Visible = True + Width = 142 + End + Begin CheckBoxChanger cbWindowsBinary + AutoDeactivate = True + Bold = False + Caption = "Windows" + DataField = "" + DataSource = "" + Enabled = False + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + State = 0 + TabIndex = 5 + TabPanelIndex = 2 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 290 + Underline = False + Value = False + Visible = True + Width = 100 + End + Begin TextFieldChanger fldWindowsBinaryURL + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 7 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 390 + Underline = False + UseFocusRing = True + Visible = False + Width = 470 + End + Begin Label lblWindowsBinaryHash + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 8 + TabPanelIndex = 2 + Text = "Hash:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 357 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin Label lblWindowsBinaryURL + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 9 + TabPanelIndex = 2 + Text = "URL:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 391 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin CheckBoxChanger cbLinuxBinary + AutoDeactivate = True + Bold = False + Caption = "Linux" + DataField = "" + DataSource = "" + Enabled = False + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + State = 0 + TabIndex = 10 + TabPanelIndex = 2 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 438 + Underline = False + Value = False + Visible = True + Width = 100 + End + Begin Label lblLinuxBinaryHash + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 13 + TabPanelIndex = 2 + Text = "Hash:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 504 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin Label lblLinuxBinaryURL + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 14 + TabPanelIndex = 2 + Text = "URL:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 538 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin TextFieldChanger fldImageURL + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "ImageURL" + DataSource = "" + Enabled = False + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 350 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 0 + TabPanelIndex = 3 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 161 + Underline = False + UseFocusRing = True + Visible = True + Width = 470 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 8 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 1 + TabPanelIndex = 3 + Text = "URL:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 161 + Transparent = False + Underline = False + Visible = True + Width = 67 + End + Begin CheckBoxChanger cbImageUseTransparency + AutoDeactivate = True + Bold = False + Caption = "Use Transparency" + DataField = "UseTransparency" + DataSource = "" + Enabled = False + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 350 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + State = 0 + TabIndex = 2 + TabPanelIndex = 3 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 129 + Underline = False + Value = False + Visible = True + Width = 180 + End + Begin HTMLViewer hvImagePreview + AutoDeactivate = True + Enabled = True + Height = 384 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Left = 271 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Renderer = 0 + Scope = 2 + TabIndex = 3 + TabPanelIndex = 3 + TabStop = True + Top = 224 + Visible = True + Width = 613 + End + Begin TextFieldChanger fldMinRequiredVersion + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "MinimumRequiriedVersion" + DataSource = "" + Enabled = False + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 483 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 15 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 147 + Underline = False + UseFocusRing = True + Visible = True + Width = 80 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 2 + InitialParent = "TabPanel1" + Italic = False + Left = 271 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 16 + TabPanelIndex = 2 + Text = "Minimum Required Version:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 148 + Transparent = False + Underline = False + Visible = True + Width = 200 + End + Begin BevelButton btnStyle + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = True + ButtonType = 0 + Caption = "B" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "Bold" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = 0 + InitialParent = "TabPanel1" + Italic = False + Left = 802 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 4 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = False + Value = False + Visible = True + Width = 26 + End + Begin BevelButton btnStyle + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "I" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "Italic" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = 1 + InitialParent = "TabPanel1" + Italic = True + Left = 830 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 5 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = False + Value = False + Visible = True + Width = 26 + End + Begin BevelButton btnStyle + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "U" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "Underline" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = 2 + InitialParent = "TabPanel1" + Italic = False + Left = 858 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 6 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = True + Value = False + Visible = True + Width = 26 + End + Begin BevelButton btnBreak + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "
" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "Insert break" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 601 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 7 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = False + Value = False + Visible = True + Width = 50 + End + Begin BevelButton btnStyle + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "p" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "Create a paragraph tag" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = 3 + InitialParent = "TabPanel1" + Italic = False + Left = 653 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 8 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = False + Value = False + Visible = True + Width = 26 + End + Begin TextFieldChanger fldMacBinaryHash + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "Drop the Mac Zip file here to calculate the hash" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = True + Scope = 0 + TabIndex = 1 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 223 + Underline = False + UseFocusRing = True + Visible = False + Width = 350 + End + Begin TextFieldChanger fldWindowsBinaryHash + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "Drop the Windows Zip file here to calculate the hash" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = True + Scope = 2 + TabIndex = 6 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 356 + Underline = False + UseFocusRing = True + Visible = False + Width = 350 + End + Begin TextFieldChanger fldLinuxBinaryURL + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 12 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 537 + Underline = False + UseFocusRing = True + Visible = False + Width = 470 + End + Begin TextFieldChanger fldWindowsExecutable + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 17 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 322 + Underline = False + UseFocusRing = True + Visible = False + Width = 470 + End + Begin Label lblWindowsExecutable + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 18 + TabPanelIndex = 2 + Text = "Executable:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 323 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin TextFieldChanger fldLinuxExecutable + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 19 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 470 + Underline = False + UseFocusRing = True + Visible = False + Width = 470 + End + Begin Label lblLinuxExecutable + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 305 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 20 + TabPanelIndex = 2 + Text = "Executable:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 471 + Transparent = False + Underline = False + Visible = False + Width = 67 + End + Begin TextFieldChanger fldLinuxBinaryHash + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "Drop the Linux Zip file here to calculate the hash" + DataField = "" + DataSource = "" + Enabled = True + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 414 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = True + LockTop = True + Mask = "" + Password = False + ReadOnly = True + Scope = 2 + TabIndex = 11 + TabPanelIndex = 2 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 503 + Underline = False + UseFocusRing = True + Visible = False + Width = 350 + End + Begin BevelButton btnList + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "ul" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "Unordered list" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = 0 + InitialParent = "TabPanel1" + Italic = False + Left = 699 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 9 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = False + Value = False + Visible = True + Width = 26 + End + Begin BevelButton btnList + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "ol" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "Ordered list" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = 1 + InitialParent = "TabPanel1" + Italic = False + Left = 727 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 10 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = False + Value = False + Visible = True + Width = 26 + End + Begin BevelButton btnStyle + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "li" + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 0 + Height = 22 + HelpTag = "List item" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = 6 + InitialParent = "TabPanel1" + Italic = False + Left = 755 + LockBottom = False + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = True + MenuValue = 0 + Scope = 0 + TabIndex = 11 + TabPanelIndex = 1 + TabStop = True + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 136 + Underline = False + Value = False + Visible = True + Width = 26 + End + Begin PushButton btnMacHashFromURL + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "From URL" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 804 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 21 + TabPanelIndex = 2 + TabStop = True + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 223 + Underline = False + Visible = False + Width = 80 + End + Begin PushButton btnWindowsHashFromURL + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "From URL" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 804 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 22 + TabPanelIndex = 2 + TabStop = True + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 356 + Underline = False + Visible = False + Width = 80 + End + Begin PushButton btnLinuxHashFromURL + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "From URL" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "TabPanel1" + Italic = False + Left = 804 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 23 + TabPanelIndex = 2 + TabStop = True + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 503 + Underline = False + Visible = False + Width = 80 + End + End + Begin TextFieldChanger fldAppName + AcceptTabs = False + Alignment = 0 + AutoDeactivate = True + AutomaticallyCheckSpelling= False + BackColor = &cFFFFFF00 + Bold = False + Border = True + CueText = "" + DataField = "AppName" + DataSource = "" + Enabled = False + Format = "" + Height = 22 + HelpTag = "" + Index = -2147483648 + Italic = False + Left = 363 + LimitText = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Mask = "" + Password = False + ReadOnly = False + Scope = 2 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + Text = "" + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 18 + Underline = False + UseFocusRing = True + Visible = True + Width = 229 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 3 + InitialParent = "" + Italic = False + Left = 251 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 11 + TabPanelIndex = 0 + Text = "App Name:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 19 + Transparent = False + Underline = False + Visible = True + Width = 100 + End + Begin CheckBoxChanger cbRequiresPayment + AutoDeactivate = True + Bold = False + Caption = "Requires Payment" + DataField = "RequiresPayment" + DataSource = "" + Enabled = False + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 693 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + State = 0 + TabIndex = 5 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 19 + Underline = False + Value = False + Visible = True + Width = 180 + End + Begin Timer tmrUpdateReleaseNotesPreview + Height = 32 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockedInPosition= False + Mode = 0 + Period = 750 + Scope = 2 + TabPanelIndex = 0 + Top = 0 + Width = 32 + End + Begin HTMLViewer hvNewWindow + AutoDeactivate = True + Enabled = False + Height = 200 + HelpTag = "" + Index = -2147483648 + Left = -371 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Renderer = 0 + Scope = 2 + TabIndex = 7 + TabPanelIndex = 0 + TabStop = True + Top = 156 + Visible = False + Width = 300 + End + Begin PushButton btnCopyPublicKey + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "Copy RSA Public Key" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 251 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Scope = 2 + TabIndex = 8 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 640 + Underline = False + Visible = True + Width = 186 + End + Begin Timer tmrUpdateImagePreview + Height = 32 + Index = -2147483648 + InitialParent = "" + Left = 20 + LockedInPosition= False + Mode = 0 + Period = 750 + Scope = 2 + TabPanelIndex = 0 + Top = 20 + Width = 32 + End + Begin Label lblPlatform + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 577 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 0 + Selectable = False + TabIndex = 6 + TabPanelIndex = 0 + Text = "None" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 53 + Transparent = False + Underline = False + Visible = True + Width = 327 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 9 + InitialParent = "" + Italic = False + Left = 492 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 13 + TabPanelIndex = 0 + Text = "Platforms:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 53 + Transparent = False + Underline = False + Visible = True + Width = 100 + End + Begin PushButton btnExport + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "Export..." + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 819 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + Scope = 2 + TabIndex = 10 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 639 + Underline = False + Visible = True + Width = 85 + End + Begin PushButton btnDuplicate + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "Dupe" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 158 + LockBottom = True + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = False + Scope = 2 + TabIndex = 15 + TabPanelIndex = 0 + TabStop = True + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 641 + Underline = False + Visible = True + Width = 65 + End + Begin BevelButton btnPreview + AcceptFocus = True + AutoDeactivate = True + BackColor = &c00000000 + Bevel = 0 + Bold = False + ButtonType = 0 + Caption = "Preview..." + CaptionAlign = 3 + CaptionDelta = 0 + CaptionPlacement= 1 + Enabled = True + HasBackColor = False + HasMenu = 1 + Height = 22 + HelpTag = "" + Icon = 0 + IconAlign = 0 + IconDX = 0 + IconDY = 0 + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 684 + LockBottom = True + LockedInPosition= False + LockLeft = False + LockRight = True + LockTop = False + MenuValue = -1 + Scope = 0 + TabIndex = 16 + TabPanelIndex = 0 + TabStop = True + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 639 + Underline = False + Value = False + Visible = True + Width = 113 + End +End +#tag EndWindow + +#tag WindowCode + #tag Event + Sub Activate() + UpdateWindowTitle + End Sub + #tag EndEvent + + #tag Event + Function CancelClose(appQuitting as Boolean) As Boolean + dim r as Boolean = false + + if self.ContentsChanged then + dim dlg as new MessageDialog + dlg.Message = "This document has been modified. Save before closing?" + dlg.ActionButton.Caption = "&Save" + dlg.ActionButton.Visible = true + dlg.CancelButton.Visible = true + dlg.AlternateActionButton.Caption = "&Don't Save" + dlg.AlternateActionButton.Visible = true + + dim btn as MessageDialogButton = dlg.ShowModalWithin( self ) + if btn is dlg.CancelButton then + r = true + elseif btn = dlg.AlternateActionButton then + r = false + else + r = not( DoSave() ) + end if + end if + + return r + + #pragma unused appQuitting + End Function + #tag EndEvent + + #tag Event + Sub Open() + AdjustControls() + End Sub + #tag EndEvent + + + #tag MenuHandler + Function FileClose() As Boolean Handles FileClose.Action + self.Close + Return True + + End Function + #tag EndMenuHandler + + #tag MenuHandler + Function FileSave() As Boolean Handles FileSave.Action + call DoSave() + Return True + + End Function + #tag EndMenuHandler + + #tag MenuHandler + Function FileSaveAs() As Boolean Handles FileSaveAs.Action + call DoSaveAs() + Return True + + End Function + #tag EndMenuHandler + + + #tag Method, Flags = &h21 + Private Sub AdjustControls() + dim trueValue as boolean = lbVersions.ListIndex <> -1 + + dim lastIndex as integer = ControlCount - 1 + for i as integer = 0 to lastIndex + + dim c as Control = self.Control( i ) + dim doIt as boolean + if ControlDataField( c ) <> "" then + doIt = true + elseif c IsA btnStyle then + doIt = true + elseif c IsA btnList then + doIt = true + end if + + if doIt then + RectControl( c ).Enabled = trueValue + end if + next i + + // + // Adjust the outliers that aren't directly set + // + + cbMacBinary.Enabled = trueValue + cbWindowsBinary.Enabled = trueValue + cbLinuxBinary.Enabled = trueValue + + btnDelete.Enabled = trueValue + btnDuplicate.Enabled = trueValue + + btnBreak.Enabled = trueValue + + // + // GetFromHash buttons + // + if cbMacBinary.Enabled and cbMacBinary.Value and fldMacBinaryURL.Text.Trim <> "" then + btnMacHashFromURL.Enabled = trueValue + else + btnMacHashFromURL.Enabled = false + end if + + if cbWindowsBinary.Enabled and cbWindowsBinary.Value and fldWindowsBinaryURL.Text.Trim <> "" then + btnWindowsHashFromURL.Enabled = trueValue + else + btnWindowsHashFromURL.Enabled = false + end if + + if cbLinuxBinary.Enabled and cbLinuxBinary.Value and fldLinuxBinaryURL.Text.Trim <> "" then + btnLinuxHashFromURL.Enabled = trueValue + else + btnWindowsHashFromURL.Enabled = false + end if + + // + // Set the platform summary + // + + if not trueValue then + + lblPlatform.Text = "" + + else + + Redim Platforms( -1 ) + if cbMacBinary.Enabled and cbMacBinary.Value then + Platforms.Append "Mac" + end if + + if cbWindowsBinary.Enabled and cbWindowsBinary.Value then + Platforms.Append "Windows" + end if + + if cbLinuxBinary.Enabled and cbLinuxBinary.Value then + Platforms.Append "Linux" + end if + + if Platforms.Ubound = -1 then + lblPlatform.Italic = true + lblPlatform.Text = "No platforms selected" + else + lblPlatform.Italic = false + lblPlatform.Text = join( Platforms, ", " ) + end if + + end if + + UpdateWindowTitle + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ApplyStyle(tag As String, insertEOL As Boolean = False) + dim eol as string = EndOfLine + eol = if( insertEOL, eol, "" ) + + tag = tag.Lowercase + + dim openTag as string = "<" + tag + ">" + eol + dim closeTag as string = eol + "" + + dim s as string = fldReleaseNotes.SelText + dim selStart as integer = fldReleaseNotes.SelStart + + fldReleaseNotes.SelText = openTag + s + closeTag + if s = "" then // No selection so position the cursor + fldReleaseNotes.SelStart = selStart + openTag.Len + end if + + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ClearFields() + dim savedDirty as boolean = self.ContentsChanged + + self.Loading = true + + dim lastIndex as integer = ControlCount - 1 + for i as integer = 0 to lastIndex + dim c as Control = self.Control( i ) + select case c + case IsA TextEdit + case IsA CheckBox + else + continue for i + end + + ControlValue( c ) = nil + next i + + hvReleaseNotesPreview.LoadPage( "", nil ) + hvImagePreview.LoadPage( "", nil ) + + self.Loading = false + + AdjustControls() + self.ContentsChanged = savedDirty + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ControlDataField(c As Control) As String + // Returns the DataField field for a control + + dim value as string + + select case c + case IsA TextEdit + dim fld as TextEdit = TextEdit( c ) + value = fld.DataField + + case IsA CheckBox + dim cb as CheckBox = CheckBox( c ) + value = cb.DataField + + end select + + return value + + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ControlValue(c As Control) As Variant + dim r as Variant + + select case c + case IsA TextEdit + dim fld as TextEdit = TextEdit( c ) + r = fld.Text + + case IsA CheckBox + dim cb as CheckBox = CheckBox( c ) + r = cb.Value + + end select + + return r + + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ControlValue(c As Control, Assigns value As Variant) + select case c + case IsA TextEdit + dim fld as TextEdit = TextEdit( c ) + dim sValue as string = value.StringValue.DefineEncoding( Encodings.UTF8 ) + fld.Text = sValue + + case IsA CheckBox + dim cb as CheckBox = CheckBox( c ) + cb.Value = value.BooleanValue + + else + raise new KeyNotFoundException + + end + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub CreateRSAKeys() + if RSAPrivateKey = "" then + if not Crypto.RSAGenerateKeyPair( 2048, RSAPrivateKey, RSAPublicKey ) then + MsgBox "Could not create RSA key pairs." + self.Close + else + self.ContentsChanged = true + end if + end if + + return + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DeleteVersion() + dim curIndex as integer = lbVersions.ListIndex + if curIndex = -1 then + return + end if + + lbVersions.ListIndex = -1 + lbVersions.RemoveRow( curIndex ) + + if curIndex > 0 then + curIndex = curIndex - 1 + end if + + if curIndex < lbVersions.ListCount then + lbVersions.ListIndex = curIndex + end if + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function DoSave() As Boolean + dim f as FolderItem = Document + if f is nil then + return DoSaveAs() + end if + + dim r as boolean + dim savedContentsChanged as boolean = self.ContentsChanged + + dim master as new JSONItem + master.Compact = false + + master.Value( kPrivateKeyName ) = RSAPrivateKey + master.Value( kPublicKeyName ) = RSAPublicKey + + dim data as JSONItem = KajuJSON + master.Value( kDataName ) = data + + dim toSave as string = master.ToString + + dim bs as BinaryStream = BinaryStream.Create( f, true ) + bs.Write( toSave ) + bs.Close + + dim tis as TextInputStream = TextInputStream.Open( f ) + dim compare as string = tis.ReadAll + tis.Close + + r = StrComp( toSave, compare, 0 ) = 0 + self.ContentsChanged = not r + + if not r then + MsgBox "Save failed!" + end if + + return r + + Exception err as RuntimeException + self.ContentsChanged = savedContentsChanged + return false + + Finally + UpdateWindowTitle + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function DoSaveAs() As Boolean + dim ext as string = FileTypes1.KajuDocument.Extensions + + dim dlg as new SaveAsDialog + dlg.PromptText = "Save the Kaju document as:" + dlg.Filter = FileTypes1.KajuDocument + dlg.SuggestedFileName = "New Version Line" + ext + + dim f as FolderItem = dlg.ShowModalWithin( self ) + + dim r as boolean + if f is nil then + r = false + + elseif f.Directory then + MsgBox "You chose a folder." + r = false + + else + // + // Adjust the name if needed + // + if not f.Exists then + if f.Name.Right( ext.Len ) <> ext then + f.Name = f.Name + ext + end if + end if + + Document = f + r = DoSave() + end if + + UpdateWindowTitle + return r + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DuplicateVersion() + dim curIndex as integer = lbVersions.ListIndex + if curIndex = -1 then + return + end if + + StoreFieldsToVersionRow() + + dim tag as Variant = lbVersions.RowTag( curIndex ) + dim listing as string = lbVersions.Cell( curIndex, 0 ) + dim newIndex as integer = curIndex + 1 + lbVersions.InsertRow( newIndex, listing ) + lbVersions.RowTag( newIndex ) = tag + lbVersions.ListIndex = newIndex + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub HashFromURL(url As String, hashField As TextField) + url = url.Trim + + if url = "" then + return + end if + + hashField.Text = "" + + dim http as new Kaju.HTTPSSocket + url = http.GetRedirectAddress( url, 5 ) + + dim file as FolderItem = GetTemporaryFolderItem + dim r as boolean = http.Get( url, file, 5 ) + if not r or http.LastErrorCode <> 0 then + + MsgBox "Could not get the executable from that url: " + str( http.LastErrorCode ) + + else + + dim hash as string = Kaju.HashOfFile( file ) + hashField.Text = hash + + end if + + Exception err As RuntimeException + MsgBox err.Message + + Finally + if file <> nil and file.Exists then + file.Delete + end if + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function IsDataValid() As Boolean + StoreFieldsToVersionRow() + + dim r as boolean = true // Assume it's fine + + if lbVersions.ListCount = 0 then + return r + end if + + dim msg as string + dim lastRow as integer = lbVersions.ListCount - 1 + for row as integer = 0 to lastRow + dim j as JSONItem = lbVersions.RowTag( row ) + dim u as new Kaju.UpdateInformation( j ) + if not u.IsValid then + r = false + msg = u.InvalidReason + lbVersions.ListIndex = row + exit for row + end if + next + + if not r then + MsgBox msg + end if + + return r + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub JSONToFields(data As JSONItem) + // + // Handle the named controls first + // + + dim savedDirty as boolean = self.ContentsChanged + + ClearFields() + + dim lastIndex as integer = ControlCount - 1 + for i as integer = 0 to lastIndex + dim c as Control = self.Control( i ) + + dim fieldName as string = ControlDataField( c ) + if fieldName <> "" then + ControlValue( c ) = data.Lookup( fieldName, nil ) + end if + + next + + // + // Binaries + // + + if data.HasName( Kaju.UpdateInformation.kMacBinaryName ) then + cbMacBinary.Value = true + dim binary as new Kaju.BinaryInformation( false, data.Value( Kaju.UpdateInformation.kMacBinaryName ) ) + fldMacBinaryHash.Text = binary.Hash + fldMacBinaryURL.Text = binary.URL + end if + + if data.HasName( Kaju.UpdateInformation.kWindowsBinaryName ) then + cbWindowsBinary.Value = true + dim binary as new Kaju.BinaryInformation( true, data.Value( Kaju.UpdateInformation.kWindowsBinaryName ) ) + fldWindowsExecutable.Text = binary.ExecutableName + fldWindowsBinaryHash.Text = binary.Hash + fldWindowsBinaryURL.Text = binary.URL + end if + + if data.HasName( Kaju.UpdateInformation.kLinuxBinaryName ) then + cbLinuxBinary.Value = true + dim binary as new Kaju.BinaryInformation( true, data.Value( Kaju.UpdateInformation.kLinuxBinaryName ) ) + fldLinuxExecutable.Text = binary.ExecutableName + fldLinuxBinaryHash.Text = binary.Hash + fldLinuxBinaryURL.Text = binary.URL + end if + + AdjustControls() + self.ContentsChanged = savedDirty + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub NewVersion() + StoreFieldsToVersionRow() + + CreateRSAKeys() + + lbVersions.AddRow "1.0.0d1" + + dim j as new JSONItem() + j.Value( "Version" ) = lbVersions.Cell( lbVersions.LastIndex, 0 ) + + dim prevIndex as integer = LastVersionRow + if prevIndex <> -1 and prevIndex < lbVersions.ListCount then + dim prevItem as JSONItem = lbVersions.RowTag( prevIndex ) + if prevItem <> nil then + dim fieldName as string = fldAppName.DataField + j.Value( fieldName ) = prevItem.Lookup( fieldName, "" ) + end if + end if + + lbVersions.RowTag( lbVersions.LastIndex ) = j + lbVersions.ListIndex = lbVersions.LastIndex + self.ContentsChanged = true + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub OpenDocument(f As FolderItem) + if f is nil or not f.Exists or f.Directory then + raise new NilObjectException + end if + + dim tis as TextInputStream = TextInputStream.Open( f ) + tis.Encoding = Encodings.UTF8 + + dim dataString as string = tis.ReadAll + tis = nil + + dataString = ReplaceLineEndings( dataString, EndOfLine ) + + dim master as new JSONItem( dataString ) + RSAPrivateKey = master.Value( kPrivateKeyName ) + RSAPublicKey = master.Value( kPublicKeyName ) + + dim data as JSONItem = master.Value( kDataName ) + dim lastIndex as integer = data.Count - 1 + for i as integer = 0 to lastIndex + dim version as JSONItem = data( i ) + lbVersions.AddRow version.Value( "Version" ).StringValue + lbVersions.RowTag( lbVersions.LastIndex ) = version + next i + + if lbVersions.ListCount <> 0 then + lbVersions.ListIndex = 0 + end if + + Document = f + self.ContentsChanged = false + + AdjustControls + + Exception err as RuntimeException + MsgBox "Could not open document." + + self.Close + return + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub StoreFieldsToVersionRow() + if LastVersionRow = -1 or LastVersionRow >= lbVersions.ListCount then + return + end if + + dim j as new JSONItem( "{}" ) + + // + // Gather the textfield data first + // + + dim lastIndex as integer = ControlCount - 1 + for i as integer = 0 to lastIndex + dim c as Control = self.Control( i ) + + dim fieldName as string = ControlDataField( c ) + if fieldName <> "" then + dim value as Variant = ControlValue( c ) + j.Value( fieldName ) = value + end if + + next + + // + // Binaries + // + + if cbMacBinary.Value then + dim binary as new Kaju.BinaryInformation( false ) + binary.Hash = fldMacBinaryHash.Text.Trim + binary.URL = fldMacBinaryURL.Text.Trim + + j.Value( Kaju.UpdateInformation.kMacBinaryName ) = binary.ToJSON + end if + + if cbWindowsBinary.Value then + dim binary as new Kaju.BinaryInformation( true ) + binary.ExecutableName = fldWindowsExecutable.Text.Trim + binary.Hash = fldWindowsBinaryHash.Text.Trim + binary.URL = fldWindowsBinaryURL.Text.Trim + + j.Value( Kaju.UpdateInformation.kWindowsBinaryName ) = binary.ToJSON + end if + + if cbLinuxBinary.Value then + dim binary as new Kaju.BinaryInformation( true ) + binary.ExecutableName = fldLinuxExecutable.Text.Trim + binary.Hash = fldLinuxBinaryHash.Text.Trim + binary.URL = fldLinuxBinaryURL.Text.Trim + + j.Value( Kaju.UpdateInformation.kLinuxBinaryName ) = binary.ToJSON + end if + + lbVersions.RowTag( LastVersionRow ) = j + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub UpdateWindowTitle() + dim f as FolderItem = Document + if f is nil then + self.Title = "Untitled" + else + dim t as string = f.Name + dim ext as string = FileTypes1.KajuDocument.Extensions + + if t.Right( ext.Len ) = ext then + t = t.Left( t.Len - ext.Len ) + end if + + self.Title = t + end if + + End Sub + #tag EndMethod + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + if mDocument is nil then + return nil + else + return mDocument.Resolve + end if + End Get + #tag EndGetter + #tag Setter + Set + mDocument = value + End Set + #tag EndSetter + Document As FolderItem + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h21 + #tag Getter + Get + StoreFieldsToVersionRow() + + dim combined as new JSONItem( "[]" ) + combined.Compact = false + + dim lastIndex as integer = lbVersions.ListCount - 1 + for row as integer = 0 to lastIndex + dim j as JSONItem = lbVersions.RowTag( row ) + if j <> nil then + combined.Append j + end if + next + + return combined + + End Get + #tag EndGetter + Private KajuJSON As JSONItem + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private LastVersionRow As Integer = -1 + #tag EndProperty + + #tag Property, Flags = &h21 + Private Loading As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mDocument As FolderItemAlias + #tag EndProperty + + #tag Property, Flags = &h21 + Private Platforms() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private RSAPrivateKey As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private RSAPublicKey As String + #tag EndProperty + + + #tag Constant, Name = kDataName, Type = String, Dynamic = False, Default = \"KajuData", Scope = Private + #tag EndConstant + + #tag Constant, Name = kPrivateKeyName, Type = String, Dynamic = False, Default = \"PrivateKey", Scope = Private + #tag EndConstant + + #tag Constant, Name = kPublicKeyName, Type = String, Dynamic = False, Default = \"PublicKey", Scope = Private + #tag EndConstant + + +#tag EndWindowCode + +#tag Events lbVersions + #tag Event + Sub Change() + StoreFieldsToVersionRow + + dim row as integer = me.ListIndex + if row <> -1 and me.RowTag( row ) <> nil then + JSONToFields( me.RowTag( row ) ) + else + ClearFields + end if + + LastVersionRow = row + + AdjustControls() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnNew + #tag Event + Sub Action() + NewVersion + + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnDelete + #tag Event + Sub Action() + DeleteVersion + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldVersion + #tag Event + Sub TextChange() + if lbVersions.ListIndex = -1 then + return + end if + + dim v as string = me.Text.Trim + if v = "" then + v = "(no version)" + end if + + lbVersions.Cell( lbVersions.ListIndex, 0 ) = v + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldReleaseNotes + #tag Event + Sub TextChange() + tmrUpdateReleaseNotesPreview.Mode = Timer.ModeSingle + tmrUpdateReleaseNotesPreview.Reset + End Sub + #tag EndEvent +#tag EndEvents +#tag Events hvReleaseNotesPreview + #tag Event + Function NewWindow() As HTMLViewer + return hvNewWindow + End Function + #tag EndEvent + #tag Event + Function CancelLoad(URL as String) As Boolean + return ( not self.Loading ) + + #pragma unused URL + End Function + #tag EndEvent +#tag EndEvents +#tag Events cbMacBinary + #tag Event + Sub Action() + fldMacBinaryHash.Visible = me.Value + fldMacBinaryURL.Visible = me.Value + lblMacBinaryHash.Visible = me.Value + lblMacBinaryURL.Visible = me.Value + btnMacHashFromURL.Visible = me.Value + + AdjustControls() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldMacBinaryURL + #tag Event + Sub TextChange() + btnMacHashFromURL.Enabled = me.Text.Trim <> "" + End Sub + #tag EndEvent +#tag EndEvents +#tag Events cbWindowsBinary + #tag Event + Sub Action() + fldWindowsExecutable.Visible = me.Value + fldWindowsBinaryHash.Visible = me.Value + fldWindowsBinaryURL.Visible = me.Value + lblWindowsExecutable.Visible = me.Value + lblWindowsBinaryHash.Visible = me.Value + lblWindowsBinaryURL.Visible = me.Value + btnWindowsHashFromURL.Visible = me.Value + + AdjustControls() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldWindowsBinaryURL + #tag Event + Sub TextChange() + btnWindowsHashFromURL.Enabled = me.Text.Trim <> "" + End Sub + #tag EndEvent +#tag EndEvents +#tag Events cbLinuxBinary + #tag Event + Sub Action() + fldLinuxExecutable.Visible = me.Value + fldLinuxBinaryHash.Visible = me.Value + fldLinuxBinaryURL.Visible = me.Value + lblLinuxExecutable.Visible = me.Value + lblLinuxBinaryHash.Visible = me.Value + lblLinuxBinaryURL.Visible = me.Value + btnLinuxHashFromURL.Visible = me.Value + + AdjustControls() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldImageURL + #tag Event + Sub TextChange() + tmrUpdateImagePreview.Mode = Timer.ModeSingle + tmrUpdateImagePreview.Reset + End Sub + #tag EndEvent +#tag EndEvents +#tag Events hvImagePreview + #tag Event + Function NewWindow() As HTMLViewer + return hvNewWindow + End Function + #tag EndEvent + #tag Event + Function CancelLoad(URL as String) As Boolean + return ( not self.Loading ) + + #pragma unused URL + End Function + #tag EndEvent +#tag EndEvents +#tag Events btnStyle + #tag Event + Sub Action(index as Integer) + dim tag as string = me.Caption + ApplyStyle( tag ) + + #pragma unused index + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnBreak + #tag Event + Sub Action() + fldReleaseNotes.SelText = "
" + EndOfLine + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldMacBinaryHash + #tag Event + Sub DropObject(obj As DragItem, action As Integer) + if obj.FolderItemAvailable then + dim f as FolderItem = obj.FolderItem + me.Text = Kaju.HashOfFile( f ) + end if + + #pragma unused action + End Sub + #tag EndEvent + #tag Event + Sub Open() + me.AcceptFileDrop( FileTypes1.ApplicationZip ) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldWindowsBinaryHash + #tag Event + Sub Open() + me.AcceptFileDrop( FileTypes1.ApplicationZip ) + End Sub + #tag EndEvent + #tag Event + Sub DropObject(obj As DragItem, action As Integer) + if obj.FolderItemAvailable then + dim f as FolderItem = obj.FolderItem + me.Text = Kaju.HashOfFile( f ) + end if + + #pragma unused action + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldLinuxBinaryURL + #tag Event + Sub TextChange() + btnLinuxHashFromURL.Enabled = me.Text.Trim <> "" + End Sub + #tag EndEvent +#tag EndEvents +#tag Events fldLinuxBinaryHash + #tag Event + Sub Open() + me.AcceptFileDrop( FileTypes1.ApplicationZip ) + End Sub + #tag EndEvent + #tag Event + Sub DropObject(obj As DragItem, action As Integer) + if obj.FolderItemAvailable then + dim f as FolderItem = obj.FolderItem + me.Text = Kaju.HashOfFile( f ) + end if + + #pragma unused action + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnList + #tag Event + Sub Action(index as Integer) + dim tag as string = me.Caption + ApplyStyle( tag, true ) + + #pragma unused index + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnMacHashFromURL + #tag Event + Sub Action() + HashFromURL( fldMacBinaryURL.Text, fldMacBinaryHash ) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnWindowsHashFromURL + #tag Event + Sub Action() + HashFromURL( fldWindowsBinaryURL.Text, fldWindowsBinaryHash ) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnLinuxHashFromURL + #tag Event + Sub Action() + HashFromURL( fldLinuxBinaryURL.Text, fldLinuxBinaryHash ) + End Sub + #tag EndEvent +#tag EndEvents +#tag Events tmrUpdateReleaseNotesPreview + #tag Event + Sub Action() + self.Loading = true + + hvReleaseNotesPreview.LoadPage( ControlValue( fldReleaseNotes ).StringValue, new FolderItem ) + + self.Loading = false + End Sub + #tag EndEvent +#tag EndEvents +#tag Events hvNewWindow + #tag Event + Function CancelLoad(URL as String) As Boolean + ShowURL( URL ) + return true + End Function + #tag EndEvent +#tag EndEvents +#tag Events btnCopyPublicKey + #tag Event + Sub Action() + CreateRSAKeys() + + dim c as new Clipboard + c.Text = RSAPublicKey + c.Close + End Sub + #tag EndEvent +#tag EndEvents +#tag Events tmrUpdateImagePreview + #tag Event + Sub Action() + self.Loading = true + + hvImagePreview.LoadURL( fldImageURL.Text ) + + self.Loading = false + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnExport + #tag Event + Sub Action() + if not IsDataValid() then + return + end if + + dim dlg as new SaveAsDialog + dlg.PromptText = "Export the file that will be served to your app through your web site:" + dlg.Filter = FileTypes1.TextHtml + dlg.ActionButtonCaption = "Export" + dlg.SuggestedFileName = "UpdateInformation.html" + + dim f as FolderItem = dlg.ShowModalWithin( self ) + if f is nil then + return + end if + + dim data as JSONItem = KajuJSON + data.Compact = false + data.EscapeSlashes = false + dim dataString as string = data.ToString + + dim sig as string = Crypto.RSASign( dataString, RSAPrivateKey ) + sig = EncodeHex( sig ) + + dataString = Kaju.kUpdatePacketMarker + sig + EndOfLine.UNIX + dataString + + dim tos as TextOutputStream = TextOutputStream.Create( f ) + tos.Write dataString + tos = nil + + Exception err As RuntimeException + MsgBox "Could not export data." + + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnDuplicate + #tag Event + Sub Action() + DuplicateVersion + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnPreview + #tag Event + Sub Open() + me.AddRow "Development" + me.AddRow "Alpha" + me.AddRow "Beta" + me.AddRow "Final" + + me.MenuValue = -1 + End Sub + #tag EndEvent + #tag Event + Sub Action() + if me.MenuValue = -1 then + return + end if + + dim uc as new Kaju.UpdateChecker( App.PrefFolder ) + uc.AllowedStage = me.MenuValue + dim s as string = KajuJSON.ToString + uc.TestUpdate( s ) + + me.MenuValue = -1 + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="BackColor" + Visible=true + Group="Appearance" + InitialValue="&hFFFFFF" + Type="Color" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Appearance" + Type="Picture" + EditorType="Picture" + #tag EndViewProperty + #tag ViewProperty + Name="CloseButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Composite" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Frame" + Visible=true + Group="Appearance" + InitialValue="0" + Type="Integer" + EditorType="Enum" + #tag EnumValues + "0 - Document" + "1 - Movable Modal" + "2 - Modal Dialog" + "3 - Floating Window" + "4 - Plain Box" + "5 - Shadowed Box" + "6 - Rounded Window" + "7 - Global Floating Window" + "8 - Sheet Window" + "9 - Metal Window" + "10 - Drawer Window" + "11 - Modeless Dialog" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="FullScreen" + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="FullScreenButton" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackColor" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Position" + InitialValue="400" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="ImplicitInstance" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Interfaces" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="LiveResize" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MacProcID" + Visible=true + Group="Appearance" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MaxHeight" + Visible=true + Group="Position" + InitialValue="32000" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MaximizeButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MaxWidth" + Visible=true + Group="Position" + InitialValue="32000" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBar" + Visible=true + Group="Appearance" + Type="MenuBar" + EditorType="MenuBar" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBarVisible" + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MinHeight" + Visible=true + Group="Position" + InitialValue="64" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MinimizeButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MinWidth" + Visible=true + Group="Position" + InitialValue="64" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Placement" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="Enum" + #tag EnumValues + "0 - Default" + "1 - Parent Window" + "2 - Main Screen" + "3 - Parent Window Screen" + "4 - Stagger" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="Resizeable" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Title" + Visible=true + Group="Appearance" + InitialValue="Untitled" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Position" + InitialValue="600" + Type="Integer" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Kaju Classes/Kaju.xojo_code b/Kaju Classes/Kaju.xojo_code new file mode 100644 index 0000000..0b8df96 --- /dev/null +++ b/Kaju Classes/Kaju.xojo_code @@ -0,0 +1,380 @@ +#tag Module +Protected Module Kaju + #tag Method, Flags = &h1 + Protected Function AppVersionString() As String + // Convenience method to return the app's version as a string + + return VersionStringFor( App.MajorVersion, App.MinorVersion, App.BugVersion, App.StageCode, App.NonReleaseVersion ) + + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub CancelUpdate() + if App.UpdateInitiater <> nil then + App.UpdateInitiater.Cancel + App.UpdateInitiater = nil + end if + + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub DeleteDSStoreRecursive(parent As FolderItem) + // Deletes the .DS_Store file wherever it occurs + + if parent is nil or not parent.Directory then + return + end if + + dim folderArr() as FolderItem + folderArr.Append parent + + dim deleteArr() as FolderItem + for i as integer = 0 to folderArr.Ubound + dim thisFolder as FolderItem = folderArr( i ) + dim thisFolderCnt as integer = thisFolder.Count + for fileIndex as integer = 1 to thisFolderCnt + dim subFile as FolderItem = thisFolder.Item( fileIndex ) + if subFile.Directory then + folderArr.Append subFile + elseif subFile.Name = ".DS_Store" then + deleteArr.Append subFile + end if + next + next + + for each f as FolderItem in deleteArr + f.Delete + next + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub DeleteRecursive(f As FolderItem) + if f is nil or not f.Exists or not f.IsWriteable then + return + end if + + if f.Directory then + + dim files() as FolderItem + dim folders() as FolderItem + + dim cnt as integer = f.Count + for i as integer = 1 to cnt + dim thisItem as FolderItem = f.Item( i ) + if thisItem.Directory then + folders.Append thisItem + else + files.Append thisItem + end if + next + + for each fldr as FolderItem in folders + DeleteRecursive( fldr ) + next + + for each file as FolderItem in files + file.Delete + next + + end if + + f.Delete + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function GetTemporaryFolder() As FolderItem + dim f as FolderItem = GetTemporaryFolderItem + f.Delete + f.CreateAsFolder + return f + + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function HashOfFile(f As FolderItem) As String + if f is nil or not f.IsReadable then + return "" + end if + + dim bs as BinaryStream = BinaryStream.Open( f ) + + const kBlock = 1000000 + + dim hasher as new MD5Digest + + while not bs.EOF + dim chunk as string = bs.Read( kBlock ) + hasher.Process( chunk ) + wend + + return EncodeHex( hasher.Value ) + + Exception err as RuntimeException + return "" + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function IsWriteableRecursive(parent As FolderItem) As Boolean + // Checks every file and folder to make sure it's writeable starting at parent + + if parent is nil then + return false + end if + + if not parent.Directory or not parent.IsWriteable then + return parent.IsWriteable + end if + + dim r as boolean = true // Assume it's all writeable + + dim folders() as FolderItem + folders.Append parent + + for folderIndex as integer = 0 to folders.Ubound + dim thisFolder as FolderItem = folders( folderIndex ) + + dim folderCnt as integer = thisFolder.Count + for itemIndex as integer = 1 to folderCnt + dim f as FolderItem = thisFolder.Item( itemIndex ) + if not f.IsWriteable then + r = false + exit for folderIndex + end if + + if f.Directory then + folders.Append f + end if + next itemIndex + next folderIndex + + return r + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub JSONToProperties(data As JSONItem, target As Object) + // Stores the values in the JSON object to the matching property in the object. + // Will only handle basic types, not objects. + + dim ti as Introspection.TypeInfo = Introspection.GetType( target ) + dim props() as Introspection.PropertyInfo = ti.GetProperties() + + for each prop as Introspection.PropertyInfo in props + if prop.CanWrite then + dim name as string = prop.Name + if data.HasName( name ) then + dim v as variant = data.Value( name ) + if not ( v IsA JSONItem ) then + prop.Value( target ) = v + end if + end if + end if + next + + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Sub StartUpdate(initiater As Kaju.UpdateInitiater) + App.UpdateInitiater = initiater + End Sub + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function TrueExecutableFile() As FolderItem + dim r as FolderItem = App.ExecutableFile + + #if TargetMacOS then + + r = r.Parent + while r.Name <> "Contents" + r = r.Parent + wend + + r = r.Parent + + #endif + + return r + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function VersionStringFor(majorVersion As Integer, minorVersion As Integer, bugVersion As Integer) As String + return VersionStringFor( majorVersion, minorVersion, bugVersion, App.Final, 0 ) + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function VersionStringFor(majorVersion As Integer, minorVersion As Integer, bugVersion As Integer, stageCode As Integer, nonReleaseVersion As Integer) As String + dim parts() as string + parts.Append str( majorVersion ) + parts.Append str( minorVersion ) + if bugVersion > 0 then + parts.Append str( bugVersion ) + end if + dim version as string = join( parts, "." ) + + if stageCode <> App.Final then + select case stageCode + case App.Development + version = version + "d" + case App.Alpha + version = version + "a" + case App.Beta + version = version + "b" + end + version = version + str( nonReleaseVersion ) + end if + + return version + + End Function + #tag EndMethod + + #tag Method, Flags = &h1 + Protected Function VersionToDouble(version As String) As Double + // Takes a version and turns it into an double that can be compared. + // Assumes that the version will have no more than 3 parts + // (NN.NN.NN) and each part will max out at 999. The version + // may trail with a development, alpha, or beta suffix, and that + // part will max out at 999. + // + // Examples: + // 1.2 + // 1.1.1 + // 1.2b4 + // 1.3a16 + // 1.3d343 + // 1.99.9a101 + + if version = "" then + return 0.0 + end if + + static rx as RegEx + if rx is nil then + rx = new RegEx + rx.SearchPattern = "(?mi-Us)^([\d.]+)(?:([dab])(\d+))?$" + end if + + dim match as RegExMatch = rx.Search( version ) + if match is nil then // Something is wrong + raise new KajuException( "Version is not properly formatted: " + version, CurrentMethodName ) + end if + + version = match.SubExpressionString( 1 ) // Everything except the non-release data + + dim nonReleaseType as string + dim nonRelease as string + if match.SubExpressionCount > 2 then + nonReleaseType = match.SubExpressionString( 2 ) + nonRelease = match.SubExpressionString( 3 ) + end if + + // + // Do the conversion + // + + dim parts() as string = version.Split( "." ) + redim parts( 2 ) + + dim r as double + for i as integer = 0 to parts.Ubound + dim thisPart as string = parts( i ) + dim thisPartVal as double = thisPart.Val + r = ( r * 1000.0 ) + thisPartVal + next + + const kDivider = 10000.0 + select case nonReleaseType + case "d" + r = r + ( nonRelease.Val / kDivider ) + case "a" + r = r + 0.1 + ( nonRelease.Val / kDivider ) + case "b" + r = r + 0.2 + ( nonRelease.Val / kDivider ) + else // Release + r = r + 0.4999 + end + + return r + + End Function + #tag EndMethod + + + #tag Note, Name = License + + The MIT License (MIT) + + Copyright (c) 2014 by Kem Tekinay. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + #tag EndNote + + + #tag Constant, Name = kUpdatePacketMarker, Type = String, Dynamic = False, Default = \"KAJU ", Scope = Protected + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Module +#tag EndModule diff --git a/Kaju Classes/Kaju/BinaryInformation.xojo_code b/Kaju Classes/Kaju/BinaryInformation.xojo_code new file mode 100644 index 0000000..5bf3698 --- /dev/null +++ b/Kaju Classes/Kaju/BinaryInformation.xojo_code @@ -0,0 +1,158 @@ +#tag Class +Protected Class BinaryInformation +Inherits Kaju.Information + #tag Event + Function IsInvalid(ByRef reason As String) As Boolean + dim r as boolean + + if not r and Hash.Trim = "" then + reason = "Missing Hash" + r = true + end if + + if not r and URL.Trim = "" then + reason = "Missing URL" + r = true + end if + + if not r and IsExecutableNameRequired and ExecutableName.Trim = "" then + reason = "Missing Executable Name" + r = true + end if + + return r + + End Function + #tag EndEvent + + + #tag Method, Flags = &h1021 + Private Sub Constructor() + // Must use one of the other contructors + End Sub + #tag EndMethod + + #tag Method, Flags = &h1000 + Sub Constructor(executableNameRequired As Boolean) + IsExecutableNameRequired = executableNameRequired + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(executableNameRequired As Boolean, data As JSONItem) + self.Constructor( executableNameRequired ) + Kaju.JSONToProperties( data, self ) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function ToJSON() As JSONItem + dim j as new JSONItem + + j.Value( "Hash" ) = Hash + j.Value( "URL" ) = URL + if ExecutableName <> "" then + j.Value( "ExecutableName" ) = ExecutableName + end if + + return j + + End Function + #tag EndMethod + + + #tag Property, Flags = &h0 + ExecutableName As String + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + if URL = "" then + return "" + end if + + dim cnt as integer = URL.CountFields( "/" ) + dim name as string = URL.NthField( "/", cnt ) + name = DecodeURLComponent( name ) + + return name + End Get + #tag EndGetter + FileName As String + #tag EndComputedProperty + + #tag Property, Flags = &h0 + Hash As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private IsExecutableNameRequired As Boolean + #tag EndProperty + + #tag Property, Flags = &h0 + URL As String + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="ExecutableName" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="FileName" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Hash" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="URL" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/Kaju/HTTPSSocket.xojo_code b/Kaju Classes/Kaju/HTTPSSocket.xojo_code new file mode 100644 index 0000000..bed2ca9 --- /dev/null +++ b/Kaju Classes/Kaju/HTTPSSocket.xojo_code @@ -0,0 +1,200 @@ +#tag Class +Protected Class HTTPSSocket +Inherits HTTPSecureSocket + #tag Method, Flags = &h0 + Sub Get(url As String) + self.Secure = IsURLSecure( url ) + super.Get( url ) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Get(url As String, file As FolderItem) + self.Secure = IsURLSecure( url ) + super.Get( url, file ) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Get(url As String, file As FolderItem, timeout As Integer) As Boolean + self.Secure = IsURLSecure( url ) + return super.Get( url, file, timeout ) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function Get(url As String, timeout As Integer) As String + self.Secure = IsURLSecure( url ) + return super.Get( url, timeout ) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub GetHeaders(url As String) + self.Secure = IsURLSecure( url ) + super.GetHeaders( url ) + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetHeaders(url As String, timeout As Integer) As InternetHeaders + self.Secure = IsURLSecure( url ) + return super.GetHeaders( url, timeout ) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function GetRedirectAddress(url As String, timeout As Integer, maximumIterations As Integer = kDefaultMaximumIterations) As String + // Gets the redirect address for a url + // Will give up after maximumIterations interations. + // Put a 0 (or less) in there for infinite + + if url = "" then + return url + end if + + dim isFinite as boolean = true + if maximumIterations < 1 then + isFinite = false + end if + + do + dim headers as InternetHeaders = GetHeaders( url, timeout ) + if headers is nil then + url = "" + exit + elseif headers.Value( "Location" ) <> "" then + url = headers.Value( "Location" ) + else + exit + end if + if isFinite then + maximumIterations = maximumIterations - 1 + end if + loop until isFinite and maximumIterations = 0 // Will never end if maxiumIterations < 0 to start + + return url.Trim + + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function IsURLSecure(url As String) As Boolean + return ForceSecure or url.Trim.Left( 8 ) = "https://" + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub SendRequest(method As String, url As String) + self.Secure = IsURLSecure( url ) + super.SendRequest( method, url ) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub SendRequest(method As String, url As String, file As FolderItem) + self.Secure = IsURLSecure( url ) + super.SendRequest( method, url, file ) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function SendRequest(method As String, url As String, file As FolderItem, timeout As Integer) As Boolean + self.Secure = IsURLSecure( url ) + return super.SendRequest( method, url, file, timeout ) + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function SendRequest(method As String, url As String, timeout As Integer) As String + self.Secure = IsURLSecure( url ) + return super.SendRequest( method, url, timeout ) + End Function + #tag EndMethod + + + #tag Property, Flags = &h0 + ForceSecure As Boolean + #tag EndProperty + + + #tag Constant, Name = kDefaultMaximumIterations, Type = Double, Dynamic = False, Default = \"5", Scope = Private + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="CertificateFile" + Visible=true + Group="Behavior" + Type="FolderItem" + EditorType="File" + #tag EndViewProperty + #tag ViewProperty + Name="CertificatePassword" + Visible=true + Group="Behavior" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="CertificateRejectionFile" + Visible=true + Group="Behavior" + Type="FolderItem" + EditorType="File" + #tag EndViewProperty + #tag ViewProperty + Name="ConnectionType" + Visible=true + Group="Behavior" + InitialValue="3" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="ForceSecure" + Group="Behavior" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + Type="Integer" + EditorType="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Secure" + Visible=true + Group="Behavior" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/Kaju/Information.xojo_code b/Kaju Classes/Kaju/Information.xojo_code new file mode 100644 index 0000000..091b29e --- /dev/null +++ b/Kaju Classes/Kaju/Information.xojo_code @@ -0,0 +1,112 @@ +#tag Class +Protected Class Information + #tag Method, Flags = &h21 + Private Sub Constructor() + // Do not allow direct instantiation + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function InvalidReason() As String + return mInvalidReason + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function ToJSON() As JSONItem + dim ti as Introspection.TypeInfo = Introspection.GetType( self ) + dim props() as Introspection.PropertyInfo = ti.GetProperties + + dim j as new JSONItem( "{}" ) + + for each prop as Introspection.PropertyInfo in props + if prop.CanRead and prop.CanWrite and prop.IsPublic then + dim value as Variant = prop.Value( self ) + if value.IsNull then + // + // Skip it + // + + elseif value.Type = Variant.TypeObject then + + if value IsA Kaju.Information then + dim child as Kaju.Information = value + j.Value( prop.Name ) = child.ToJSON + end if + + else + + j.Value( prop.Name ) = value + + end if + end if + next + + return j + + End Function + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event IsInvalid(ByRef reason As String) As Boolean + #tag EndHook + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + dim reason as string + dim r as boolean = not RaiseEvent IsInvalid( reason ) + mInvalidReason = reason + + return r + End Get + #tag EndGetter + IsValid As Boolean + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private mInvalidReason As String + #tag EndProperty + + + #tag ViewBehavior + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/Kaju/KajuException.xojo_code b/Kaju Classes/Kaju/KajuException.xojo_code new file mode 100644 index 0000000..f73665b --- /dev/null +++ b/Kaju Classes/Kaju/KajuException.xojo_code @@ -0,0 +1,76 @@ +#tag Class +Class KajuException +Inherits RuntimeException + #tag Method, Flags = &h0 + Sub Constructor(msg As String, methodName As String) + self.Message = msg + self.Stack.Append methodName + + End Sub + #tag EndMethod + + + #tag Constant, Name = kErrorCantFindLibsFolder, Type = String, Dynamic = False, Default = \"Can\xE2\x80\x99t locate this applications Libs folder.", Scope = Public + #tag EndConstant + + #tag Constant, Name = kErrorImproperFunction, Type = String, Dynamic = False, Default = \"This function cannot be used on this platform.", Scope = Public + #tag EndConstant + + #tag Constant, Name = kErrorMissingUpdateURL, Type = String, Dynamic = False, Default = \"The update URL is missing.", Scope = Public + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="ErrorNumber" + Group="Behavior" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Message" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Reason" + Group="Behavior" + Type="Text" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/Kaju/UpdateChecker.xojo_code b/Kaju Classes/Kaju/UpdateChecker.xojo_code new file mode 100644 index 0000000..84407af --- /dev/null +++ b/Kaju Classes/Kaju/UpdateChecker.xojo_code @@ -0,0 +1,690 @@ +#tag Class +Protected Class UpdateChecker + #tag Method, Flags = &h0 + Sub Constructor(preferencesFolder As FolderItem) + self.PrefFile = preferencesFolder.Child( kPreferencesName ) + + LoadPrefs() + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Destructor() + SavePrefs() + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function DryRun() As Boolean + return mDryRun + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Execute() + // Pull the data from the URL, check it, and preset the window if needed + // Returns true if the app should quit in preparation of the update. + // + // The caller should be prepared to handle an exception in case of error. + + // + // If there is already an update in progress, do nothing + // + if UpdateWindowIsOpen then + mResult = ResultType.UpdateAlreadyInProgress + return + end if + + // + // Make sure the OS is supported + // + if not OSIsSupported() then + mResult = ResultType.UnsupportedOS + return + end if + + // + // Check for write permission + // + if true then // Scope + + dim executable as FolderItem = Kaju.TrueExecutableFile + + #if TargetMacOS then + if not executable.Parent.IsWriteable or not Kaju.IsWriteableRecursive( executable ) then + mResult = ResultType.NoWritePermission + return + end if + #else + if not Kaju.IsWriteableRecursive( executable.Parent ) then + mResult = ResultType.NoWritePermission + return + end if + #endif + + end if + + mDryRun = false + + // + // Make sure we have some URL + // + + if UpdateURL.Trim = "" then + raise new KajuException( KajuException.kErrorMissingUpdateURL, CurrentMethodName ) + end if + + // + // Look for redirection + // + dim url as string = self.UpdateURL + if AllowRedirection then + dim redirector as new Kaju.HTTPSSocket + url = redirector.GetRedirectAddress( url, 5 ) + end if + + // + // Repeat the check until we get data or the user gives up + // + do + + dim http as new Kaju.HTTPSSocket + + dim raw as string = http.Get( url, 5 ) + if http.HTTPStatusCode = 404 then // Not found + mResult = ResultType.NoUpdateAvailable + exit do + + elseif raw = "" then + if HandleError( kErrorNoUpdateData ) then + continue do + else + exit do + end if + end if + + raw = raw.DefineEncoding( Encodings.UTF8 ) + + dim firstLine as string + dim remainder as string + SeparatePacket( raw, firstLine, remainder ) + raw = remainder + + dim sig as string = firstLine.Left( kUpdatePacketMarker.Len ) + if StrComp( sig, kUpdatePacketMarker, 0 ) <> 0 then + if HandleError( kErrorIncorrectPacketMarker ) then + continue do + else + exit do + end if + end if + + sig = firstLine.Mid( sig.Len + 1 ) + sig = DecodeHex( sig ) + if not Crypto.RSAVerifySignature( raw, sig, ServerPublicRSAKey ) then + if HandleError( kErrorIncorrectPacketSignature ) then + continue do + else + exit do + end if + end if + + if ProcessUpdateData( raw ) then + exit do + end if + loop + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function HandleError(msg As String) As Boolean + // Displays a dialog to the user with the message and asks if they want to try again now or later + // Returns True to try now, False to try later + + dim r as boolean + + if IsAllowed( kAllowErrorDialog ) then + // + // The dialog is allowed + // + dim dlg as new MessageDialog + dlg.ActionButton.Visible = true + dlg.ActionButton.Caption = "Try Again" + dlg.CancelButton.Visible = true + dlg.CancelButton.Caption = "Later" + dlg.AlternateActionButton.Visible = false + dlg.Message = "An error has occurred. Would you like to try again now or later?" + dlg.Explanation = msg + + dim btn as MessageDialogButton = dlg.ShowModal + if btn is dlg.ActionButton then + r = true + else + r = false + end if + + end if + + // + // If the dialog wasn't allowed, just try again later + // + if not r then + mResult = ResultType.Error + end if + + return r + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Attributes( hidden ) Sub IgnoreVersion(version As String) + if version <> "" and IgnoreVersionsPref.IndexOf( version ) = -1 then + IgnoreVersionsPref.Append version + end if + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function IsAllowed(testValue As Integer) As Boolean + if testValue = 0 then // Special case + return AllowedInteraction = 0 + else + dim result as integer = AllowedInteraction and testValue + return result = testValue + end if + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function JSONToStringArray(data As JSONItem) As String() + dim ub as integer = data.Count - 1 + dim arr() as string + redim arr( ub ) + + for i as integer = 0 to ub + arr( i ) = data( i ) + next + + return arr + + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub LoadPrefs() + // Load the preferences from the known file + // + // Preferences are a JSON file + + if not PrefFile.Exists then + // + // Nothing to load + // + return + end if + + dim tis as TextInputStream = TextInputStream.Open( PrefFile ) + dim raw as string = tis.ReadAll( Encodings.UTF8 ) + + dim j as new JSONItem( raw ) + + // + // Load the individual variables here + // + + dim ti as Introspection.TypeInfo = Introspection.GetType( self ) + dim props() as Introspection.PropertyInfo = ti.GetProperties + for each prop as Introspection.PropertyInfo in props + dim thisName as string = prop.Name + if StrComp( thisName.Right( 4 ), "Pref", 0 ) <> 0 or not j.HasName( prop.Name ) then + continue for prop + end if + + if prop.PropertyType.Name = "String()" then + prop.Value( self ) = JSONToStringArray( j.Value( thisName ) ) + else + prop.Value( self ) = j.Value( thisName ) + end if + next + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Shared Function OSIsSupported() As Boolean + // Ensures that the right tools are available on the current OS + + dim r as boolean = true // Assume it's fine + + #if TargetMacOS then + + r = true // If this app can run, it has the right tools + + #elseif TargetWin32 then + + dim sh as new Shell + sh.Execute "XCOPY /?" + r = sh.ErrorCode = 0 + + #else // Linux + + dim cmds() as string = array( "rsync --version", "/usr/bin/logger --version" ) + + dim sh as new shell + for each cmd as string in cmds + sh.Execute cmd + if sh.ErrorCode <> 0 then + r = false + exit + end if + next + + #endif + + return r + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ProcessUpdateData(raw As String) As Boolean + // Return true if there was no error or if the user wants to try later + + mResult = ResultType.NoUpdateAvailable // Assume this is true + + dim j as new JSONItem( raw ) + dim versionDouble as double = if( DryRun, -1.0, Kaju.VersionToDouble( Kaju.AppVersionString ) ) + + // + // Get an array of the info + // + dim ub as integer = j.Count - 1 + dim info() as Kaju.UpdateInformation + dim updateIsRequired as boolean + for i as integer = 0 to ub + dim thisInfo as new Kaju.UpdateInformation( j( i ) ) + + // + // See if the binary information is present + // + if thisInfo.PlatformBinary is nil then + continue for i + end if + + // + // See if the stage on this update is allowed + // + if thisInfo.StageCode < AllowedStage then + continue for i + end if + + // + // See if this update is for a higher version + // + if thisInfo.VersionAsDouble <= versionDouble then + continue for i + end if + + // + // See if this update is required + // + dim thisUpdateIsRequired as boolean + if thisInfo.MinimumRequiredVersion <> "" and Kaju.VersionToDouble( thisInfo.MinimumRequiredVersion ) > versionDouble then + thisUpdateIsRequired = true + updateIsRequired = true + end if + + // + // An ignored version? (but only if not required) + // + if not thisUpdateIsRequired and HonorIgnored and IgnoreVersionsPref.IndexOf( thisInfo.Version ) <> -1 then + mResult = ResultType.IgnoredUpdateAvailable + continue for i + end if + + // + // This is a viable update + // + + info.Append thisInfo + next + + if info.Ubound <> -1 then + // + // There are updates + // + mResult = if( updateIsRequired, ResultType.RequiredUpdateAvailable, ResultType.UpdateAvailable ) + if IsAllowed( kAllowUpdateWindow ) then + KajuUpdateWindow.ChooseUpdate( self, info ) + end if + end if + + return true + + Exception err as RuntimeException + return not HandleError( kErrorBadUpdateData ) + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub ResetIgnored() + redim IgnoreVersionsPref( -1 ) + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function Result() As ResultType + return mResult + + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SavePrefs() + dim j as new JSONItem + + // + // Save the individual prefs here. + // Properties that end with "Pref" will be saved + // + + dim ti as Introspection.TypeInfo = Introspection.GetType( self ) + dim props() as Introspection.PropertyInfo = ti.GetProperties + for each prop as Introspection.PropertyInfo in props + if StrComp( prop.Name.Right( 4 ), "Pref", 0 ) = 0 then + if prop.PropertyType.Name = "String()" then + j.Value( prop.Name ) = StringArrayToJSON( prop.Value( self ) ) + else + j.Value( prop.Name ) = prop.Value( self ) + end if + end if + next + + // + // Save the file + // + + j.Compact = false + dim raw as string = j.ToString + + dim tos as TextOutputStream = TextOutputStream.Create( PrefFile ) + tos.Write raw + tos = nil + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub SeparatePacket(raw As String, ByRef firstLine As String, ByRef remainder As String) + // Separate the incoming packet by the EOL when we don't know exactly what + // the EOL is. + + dim rx as new RegEx + rx.SearchPattern = "\A([^\r\n]*)\R([\s\S]*)\z" + + dim match as RegExMatch = rx.Search( raw ) + if match is nil then + // + // Really shouldn't happen + // + firstLine = raw + remainder = "" + + else + + firstLine = match.SubExpressionString( 1 ) + remainder = match.SubExpressionString( 2 ) + + end if + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function StringArrayToJSON(arr() As String) As JSONItem + dim j as new JSONItem( "[]" ) + for i as integer = 0 to arr.Ubound + j.Append arr( i ) + next + + return j + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub TestUpdate(jsonString As String) + // Allows a dry run with the update information that would otherwise be obtained + // from the UpdateURL + + // + // If there is already an update in progress, do nothing + // + if UpdateWindowIsOpen then + mResult = ResultType.UpdateAlreadyInProgress + return + end if + + mDryRun = true + + do + if ProcessUpdateData( jsonString ) then + exit do + end if + loop + + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event ReadyToInstall() + #tag EndHook + + #tag Hook, Flags = &h0 + Event RequiredUpdateDeclined() + #tag EndHook + + + #tag Property, Flags = &h0 + AllowedInteraction As UInt32 = kAllowAll + #tag EndProperty + + #tag Property, Flags = &h0 + AllowedStage As Integer = App.Development + #tag EndProperty + + #tag Property, Flags = &h0 + AllowRedirection As Boolean = False + #tag EndProperty + + #tag Property, Flags = &h0 + DefaultImage As Picture + #tag EndProperty + + #tag Property, Flags = &h0 + DefaultUseTransparency As Boolean = True + #tag EndProperty + + #tag Property, Flags = &h0 + HonorIgnored As Boolean = True + #tag EndProperty + + #tag Property, Flags = &h21 + Private IgnoreVersionsPref() As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mDryRun As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mResult As ResultType = ResultType.NotYetChecked + #tag EndProperty + + #tag Property, Flags = &h21 + Private PrefFile As FolderItem + #tag EndProperty + + #tag Property, Flags = &h0 + QuitOnCancelIfRequired As Boolean = True + #tag EndProperty + + #tag Property, Flags = &h0 + ServerPublicRSAKey As String + #tag EndProperty + + #tag Property, Flags = &h0 + UpdateURL As String + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + dim r as boolean + + dim lastIndex as integer = WindowCount - 1 + for i as integer = 0 to lastIndex + if Window( i ) IsA KajuUpdateWindow then + r = true + exit + end if + next + + return r + End Get + #tag EndGetter + UpdateWindowIsOpen As Boolean + #tag EndComputedProperty + + + #tag Constant, Name = kAllowAll, Type = Double, Dynamic = False, Default = \"&hFFFF", Scope = Public + #tag EndConstant + + #tag Constant, Name = kAllowErrorDialog, Type = Double, Dynamic = False, Default = \"&b00001000", Scope = Public + #tag EndConstant + + #tag Constant, Name = kAllowNone, Type = Double, Dynamic = False, Default = \"0", Scope = Public + #tag EndConstant + + #tag Constant, Name = kAllowUpdateWindow, Type = Double, Dynamic = False, Default = \"&b10000000", Scope = Public + #tag EndConstant + + #tag Constant, Name = kErrorBadUpdateData, Type = String, Dynamic = False, Default = \"The update data cannot be read.", Scope = Private + #tag EndConstant + + #tag Constant, Name = kErrorIncorrectPacketMarker, Type = String, Dynamic = False, Default = \"The update packet signature marker was incorrect.", Scope = Private + #tag EndConstant + + #tag Constant, Name = kErrorIncorrectPacketSignature, Type = String, Dynamic = False, Default = \"The RSA signature of the update packet cannot be verified.", Scope = Private + #tag EndConstant + + #tag Constant, Name = kErrorNoUpdateData, Type = String, Dynamic = False, Default = \"No update data was available.", Scope = Private + #tag EndConstant + + #tag Constant, Name = kPreferencesName, Type = String, Dynamic = False, Default = \"Kaju_Preferences", Scope = Private + #tag EndConstant + + + #tag Enum, Name = ResultType, Type = Integer, Flags = &h0 + NotYetChecked = -9999 + UpdateAlreadyInProgress = -100 + UnsupportedOS = -70 + NoWritePermission = -50 + Error = -1 + NoUpdateAvailable = 0 + IgnoredUpdateAvailable + UpdateAvailable + RequiredUpdateAvailable + #tag EndEnum + + + #tag ViewBehavior + #tag ViewProperty + Name="AllowedStage" + Group="Behavior" + InitialValue="App.Development" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="AllowRedirection" + Group="Behavior" + InitialValue="False" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="DefaultImage" + Group="Behavior" + Type="Picture" + #tag EndViewProperty + #tag ViewProperty + Name="DefaultUseTransparency" + Group="Behavior" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="HonorIgnored" + Group="Behavior" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="QuitOnCancelIfRequired" + Group="Behavior" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="ServerPublicRSAKey" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="UpdateURL" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="UpdateWindowIsOpen" + Group="Behavior" + Type="Boolean" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/Kaju/UpdateInformation.xojo_code b/Kaju Classes/Kaju/UpdateInformation.xojo_code new file mode 100644 index 0000000..1c3ea9d --- /dev/null +++ b/Kaju Classes/Kaju/UpdateInformation.xojo_code @@ -0,0 +1,320 @@ +#tag Class +Protected Class UpdateInformation +Inherits Kaju.Information + #tag Event + Function IsInvalid(ByRef reason As String) As Boolean + static rxVersion as RegEx + if rxVersion is nil then + rxVersion = new RegEx + rxVersion.SearchPattern = "(?mi-Us)\A\d+(\.\d+){0,2}([dab]\d+)?\z" + end if + + dim r as boolean + + if not r and rxVersion.Search( Version ) is nil then + reason = "Version must be in one of these forms: 1, 1.2, 1.2.3, 1.2d4, 1.2a4, 1.2b4, 1.2.4b4, etc." + r = true + end if + + if not r and AppName.Trim = "" then + reason = "Missing app name" + r = true + end if + + if not r and MacBinary <> nil and not MacBinary.IsValid then + reason = "Mac Binary information is not valid: " + MacBinary.InvalidReason + r = true + end if + + if not r and WindowsBinary <> nil and not WindowsBinary.IsValid then + reason = "Windows Binary information is not valid: " + WindowsBinary.InvalidReason + r = true + end if + + if not r and LinuxBinary <> nil and not LinuxBinary.IsValid then + reason = "Linux Binary information is not valid: " + LinuxBinary.InvalidReason + r = true + end if + + return r + End Function + #tag EndEvent + + + #tag Method, Flags = &h1000 + Sub Constructor() + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Constructor(data As JSONItem) + Kaju.JSONToProperties( data, self ) + + if data.HasName( kMacBinaryName ) then + MacBinary = new Kaju.BinaryInformation( false, data.Value( kMacBinaryName ) ) + end if + + if data.HasName( kWindowsBinaryName ) then + WindowsBinary = new Kaju.BinaryInformation( true, data.Value( kWindowsBinaryName ) ) + end if + + if data.HasName( kLinuxBinaryName ) then + LinuxBinary = new Kaju.BinaryInformation( true, data.Value( kLinuxBinaryName ) ) + end if + + End Sub + #tag EndMethod + + + #tag Property, Flags = &h0 + AppName As String + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + if mImage <> nil then + return mImage + end if + + dim url as string = ImageURL.Trim + + if url = "" then + return nil + end if + + // + // Get the image + // + + dim http as new Kaju.HTTPSSocket + url = http.GetRedirectAddress( url, 5 ) + + dim data as string = http.Get( url, 5 ) + + if data = "" then + return nil + end if + + mImage = Picture.FromData( data ) + + Exception err as RuntimeException + mImage = nil + + Finally + return mImage + + End Get + #tag EndGetter + Image As Picture + #tag EndComputedProperty + + #tag Property, Flags = &h0 + ImageURL As String + #tag EndProperty + + #tag Property, Flags = &h0 + LinuxBinary As Kaju.BinaryInformation + #tag EndProperty + + #tag Property, Flags = &h0 + MacBinary As Kaju.BinaryInformation + #tag EndProperty + + #tag Property, Flags = &h21 + Private mImage As Picture + #tag EndProperty + + #tag Property, Flags = &h0 + MinimumRequiredVersion As String + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + dim binaryInfo as Kaju.BinaryInformation + + #if TargetMacOS then + binaryInfo = MacBinary + #elseif TargetWin32 then + binaryInfo = WindowsBinary + #else // Linux + binaryInfo = LinuxBinary + #endif + + return binaryInfo + End Get + #tag EndGetter + PlatformBinary As Kaju.BinaryInformation + #tag EndComputedProperty + + #tag Property, Flags = &h0 + ReleaseNotes As String + #tag EndProperty + + #tag Property, Flags = &h0 + RequiresPayment As Boolean + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + static rx as RegEx + if rx is nil then + rx = new RegEx + rx.SearchPattern = "[dab]" + end if + + dim match as RegExMatch = rx.Search( Version ) + if match is nil then + + return App.Final + + else + + select case match.SubExpressionString( 0 ) + case "d" + return App.Development + case "a" + return App.Alpha + case "b" + return App.Beta + end + + end if + End Get + #tag EndGetter + StageCode As Integer + #tag EndComputedProperty + + #tag Property, Flags = &h0 + UseTransparency As Boolean = True + #tag EndProperty + + #tag Property, Flags = &h0 + Version As String + #tag EndProperty + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + return Kaju.VersionToDouble( Version ) + End Get + #tag EndGetter + VersionAsDouble As Double + #tag EndComputedProperty + + #tag Property, Flags = &h0 + WindowsBinary As Kaju.BinaryInformation + #tag EndProperty + + + #tag Constant, Name = kLinuxBinaryName, Type = String, Dynamic = False, Default = \"LinuxBinary", Scope = Public + #tag EndConstant + + #tag Constant, Name = kMacBinaryName, Type = String, Dynamic = False, Default = \"MacBinary", Scope = Public + #tag EndConstant + + #tag Constant, Name = kWindowsBinaryName, Type = String, Dynamic = False, Default = \"WindowsBinary", Scope = Public + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="AppName" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Image" + Group="Behavior" + Type="Picture" + #tag EndViewProperty + #tag ViewProperty + Name="ImageURL" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="IsValid" + Group="Behavior" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MinimumRequiredVersion" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="ReleaseNotes" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="RequiresPayment" + Group="Behavior" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="StageCode" + Group="Behavior" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="UseTransparency" + Group="Behavior" + InitialValue="True" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Version" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="VersionAsDouble" + Group="Behavior" + Type="Double" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/Kaju/UpdateInitiater.xojo_code b/Kaju Classes/Kaju/UpdateInitiater.xojo_code new file mode 100644 index 0000000..f6c6206 --- /dev/null +++ b/Kaju Classes/Kaju/UpdateInitiater.xojo_code @@ -0,0 +1,500 @@ +#tag Class +Protected Class UpdateInitiater + #tag Method, Flags = &h21 + Private Function ArrayToShellScript(arr() As String, variableName As String) As String + // Converts the given array to shell script code to form an array + + dim r as string + + #if TargetMacOS or TargetLinux then + + dim builder() as string + for i as integer = 0 to arr.Ubound + builder.Append variableName + builder.Append "[" + builder.Append str( i ) + builder.Append "]=" + builder.Append ShellQuote( arr( i ) ) + builder.Append EndOfLine.UNIX + next i + + r = join( builder, "" ) + + #else // Windows + // + // No array in Windows + // + raise new Kaju.KajuException( Kaju.KajuException.kErrorImproperFunction, CurrentMethodName ) + #endif + + return r + + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Cancel() + ReplacementAppFolder = nil + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Destructor() + RunScript + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function GetManifest(sourceFolder As FolderItem, excludeName As String) As String() + // Retrieves the names of the files and folders contained in the sourceFolder + // except for the excluded name and .DS_Store + + dim r() as string + + dim cnt as integer = sourceFolder.Count + for i as integer = 1 to cnt + dim f as FolderItem = sourceFolder.Item( i ) + dim name as string = f.Name + if name <> excludeName and name <> ".DS_Store" then + r.Append name + end if + next + + return r + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RunScript() + if ReplacementAppFolder is nil or not ReplacementAppFolder.Exists then + ReplacementAppFolder = nil + return + end if + + // + // Set up a temporary folder + // + dim tempFolder as FolderItem + #if DebugBuild then + tempFolder = SpecialFolder.Desktop.Child( "KajuTempFolder" + str( Ticks ) ) + if tempFolder.Exists then + Kaju.DeleteRecursive( tempFolder ) + end if + tempFolder.CreateAsFolder + #elseif TargetWin32 then + dim parent as FolderItem = App.ExecutableFile.Parent + dim folderName as string = App.ExecutableFile.Name + "-tempfolder" + tempFolder = parent.Child( folderName ) + Kaju.DeleteRecursive( tempFolder ) + tempFolder.CreateAsFolder + #else + tempFolder = Kaju.GetTemporaryFolder + #endif + + // + // Set up the PID file + // + dim pid as FolderItem = GetTemporaryFolderItem() + + #if TargetMacOS then + RunScriptMac( tempFolder, pid ) + #elseif TargetLinux then + RunScriptLinux( tempFolder, pid ) + #else // Windows + RunScriptWindows( tempFolder, pid ) + #endif + + Exception err As RuntimeException + MsgBox "Could not complete update - " + err.Message + + Finally + + if pid <> nil then + pid.Delete + end if + + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RunScriptLinux(tempFolder As FolderItem, pid As FolderItem) + dim script as string = kUpdaterScript + + // + // Get a FolderItem for the current executable + // + dim executable as FolderItem = App.ExecutableFile + + script = script.ReplaceAll( kMarkerAppName, ShellQuote( executable.Name ) ) + script = script.ReplaceAll( kMarkerAppParent, ShellPathQuote( executable.Parent ) ) + script = script.ReplaceAll( kMarkerNewAppName, ShellQuote( ReplacementExecutableName ) ) + script = script.ReplaceAll( kMarkerNewAppParent, ShellPathQuote( ReplacementAppFolder ) ) + script = script.ReplaceAll( kMarkerTempFolder, ShellPathQuote( TempFolder ) ) + + script = script.ReplaceAll( kMarkerPIDFilePath, ShellPathQuote( pid ) ) + + // + // Get the names of the other files/folders in the replacement folder + // + dim otherFiles() as string = GetManifest( ReplacementAppFolder, ReplacementExecutableName ) + + // + // Fill in the other array + // + if true then // Scope + script = script.Replace( kMarkerOtherUbound, str( otherFiles.Ubound ) ) + + dim segment as string = ArrayToShellScript( otherFiles, kOtherArrayVariableName ) + script = script.Replace( kMarkerOtherArray, segment ) + end if + + // + // Prepare for saving + // + script = ReplaceLineEndings( script, EndOfLine.UNIX ) + + // + // Save it + // + dim scriptFile as FolderItem = saveScript( script, tempFolder ) + if scriptFile <> nil then + // + // Adjust the permissions + // + dim p as new Permissions( scriptFile.Permissions ) + p.OwnerExecute = true + p.GroupExecute = true + p.OthersExecute = true + scriptFile.Permissions = p + + // + // Run the script + // + dim sh as new Shell + sh.Mode = 1 // Asynchronous + + dim cmd as string + cmd = "/usr/bin/nohup " + ShellQuote( scriptFile.NativePath ) + " &" + + sh.Execute( cmd ) + dim targetTicks as integer = Ticks + 60 + while Ticks < targetTicks + sh.Poll + App.YieldToNextThread + wend + + end if + + return + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RunScriptMac(tempFolder As FolderItem, pid As FolderItem) + dim script as string = kUpdaterScript + + // + // Get a FolderItem for the current app + // + dim appFolderItem as FolderItem = Kaju.TrueExecutableFile + + // + // Change the name of the replacment app to match + // + if ReplacementAppFolder.Name <> appFolderItem.Name then + ReplacementAppFolder.Name = appFolderItem.Name + end if + + script = script.ReplaceAll( kMarkerAppName, ShellQuote( appFolderItem.Name ) ) + script = script.ReplaceAll( kMarkerAppParent, ShellPathQuote( appFolderItem.Parent ) ) + script = script.ReplaceAll( kMarkerNewAppName, ShellQuote( ReplacementAppFolder.Name ) ) + script = script.ReplaceAll( kMarkerNewAppParent, ShellPathQuote( ReplacementAppFolder.Parent ) ) + script = script.ReplaceAll( kMarkerTempFolder, ShellPathQuote( TempFolder ) ) + + script = script.ReplaceAll( kMarkerPIDFilePath, ShellPathQuote( pid ) ) + + // + // Prepare for saving + // + script = ReplaceLineEndings( script, EndOfLine.UNIX ) + + // + // Save it + // + dim scriptFile as FolderItem = saveScript( script, tempFolder ) + if scriptFile <> nil then + // + // Adjust the permissions + // + dim p as new Permissions( scriptFile.Permissions ) + p.OwnerExecute = true + p.GroupExecute = true + p.OthersExecute = true + scriptFile.Permissions = p + + // + // Run the script + // + dim sh as new Shell + sh.Mode = 1 // Asynchronous + + dim cmd as string + cmd = "nohup " + ShellQuote( scriptFile.NativePath ) + " &" + + sh.Execute( cmd ) + dim targetTicks as integer = Ticks + 60 + while Ticks < targetTicks + sh.Poll + App.YieldToNextThread + wend + + end if + + return + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub RunScriptWindows(tempFolder As FolderItem, pid As FolderItem) + dim script as string = kUpdaterScript + + // + // Get a FolderItem for the current executable + // + dim executable as FolderItem = App.ExecutableFile + + script = script.ReplaceAll( kMarkerAppName, executable.Name ) + script = script.ReplaceAll( kMarkerAppParent, ShellPathQuote( executable.Parent ) ) + script = script.ReplaceAll( kMarkerNewAppName, ReplacementExecutableName ) + script = script.ReplaceAll( kMarkerNewAppParent, ShellPathQuote( ReplacementAppFolder ) ) + script = script.ReplaceAll( kMarkerTempFolder, ShellPathQuote( TempFolder ) ) + script = script.ReplaceAll( kMarkerDecompressedFolderPath, ShellPathQuote( ReplacementAppFolder.Parent ) ) + script = script.ReplaceAll( kMarkerPIDFilePath, ShellPathQuote( pid ) ) + + // + // Get the names of the other files/folders in the replacement folder + // + dim otherFiles() as string = GetManifest( ReplacementAppFolder, ReplacementExecutableName ) + + // + // Fill in the other array + // Since Windows batch files don't really do array, we will pull out the section of + // code that serves as a template and repeat is for each file + // + dim rx as new RegEx + rx.SearchPattern = kMarkerWinArrayStart + "(.+)" + kMarkerWinArrayEnd + rx.Options.DotMatchAll = true + rx.Options.Greedy = false + + dim match as RegExMatch = rx.Search( script ) + dim masterTemplate as string = match.SubExpressionString( 1 ) + + dim arr() as string + for each file as string in otherFiles + dim template as string = masterTemplate + template = template.ReplaceAll( kMarkerWinOther, file ) + template = template.ReplaceAll( kMarkerWinOtherWithoutSpaces, file.ReplaceAll( " ", "_" ) ) + arr.Append template + next + dim replacement as string = join( arr, "" ) + replacement = replacement.ReplaceAll( "\", "\\" ) + replacement = replacement.ReplaceAll( "$", "\$" ) + rx.ReplacementPattern = replacement + script = rx.Replace( script ) + + // + // Prepare for saving + // + script = ReplaceLineEndings( script, EndOfLine.Windows ) + + // + // Save it + // + dim scriptFile as FolderItem = SaveScript( script, tempFolder ) + if scriptFile <> nil then + // + // Run the script + // + scriptFile.Launch + dim targetTicks as integer = Ticks + 60 + while Ticks < targetTicks + App.YieldToNextThread + wend + + end if + + return + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function SaveScript(script As String, tempFolder As FolderItem) As FolderItem + dim scriptName as string = kScriptName + dim scriptFile as FolderItem = tempFolder.Child( scriptName ) + dim bs as BinaryStream = BinaryStream.Create( scriptFile, true ) + bs.Write( script ) + if bs.LastErrorCode <> 0 then + MsgBox "Error writing script file: " + str( bs.LastErrorCode ) + scriptFile = nil + end if + bs.Close + bs = nil + + return scriptFile + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ShellPathQuote(f As FolderItem) As String + #if TargetWin32 then + const kSlash = "\" + #else + const kSlash = "/" + #endif + + dim s as string = f.NativePath + + dim properLen as integer = s.Len + while s.Mid( properLen, 1 ) = kSlash + properLen = properLen - 1 + wend + s = s.Left( properLen ) + + #if not TargetWin32 then + s = ShellQuote( s ) + #endif + + return s + + End Function + #tag EndMethod + + #tag Method, Flags = &h21 + Private Function ShellQuote(s As String) As String + #if TargetWin32 then + + s = """" + s + """" + + #else + + const kQuote = "'" + const kReplacement = "'\''" + + s = s.ReplaceAll( kQuote, kReplacement ) + s = kQuote + s + kQuote + + #endif + + + return s + End Function + #tag EndMethod + + + #tag Property, Flags = &h0 + ReplacementAppFolder As FolderItem + #tag EndProperty + + #tag Property, Flags = &h0 + ReplacementExecutableName As String + #tag EndProperty + + + #tag Constant, Name = kMarkerAppName, Type = String, Dynamic = False, Default = \"@@APP_NAME@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerAppParent, Type = String, Dynamic = False, Default = \"@@APP_PARENT@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerDecompressedFolderPath, Type = String, Dynamic = False, Default = \"@@DECOMPRESSED_FOLDER@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerNewAppName, Type = String, Dynamic = False, Default = \"@@NEW_APP_NAME@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerNewAppParent, Type = String, Dynamic = False, Default = \"@@NEW_APP_PARENT@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerOtherArray, Type = String, Dynamic = False, Default = \"@@NEW_APP_OTHER_ARRAY@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerOtherUbound, Type = String, Dynamic = False, Default = \"@@NEW_APP_OTHER_UB@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerPIDFilePath, Type = String, Dynamic = False, Default = \"@@PID_FILE_PATH@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerTempFolder, Type = String, Dynamic = False, Default = \"@@TEMP_FOLDER@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerWinArrayEnd, Type = String, Dynamic = False, Default = \"REM END PSEUDO-ARRAY", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerWinArrayStart, Type = String, Dynamic = False, Default = \"REM BEGIN PSEUDO-ARRAY", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerWinOther, Type = String, Dynamic = False, Default = \"@@OTHER_NAME@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMarkerWinOtherWithoutSpaces, Type = String, Dynamic = False, Default = \"@@OTHER_NAME_WO_SPACES@@", Scope = Private + #tag EndConstant + + #tag Constant, Name = kOtherArrayVariableName, Type = String, Dynamic = False, Default = \"NEW_APP_OTHER_NAME", Scope = Private + #tag EndConstant + + #tag Constant, Name = kScriptName, Type = String, Dynamic = False, Default = \"kaju_updater.sh", Scope = Private + #Tag Instance, Platform = Windows, Language = Default, Definition = \"kaju_updater.bat" + #tag EndConstant + + #tag Constant, Name = kUpdaterScript, Type = String, Dynamic = False, Default = \"", Scope = Private + #Tag Instance, Platform = Mac OS, Language = Default, Definition = \"#!/bin/bash\n\n#\n# FUNCTIONS\n#\n\nfunction log_cmd {\n /usr/bin/logger -t \"Kaju Update Script\" $@\n}\n\n# END FUNCTIONS\n\n#\n# These will be filled in by the calling app\n#\n\nAPP_NAME\x3D@@APP_NAME@@\nAPP_PARENT\x3D@@APP_PARENT@@\nNEW_APP_NAME\x3D@@NEW_APP_NAME@@\nNEW_APP_PARENT\x3D@@NEW_APP_PARENT@@\nTEMP_FOLDER_PATH\x3D@@TEMP_FOLDER@@\nPID_FILE\x3D@@PID_FILE_PATH@@\n\n#\n# -----------------\n#\n\nreadonly true\x3D1\nreadonly false\x3D0\n\nAPP_PATH\x3D$APP_PARENT/$APP_NAME\nNEW_APP_PATH\x3D$NEW_APP_PARENT/$NEW_APP_NAME\n\nRENAMED_APP_NAME\x3D`echo \"$APP_NAME\" | /usr/bin/sed -E s/\\.[aA][pP]{2}//`-`date +%Y%m%d%H%M%S`.app\nRENAMED_APP_PATH\x3D$APP_PARENT/$RENAMED_APP_NAME\n\nlog_cmd \"STARTING UPDATE OF $APP_NAME\"\n\ncounter\x3D10\nwhile [ -f \"$PID_FILE\" ]\ndo\n log_cmd \"Checking to see if $PIDFILE exists\x2C $counter\"\n sleep 1\n \n let counter\x3Dcounter-1\n \n if [ $counter \x3D\x3D 0 ]\n then\n \tlog_cmd \'ERROR: Could not update app\x2C it never quit\'\n \texit 1\n fi\ndone\n\nPROCEED\x3D$true\n\n#\n# Rename the old application\n#\nlog_cmd \"Renaming old application $APP_NAME to $RENAMED_APP_NAME\"\nmv \"$APP_PATH\" \"$RENAMED_APP_PATH\"\n\n#\n# Make sure that succeeded\n#\nif [ $\? \x3D\x3D 0 ]\nthen\n log_cmd \'...confirmed\'\nelse\n log_cmd \"Could not rename old application to $RENAMED_APP_PATH\"\n PROCEED\x3D0\nfi\n\n#\n# Move in the replacement app\n#\nif [ $PROCEED \x3D\x3D $true ]\nthen\n log_cmd \"Moving new application $NEW_APP_PATH to folder $APP_PARENT\"\n mv \"$NEW_APP_PATH\" \"$APP_PARENT\"\n\n #\n # Make sure that worked\n #\n if [ $\? \x3D\x3D 0 ]\n then\n log_cmd \'...confirmed\'\n else\n log_cmd \"Could not move in new application\"\n log_cmd \"Attempting to restore old application and launch it\"\n mv \"$RENAMED_APP_PATH\" \"$APP_PATH\"\n open \"$APP_PATH\"\n PROCEED\x3D$false\n fi\nfi\n\nif [ $PROCEED \x3D\x3D $true ]\nthen\n log_cmd \"Removing old application $RENAMED_APP_NAME\"\n rm -fr \"$RENAMED_APP_PATH\"\n \n APP_PATH\x3D$APP_PARENT/$NEW_APP_NAME\n log_cmd \"Starting new application at $APP_PATH\"\n \n open \"$APP_PATH\"\nfi\n\nif [ $PROCEED \x3D\x3D $true ]\nthen\n log_cmd \'Removing temp folder\'\n rm -fr \"$TEMP_FOLDER_PATH\"\nfi\n" + #Tag Instance, Platform = Linux, Language = Default, Definition = \"#!/bin/bash\n\n#\n# FUNCTIONS\n#\n\nfunction log_cmd {\n\t/usr/bin/logger -t \"Kaju Update Script\" $@\n}\n\n# END FUNCTIONS\n\n#\n# These will be filled in by the calling app\n#\n\nAPP_NAME\x3D@@APP_NAME@@\nAPP_PARENT\x3D@@APP_PARENT@@\nNEW_APP_NAME\x3D@@NEW_APP_NAME@@\nNEW_APP_PARENT\x3D@@NEW_APP_PARENT@@\nTEMP_FOLDER_PATH\x3D@@TEMP_FOLDER@@\nPID_FILE\x3D@@PID_FILE_PATH@@\n\n#\n# This array will store the names of the items next to the executable\n# under the variable NEW_APP_OTHER_NAME\n#\nNEW_APP_OTHER_UB\x3D@@NEW_APP_OTHER_UB@@\n\n@@NEW_APP_OTHER_ARRAY@@\n\n#\n# -----------------\n#\n\nreadonly true\x3D1\nreadonly false\x3D0\n\nAPP_PATH\x3D$APP_PARENT/$APP_NAME\n\nBACKUP_PARENT\x3D$APP_PARENT/${APP_NAME}-`date +%Y%m%d%H%M%S`\nmkdir \"$BACKUP_PARENT\"\n\nlog_cmd \"STARTING UPDATE OF $APP_NAME\"\n\ncounter\x3D10\nwhile [ -f \"$PID_FILE\" ]\ndo\n\tlog_cmd \"Checking to see if $PIDFILE exists\x2C $counter\"\n\tsleep 1\n\t\n\tlet counter\x3Dcounter-1\n\t\n\tif [ $counter \x3D\x3D 0 ]\n\tthen\n\t\tlog_cmd \'ERROR: Could not update app\x2C it never quit\'\n\t\texit 1\n\tfi\ndone\n\nPROCEED\x3D$true\n\n#\n# Move the other items\n#\nlog_cmd \"Copying other items to backup $BACKUP_PARENT\"\n\ncounter\x3D0\nwhile [ $counter -le $NEW_APP_OTHER_UB ]\ndo\n\tthis_item\x3D${NEW_APP_OTHER_NAME[$counter]}\n\tlog_cmd \"Looking for item $this_item in $APP_PARENT\"\n\t\n\tthis_path\x3D$APP_PARENT/$this_item\n\tif [ -d \"$this_path\" ] || [ -f \"$this_path\" ]\n\tthen\n\t\tlog_cmd \"...found\x2C copying\"\n\t\tcp -pr \"$this_path\" \"$BACKUP_PARENT\"\n\t\tif [ $\? \x3D\x3D 0 ]\n\t\tthen\n\t\t\tlog_cmd \"...confirmed\"\n\t\telse\n\t\t\t log_cmd \"...FAILED!\"\n\t\t\t PROCEED\x3D$false\n\t\t\t break\n\t\tfi\n\tfi\n\t(( counter++ ))\ndone\n\n#\n# Move the executable\n#\nif [ $PROCEED \x3D\x3D $true ]\nthen\n\tlog_cmd \"Moving the executable $APP_NAME to backup\"\n\tmv \"$APP_PARENT/$APP_NAME\" \"$BACKUP_PARENT\"\n\tif [ $\? \x3D\x3D 0 ]\n\tthen\n\t\tlog_cmd \"...confirmed\"\n\telse\n\t\tlog_cmd \"...FAILED! (Error $\?)\"\n\t\tPROCEED\x3D$false\n\tfi\nfi\n\n#\n# Make sure there wasn\'t an error during the backup\n#\nif [ $PROCEED \x3D\x3D $true ]\nthen\n\tlog_cmd \'All items backed up\'\nelse\n\tlog_cmd \'Attempting to copy items back to parent\'\n\trsync -a --exclude\x3D\'.DS_Store\' \"${BACKUP_PARENT}/\" \"$APP_PARENT\"\nfi\n\n#\n# Move in the replacement files\n#\nif [ $PROCEED \x3D\x3D $true ]\nthen\n\tlog_cmd \"Copying files from $NEW_APP_PARENT to folder $APP_PARENT\"\n\trsync -a --exclude\x3D\'.DS_Store\' \"${NEW_APP_PARENT}/\" \"$APP_PARENT\"\n\t\n\tif [ $\? \x3D\x3D 0 ]\n\tthen\n\t\tlog_cmd \'...confirmed\'\n\telse\n\t\tlog_cmd \"...FAILED! (Error $\?)\"\n\t\tlog_cmd \"Attempting to restore old application\"\n\t\trsync -a --exclude\x3D\'.DS_Store\' \"${BACKUP_PARENT}/\" \"$APP_PARENT\"\n\t\tPROCEED\x3D$false\n\t\tbreak\n\tfi\nfi\n\n#\n# Removed the backup folder if everything has gone swimmingly so\n#\nif [ $PROCEED \x3D\x3D $true ]\nthen\n\tlog_cmd \'Removing backup\'\n\trm -r \"$BACKUP_PARENT\"\nfi\n\n#\n# Launch the application\n#\nif [ $PROCEED \x3D $true ]\nthen\n\tlog_cmd \'Making the new app executable\'\n\tchmod +x \"$APP_PARENT/$NEW_APP_NAME\"\n\tlog_cmd \'Launching new app\'\n\t\"$APP_PARENT/$NEW_APP_NAME\"\nelse\n\tlog_cmd \'Launching old app\'\n\t\"$APP_PARENT/$APP_NAME\"\nfi\n\nlog_cmd \'Removing temp folder\'\nrm -fr \"$TEMP_FOLDER_PATH\"\n" + #Tag Instance, Platform = Windows, Language = Default, Definition = \"@ECHO OFF \n\nREM\nREM These will be filled in by the calling app\nREM\n\nSET APP_NAME\x3D@@APP_NAME@@\nSET APP_PARENT\x3D@@APP_PARENT@@\nSET NEW_APP_NAME\x3D@@NEW_APP_NAME@@\nSET NEW_APP_PARENT\x3D@@NEW_APP_PARENT@@\nSET TEMP_FOLDER_PATH\x3D@@TEMP_FOLDER@@\nSET DECOMPRESSED_FOLDER_PATH\x3D@@DECOMPRESSED_FOLDER@@\nSET PID_FILE\x3D@@PID_FILE_PATH@@\n\nREM\nREM -----------------\nREM\n\nSET TODAY_DATE\x3D%DATE:~10\x2C4%-%DATE:~4\x2C2%-%DATE:~7\x2C2% %TIME:~0\x2C2%:%TIME:~3\x2C2%:%TIME:~6\x2C2%\nSET APP_PATH\x3D%APP_PARENT%\\%APP_NAME%\n\nSET BACKUP_PARENT\x3D%APP_PATH%-%DATE:~10\x2C4%%DATE:~4\x2C2%%DATE:~7\x2C2%%TIME:~0\x2C2%%TIME:~3\x2C2%%TIME:~6\x2C2%\n\nSET LOGGER\x3D%APP_PARENT%\\%NEW_APP_NAME% Update Log.txt\nECHO \"STARTED ON %TODAY_DATE%\" >> \"%LOGGER%\" 2>&1\n\nFOR /L %%i IN (1\x2C1\x2C10) DO (\n\tIF NOT EXIST \"%PID_FILE%\" (\n\t\tGOTO :program_exited\n\t)\n\n\tREM Windows version of sleep 1. Starting in Windows Vista\x2C the sleep command was removed.\n\tping -n 2 127.0.0.1 >nul\n\n\tIF %%i \x3D\x3D 10 (\n\t\tECHO ERROR: Could not update app\x2C it never quit >> \"%LOGGER%\" 2>&1\n\t\tEXIT /B 1\n\t)\n)\n:program_exited\n\nmkdir \"%BACKUP_PARENT%\"\n\nSET PROCEED\x3D1\n\nREM\nREM Move the other items\nREM\nECHO \"Copying items to backup %BACKUP_PARENT%\" >> \"%LOGGER%\" 2>&1\n\nREM We will need to manually populate these move commands. Windows Batch doesn\'t really handle arrays\x2C\nREM only looping through space delimited elements of a string. Below is a template for moving one such file.\n\nREM BEGIN PSEUDO-ARRAY\nSET THIS_ITEM\x3D@@OTHER_NAME@@\nSET THIS_PATH\x3D%APP_PARENT%\\%THIS_ITEM%\nECHO \"Looking for item %THIS_PATH%\" >> \"%LOGGER%\" 2>&1\nIF EXIST \"%THIS_PATH%\" (\n\tGOTO :copy_@@OTHER_NAME_WO_SPACES@@\n)\nECHO \"...not found as file\x2C trying as directory\" >> \"%LOGGER%\" 2>&1\nIF EXIST \"%THIS_PATH%\\NUL\" (\n\tGOTO :copy_@@OTHER_NAME_WO_SPACES@@\n) ELSE (\n\tECHO \"...NOT FOUND!\" >> \"%LOGGER%\" 2>&1\n\tGOTO :finished_with_@@OTHER_NAME_WO_SPACES@@\n)\n\n:copy_@@OTHER_NAME_WO_SPACES@@\n\nECHO \"...found\x2C copying\" >> \"%LOGGER%\" 2>&1\nCOPY \"%THIS_PATH%\" \"%BACKUP_PARENT%\" >> \"%LOGGER%\" 2>&1\nIF %ERRORLEVEL% NEQ 0 (\n\tECHO \"...FAILED! (Error %ERRORLEVEL%)\" >> \"%LOGGER%\" 2>&1\n\tSET PROCEED\x3D0\n\tGOTO :restore_from_backup\n) ELSE (\n\tECHO \"...confirmed\" >> \"%LOGGER%\" 2>&1\n)\n\n:finished_with_@@OTHER_NAME_WO_SPACES@@\n\nREM END PSEUDO-ARRAY\n\nREM\nREM Move the executable to backup\nREM\nIF %PROCEED% \x3D\x3D 1 (\n\tECHO \"Moving the executable %APP_NAME% to backup\" >> \"%LOGGER%\" 2>&1\n\tMOVE \"%APP_PATH%\" \"%BACKUP_PARENT%\" >> \"%LOGGER%\" 2>&1\n\tIF %ERRORLEVEL% NEQ 0 (\n\t\tECHO \"...FAILED! (Error %ERRORLEVEL%)\" >> \"%LOGGER%\" 2>&1\n\t\tSET PROCEED\x3D0\n\t\tGOTO :restore_from_backup\n\t) ELSE (\n\t\tECHO \"...confirmed\" >> \"%LOGGER%\" 2>&1\n\t)\n)\n\nREM\nREM Make sure there wasn\'t an error during the move\nREM\nIF %PROCEED% \x3D\x3D 1 (\n\tECHO \"All items moved to backup\" >> \"%LOGGER%\" 2>&1\n)\n\nREM\nREM Copy in the replacement files\nREM\n\nIF %PROCEED% \x3D\x3D 1 (\n\tECHO \"Copying files from %NEW_APP_PARENT% to folder %APP_PARENT%\" >> \"%LOGGER%\" 2>&1\n\tXCOPY /y /e /k \"%NEW_APP_PARENT%\" \"%APP_PARENT%\" >> \"%LOGGER%\" 2>&1\n\tIF %ERRORLEVEL% NEQ 0 (\n\t\tECHO \"...FAILED! (Error %ERRORLEVEL%)\" >> \"%LOGGER%\" 2>&1\n\t\tSET PROCEED\x3D0\n\t\tGOTO :restore_from_backup\n\t) ELSE (\n\t\tECHO \"...confirmed\" >> \"%LOGGER%\" 2>&1\n\t)\n)\n\nREM\nREM If we get here\x2C it all worked\nREM\nIF %PROCEED% \x3D\x3D 1 (\n\tGOTO :all_succeeded\n)\n\n:restore_from_backup\nIF %PROCEED% \x3D\x3D 0 (\n\tECHO \"Attempting to restore old application\" >> \"%LOGGER%\" 2>&1\n\n\tXCOPY /y /e /k \"%BACKUP_PARENT%\" \"%APP_PARENT%\" >> \"%LOGGER%\" 2>&1\n\tIF %ERRORLEVEL% EQU 0 (\n\t\tRMDIR /S /Q \"%BACKUP_PARENT%\" >> \"%LOGGER%\" 2>&1\n\t)\n)\nGOTO :launch_application\n\n:all_succeeded\nREM\nREM Remove the backup and decompressed folders if everything has gone swimmingly so far\nREM\nIF %PROCEED% \x3D\x3D 1 (\n\tECHO \"Removing backup\" >> \"%LOGGER%\" 2>&1\n\tRMDIR /S /Q \"%BACKUP_PARENT%\" >> \"%LOGGER%\" 2>&1\n\tECHO \"Removing decompressed folder\" >> \"%LOGGER%\" 2>&1\n\tRMDIR /S /Q \"%DECOMPRESSED_FOLDER_PATH%\" >> \"%LOGGER%\" 2>&1\n)\n\nREM\nREM Launch the application\nREM\n:launch_application\nIF %PROCEED% \x3D\x3D 1 (\n\tECHO \"Launching new app\" >> \"%LOGGER%\" 2>&1\n\tSTART \"\" \"%APP_PARENT%\\%NEW_APP_NAME%\"\n) ELSE (\n\tECHO \"Launching old app\" >> \"%LOGGER%\" 2>&1\n\tSTART \"\" \"%APP_PATH%\"\n)\n\nECHO \"Removing temp folder\" >> \"%LOGGER%\" 2>&1\nRMDIR /S /Q \"%TEMP_FOLDER_PATH%\" >> \"%LOGGER%\" 2>&1\n" + #tag EndConstant + + + #tag ViewBehavior + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + InitialValue="-2147483648" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="ReplacementExecutableName" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/Kaju/ZipShell.xojo_code b/Kaju Classes/Kaju/ZipShell.xojo_code new file mode 100644 index 0000000..b146919 --- /dev/null +++ b/Kaju Classes/Kaju/ZipShell.xojo_code @@ -0,0 +1,321 @@ +#tag Class +Protected Class ZipShell +Inherits Shell + #tag Event + Sub Completed() + DoCompleted() + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Compress(source As FolderItem, destFile As FolderItem = Nil) + if destFile is nil then + destFile = source.Parent.Child( source.Name + ".zip" ) + elseif destFile.Directory then + destFile = destFile.Child( source.Name + ".zip" ) + end if + + ResultFolderItem = destFile + + #if TargetMacOS then + + dim cmd as string = kDittoCmd + "-c -k --sequesterRsrc --keepParent " + + cmd = cmd + source.ShellPath + " " + destFile.ShellPath + Execute( cmd ) + + if Mode <> 0 then + mCurrentOperation = Operation.Compressing + end if + + #endif + + + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Function ContentsOf(zipFile As FolderItem) As String + // Gets the contents of a the given zip file. + // For this purpose, assumes that there is only one main item within the + // file. + + dim sh as new Shell + sh.Mode = 0 + + dim cmd as string + + #if TargetMacOS then + cmd = kZipInfoCmd + "-1 " + zipFile.ShellPath + #endif + + sh.Execute( cmd ) + dim result as string = sh.Result + result = ReplaceLineEndings( result, EndOfLine.UNIX ) + result = result.NthFieldB( EndOfLine.UNIX, 1 ) + + if result.Right( kPathSep.Len ) = kPathSep then + result = result.Left( result.Len - kPathSep.Len ) + end if + + return result + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Function CurrentOperation() As Operation + return mCurrentOperation + End Function + #tag EndMethod + + #tag Method, Flags = &h0 + Sub Decompress(file As FolderItem, toFolder As FolderItem = Nil) + if toFolder is nil then + toFolder = file.Parent + end if + + ResultFolderItem = toFolder + ZipFile = file + + if Mode <> 0 then + mCurrentOperation = Operation.Decompressing + end if + + #if TargetWin32 then + + WindowsUnzip( file, toFolder ) + + #else + + dim cmd as string + #if TargetMacOS then + + cmd = kDittoCmd + "-x -k " + cmd = cmd + file.ShellPath + " " + toFolder.ShellPath + + #elseif TargetLinux then + + cmd = kUnzipCmd + file.ShellPath + " -d " + toFolder.ShellPath + + #endif + + Execute cmd + + #endif + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DoCompleted() + if ErrorCode <> 0 then + + RaiseEvent Error() + + else + + ResultFolderItem = new FolderItem( ResultFolderItem.NativePath, FolderItem.PathTypeNative ) + + select case CurrentOperation + case Operation.Compressing + RaiseEvent CompressCompleted( ResultFolderItem) + + case Operation.Decompressing + RaiseEvent DecompressCompleted( ZipFile, ResultFolderItem ) + + end + + end if + + ResultFolderItem = nil + ZipFile = nil + mCurrentOperation = Operation.None + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub WindowsUnzip(zipFile As FolderItem, extractTo As FolderItem) + #if TargetWin32 then + + dim zipFilePath as string = zipFile.NativePath + dim extractToPath as string = extractTo.NativePath + + dim zipParams(1) as variant + zipParams(1) = zipFilePath + dim extractParams(1) As variant + extractParams(1) = extractToPath + + // + // If the extraction location does not exist create it + // + dim fso as new OLEObject( "Scripting.FileSystemObject" ) + if not fso.FolderExists( extractToPath ) then + fso.CreateFolder( extractToPath ) + end if + + dim objShell as new OLEObject( "Shell.Application" ) + dim myFolder1 as OLEObject = objShell.Invoke( "NameSpace", zipParams ) + dim myFolder2 as OLEObject = objShell.Invoke( "NameSpace", extractParams ) + + + // More info see: http://msdn.microsoft.com/en-us/library/windows/desktop/bb787868%28v=vs.85%29.aspx + // Also at http://msdn.microsoft.com/en-us/library/windows/desktop/bb787866%28v=vs.85%29.aspx + + // + // Extract the contents of the zip file. + // + dim copyHereOpts as integer = _ + kCopyHereOptionNoProgressDialog + _ + kCopyHereOptionYesToAll + _ + kCopyHereOptionNoDirectoryConfirmation + _ + kCopyHereOptionNoDialogOnError + myFolder2.CopyHere( myFolder1.Items, copyHereOpts ) + + fso = Nil + objShell = Nil + + DoCompleted() + + Exception err as RuntimeException + RaiseEvent Error() + + #else + + #pragma unused zipFile + #pragma unused extractTo + + raise new Kaju.KajuException( Kaju.KajuException.kErrorImproperFunction, CurrentMethodName ) + + #endif + + End Sub + #tag EndMethod + + + #tag Hook, Flags = &h0 + Event CompressCompleted(file As FolderItem) + #tag EndHook + + #tag Hook, Flags = &h0 + Event DecompressCompleted(zipFile As FolderItem, containingFolder As FolderItem) + #tag EndHook + + #tag Hook, Flags = &h0 + Event Error() + #tag EndHook + + + #tag Property, Flags = &h21 + Private mCurrentOperation As Operation + #tag EndProperty + + #tag Property, Flags = &h21 + Private ResultFolderItem As FolderItem + #tag EndProperty + + #tag Property, Flags = &h21 + Private ZipFile As FolderItem + #tag EndProperty + + + #tag Constant, Name = kCopyHereOptionNoDialogOnError, Type = Double, Dynamic = False, Default = \"1024", Scope = Private + #tag EndConstant + + #tag Constant, Name = kCopyHereOptionNoDirectoryConfirmation, Type = Double, Dynamic = False, Default = \"512", Scope = Private + #tag EndConstant + + #tag Constant, Name = kCopyHereOptionNoProgressDialog, Type = Double, Dynamic = False, Default = \"4", Scope = Private + #tag EndConstant + + #tag Constant, Name = kCopyHereOptionYesToAll, Type = Double, Dynamic = False, Default = \"16", Scope = Private + #tag EndConstant + + #tag Constant, Name = kDittoCmd, Type = String, Dynamic = False, Default = \"", Scope = Private + #Tag Instance, Platform = Mac OS, Language = Default, Definition = \"/usr/bin/ditto " + #tag EndConstant + + #tag Constant, Name = kPathSep, Type = String, Dynamic = False, Default = \"/", Scope = Private + #Tag Instance, Platform = Windows, Language = Default, Definition = \"\\" + #tag EndConstant + + #tag Constant, Name = kUnzipCmd, Type = String, Dynamic = False, Default = \"", Scope = Private + #Tag Instance, Platform = Linux, Language = Default, Definition = \"/usr/bin/unzip " + #tag EndConstant + + #tag Constant, Name = kZipInfoCmd, Type = String, Dynamic = False, Default = \"/usr/bin/zipinfo ", Scope = Private + #tag EndConstant + + + #tag Enum, Name = Operation, Type = Integer, Flags = &h0 + None + Compressing + Decompressing + Listing + #tag EndEnum + + + #tag ViewBehavior + #tag ViewProperty + Name="Arguments" + Visible=true + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Backend" + Visible=true + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Canonical" + Visible=true + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Index" + Visible=true + Group="ID" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Left" + Visible=true + Group="Position" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Mode" + Visible=true + Type="Integer" + EditorType="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="TimeOut" + Visible=true + Type="Integer" + EditorType="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Top" + Visible=true + Group="Position" + Type="Integer" + #tag EndViewProperty + #tag EndViewBehavior +End Class +#tag EndClass diff --git a/Kaju Classes/KajuUpdateWindow.xojo_window b/Kaju Classes/KajuUpdateWindow.xojo_window new file mode 100644 index 0000000..be1e092 --- /dev/null +++ b/Kaju Classes/KajuUpdateWindow.xojo_window @@ -0,0 +1,1446 @@ +#tag Window +Begin Window KajuUpdateWindow + BackColor = &cFFFFFF00 + Backdrop = 0 + CloseButton = False + Compatibility = "" + Composite = True + Frame = 0 + FullScreen = False + FullScreenButton= False + HasBackColor = False + Height = 600 + ImplicitInstance= True + LiveResize = False + MacProcID = 0 + MaxHeight = 32000 + MaximizeButton = False + MaxWidth = 32000 + MenuBar = 0 + MenuBarVisible = True + MinHeight = 64 + MinimizeButton = False + MinWidth = 64 + Placement = 2 + Resizeable = False + Title = "#kWindowTitle" + Visible = True + Width = 800 + Begin HTMLViewer hvNotes + AutoDeactivate = True + Enabled = True + Height = 445 + HelpTag = "" + Index = -2147483648 + Left = 149 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Renderer = 1 + Scope = 2 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + Top = 84 + Visible = True + Width = 631 + End + Begin PushButton btnOK + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "#kInstallButton" + Default = True + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 630 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 555 + Underline = False + Visible = True + Width = 150 + End + Begin PushButton btnCancel + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = True + Caption = "#kRemindMeLaterButton" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 468 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 555 + Underline = False + Visible = True + Width = 150 + End + Begin PushButton btnSkipVersion + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "#kSkipVersionButton" + Default = False + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 149 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 2 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 555 + Underline = False + Visible = True + Width = 150 + End + Begin Label lblMain + AutoDeactivate = True + Bold = True + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 149 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 5 + TabPanelIndex = 0 + Text = "Untitled" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 14 + Transparent = True + Underline = False + Visible = True + Width = 631 + End + Begin Label lblSecondary + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 25 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 149 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = True + Scope = 2 + Selectable = False + TabIndex = 6 + TabPanelIndex = 0 + Text = "Untitled" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 39 + Transparent = True + Underline = False + Visible = True + Width = 631 + End + Begin Label Label1 + AutoDeactivate = True + Bold = True + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 149 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 7 + TabPanelIndex = 0 + Text = "#kReleaseNotesLabel" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 64 + Transparent = True + Underline = False + Visible = True + Width = 631 + End + Begin Label lblInstallMessage + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 30 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 149 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = True + Scope = 2 + Selectable = False + TabIndex = 8 + TabPanelIndex = 0 + Text = "Downloading..." + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 555 + Transparent = False + Underline = False + Visible = False + Width = 294 + End + Begin ProgressBar pbProgress + AutoDeactivate = True + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Maximum = 0 + Scope = 2 + TabPanelIndex = 0 + Top = 555 + Value = 0 + Visible = False + Width = 117 + End + Begin Kaju.HTTPSSocket hsSocket + CertificateFile = + CertificatePassword= "" + CertificateRejectionFile= + ConnectionType = 2 + Height = 32 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockedInPosition= False + Scope = 2 + Secure = False + TabPanelIndex = 0 + Top = 0 + Width = 32 + End + Begin Kaju.ZipShell shZipper + Arguments = "" + Backend = "" + Canonical = False + Height = 32 + Index = -2147483648 + InitialParent = "" + Left = 0 + LockedInPosition= False + Mode = 1 + Scope = 2 + TabPanelIndex = 0 + TimeOut = 0 + Top = 0 + Width = 32 + End + Begin HTMLViewer hvNewWindow + AutoDeactivate = True + Enabled = True + Height = 200 + HelpTag = "" + Index = -2147483648 + Left = -345 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Renderer = 0 + Scope = 2 + TabIndex = 9 + TabPanelIndex = 0 + TabStop = True + Top = 119 + Visible = False + Width = 300 + End + Begin Label lblVersions + AutoDeactivate = True + Bold = True + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 2 + Selectable = False + TabIndex = 10 + TabPanelIndex = 0 + Text = "#kVersionsLabel" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "SmallSystem" + TextSize = 0.0 + TextUnit = 0 + Top = 64 + Transparent = True + Underline = False + Visible = True + Width = 117 + End + Begin PopupMenu pumUpdates + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + InitialValue = "" + Italic = False + Left = 36 + ListIndex = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + TabIndex = 11 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 84 + Underline = False + Visible = True + Width = 101 + End +End +#tag EndWindow + +#tag WindowCode + #tag Event + Sub Close() + if CurrentStage = Stage.Cancelled then + for each f as FolderItem in DeleteOnCancel + Kaju.DeleteRecursive( f ) + next + end if + + for each f as FolderItem in DeleteOnClose + Kaju.DeleteRecursive( f ) + next + + End Sub + #tag EndEvent + + #tag Event + Sub Open() + #if not TargetMacOS then + // + // Switch the buttons around for other platforms + // + dim farLeft as integer = btnCancel.Left + btnCancel.Left = btnOK.Left + btnOK.Left = farLeft + + const kAddition = 10 + + btnCancel.Height = btnCancel.Height + kAddition + btnOK.Height = btnOK.Height + kAddition + btnSkipVersion.Height = btnSkipVersion.Height + kAddition + + // + // Make the pop-up menu bigger + // + pumUpdates.Height = pumUpdates.Height + kAddition + + self.Height = self.Height + kAddition + + #endif + + End Sub + #tag EndEvent + + #tag Event + Sub Paint(g As Graphics, areas() As REALbasic.Rect) + // + // Draw a border around the release notes (Mac only) + // + + const kThickness = 1 + + g.DrawPicture BackgroundImage, 0, 0, g.Width, g.Height + + dim drawLeft as integer = hvNotes.Left - kThickness + dim drawTop as integer = hvNotes.Top - kThickness + dim drawRight as integer = hvNotes.Left + hvNotes.Width + dim drawBottom as integer = hvNotes.Top + hvNotes.Height + dim drawWidth as integer = drawRight - drawLeft + kThickness + dim drawHeight as integer = drawBottom - drawTop + kThickness + + g.PenHeight = kThickness + g.PenWidth = kThickness + + g.ForeColor = &c00000000 // Black + g.DrawRect drawLeft, drawTop, drawWidth, drawHeight + + #if RBVersion > 2012.02 then + #pragma unused areas + #endif + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Sub Cancel() + CurrentStage = Stage.Cancelled + + SelectedUpdate = nil + if Initiater <> nil then + Initiater.Cancel + Initiater = nil + end if + Kaju.CancelUpdate + + if hsSocket.IsConnected then + hsSocket.Disconnect + end if + + if shZipper.IsRunning then + shZipper.Close + end if + + if Checker.QuitOnCancelIfRequired and Checker.Result = Kaju.UpdateChecker.ResultType.RequiredUpdateAvailable then + quit + else + self.Close + end if + End Sub + #tag EndMethod + + #tag Method, Flags = &h0 + Sub ChooseUpdate(checker As Kaju.UpdateChecker, updates() As Kaju.UpdateInformation) + self.Checker = checker + CurrentStage = Stage.ChoosingUpdate + + // + // Sort the updates + // + dim vers() as double + for i as integer = 0 to updates.Ubound + vers.Append updates( i ).VersionAsDouble + next + + vers.SortWith( updates ) + + // + // Reverse the sort + // + dim reverse() as integer + for i as integer = 0 to updates.Ubound + reverse.Append( updates.Ubound - i ) + next + + reverse.SortWith( updates ) + + // + // Set up the labels + // + + if Updates.Ubound = 0 then + lblMain.Text = kMainNoticeOne + lblSecondary.Text = kSecondaryNoticeOne + lblSecondary.Text = lblSecondary.Text.ReplaceAll( kNewVersionMarker, Updates( 0 ).Version ) + else + lblMain.Text = kMainNoticeMultiple + lblSecondary.Text = kSecondaryNoticeMultiple + end if + + lblMain.Text = lblMain.Text.ReplaceAll( kAppMarker, AppName ) + lblSecondary.Text = lblSecondary.Text.ReplaceAll( kThisVersionMarker, Kaju.AppVersionString ) + + // + // Set up the menu with the available updates. + // It will set up the rest of the controls. + // + + for i as integer = 0 to Updates.Ubound + dim update as Kaju.UpdateInformation = updates( i ) + pumUpdates.AddRow update.Version + pumUpdates.RowTag( i ) = update + next + + pumUpdates.ListIndex = 0 + + if updates.Ubound = 0 then + // + // Only one update so hide the menu + // + lblVersions.Visible = false + pumUpdates.Visible = false + end if + + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub DisplayVersionInfo(update As Kaju.UpdateInformation) + // + // Fill in the viewer + // + + self.Loading = true + + // + // Get the background picture, if any + // + dim useTransparency as boolean = update.UseTransparency + dim p as Picture = update.Image + if p is nil then + p = Checker.DefaultImage + useTransparency = Checker.DefaultUseTransparency + end if + + if p <> nil and useTransparency then + dim faded as new Picture( p.Width, p.Height, 32 ) + faded.Transparent = Picture.TransparentWhite + + const kTransparencyPercent = 50.0 + #if TargetWin32 then + if App.UseGDIPlus then + #endif + faded.Graphics.Transparency = kTransparencyPercent + #if TargetWin32 then + end if + #endif + + faded.Graphics.DrawPicture( p, 0, 0 ) + + #if TargetWin32 then + if App.UseGDIPlus then + #endif + dim mask as new Picture( p.Width, p.Height ) + mask.Graphics.DrawPicture( p.Mask, 0, 0 ) + faded.Mask = mask + #if TargetWin32 then + end if + #endif + + p = faded + end if + + self.BackgroundImage = p + + // + // Show the release notes + // + dim source as string = update.ReleaseNotes + if source = "" then + source = "NO UPDATE INFORMATION" + end if + + static tempFile as FolderItem = GetTemporaryFolderItem + hvNotes.LoadPage( source, tempFile ) + + #if DebugBuild then + if not tempFile.Exists then + break + end if + #endif + + // + // hvNotes.CancelLoad will set self.Loading back to false + // + + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub Show() + // Override super's show + + super.Show + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ShowError(msg As String = "") + if msg.Trim = "" then + msg = kGenericErrorMessage + end if + + lblInstallMessage.Visible = true + lblInstallMessage.Text = msg + pbProgress.Visible = false + + btnOK.Enabled = false + btnCancel.Caption = kTryLaterButton + btnSkipVersion.Visible = false + + CurrentStage = Stage.UpdateError + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ShowModal() + // Override super's ShowModal + + // Do nothing + End Sub + #tag EndMethod + + #tag Method, Flags = &h21 + Private Sub ShowModalWithin(parentWindow As Window) + // Ovverride super's ShowModalWithin + + // Do nothing + + #pragma unused parentWindow + End Sub + #tag EndMethod + + + #tag ComputedProperty, Flags = &h0 + #tag Getter + Get + if mAppName = "" then + mAppName = kThisApplication + end if + + return mAppName + End Get + #tag EndGetter + #tag Setter + Set + mAppName = value + End Set + #tag EndSetter + AppName As String + #tag EndComputedProperty + + #tag ComputedProperty, Flags = &h21 + #tag Getter + Get + return mBackgroundImage + + End Get + #tag EndGetter + #tag Setter + Set + mBackgroundImage = value + self.Invalidate + End Set + #tag EndSetter + Private BackgroundImage As Picture + #tag EndComputedProperty + + #tag Property, Flags = &h21 + Private Checker As Kaju.UpdateChecker + #tag EndProperty + + #tag Property, Flags = &h21 + Private CurrentStage As Stage + #tag EndProperty + + #tag Property, Flags = &h21 + #tag Note + Files/folders that shoudl be deleted if the user cancelled + #tag EndNote + Private DeleteOnCancel() As FolderItem + #tag EndProperty + + #tag Property, Flags = &h21 + #tag Note + Files/Folders that should be deleted upon window close + #tag EndNote + Private DeleteOnClose() As FolderItem + #tag EndProperty + + #tag Property, Flags = &h21 + Private DownloadFile As FolderItem + #tag EndProperty + + #tag Property, Flags = &h21 + Private Initiater As Kaju.UpdateInitiater + #tag EndProperty + + #tag Property, Flags = &h21 + Private Loading As Boolean + #tag EndProperty + + #tag Property, Flags = &h21 + Private mAppName As String + #tag EndProperty + + #tag Property, Flags = &h21 + Private mBackgroundImage As Picture + #tag EndProperty + + #tag Property, Flags = &h0 + SelectedUpdate As Kaju.UpdateInformation + #tag EndProperty + + + #tag Constant, Name = kAppMarker, Type = String, Dynamic = False, Default = \"<>", Scope = Private + #tag EndConstant + + #tag Constant, Name = kBadDownloadMessage, Type = String, Dynamic = False, Default = \"The downloaded file appears to be corrupted.", Scope = Private + #tag EndConstant + + #tag Constant, Name = kCancelButton, Type = String, Dynamic = False, Default = \"&Cancel", Scope = Private + #tag EndConstant + + #tag Constant, Name = kDownloadingMessage, Type = String, Dynamic = False, Default = \"Downloading...", Scope = Private + #tag EndConstant + + #tag Constant, Name = kDryRunMessage, Type = String, Dynamic = False, Default = \"(Dry run\x2C not really installing)", Scope = Private + #tag EndConstant + + #tag Constant, Name = kGenericErrorMessage, Type = String, Dynamic = False, Default = \"An error has occurred.", Scope = Private + #tag EndConstant + + #tag Constant, Name = kInstallButton, Type = String, Dynamic = False, Default = \"Install Update", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMainNoticeMultiple, Type = String, Dynamic = False, Default = \"New versions of <> are available!", Scope = Private + #tag EndConstant + + #tag Constant, Name = kMainNoticeOne, Type = String, Dynamic = False, Default = \"A new version of <> is available!", Scope = Private + #tag EndConstant + + #tag Constant, Name = kNewVersionMarker, Type = String, Dynamic = False, Default = \"<>", Scope = Private + #tag EndConstant + + #tag Constant, Name = kPaymentRequiredMessage, Type = String, Dynamic = False, Default = \"This update is not free and will require payment. Proceed anyway\?", Scope = Private + #tag EndConstant + + #tag Constant, Name = kProceedButton, Type = String, Dynamic = False, Default = \"Proceed", Scope = Private + #tag EndConstant + + #tag Constant, Name = kProcessingFileMessage, Type = String, Dynamic = False, Default = \"Processing file...", Scope = Private + #tag EndConstant + + #tag Constant, Name = kQuitButton, Type = String, Dynamic = False, Default = \"Quit && Install", Scope = Private + #tag EndConstant + + #tag Constant, Name = kReadyMessage, Type = String, Dynamic = False, Default = \"Ready to install", Scope = Private + #tag EndConstant + + #tag Constant, Name = kReleaseNotesLabel, Type = String, Dynamic = False, Default = \"Release Notes:", Scope = Private + #tag EndConstant + + #tag Constant, Name = kRemindMeLaterButton, Type = String, Dynamic = False, Default = \"Remind Me Later", Scope = Private + #tag EndConstant + + #tag Constant, Name = kSecondaryNoticeMultiple, Type = String, Dynamic = False, Default = \"You have version <> and there are multiple updates available. Install one\?", Scope = Private + #tag EndConstant + + #tag Constant, Name = kSecondaryNoticeOne, Type = String, Dynamic = False, Default = \"You have version <>. Would you like to install version <>\?", Scope = Private + #tag EndConstant + + #tag Constant, Name = kSkipVersionButton, Type = String, Dynamic = False, Default = \"Skip Version", Scope = Private + #tag EndConstant + + #tag Constant, Name = kStopButton, Type = String, Dynamic = False, Default = \"Stop", Scope = Private + #tag EndConstant + + #tag Constant, Name = kThisApplication, Type = String, Dynamic = False, Default = \"this application", Scope = Private + #tag EndConstant + + #tag Constant, Name = kThisVersionMarker, Type = String, Dynamic = False, Default = \"<>", Scope = Private + #tag EndConstant + + #tag Constant, Name = kTryLaterButton, Type = String, Dynamic = False, Default = \"Try Later", Scope = Private + #tag EndConstant + + #tag Constant, Name = kVersionsLabel, Type = String, Dynamic = False, Default = \"Available Versions:", Scope = Private + #tag EndConstant + + #tag Constant, Name = kWindowTitle, Type = String, Dynamic = False, Default = \"Update Available", Scope = Private + #tag EndConstant + + + #tag Enum, Name = Stage, Type = Integer, Flags = &h21 + ChoosingUpdate + InstallingUpdate + WaitingToQuit + UpdateError + Cancelled + #tag EndEnum + + +#tag EndWindowCode + +#tag Events hvNotes + #tag Event + Function NewWindow() As HTMLViewer + return hvNewWindow + End Function + #tag EndEvent + #tag Event + Function CancelLoad(URL as String) As Boolean + dim r as boolean = not Loading + Loading = false + return r + + #pragma unused URL + End Function + #tag EndEvent + #tag Event + Sub Error(errorNumber as Integer, errorMessage as String) + break + + #pragma unused errorNumber + #pragma unused errorMessage + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnOK + #tag Event + Sub Action() + select case CurrentStage + case Stage.ChoosingUpdate + // + // The update has been chosen + // + + if true then // Scope + dim chosen as Kaju.UpdateInformation = pumUpdates.RowTag( pumUpdates.ListIndex ) + if chosen.RequiresPayment then + dim dlg as new MessageDialog + dlg.ActionButton.Visible = true + dlg.ActionButton.Caption = kProceedButton + dlg.CancelButton.Visible = true + dlg.Message = kPaymentRequiredMessage + dim btn as MessageDialogButton = dlg.ShowModalWithin( self ) + + if btn is dlg.CancelButton then + return + end if + end if + SelectedUpdate = chosen + end if + + btnOK.Enabled = false + + btnCancel.Caption = kStopButton + + btnSkipVersion.Visible = false + pbProgress.Visible = true + lblInstallMessage.Visible = true + + pumUpdates.Enabled = false + + CurrentStage = Stage.InstallingUpdate + + if Checker.DryRun then + + lblInstallMessage.Text = kDryRunMessage + + else + + lblInstallMessage.Text = kDownloadingMessage + + dim tempFolder as FolderItem = Kaju.GetTemporaryFolder + DeleteOnCancel.Append tempFolder + + DownloadFile = tempFolder.Child( SelectedUpdate.PlatformBinary.FileName ) + DeleteOnClose.Append DownloadFile + + dim url as string = SelectedUpdate.PlatformBinary.URL + + // + // Check for redirection + // + url = hsSocket.GetRedirectAddress( url, 5 ) + + // + // Start the download + // + hsSocket.Get( url, DownloadFile ) + + end if + + case Stage.WaitingToQuit + // + // The user chose Quit & Install + // + + Kaju.StartUpdate( self.Initiater ) + + // + // Move this window to the back + // + dim lastWindowIndex as integer = WindowCount - 1 + if not( Window( lastWindowIndex ) Is self ) then + dim showIndex as integer = lastWindowIndex + for windowIndex as integer = lastWindowIndex downto 0 + dim w as Window = Window( showIndex ) + if w Is self then + showIndex = showIndex - 1 + else + w.Show + end if + next + end if + + Quit + + end + + + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnCancel + #tag Event + Sub Action() + self.Cancel + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnSkipVersion + #tag Event + Sub Action() + // + // We can only ignore versions if we already have the minimum requried + // + + dim info as Kaju.UpdateInformation = pumUpdates.RowTag( pumUpdates.ListIndex ) + + if info.MinimumRequiredVersion <> "" and _ + Kaju.VersionToDouble( Kaju.AppVersionString ) < Kaju.VersionToDouble( info.MinimumRequiredVersion ) then + + MsgBox "You cannot skip versions until you have updated to version " + info.MinimumRequiredVersion + " or beyond." + + else + + Checker.IgnoreVersion( info.Version ) + + if pumUpdates.ListCount = 1 then + SelectedUpdate = nil + Kaju.CancelUpdate + self.Close + else + pumUpdates.RemoveRow( pumUpdates.ListIndex ) + pumUpdates.ListIndex = 0 + end if + + end if + End Sub + #tag EndEvent +#tag EndEvents +#tag Events hsSocket + #tag Event + Sub ReceiveProgress(bytesReceived as integer, totalBytes as integer, newData as string) + if CurrentStage = Stage.Cancelled then + // + // Do nothing + // + return + end if + + pbProgress.Maximum = totalBytes + pbProgress.Value = bytesReceived + + + #pragma unused newData + End Sub + #tag EndEvent + #tag Event + Sub DownloadComplete(url as string, httpStatus as integer, headers as internetHeaders, file as folderItem) + if CurrentStage = Stage.Cancelled then + // + // Do nothing + // + return + end if + + pbProgress.Maximum = -1 + pbProgress.Value = 0 + + if httpStatus <> 200 then + + ShowError() + + elseif Kaju.HashOfFile( file ) <> SelectedUpdate.PlatformBinary.Hash then + + ShowError( kBadDownloadMessage ) + + else + // + // We have the file and it appears to be good + // + + lblInstallMessage.Text = kProcessingFileMessage + + dim targetFolder as FolderItem + #if TargetWin32 then + dim targetFolderName as string = SelectedUpdate.AppName + "- decompressed" + targetFolder = App.ExecutableFile.Parent + targetFolder = targetFolder.Child( targetFolderName ) + Kaju.DeleteRecursive( targetFolder ) + #else + targetFolder = file.Parent.Child( "decompressed" ) + #endif + DeleteOnCancel.Append targetFolder + shZipper.Decompress( file, targetFolder ) + end if + + + #pragma unused url + #pragma unused headers + End Sub + #tag EndEvent +#tag EndEvents +#tag Events shZipper + #tag Event + Sub DecompressCompleted(zipFile As FolderItem, containingFolder As FolderItem) + // + // No matter what, we don't need the zip file anymore + // + zipFile.Delete + + if CurrentStage = Stage.Cancelled then + // + // Do nothing + // + return + end if + + dim cnt as integer = containingFolder.Count + + if cnt = 0 or SelectedUpdate is nil then + + ShowError() + + else + + // + // Delete .DS_Store files + // + #if not TargetMacOS then + Kaju.DeleteDSStoreRecursive( containingFolder ) + #endif + + // + // Find the executable + // + dim item as FolderItem + for i as integer = 1 to containingFolder.Count + dim f as FolderItem = containingFolder.Item( i ) + dim name as string = f.Name + dim leftChars as string = name.Left( 1 ) + if leftChars <> "." and leftChars <> "_" then + item = f + exit + end if + next + + if item is nil then + + ShowError() + + else + + Initiater = new Kaju.UpdateInitiater + Initiater.ReplacementAppFolder = item + Initiater.ReplacementExecutableName = SelectedUpdate.PlatformBinary.ExecutableName + + btnOK.Enabled = true + btnOK.Caption = kQuitButton + btnCancel.Visible = true + btnCancel.Caption = kCancelButton + + pbProgress.Visible = false + + lblInstallMessage.Visible = true + lblInstallMessage.Text = kReadyMessage + + CurrentStage = Stage.WaitingToQuit + + end if + + end if + + End Sub + #tag EndEvent + #tag Event + Sub Error() + if CurrentStage = Stage.Cancelled then + // + // Do nothing + // + return + end if + + ShowError() + End Sub + #tag EndEvent +#tag EndEvents +#tag Events hvNewWindow + #tag Event + Function CancelLoad(URL as String) As Boolean + ShowURL( URL ) + return true + + End Function + #tag EndEvent +#tag EndEvents +#tag Events pumUpdates + #tag Event + Sub Change() + // + // Fill in the viewer + // + + if me.ListIndex = -1 then + if me.ListCount <> 0 then + me.ListIndex = 0 + end if + return + end if + + dim update as Kaju.UpdateInformation = me.RowTag( me.ListIndex ) + DisplayVersionInfo( update ) + + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="AppName" + Group="Behavior" + Type="String" + EditorType="MultiLineEditor" + #tag EndViewProperty + #tag ViewProperty + Name="BackColor" + Visible=true + Group="Appearance" + InitialValue="&hFFFFFF" + Type="Color" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Appearance" + Type="Picture" + EditorType="Picture" + #tag EndViewProperty + #tag ViewProperty + Name="CloseButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Composite" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Frame" + Visible=true + Group="Appearance" + InitialValue="0" + Type="Integer" + EditorType="Enum" + #tag EnumValues + "0 - Document" + "1 - Movable Modal" + "2 - Modal Dialog" + "3 - Floating Window" + "4 - Plain Box" + "5 - Shadowed Box" + "6 - Rounded Window" + "7 - Global Floating Window" + "8 - Sheet Window" + "9 - Metal Window" + "10 - Drawer Window" + "11 - Modeless Dialog" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="FullScreen" + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="FullScreenButton" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackColor" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Position" + InitialValue="400" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="ImplicitInstance" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Interfaces" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="LiveResize" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MacProcID" + Visible=true + Group="Appearance" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MaxHeight" + Visible=true + Group="Position" + InitialValue="32000" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MaximizeButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MaxWidth" + Visible=true + Group="Position" + InitialValue="32000" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBar" + Visible=true + Group="Appearance" + Type="MenuBar" + EditorType="MenuBar" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBarVisible" + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MinHeight" + Visible=true + Group="Position" + InitialValue="64" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MinimizeButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MinWidth" + Visible=true + Group="Position" + InitialValue="64" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Placement" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="Enum" + #tag EnumValues + "0 - Default" + "1 - Parent Window" + "2 - Main Screen" + "3 - Parent Window Screen" + "4 - Stagger" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="Resizeable" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Title" + Visible=true + Group="Appearance" + InitialValue="Untitled" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Position" + InitialValue="600" + Type="Integer" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Kaju Update Test v1.kaju b/Kaju Update Test v1.kaju new file mode 100644 index 0000000..c7c4eda --- /dev/null +++ b/Kaju Update Test v1.kaju @@ -0,0 +1,121 @@ +{ + "PrivateKey":"308204BB020100300D06092A864886F70D0101010500048204A5308204A10201000282010100D1DE526C8D98CCBFFDB4BD71487AC16205CF851696FB2910ABBC564BFEC1261A53A90794102BCC80EFB3CED3F8E73D90FF4C426D2315DE5E31A1A6C7563A21EADBD91B1DD637FAE0BED539C186BCB81DD865CC2A2F9427F717AA5E837C53AB90691569FC45EE17AF0ACD80E0C24C864EE86D4DBB7A6010E09B4E0BC556004E02980388C654A1C676A31E3AF788754E0CF7DEEC8236D55EDD5BB7490011B27CDEE5E254099FDE98C17D5F85014622D64C3BFB6A77200050FB2C8DF9A1ACEE50CF5A8353CE68304F91EC4F463E76BCF90A15152D03308B229FFE91E4906990D0E5F2E5C3ACC106E58DB1A37095DCBD5E233D7ED4A41AA263A73C54D4F12A113881020111028201002B354D3477815754B43BCCA661BEEB942E5F6E395B51D3BFAAE302B54B09A5F65C84AEBC99EAE656C7F0503AB33EAACB076286258739354FA0CE75290A392519C3D9DFEF8DFC776A81A45EB6E708BC7E9D7E5EBD6426083A66C131938A89B2617F0BF02C68C013EF550C311F370FC14C7B259000F38C3FB5C59F208A82A5B5B4DDCEF8E9E0B91F862F58B67963C5C7E13E048D8F26984632A3E18F4D9EE0049236F6F4AF272341F51DB8902F2C1E741140A2B6FF7C0334842EFB4EABFD265327237382BCBB6830CFC848E2C8353960A22CE8363B4F299D9B3065BC5C4489589C5AF4B8389E5BBB5E82C0CFBB40A03C832A407F6120A58D95BDA473B730CA7A7702818100DA9DB083837A18AD27CE7FC6A8762982B98939FB508E2104D1C1755E65D09A1D544FFDC4F11A78903B56DFD0A1237AB71D27417007A51DCE32E8E8EDD9295930FA78EEDC18370EBFA480E3670D5522DC8DFA8A94A553219C167E9C08D6442138BBD8972A5CD068E2CC95E9C82C6111909F41BD91E063DC82ACB5A62706ED73D302818100F5C1B189B236CB86DE7C44E34487A2D67ED1DE61E1ACC45049651B965F0F836918E2D65FA78696AF43F5E8DDCEB4958A772B8B9BE294C661CC4E24DE2AC1ACE046227C390ADA9CE10D47CC2F8B75B7AC1AB2BF979D0028122192DA3521D625DA58B1ADB8879D80DFD7AE281B9B08DD833EBAAC5F09638E4D3FA4FCBE119571DB0281804D2898A6E31C08B595942D18F029B44C417BBA1C76C8C05C0DCBCF12420D6391E185A4DC18DC2A8D421EA958B157D0F555956263C67682FD7B614326C51DA7023A48CCC626AA05349469B9ABE69684C6503A4F074968A273533BBE99B508FCAA9CA6CBF0D576BB9B5743F8288822426F474461064F324DD3C47C58C27AEA651D02818100D8D8151F24C6EFD15AE61EAA69A4DAF97EF569DDE53E16A131B390CFF98628A806E644AEC0FE48B8C37EA04B3DEAA210C380C67A7CA163BFB444F35A9E326B5C7A1E6D9BBE486C4E1AC6E15720B32997DB526CD1120F326A59DBEDB669265DA28A7EA85786B8266B27B7C9094C8F5A0A648698179EDF5F715646486B78ED46570281806AD89D03EF77E1E3507B3105D04DCA1DCF84CCC9B5B4AE02BC1084C20C476BDFCB2E2EF6A04F1AB0AC60CD42A2696487E00477D5C27435F1BA04CED16F741CC4713EE0DF7DD8BE0596A42A5995886D0C989AB8DA642CE0803F4C7BA4F36795025CFEF4B33423A4309309DE6EDB6F65AF71CD3A79CD67458A1067DE6A21067E83", + "PublicKey":"30820120300D06092A864886F70D01010105000382010D00308201080282010100D1DE526C8D98CCBFFDB4BD71487AC16205CF851696FB2910ABBC564BFEC1261A53A90794102BCC80EFB3CED3F8E73D90FF4C426D2315DE5E31A1A6C7563A21EADBD91B1DD637FAE0BED539C186BCB81DD865CC2A2F9427F717AA5E837C53AB90691569FC45EE17AF0ACD80E0C24C864EE86D4DBB7A6010E09B4E0BC556004E02980388C654A1C676A31E3AF788754E0CF7DEEC8236D55EDD5BB7490011B27CDEE5E254099FDE98C17D5F85014622D64C3BFB6A77200050FB2C8DF9A1ACEE50CF5A8353CE68304F91EC4F463E76BCF90A15152D03308B229FFE91E4906990D0E5F2E5C3ACC106E58DB1A37095DCBD5E233D7ED4A41AA263A73C54D4F12A113881020111", + "KajuData":[ + { + "Version":"1.1", + "ReleaseNotes":"

This is a fake update for v.1.1. It will really point to a binary of 1.0.<\/p>\n\r

In other words, the app will replace itself.<\/p>\n\n

NOTE<\/b>: The update will warn you that the update will require a payment. THIS IS A LIE!!<\/u> It’s only set that way to test the “Requires Payment” option.<\/p>\n\n

Changes<\/h3>\n
    \n
  • There really aren’t any.<\/li>\n
  • This is just here as a placeholder.<\/li>\n<\/ul>", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":true, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"1.2.2d5", + "ReleaseNotes":"

    A phony development version pointing to the same binary. Meant as a placeholder to demonstrate multiple versions.<\/p>", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"1.2.1a4", + "ReleaseNotes":"

    A phony alpha pointing to the same binary. Meant as a placeholder to demonstrate multiple versions.<\/p>", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"1.2b2", + "ReleaseNotes":"

    A phony beta pointing to the same binary. Meant as a placeholder to demonstrate multiple versions.<\/p>\n\n

    IMPORTANT<\/b>: The URL’s for the binaries have been intentionally switched to test the hash function. Attempting to update to this version should result in an error.<\/b>", + "ImageURL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Some_Image_beta.png", + "UseTransparency":true, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"F4BE059EE89F03A41B865CFAEE9E0593", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Linux.zip" + }, + "WindowsBinary":{ + "Hash":"F53D5BD5DCA41CFBFDB45C3A44304073", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Mac.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"636A88BC9A02A4B559922DEE233FF984", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"0.9", + "ReleaseNotes":"

    A fake release that should never appear. (Except in Preview.)<\/p>", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http:\/\/www.mactechnologies.com\/Kaju_Test\/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + } + ] +} \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..96289a4 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 by Kem Tekinay. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Platform Scripts/Kaju Linux Script.sh b/Platform Scripts/Kaju Linux Script.sh new file mode 100755 index 0000000..54d08f7 --- /dev/null +++ b/Platform Scripts/Kaju Linux Script.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +# +# FUNCTIONS +# + +function log_cmd { + /usr/bin/logger -t "Kaju Update Script" $@ +} + +# END FUNCTIONS + +# +# These will be filled in by the calling app +# + +APP_NAME=@@APP_NAME@@ +APP_PARENT=@@APP_PARENT@@ +NEW_APP_NAME=@@NEW_APP_NAME@@ +NEW_APP_PARENT=@@NEW_APP_PARENT@@ +TEMP_FOLDER_PATH=@@TEMP_FOLDER@@ +PID_FILE=@@PID_FILE_PATH@@ + +# +# This array will store the names of the items next to the executable +# under the variable NEW_APP_OTHER_NAME +# +NEW_APP_OTHER_UB=@@NEW_APP_OTHER_UB@@ + +@@NEW_APP_OTHER_ARRAY@@ + +# +# ----------------- +# + +readonly true=1 +readonly false=0 + +APP_PATH=$APP_PARENT/$APP_NAME + +BACKUP_PARENT=$APP_PARENT/${APP_NAME}-`date +%Y%m%d%H%M%S` +mkdir "$BACKUP_PARENT" + +log_cmd "STARTING UPDATE OF $APP_NAME" + +counter=10 +while [ -f "$PID_FILE" ] +do + log_cmd "Checking to see if $PIDFILE exists, $counter" + sleep 1 + + let counter=counter-1 + + if [ $counter == 0 ] + then + log_cmd 'ERROR: Could not update app, it never quit' + exit 1 + fi +done + +PROCEED=$true + +# +# Move the other items +# +log_cmd "Copying other items to backup $BACKUP_PARENT" + +counter=0 +while [ $counter -le $NEW_APP_OTHER_UB ] +do + this_item=${NEW_APP_OTHER_NAME[$counter]} + log_cmd "Looking for item $this_item in $APP_PARENT" + + this_path=$APP_PARENT/$this_item + if [ -d "$this_path" ] || [ -f "$this_path" ] + then + log_cmd "...found, copying" + cp -pr "$this_path" "$BACKUP_PARENT" + if [ $? == 0 ] + then + log_cmd "...confirmed" + else + log_cmd "...FAILED!" + PROCEED=$false + break + fi + fi + (( counter++ )) +done + +# +# Move the executable +# +if [ $PROCEED == $true ] +then + log_cmd "Moving the executable $APP_NAME to backup" + mv "$APP_PARENT/$APP_NAME" "$BACKUP_PARENT" + if [ $? == 0 ] + then + log_cmd "...confirmed" + else + log_cmd "...FAILED! (Error $?)" + PROCEED=$false + fi +fi + +# +# Make sure there wasn't an error during the backup +# +if [ $PROCEED == $true ] +then + log_cmd 'All items backed up' +else + log_cmd 'Attempting to copy items back to parent' + rsync -a --exclude='.DS_Store' "${BACKUP_PARENT}/" "$APP_PARENT" +fi + +# +# Move in the replacement files +# +if [ $PROCEED == $true ] +then + log_cmd "Copying files from $NEW_APP_PARENT to folder $APP_PARENT" + rsync -a --exclude='.DS_Store' "${NEW_APP_PARENT}/" "$APP_PARENT" + + if [ $? == 0 ] + then + log_cmd '...confirmed' + else + log_cmd "...FAILED! (Error $?)" + log_cmd "Attempting to restore old application" + rsync -a --exclude='.DS_Store' "${BACKUP_PARENT}/" "$APP_PARENT" + PROCEED=$false + break + fi +fi + +# +# Removed the backup folder if everything has gone swimmingly so +# +if [ $PROCEED == $true ] +then + log_cmd 'Removing backup' + rm -r "$BACKUP_PARENT" +fi + +# +# Launch the application +# +if [ $PROCEED = $true ] +then + log_cmd 'Making the new app executable' + chmod +x "$APP_PARENT/$NEW_APP_NAME" + log_cmd 'Launching new app' + "$APP_PARENT/$NEW_APP_NAME" +else + log_cmd 'Launching old app' + "$APP_PARENT/$APP_NAME" +fi + +log_cmd 'Removing temp folder' +rm -fr "$TEMP_FOLDER_PATH" diff --git a/Platform Scripts/Kaju Mac Script.sh b/Platform Scripts/Kaju Mac Script.sh new file mode 100755 index 0000000..ccef3ed --- /dev/null +++ b/Platform Scripts/Kaju Mac Script.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# +# FUNCTIONS +# + +function log_cmd { + /usr/bin/logger -t "Kaju Update Script" $@ +} + +# END FUNCTIONS + +# +# These will be filled in by the calling app +# + +APP_NAME=@@APP_NAME@@ +APP_PARENT=@@APP_PARENT@@ +NEW_APP_NAME=@@NEW_APP_NAME@@ +NEW_APP_PARENT=@@NEW_APP_PARENT@@ +TEMP_FOLDER_PATH=@@TEMP_FOLDER@@ +PID_FILE=@@PID_FILE_PATH@@ + +# +# ----------------- +# + +readonly true=1 +readonly false=0 + +APP_PATH=$APP_PARENT/$APP_NAME +NEW_APP_PATH=$NEW_APP_PARENT/$NEW_APP_NAME + +RENAMED_APP_NAME=`echo "$APP_NAME" | /usr/bin/sed -E s/\.[aA][pP]{2}//`-`date +%Y%m%d%H%M%S`.app +RENAMED_APP_PATH=$APP_PARENT/$RENAMED_APP_NAME + +log_cmd "STARTING UPDATE OF $APP_NAME" + +counter=10 +while [ -f "$PID_FILE" ] +do + log_cmd "Checking to see if $PIDFILE exists, $counter" + sleep 1 + + let counter=counter-1 + + if [ $counter == 0 ] + then + log_cmd 'ERROR: Could not update app, it never quit' + exit 1 + fi +done + +PROCEED=$true + +# +# Rename the old application +# +log_cmd "Renaming old application $APP_NAME to $RENAMED_APP_NAME" +mv "$APP_PATH" "$RENAMED_APP_PATH" + +# +# Make sure that succeeded +# +if [ $? == 0 ] +then + log_cmd '...confirmed' +else + log_cmd "Could not rename old application to $RENAMED_APP_PATH" + PROCEED=0 +fi + +# +# Move in the replacement app +# +if [ $PROCEED == $true ] +then + log_cmd "Moving new application $NEW_APP_PATH to folder $APP_PARENT" + mv "$NEW_APP_PATH" "$APP_PARENT" + + # + # Make sure that worked + # + if [ $? == 0 ] + then + log_cmd '...confirmed' + else + log_cmd "Could not move in new application" + log_cmd "Attempting to restore old application and launch it" + mv "$RENAMED_APP_PATH" "$APP_PATH" + open "$APP_PATH" + PROCEED=$false + fi +fi + +if [ $PROCEED == $true ] +then + log_cmd "Removing old application $RENAMED_APP_NAME" + rm -fr "$RENAMED_APP_PATH" + + APP_PATH=$APP_PARENT/$NEW_APP_NAME + log_cmd "Starting new application at $APP_PATH" + + open "$APP_PATH" +fi + +if [ $PROCEED == $true ] +then + log_cmd 'Removing temp folder' + rm -fr "$TEMP_FOLDER_PATH" +fi diff --git a/Platform Scripts/Kaju Windows Script.txt b/Platform Scripts/Kaju Windows Script.txt new file mode 100644 index 0000000..f721ced --- /dev/null +++ b/Platform Scripts/Kaju Windows Script.txt @@ -0,0 +1,165 @@ +@ECHO OFF + +REM +REM These will be filled in by the calling app +REM + +SET APP_NAME=@@APP_NAME@@ +SET APP_PARENT=@@APP_PARENT@@ +SET NEW_APP_NAME=@@NEW_APP_NAME@@ +SET NEW_APP_PARENT=@@NEW_APP_PARENT@@ +SET TEMP_FOLDER_PATH=@@TEMP_FOLDER@@ +SET DECOMPRESSED_FOLDER_PATH=@@DECOMPRESSED_FOLDER@@ +SET PID_FILE=@@PID_FILE_PATH@@ + +REM +REM ----------------- +REM + +SET TODAY_DATE=%DATE:~10,4%-%DATE:~4,2%-%DATE:~7,2% %TIME:~0,2%:%TIME:~3,2%:%TIME:~6,2% +SET APP_PATH=%APP_PARENT%\%APP_NAME% + +SET BACKUP_PARENT=%APP_PATH%-%DATE:~10,4%%DATE:~4,2%%DATE:~7,2%%TIME:~0,2%%TIME:~3,2%%TIME:~6,2% + +SET LOGGER=%APP_PARENT%\%NEW_APP_NAME% Update Log.txt +ECHO "STARTED ON %TODAY_DATE%" >> "%LOGGER%" 2>&1 + +FOR /L %%i IN (1,1,10) DO ( + IF NOT EXIST "%PID_FILE%" ( + GOTO :program_exited + ) + + REM Windows version of sleep 1. Starting in Windows Vista, the sleep command was removed. + ping -n 2 127.0.0.1 >nul + + IF %%i == 10 ( + ECHO ERROR: Could not update app, it never quit >> "%LOGGER%" 2>&1 + EXIT /B 1 + ) +) +:program_exited + +mkdir "%BACKUP_PARENT%" + +SET PROCEED=1 + +REM +REM Move the other items +REM +ECHO "Copying items to backup %BACKUP_PARENT%" >> "%LOGGER%" 2>&1 + +REM We will need to manually populate these move commands. Windows Batch doesn't really handle arrays, +REM only looping through space delimited elements of a string. Below is a template for moving one such file. + +REM BEGIN PSEUDO-ARRAY +SET THIS_ITEM=@@OTHER_NAME@@ +SET THIS_PATH=%APP_PARENT%\%THIS_ITEM% +ECHO "Looking for item %THIS_PATH%" >> "%LOGGER%" 2>&1 +IF EXIST "%THIS_PATH%" ( + GOTO :copy_@@OTHER_NAME_WO_SPACES@@ +) +ECHO "...not found as file, trying as directory" >> "%LOGGER%" 2>&1 +IF EXIST "%THIS_PATH%\NUL" ( + GOTO :copy_@@OTHER_NAME_WO_SPACES@@ +) ELSE ( + ECHO "...NOT FOUND!" >> "%LOGGER%" 2>&1 + GOTO :finished_with_@@OTHER_NAME_WO_SPACES@@ +) + +:copy_@@OTHER_NAME_WO_SPACES@@ + +ECHO "...found, copying" >> "%LOGGER%" 2>&1 +COPY "%THIS_PATH%" "%BACKUP_PARENT%" >> "%LOGGER%" 2>&1 +IF %ERRORLEVEL% NEQ 0 ( + ECHO "...FAILED! (Error %ERRORLEVEL%)" >> "%LOGGER%" 2>&1 + SET PROCEED=0 + GOTO :restore_from_backup +) ELSE ( + ECHO "...confirmed" >> "%LOGGER%" 2>&1 +) + +:finished_with_@@OTHER_NAME_WO_SPACES@@ + +REM END PSEUDO-ARRAY + +REM +REM Move the executable to backup +REM +IF %PROCEED% == 1 ( + ECHO "Moving the executable %APP_NAME% to backup" >> "%LOGGER%" 2>&1 + MOVE "%APP_PATH%" "%BACKUP_PARENT%" >> "%LOGGER%" 2>&1 + IF %ERRORLEVEL% NEQ 0 ( + ECHO "...FAILED! (Error %ERRORLEVEL%)" >> "%LOGGER%" 2>&1 + SET PROCEED=0 + GOTO :restore_from_backup + ) ELSE ( + ECHO "...confirmed" >> "%LOGGER%" 2>&1 + ) +) + +REM +REM Make sure there wasn't an error during the move +REM +IF %PROCEED% == 1 ( + ECHO "All items moved to backup" >> "%LOGGER%" 2>&1 +) + +REM +REM Copy in the replacement files +REM + +IF %PROCEED% == 1 ( + ECHO "Copying files from %NEW_APP_PARENT% to folder %APP_PARENT%" >> "%LOGGER%" 2>&1 + XCOPY /y /e /k "%NEW_APP_PARENT%" "%APP_PARENT%" >> "%LOGGER%" 2>&1 + IF %ERRORLEVEL% NEQ 0 ( + ECHO "...FAILED! (Error %ERRORLEVEL%)" >> "%LOGGER%" 2>&1 + SET PROCEED=0 + GOTO :restore_from_backup + ) ELSE ( + ECHO "...confirmed" >> "%LOGGER%" 2>&1 + ) +) + +REM +REM If we get here, it all worked +REM +IF %PROCEED% == 1 ( + GOTO :all_succeeded +) + +:restore_from_backup +IF %PROCEED% == 0 ( + ECHO "Attempting to restore old application" >> "%LOGGER%" 2>&1 + + XCOPY /y /e /k "%BACKUP_PARENT%" "%APP_PARENT%" >> "%LOGGER%" 2>&1 + IF %ERRORLEVEL% EQU 0 ( + RMDIR /S /Q "%BACKUP_PARENT%" >> "%LOGGER%" 2>&1 + ) +) +GOTO :launch_application + +:all_succeeded +REM +REM Remove the backup and decompressed folders if everything has gone swimmingly so far +REM +IF %PROCEED% == 1 ( + ECHO "Removing backup" >> "%LOGGER%" 2>&1 + RMDIR /S /Q "%BACKUP_PARENT%" >> "%LOGGER%" 2>&1 + ECHO "Removing decompressed folder" >> "%LOGGER%" 2>&1 + RMDIR /S /Q "%DECOMPRESSED_FOLDER_PATH%" >> "%LOGGER%" 2>&1 +) + +REM +REM Launch the application +REM +:launch_application +IF %PROCEED% == 1 ( + ECHO "Launching new app" >> "%LOGGER%" 2>&1 + START "" "%APP_PARENT%\%NEW_APP_NAME%" +) ELSE ( + ECHO "Launching old app" >> "%LOGGER%" 2>&1 + START "" "%APP_PATH%" +) + +ECHO "Removing temp folder" >> "%LOGGER%" 2>&1 +RMDIR /S /Q "%TEMP_FOLDER_PATH%" >> "%LOGGER%" 2>&1 diff --git a/README.md b/README.md index 83708fd..b79f66f 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,304 @@ -# README # +# README -A Xojo module and Admin app to enable self-update in Xojo apps. +A Xojo module and Admin app to enable self-updating Xojo apps. -## Specifications ## +## General Information -### General Information ### +Kaju is a pull system where the client application gets information from a known URL. It is designed as a series of Xojo classes in a central module and a Window. It is meant to be a single, turnkey solution with minimal involvement by you. In other words, set it up, write the code to call it, then forget about it. -Kaju is a pull system where the client gets information from known url. It is designed as a series of Xojo classes. +## How To Use It -The update JSON file will be created by the admin app and placed in a folder that indicates the app name and version, or whatever other identification that's appropriate for an update line, and will create a unique URL. For example, the resulting url might be `http://www.example.com/updates/myapp5/index.html` for updates related to version 5, and `http://www.example.com/updates/myapp6/index.html` for the version 6 line. The update information in version 5 can include a link to the version 6 binary and set the "Requires Payment" flag to true so version 5 users can know that there is a new version that they will be asked to pay for. The user can still choose to ignore that and continue updating version 5 instead. +### Installation -We recommend that the latest version of any line use a static url. That way, if the user chooses to update to new, paid version, they will always get the latest. +Open the included Admin App or Test App, copy the Kaju Classes folder, then paste it into your project. -The JSON will contain all information about every available version for that line. For example, there might be a release 6.0.1, but a beta 6.1b4 and an alpha 6.2a8. The client will get that information and will determine which versions are appropriate to present to the user. +### Special Actions -The client will have the server's public RSA key. The server will use that key to sign the JSON and provide the signature for the binary. Each line should have its own private key. +You need to add one property to your App class, `UpdateInitiater As Kaju.UpdateInitiater`. Kaju expects to find that and will handle it for you. -### JSON Specs ### +The only special code you'll need is in the `CancelClose` event of any window where the close is actually being cancelled, i.e., where you have the event return `True`. In those cases, you must call `Kaju.CancelUpdate`. (It doesn't matter if there is an update scheduled at the time.) This will prevent an update from happening if the user quits later without choosing Quit & Install again. That code should look something like this: -The JSON will contains the following fields for each version. +```Xojo +Event CancelClose(appQuitting As Boolean) As Boolean + // You've determined that you need to cancel the + // close, so... + if appQuitting then // This "if" is not strictly necessary + Kaju.CancelUpdate + end if + return true +End Event +``` - App Name (string) - Version (string) - Is Release (bool) - Is Required (bool, only if Is Release) - Requires Payment (bool) - Display Notes (string) - Binary Signature (string) - Mac URL (string) - Windows URL (string) - Linux URL (string) +### Implementation + +Create a new `Kaju.UpdateChecker` instance and fill in its properties. In the `Constructor`, you have to provide a FolderItem for a folder where Kaju can save its preferences, one that is unique to your app. At the least, you must also set the `ServerPublicRSAKey` (more on this later) and the `UpdateURL` where it will get its update information. If that URL (or any URL) starts with "https:", it will accessed securely. (Conversely, a URL that does not start with "https:" will be accessed normally.) + +Call the `Execute` method of the `Kaju.UpdateChecker`. That's it. Kaju will handle everything else by going to the `UpdateURL` to see if there are any updates available for that version of the app, then ask the user about them. If the user chooses to update, the class will download and verify the binary, then offer the user the opportunity to Quit & Install or Cancel. If they choose to install, Quit will be called. + +Since none of this is modal, the user can continue to use your app with the update window waiting in the background. If they do choose to install, the update window will be sent to the back so it will be closed last. + +To discover what `UpdateChecker` found, you can check the `Result` method after calling `Execute`. It returns a value from the `Kaju.UpdateChecker.ResultType` enum. + +### Minimum OS + +Kaju will work the same way on Mac, Windows, and Linux. All recent versions of MacOS and Linux should be supported. Windows Vista and later are supported. + +If Kaju cannot find the tools it needs, the `Result` will be set to `UnsupportedOS` after you call `Execute`. + +## What Else? + +### Required Updates + +If you set up a minimim required version in your update information, Kaju may find that a particular update is "required". For example, if the user is using v.1.0 and you've discovered a bug that necessitates an update to at least v.1.1, you would set that as the minimum required version. In the future, even as you release v.1.2, 1.3, etc, you would leave the minimum required as v.1.1 so Kaju knows to force the users of 1.0 to update. + +After calling `Kaju.UpdateChecker.Execute`, the `Result` method will tell you if a required update was found. In that case, it's up to you to take special actions to make sure that your app cannot be used until it is updated. To help, there is the `Kaju.UpdateChecker.QuitOnCancelIfRequired` property that is `True` by default. If the user tries to cancel a required update, the app will quit. + +### Other Features + +There are other features of `Kaju.UpdateChecker` that may be helpful. You can control the interface that will be presented to the user through the `AllowedInteraction` property. You can prevent Kaju from showing an error dialog, the update window, or both. Use the constants within `Kaju.UpdateChecker` to set this value. Values can be added together for better control (although, as of this writing, there are just the two values). + +You can control the types of updates a user may see by setting `Kaju.UpdateChecker.AllowedStage`. The stage codes are the same as those found in the `App` class, so setting that property to `App.Final` means that the user will not see any development, alpha, or beta releases. + +If an update was found, the update window will have opened. You can check through the `UpdateWindowIsOpen` property at any time. + +The user can choose to ignore certain versions as long as they are not marked as required. You can set `HonorIgnored` to `False` to bypass that temporarily and present even ignored versions to your user, or you can clear the database of ignored versions entirely with the `ResetIgnored` method. + +You may choose to specify an update URL that redirects to another location. By default, Kaju will not allow that, but if you really need to do it, set the `Kaju.UpdateChecker.AllowRedirection` property to `True`. + +### One At A Time + +You can create several instances of `Kaju.UpdateChecker` if you'd like, but only one update can run at any time. If an update is already in progress, `Execute` won't do anything and will let you know through the `Result`. + +### Images + +You can set a background image for the window in two places. Within the app, you may set `Kaju.UpdateChecker.DefaultImage` for any updates that do not specify a specific image. In the Admin app, you may specify a URL to an image for a particular version. For example, if the default image is your app's logo, but a beta should have "beta" stamped across it, you can do that. + +You can also set `Kaju.UpdateChecker.DefaultUseTransparency` or set "Use Transparency" for an image provided through the Admin app. Transparency is set to 50% when `True`. + +An image will cover the entire window without cropping or scaling starting at the upper, left corner. Accordingly, you can provide an entire background image or just an icon. + +### Limitations + +Kaju does not elevate permissions. If the user does not have write permission for the executable or its parent, `ResultType.NoWritePermission` will be returned in `Kaju.UpdateChecker.Result`. + +### Platform Differences + +Kaju will act the same way across plaforms except for one point: Since the Mac executable is always one package, it will be replaced entirely. Anything stored within the package that was put there after initial installation will be deleted. + +On Windows and Linux, since executable folders can be combined, only the files that are found in the update will be replaced. Any additional files or even older, no-longer-used files, will remain untouched. If you want to make sure some older file is removed by the update, put an empty placeholder into the update package. + +## Step By Step + +* Copy the Kaju Classes folder into your project. +* Add to your App instance the property `UpdateInitiater As Kaju.UpdateInitiater`. You do not need to do anything more with this property. +* Run the Kaju Admin app through the included project and save a new document with an appropriate name, something like "MyApp v.1.kaju". You don't have to add any updates at this time. +* Copy the RSA public key with the appropriate button. A key pair is generated every time a new document is created and it is this key that will ensure that your app is getting legitimate, uncorrupted update information. **Do not lose this file after releasing your app!** If you do, users of older versions will no longer be able to update. +* In an appropriate place within your project, add code that looks something like this: + +```Xojo +dim updater as new Kaju.UpdateChecker( myAppPrefFolder ) +updater.ServerPublicRSAKey = "12345..." // The key you copied from the Admin app +updater.UpdateURL = "http://www...." // Where the update info will be posted + +updater.Execute +``` + +At a bare minimum, that's it. + +## The Admin App + +The included Admin app makes it easy to set up your update file. Start it up and use the "+" at the bottom, left to add a version. Fill in the information for the release. + +When you're done, save the file, then export the HTML data. It is this exported file that you will post to your web site and the final URL should match the URL you included within your app. + +### Some Details + +Add an entry for each *current* version of your app. You do not need a history since a user wants to update to the latest and does not need to see every intermediate version (unless there is a reason that you want that). + +The release notes are created in HTML and some simple tools are provided for making that a bit easier. You can see a preview of the release notes as a you type and use the Preview button to see how Kaju will present the update window under various circumstances. The HTML can be as simple or as complex as you'd like. + +**Note**: WebKit is used on all platforms to ensure consistency. This will increase the size of your project on Windows and Linux. + +### Links In Release Notes + +Links in the release notes will be ignored *unless* you include `target="_blank"` as part of the URL. For example, `my site` will force the link to open in the user's browser. + +### Binaries + +Your compiled apps for each platform must be zipped and named appropriately. For the Mac, zip the application. For the other platforms, zip the folder that contains the executable and supporting folders. + +For each version in your admin file, check each platform to which it applies, provide the URL where that binary will be found, then drop each binary onto the appropriate field to calculate its hash. You can also post the binary to your web site, enter the URL, then use the button to calculate the hash from the URL. (It will download the binary, calculate the hash, then delete it.) + +**Note**: If the URL to a binary starts with "https:", a secure connection will be used automatically. + +For Windows and Linux, you must also provide the exact name of the executable. If your app is called "My Great App", the Linux executable name will be "My Great App" and the Windows name will be "My Great App.exe". + +## JSON Specs + +The update information that you will post will be a JSON file that is generated by the Admin app as an HTML file. We recommend that each version line gets its own folder on your web server. + +The JSON will contain these fields for each version. + + AppName (string) + Version (string) + MinimumRequiredVersion (string) + RequiresPayment (bool) + ReleaseNotes (string) + MacBinary (dictionary) + URL (string) + Signature (string) + WindowsBinary (dictionary) + ExecutableName + URL (string) + Hash (string) + LinuxBinary (dictionary) + ExecutableName + URL (string) + Hash (string) + ImageURL (string) + UseTransparency (bool, default = true) A sample JSON that will be returned by the server: - [ - { - "App Name" : "My App" , +```JSON +[ + { + "AppName" : "My App" , "Version" : "6.0.2", - "Is Release" : true , - "Is Required" : false , - "Display Notes" : "The release notes" , - "Binary Signature" : "ABCDEF" , - "Mac URL" : "http://www.site.com/download_path_Mac" , - "Windows URL" : "http://www.site.com/download_path_Windows" - } , - { - "App Name" : "My App" , + "MinimumRequiredVersion" : "6.0.1" , + "ReleaseNotes" : "The release notes" , + "MacBinary" : + { + "URL" : "http://www.site.com/download_path_Mac" , + "Signature" : "ABCDEF" + } , + "WindowsBinary" : + { + "ExecutableName" : "My App.exe" , + "URL" : "http://www.site.com/download_path_Windows" , + "Signature" : "123456" + } , + "LinuxBinary" : + { + "ExecutableName" : "My App" , + "URL" : "http://www.site.com/download_path_Linux" , + "Hash" : "ABC123" + } , + "ImageURL" : "http://www.site.com/image.png" , + "UseTransparency" : true + } , + { + "AppName" : "My App" , "Version" : "6.1b4" , - "Is Release" : false , - "Display Notes" : "The beta release notes" , - "Binary Signature" : "0123456" , - "Mac URL" : "http://www.site.com/other_download_path" - } - ] + "ReleaseNotes" : "The beta release notes" , + "MacBinary" : + { + "URL" : "http://www.site.com/other_download_path" , + "Hash" :"0123456" + } + } +] +``` + +**NOTE**: The ExecutableName will be used by the updater script while the AppName is what will display in the updater window. These may be the same or different. Since the ExecutableName of the Mac app can be discovered, it is not needed for the Mac binary. + +The file will be prefixed by the signature of the JSON string in hex format. The entire file would look something like this: + +```JSON +KAJU ABCDEF +[ + +] +``` + +If an update does not apply to a particular platform, it will be missing binary information for that platform and will be ignored. + +The JSON will contain all information about every available version for that line. For example, there might be a release 6.0.1, a beta 6.1b4, and an alpha 6.2a8. Kaju will download all of that information and determine which ones are appropriate to present as options to the user. + +### "RequiresPayment" Flag + +The "RequiresPayment" flag can be used to warn users of paid updates. For example, if they are on v.5.1 of your app, you can include information about the paid v.6.0. (We recommend that future 6.0 updates be in their own line, i.e., their own Kaju file.) + +## General Recommendations + +We recommend that the latest version of any line use a static URL. For example, even if your app is at version 5.5, name the zipped file something like "My_App_5_Mac.zip" and place it in the My_App/v5/ folder on your web server. When you release version 6, name it "My_App_6_Mac.zip" and put it in the My_App/v6/ folder on the server. That way, no matter when the user chooses to update, the same URL will always lead them to the latest version. + +## The Classes + +There is only one class (`Kaju.UpdateChecker`) and one method in the Kaju module (`CancelUpdate`) that are of concern. The other classes and methods support these. + +**Class:** `Kaju.UpdateChecker` + +|Property|Type|Description|Required| +|:---|:---:|:---|---:| +|AllowedInteraction|UInt32|Determines what windows Kaju is allowed to display; Use the available constants|n| +|AllowedStage|Integer|What stage of updates the user may see (App.Final, App.Beta, App.Alpha, or App.Development)|n| +|AllowRedirection|Boolean|If `True`, the `UpdateURL` may redirect to another URL (default: `False`)|n| +|DefaultImage|Picture|The background image that will be displayed in the window when an image is not provided by the update|n| +|DefaultUseTransparency|Boolean|If `True`, transparency will be set to 50%|n| +|HonorIgnored|Boolean|If `False`, the user will be presented with updates they previously set to "ignore" (default: `True`)|n| +|QuitOnCancelIfRequired|Boolean|When `True` (default), canceling a required update will call Quit|n| +|ServerPublicRSAKey|String|The public key as found in the Admin app file|**yes**| +|UpdateURL|String|The URL where the update info will be found|**yes**| +|UpdateWindowIsOpen|Boolean|Read-only property to determine if the update window is currently open|n| + +|Method|Description| +|:-----|:---------| +|Constructor(preferencesFolder As FolderItem)|Create the new instance around the given preferences folder| +|Execute|Start the update process| +|ResetIgnored|Reset the list of ignored updates| +|Result As ResultType|The result of the last Execute| + +|Shared Method|Description| +|:---|:---| +|OSIsSupported As Boolean|Reports if the OS has the right tools installed| + +**Module:** `Kaju` + +|Method|Description| +|:---|:---| +|CancelUpdate|Cancels any pending update; use in your windows' CancelClose event| + +There are other methods in the Kaju module that you might find useful but we are not documenting them. + +## Contributions + +Contributions to this project are welcome. Fork it to your own repo, then submit changes. However, be forewarned that only those changes that we deem universally useful will be included. + +## Who Did This? + +This project was designed and implemented by: -The file will be prefixed by the signature of the JSON string in hex format. The entire file would look like this: +* Kem Tekinay (ktekinay at mactechnologies.com) +* Luke Cowgar (lcowgar at advancedpricing.com) +* Jeremy Cowgar (jeremy at cowgar.com) - KAJU ABCDEF - [] +With thanks to John Hansen and Paul Lefebvre. -A missing or blank url means that the update does not apply to that platform. A missing boolean flag is assumed to be false. +With special thanks to [Advanced Medical Pricing Solutions, Inc.](http://www.advancedpricing.com), for making this possible. -It will be up to the implementer to enforce the "Is Required" flag on the client end. Only the release version will have a flag since development versions are always optional. +## FAQ -## Who Did This? ## +**How much does this cost?** -This project was designed and implemented by Kem Tekinay (ktekinay at mactechnologies.com) and Jeremy Cowgar (jeremy at cowgar.com). +One-TRILLION dollars!! Or nothing, your choice. +You may freely use this in any project, but don't come back to us if it erases your hard drive or paints your house yellow or something. See the included LICENSE.txt file for details. -## Other Stuff ## +**How does it work?** -### What is this repository for? ### +Kaju uses a shell script on all platforms that is initiated when the app quits. The script backs up the original app, copies the files of the new app over the old, deletes the backup, and starts the new app. If anything goes wrong, the old app is restored and started instead. -* Quick summary -* Version -* [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo) +**Does the end user have to install any additional apps?** -### How do I get set up? ### +No. -* Summary of set up -* Configuration -* Dependencies -* Database configuration -* How to run tests -* Deployment instructions +**Really?!? Even on Windows?** -### Contribution guidelines ### +No. Thanks to John Hansen and code he provided on the [Xojo Forums](https://forum.xojo.com), Windows will even unzip the downloaded file without any extra help. -* Writing tests -* Code review -* Other guidelines +## Release Notes -### Who do I talk to? ### +1.0 (Jan. 5, 2015) -* Repo owner or admin -* Other community or team contact \ No newline at end of file +- Initial release. \ No newline at end of file diff --git a/Client Harness/App.xojo_code b/Update Test App/App.xojo_code similarity index 62% rename from Client Harness/App.xojo_code rename to Update Test App/App.xojo_code index b6e1662..3c86942 100644 --- a/Client Harness/App.xojo_code +++ b/Update Test App/App.xojo_code @@ -1,6 +1,33 @@ #tag Class Protected Class App Inherits Application + #tag Event + Sub Open() + mPrefFolder = SpecialFolder.ApplicationData.Child( "Kaju Update Test" ) + if not mPrefFolder.Exists then + mPrefFolder.CreateAsFolder + end if + + End Sub + #tag EndEvent + + + #tag Method, Flags = &h0 + Function PrefFolder() As FolderItem + return mPrefFolder + End Function + #tag EndMethod + + + #tag Property, Flags = &h21 + Private mPrefFolder As FolderItem + #tag EndProperty + + #tag Property, Flags = &h0 + UpdateInitiater As Kaju.UpdateInitiater + #tag EndProperty + + #tag Constant, Name = kEditClear, Type = String, Dynamic = False, Default = \"&Delete", Scope = Public #Tag Instance, Platform = Windows, Language = Default, Definition = \"&Delete" #Tag Instance, Platform = Linux, Language = Default, Definition = \"&Delete" diff --git a/Update Test App/Build Automation.xojo_code b/Update Test App/Build Automation.xojo_code new file mode 100644 index 0000000..f9b6aee --- /dev/null +++ b/Update Test App/Build Automation.xojo_code @@ -0,0 +1,14 @@ +#tag BuildAutomation + Begin BuildStepList Linux + Begin BuildProjectStep Build + End + End + Begin BuildStepList Mac OS X + Begin BuildProjectStep Build + End + End + Begin BuildStepList Windows + Begin BuildProjectStep Build + End + End +#tag EndBuildAutomation diff --git a/Update Test App/Kaju Update Test.xojo_project b/Update Test App/Kaju Update Test.xojo_project new file mode 100644 index 0000000..a1b195e --- /dev/null +++ b/Update Test App/Kaju Update Test.xojo_project @@ -0,0 +1,51 @@ +Type=Desktop +RBProjectVersion=2014.031 +MinIDEVersion=20070100 +Class=App;App.xojo_code;&h503AC7A0;&h0;false +Window=Window1;Window1.xojo_window;&h4308442C;&h0;false +MenuBar=MainMenuBar;MainMenuBar.xojo_menu;&h671CDA54;&h0;false +Picture=Some_Image;../External Images/Some_Image.png;&h1B5C6827;&h0;false;0;&h0 +BuildSteps=Build Automation;Build Automation.xojo_code;&h17423755;&h0;false +Folder=Kaju Classes;../Kaju Classes;&h2570CF64;&h0;false +Module=Kaju;../Kaju Classes/Kaju.xojo_code;&h11400316;&h2570CF64;false +Class=UpdateChecker;../Kaju Classes/Kaju/UpdateChecker.xojo_code;&h70CF0A50;&h11400316;false +Class=KajuException;../Kaju Classes/Kaju/KajuException.xojo_code;&h3047231F;&h11400316;false +Class=UpdateInformation;../Kaju Classes/Kaju/UpdateInformation.xojo_code;&h79AE1ADC;&h11400316;false +Class=HTTPSSocket;../Kaju Classes/Kaju/HTTPSSocket.xojo_code;&h5ACDFAEB;&h11400316;false +Class=BinaryInformation;../Kaju Classes/Kaju/BinaryInformation.xojo_code;&hE8D70B5;&h11400316;false +Class=ZipShell;../Kaju Classes/Kaju/ZipShell.xojo_code;&hCC15E9B;&h11400316;false +Class=Information;../Kaju Classes/Kaju/Information.xojo_code;&h6497A7C4;&h11400316;false +Class=UpdateInitiater;../Kaju Classes/Kaju/UpdateInitiater.xojo_code;&h4DBB48E5;&h11400316;false +Window=KajuUpdateWindow;../Kaju Classes/KajuUpdateWindow.xojo_window;&h18AE3D9;&h2570CF64;false +DefaultWindow=Window1 +AppMenuBar=MainMenuBar +MajorVersion=1 +MinorVersion=0 +SubVersion=0 +NonRelease=0 +Release=3 +InfoVersion= +LongVersion= +ShortVersion= +WinCompanyName=MacTechnologies Consulting +WinInternalName= +WinProductName= +WinFileDescription= +AutoIncrementVersionInformation=False +BuildFlags=&h1990 +BuildLanguage=&h0 +DebugLanguage=&h0 +Region= +WindowsName=Kaju Update Test.exe +MacCarbonMachName=Kaju Update Test +LinuxX86Name=Kaju Update Test +MacCreator= +MDI=0 +MDICaption= +DefaultEncoding=&h0 +AppIcon=Kaju Update Test.xojo_resources;&h0 +OSXBundleID=com.mactechnologies.kajuupdatetest +DebuggerCommandLine= +UseGDIPlus=True +UseBuildsFolder=True +IsWebProject=False diff --git a/Client Harness/Xojo Self-Updater Harness.xojo_resources b/Update Test App/Kaju Update Test.xojo_resources similarity index 100% rename from Client Harness/Xojo Self-Updater Harness.xojo_resources rename to Update Test App/Kaju Update Test.xojo_resources diff --git a/Client Harness/MainMenuBar.xojo_menu b/Update Test App/MainMenuBar.xojo_menu similarity index 100% rename from Client Harness/MainMenuBar.xojo_menu rename to Update Test App/MainMenuBar.xojo_menu diff --git a/Update Test App/Window1.xojo_window b/Update Test App/Window1.xojo_window new file mode 100644 index 0000000..5242bf3 --- /dev/null +++ b/Update Test App/Window1.xojo_window @@ -0,0 +1,647 @@ +#tag Window +Begin Window Window1 + BackColor = &cFFFFFF00 + Backdrop = 0 + CloseButton = True + Compatibility = "" + Composite = False + Frame = 0 + FullScreen = False + FullScreenButton= False + HasBackColor = False + Height = 400 + ImplicitInstance= True + LiveResize = True + MacProcID = 0 + MaxHeight = 32000 + MaximizeButton = True + MaxWidth = 32000 + MenuBar = 1729944148 + MenuBarVisible = True + MinHeight = 64 + MinimizeButton = True + MinWidth = 64 + Placement = 0 + Resizeable = True + Title = "Kaju Update Test" + Visible = True + Width = 600 + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 70 + HelpTag = "" + Index = 0 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = True + Scope = 0 + Selectable = False + TabIndex = 0 + TabPanelIndex = 0 + TabStop = True + Text = "This will check a predefined web site for an update. It will always show an update for 1.1 and offer to download it, but the download is, in fact, v.1.0. In other words, the app will replace itself (sometimes with an older version) just so the process can be repeated." + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 14 + Transparent = False + Underline = False + Visible = True + Width = 547 + End + Begin PushButton btnExecute + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "Check" + Default = True + Enabled = True + Height = 45 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + TabIndex = 1 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 272 + Underline = False + Visible = True + Width = 129 + End + Begin CheckBox cbHonorIgnored + AutoDeactivate = True + Bold = False + Caption = "Honor Ignored" + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + State = 1 + TabIndex = 2 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 111 + Underline = False + Value = True + Visible = True + Width = 141 + End + Begin Label lblResult + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 63 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 235 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = True + Scope = 0 + Selectable = False + TabIndex = 3 + TabPanelIndex = 0 + TabStop = True + Text = "Press check to get a result" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 284 + Transparent = False + Underline = False + Visible = True + Width = 332 + End + Begin PushButton btnResetIgnored + AutoDeactivate = True + Bold = False + ButtonStyle = "0" + Cancel = False + Caption = "Reset Ignored" + Default = False + Enabled = True + Height = 30 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 191 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + TabIndex = 4 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 111 + Underline = False + Visible = True + Width = 139 + End + Begin CheckBox cbAllowWindow + AutoDeactivate = True + Bold = False + Caption = "Allow Update Window" + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + State = 1 + TabIndex = 5 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 143 + Underline = False + Value = True + Visible = True + Width = 183 + End + Begin CheckBox cbAllowErrorDialog + AutoDeactivate = True + Bold = False + Caption = "Allow Error Dialog" + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + State = 1 + TabIndex = 6 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 175 + Underline = False + Value = True + Visible = True + Width = 183 + End + Begin PopupMenu pumStageAllowed + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 30 + HelpTag = "" + Index = -2147483648 + InitialParent = "" + InitialValue = "" + Italic = False + Left = 191 + ListIndex = 0 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Scope = 0 + TabIndex = 7 + TabPanelIndex = 0 + TabStop = True + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 197 + Underline = False + Visible = True + Width = 171 + End + Begin Label Label1 + AutoDeactivate = True + Bold = False + DataField = "" + DataSource = "" + Enabled = True + Height = 20 + HelpTag = "" + Index = 1 + InitialParent = "" + Italic = False + Left = 20 + LockBottom = False + LockedInPosition= False + LockLeft = True + LockRight = False + LockTop = True + Multiline = False + Scope = 0 + Selectable = False + TabIndex = 8 + TabPanelIndex = 0 + TabStop = True + Text = "Min. Stage Allowed:" + TextAlign = 0 + TextColor = &c00000000 + TextFont = "System" + TextSize = 0.0 + TextUnit = 0 + Top = 207 + Transparent = False + Underline = False + Visible = True + Width = 152 + End +End +#tag EndWindow + +#tag WindowCode + #tag Event + Sub Open() + dim u as new Kaju.UpdateChecker( App.PrefFolder ) + u.ServerPublicRSAKey = _ + "30820120300D06092A864886F70D01010105000382010D00308201080282010100D1DE526C8D98CCBFFDB4BD71487AC16205CF851696FB2910ABBC564BFEC1261A53A90794102BCC80EFB3CED3F8E73D90FF4C426D2315DE5E31A1A6C7563A21EADBD91B1DD637FAE0BED539C186BCB81DD865CC2A2F9427F717AA5E837C53AB90691569FC45EE17AF0ACD80E0C24C864EE86D4DBB7A6010E09B4E0BC556004E02980388C654A1C676A31E3AF788754E0CF7DEEC8236D55EDD5BB7490011B27CDEE5E254099FDE98C17D5F85014622D64C3BFB6A77200050FB2C8DF9A1ACEE50CF5A8353CE68304F91EC4F463E76BCF90A15152D03308B229FFE91E4906990D0E5F2E5C3ACC106E58DB1A37095DCBD5E233D7ED4A41AA263A73C54D4F12A113881020111" + u.UpdateURL = "http://www.mactechnologies.com/Kaju_Test/UpdateInformation.html" + + u.DefaultImage = Some_Image + u.DefaultUseTransparency = true + + Checker = u + End Sub + #tag EndEvent + + + #tag Property, Flags = &h0 + Checker As Kaju.UpdateChecker + #tag EndProperty + + +#tag EndWindowCode + +#tag Events btnExecute + #tag Event + Sub Action() + Checker.HonorIgnored = cbHonorIgnored.Value + + dim allowWindow as integer = if( cbAllowWindow.Value, Kaju.UpdateChecker.kAllowUpdateWindow, 0 ) + dim allowErrorDialog as integer = if( cbAllowErrorDialog.Value, Kaju.UpdateChecker.kAllowErrorDialog, 0 ) + Checker.AllowedInteraction = allowWindow + allowErrorDialog + + Checker.AllowedStage = pumStageAllowed.RowTag( pumStageAllowed.ListIndex ) + + Checker.Execute() + + select case Checker.Result + case Kaju.UpdateChecker.ResultType.UpdateAlreadyInProgress + lblResult.Text = "Update already in progress" + + case Kaju.UpdateChecker.ResultType.UnsupportedOS + lblResult.Text = "This OS is not supported (missing required tools)" + + case Kaju.UpdateChecker.ResultType.NoWritePermission + lblResult.Text = "Aborted (no write permission)" + + case Kaju.UpdateChecker.ResultType.Error + lblResult.Text = "Error, user chose to try later" + + case Kaju.UpdateChecker.ResultType.IgnoredUpdateAvailable + lblResult.Text = "Updates available, but ignored" + + case Kaju.UpdateChecker.ResultType.NoUpdateAvailable + lblResult.Text = "No updates available" + + case Kaju.UpdateChecker.ResultType.UpdateAvailable + lblResult.Text = "Updates available" + + case Kaju.UpdateChecker.ResultType.RequiredUpdateAvailable + lblResult.Text = "Required update available" + + else + lblResult.Text = "UNKNOWN RESULT" + end + + return + End Sub + #tag EndEvent +#tag EndEvents +#tag Events btnResetIgnored + #tag Event + Sub Action() + Checker.ResetIgnored + End Sub + #tag EndEvent +#tag EndEvents +#tag Events pumStageAllowed + #tag Event + Sub Open() + me.AddRow( "Development" ) + me.RowTag( me.ListCount - 1 ) = App.Development + + me.AddRow( "Alpha" ) + me.RowTag( me.ListCount - 1 ) = App.Alpha + + me.AddRow( "Beta" ) + me.RowTag( me.ListCount - 1 ) = App.Beta + + me.AddRow( "Final" ) + me.RowTag( me.ListCount - 1 ) = App.Final + + me.ListIndex = 0 + End Sub + #tag EndEvent +#tag EndEvents +#tag ViewBehavior + #tag ViewProperty + Name="BackColor" + Visible=true + Group="Appearance" + InitialValue="&hFFFFFF" + Type="Color" + #tag EndViewProperty + #tag ViewProperty + Name="Backdrop" + Visible=true + Group="Appearance" + Type="Picture" + EditorType="Picture" + #tag EndViewProperty + #tag ViewProperty + Name="CloseButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Composite" + Group="Appearance" + InitialValue="False" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Frame" + Visible=true + Group="Appearance" + InitialValue="0" + Type="Integer" + EditorType="Enum" + #tag EnumValues + "0 - Document" + "1 - Movable Modal" + "2 - Modal Dialog" + "3 - Floating Window" + "4 - Plain Box" + "5 - Shadowed Box" + "6 - Rounded Window" + "7 - Global Floating Window" + "8 - Sheet Window" + "9 - Metal Window" + "10 - Drawer Window" + "11 - Modeless Dialog" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="FullScreen" + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="FullScreenButton" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="HasBackColor" + Visible=true + Group="Appearance" + InitialValue="False" + Type="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Height" + Visible=true + Group="Position" + InitialValue="400" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="ImplicitInstance" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Interfaces" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="LiveResize" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MacProcID" + Group="Appearance" + InitialValue="0" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MaxHeight" + Visible=true + Group="Position" + InitialValue="32000" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MaximizeButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MaxWidth" + Visible=true + Group="Position" + InitialValue="32000" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBar" + Visible=true + Group="Appearance" + Type="MenuBar" + EditorType="MenuBar" + #tag EndViewProperty + #tag ViewProperty + Name="MenuBarVisible" + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MinHeight" + Visible=true + Group="Position" + InitialValue="64" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="MinimizeButton" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="MinWidth" + Visible=true + Group="Position" + InitialValue="64" + Type="Integer" + #tag EndViewProperty + #tag ViewProperty + Name="Name" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Placement" + Visible=true + Group="Position" + InitialValue="0" + Type="Integer" + EditorType="Enum" + #tag EnumValues + "0 - Default" + "1 - Parent Window" + "2 - Main Screen" + "3 - Parent Window Screen" + "4 - Stagger" + #tag EndEnumValues + #tag EndViewProperty + #tag ViewProperty + Name="Resizeable" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Super" + Visible=true + Group="ID" + Type="String" + EditorType="String" + #tag EndViewProperty + #tag ViewProperty + Name="Title" + Visible=true + Group="Appearance" + InitialValue="Untitled" + Type="String" + #tag EndViewProperty + #tag ViewProperty + Name="Visible" + Visible=true + Group="Appearance" + InitialValue="True" + Type="Boolean" + EditorType="Boolean" + #tag EndViewProperty + #tag ViewProperty + Name="Width" + Visible=true + Group="Position" + InitialValue="600" + Type="Integer" + #tag EndViewProperty +#tag EndViewBehavior diff --git a/Update Test Files (Upload These)/Kaju_Update_Test_Linux.zip b/Update Test Files (Upload These)/Kaju_Update_Test_Linux.zip new file mode 100644 index 0000000..bfb3fc9 Binary files /dev/null and b/Update Test Files (Upload These)/Kaju_Update_Test_Linux.zip differ diff --git a/Update Test Files (Upload These)/Kaju_Update_Test_Mac.zip b/Update Test Files (Upload These)/Kaju_Update_Test_Mac.zip new file mode 100644 index 0000000..d555be1 Binary files /dev/null and b/Update Test Files (Upload These)/Kaju_Update_Test_Mac.zip differ diff --git a/Update Test Files (Upload These)/Kaju_Update_Test_Win.zip b/Update Test Files (Upload These)/Kaju_Update_Test_Win.zip new file mode 100644 index 0000000..fe15bd6 Binary files /dev/null and b/Update Test Files (Upload These)/Kaju_Update_Test_Win.zip differ diff --git a/Update Test Files (Upload These)/Some_Image_beta.png b/Update Test Files (Upload These)/Some_Image_beta.png new file mode 100644 index 0000000..e8e1142 Binary files /dev/null and b/Update Test Files (Upload These)/Some_Image_beta.png differ diff --git a/Update Test Files (Upload These)/UpdateInformation.html b/Update Test Files (Upload These)/UpdateInformation.html new file mode 100644 index 0000000..cf31d88 --- /dev/null +++ b/Update Test Files (Upload These)/UpdateInformation.html @@ -0,0 +1,118 @@ +KAJU 45F07892CD454DC66666A5B5397E05DCAADAC67324F4FA99E38A8030FC0F7DB3C376235F704B21B11A525126C958BD702A4BF8D353946B56AD42076D230573C0D30CF7D221EE69AC8E2D9B234CF4B6242316BDB367543BE71A97418D1C57090C20CFAAFED9846359E66827F3A75819784D599EDB6D1A38D3C70C05E6E8CC0AEE8994A124571FBE2727FC0B5659ECED50D091C547F8B0D649AAD76775A795945BC1D2A3F68BEEFAEA2F421B257F321C969EC046030FB5C34664884E02260E5A9D65DF923251CAB1738761EF2AB54E8E88FB3CE4E7873298E3481F646D5EF54D2D24FCD657931A9B70AE85E05789B5F06E791ADA23EAADCEF91CB1B37DC90D1D17 +[ + { + "Version":"1.1", + "ReleaseNotes":"

    This is a fake update for v.1.1. It will really point to a binary of 1.0.

    \n\r

    In other words, the app will replace itself.

    \n\n

    NOTE: The update will warn you that the update will require a payment. THIS IS A LIE!! It’s only set that way to test the “Requires Payment” option.

    \n\n

    Changes

    \n
      \n
    • There really aren’t any.
    • \n
    • This is just here as a placeholder.
    • \n
    ", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":true, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"1.2.2d5", + "ReleaseNotes":"

    A phony development version pointing to the same binary. Meant as a placeholder to demonstrate multiple versions.

    ", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"1.2.1a4", + "ReleaseNotes":"

    A phony alpha pointing to the same binary. Meant as a placeholder to demonstrate multiple versions.

    ", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"1.2b2", + "ReleaseNotes":"

    A phony beta pointing to the same binary. Meant as a placeholder to demonstrate multiple versions.

    \n\n

    IMPORTANT: The URL’s for the binaries have been intentionally switched to test the hash function. Attempting to update to this version should result in an error.", + "ImageURL":"http://www.mactechnologies.com/Kaju_Test/Some_Image_beta.png", + "UseTransparency":true, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"F4BE059EE89F03A41B865CFAEE9E0593", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Linux.zip" + }, + "WindowsBinary":{ + "Hash":"F53D5BD5DCA41CFBFDB45C3A44304073", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Mac.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"636A88BC9A02A4B559922DEE233FF984", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test" + } + }, + { + "Version":"0.9", + "ReleaseNotes":"

    A fake release that should never appear. (Except in Preview.)

    ", + "ImageURL":"", + "UseTransparency":false, + "MinimumRequiriedVersion":"", + "AppName":"Kaju Update Test", + "RequiresPayment":false, + "MacBinary":{ + "Hash":"B8BD64A5344FD70D6589F524ABAD4641", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Mac.zip" + }, + "WindowsBinary":{ + "Hash":"C48638358EA795D7667AF3E7EA91DCF8", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Win.zip", + "ExecutableName":"Kaju Update Test.exe" + }, + "LinuxBinary":{ + "Hash":"B142E03F2AA5048DE5D9ECAB85ED4A96", + "URL":"http://www.mactechnologies.com/Kaju_Test/Kaju_Update_Test_Linux.zip", + "ExecutableName":"Kaju Update Test" + } + } +] \ No newline at end of file