├── .gitattributes ├── .gitignore ├── Medusa.iss ├── README.md ├── appveyor.yml ├── assets ├── Wizard.bmp ├── WizardSmall.bmp ├── github.ico └── medusa.ico ├── idp ├── idp.iss └── unicode │ ├── idp.dll │ └── idplang │ ├── ChineseSimplified.iss │ ├── belarusian.iss │ ├── brazilianPortuguese.iss │ ├── bulgarian.iss │ ├── czech.iss │ ├── default.iss │ ├── finnish.iss │ ├── french.iss │ ├── german.iss │ ├── hungarian.iss │ ├── italian.iss │ ├── polish.iss │ ├── russian.iss │ ├── slovak.iss │ └── spanish.iss ├── run_installer.cmd ├── seed.ini ├── seed_data.cmd └── utils ├── 7za-License.txt ├── 7za.exe ├── nssm-License.txt ├── nssm32.exe └── nssm64.exe /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | * eol=lf 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Output/ 2 | repo/ 3 | files/ 4 | .vscode/ 5 | -------------------------------------------------------------------------------- /Medusa.iss: -------------------------------------------------------------------------------- 1 | #include <./idp/idp.iss> 2 | 3 | #define MedusaInstallerVersion "v0.6" 4 | 5 | #define AppId "{{991BED37-186A-5451-9E77-C3DCE91D56C7}" 6 | #define AppName "Medusa" 7 | #define AppPublisher "Medusa" 8 | #define AppURL "https://github.com/pymedusa/Medusa" 9 | #define AppServiceName AppName 10 | #define AppServiceDescription "Automatic Video Library Manager for TV Shows" 11 | #define ServiceStartIcon "{group}\Start " + AppName + " Service" 12 | #define ServiceStopIcon "{group}\Stop " + AppName + " Service" 13 | #define ServiceEditIcon "{group}\Edit " + AppName + " Service" 14 | 15 | #define DefaultPort 8081 16 | 17 | #define InstallerVersion 10006 18 | #define InstallerSeedUrl "https://raw.githubusercontent.com/pymedusa/MedusaInstaller/master/seed.ini" 19 | #define AppRepoUrl "https://github.com/pymedusa/Medusa.git" 20 | #define AppSize 246784000 21 | 22 | [Setup] 23 | AppId={#AppId} 24 | AppName={#AppName} 25 | AppVerName={#AppName} 26 | AppPublisher={#AppPublisher} 27 | AppPublisherURL={#AppURL} 28 | AppSupportURL={#AppURL} 29 | AppUpdatesURL={#AppURL} 30 | DefaultDirName={sd}\{#AppName} 31 | DefaultGroupName={#AppName} 32 | AllowNoIcons=yes 33 | DisableWelcomePage=no 34 | DisableDirPage=no 35 | DisableProgramGroupPage=no 36 | ArchitecturesInstallIn64BitMode=x64 37 | OutputBaseFilename={#AppName}Installer 38 | SolidCompression=yes 39 | UninstallDisplayIcon={app}\Installer\medusa.ico 40 | UninstallFilesDir={app}\Installer 41 | SetupIconFile=assets\medusa.ico 42 | WizardImageFile=assets\Wizard.bmp 43 | WizardSmallImageFile=assets\WizardSmall.bmp 44 | WizardStyle=modern 45 | WizardResizable=no 46 | 47 | [Types] 48 | Name: "full"; Description: "Full installation" 49 | Name: "custom"; Description: "Custom installation"; Flags: iscustom 50 | 51 | [Components] 52 | Name: "application"; Description: {#AppName}; ExtraDiskSpaceRequired: {#AppSize}; Types: "full custom"; Flags: fixed 53 | Name: "python"; Description: "Python"; ExtraDiskSpaceRequired: 36000000; Types: "full custom" 54 | Name: "git"; Description: "Git"; ExtraDiskSpaceRequired: 55000000; Types: "full custom" 55 | 56 | [Tasks] 57 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked 58 | 59 | [Files] 60 | Source: "utils\7za.exe"; Flags: dontcopy 61 | Source: "assets\medusa.ico"; DestDir: "{app}\Installer" 62 | Source: "assets\github.ico"; DestDir: "{app}\Installer" 63 | Source: "utils\nssm32.exe"; DestDir: "{app}\Installer"; DestName: "nssm.exe"; Check: not Is64BitInstallMode 64 | Source: "utils\nssm64.exe"; DestDir: "{app}\Installer"; DestName: "nssm.exe"; Check: Is64BitInstallMode 65 | 66 | [Dirs] 67 | Name: "{app}\Data" 68 | 69 | [Icons] 70 | ;Desktop 71 | Name: "{commondesktop}\{#AppName}"; Filename: "http://localhost:{code:GetWebPort}/"; IconFilename: "{app}\Installer\medusa.ico"; Tasks: desktopicon 72 | ;Start menu 73 | Name: "{group}\{#AppName}"; Filename: "http://localhost:{code:GetWebPort}/"; IconFilename: "{app}\Installer\medusa.ico" 74 | Name: "{group}\{#AppName} on GitHub"; Filename: "{#AppURL}"; IconFilename: "{app}\Installer\github.ico"; Flags: excludefromshowinnewinstall 75 | Name: "{#ServiceStartIcon}"; Filename: "{app}\Installer\nssm.exe"; Parameters: "start ""{#AppServiceName}"""; Flags: excludefromshowinnewinstall 76 | Name: "{#ServiceStopIcon}"; Filename: "{app}\Installer\nssm.exe"; Parameters: "stop ""{#AppServiceName}"""; Flags: excludefromshowinnewinstall 77 | Name: "{#ServiceEditIcon}"; Filename: "{app}\Installer\nssm.exe"; Parameters: "edit ""{#AppServiceName}"""; AfterInstall: ModifyServiceLinks; Flags: excludefromshowinnewinstall 78 | 79 | [Run] 80 | ;Medusa 81 | Filename: "{code:GetGitExecutable}"; Parameters: "clone ""{param:LOCALREPO|{#AppRepoUrl}}"" ""{app}\{#AppName}"" --branch {code:GetBranch}"; StatusMsg: "Installing {#AppName}..." 82 | ;Service 83 | Filename: "{app}\Installer\nssm.exe"; Parameters: "start ""{#AppServiceName}"""; Flags: runhidden; BeforeInstall: CreateService; StatusMsg: "Starting {#AppName} service..." 84 | ;Open 85 | Filename: "http://localhost:{code:GetWebPort}/"; Flags: postinstall shellexec; Description: "Open {#AppName} in browser" 86 | 87 | [UninstallRun] 88 | ;Service 89 | Filename: "{app}\Installer\nssm.exe"; Parameters: "remove ""{#AppServiceName}"" confirm"; Flags: runhidden 90 | 91 | [UninstallDelete] 92 | Type: filesandordirs; Name: "{app}\Python" 93 | Type: filesandordirs; Name: "{app}\Git" 94 | Type: filesandordirs; Name: "{app}\Installer" 95 | Type: filesandordirs; Name: "{app}\{#AppName}" 96 | Type: dirifempty; Name: "{app}" 97 | 98 | [Messages] 99 | WelcomeLabel2=This will install [name/ver] on your computer.%n%nYou will need Internet connectivity in order to download the required packages. 100 | SelectComponentsLabel2=Select the components you want to install; clear the components you do not want to install. Click Next when you are ready to continue. \ 101 | %n%nPlease note: \ 102 | %n By default, this installer provides the dependencies required to run [name]. \ 103 | %n If you have Git or Python already installed on your system, and you would prefer to use those versions, \ 104 | %n you may deselect the dependencies you already have and provide a path to the already-installed versions. 105 | AboutSetupNote=MedusaInstaller {#MedusaInstallerVersion} 106 | BeveledLabel=MedusaInstaller {#MedusaInstallerVersion} 107 | 108 | [INI] 109 | Filename: "{app}\Data\config.ini"; Section: "General"; Key: "web_port"; String: "{code:GetWebPort}"; Flags: createkeyifdoesntexist 110 | 111 | [Code] 112 | type 113 | TDependency = record 114 | Name: String; 115 | URL: String; 116 | Filename: String; 117 | Size: Integer; 118 | SHA1: String; 119 | Version: String; 120 | end; 121 | 122 | TBrowseOptions = record 123 | Caption: TNewStaticText; 124 | Path: TNewStaticText; 125 | Browse: TNewButton; 126 | end; 127 | 128 | TInstallOptions = record 129 | Page: TWizardPage; 130 | WebPort: TNewEdit; 131 | Branch: TNewCheckListBox; 132 | Python: TBrowseOptions; 133 | Git: TBrowseOptions; 134 | end; 135 | 136 | IShellLinkW = interface(IUnknown) 137 | '{000214F9-0000-0000-C000-000000000046}' 138 | procedure Dummy; 139 | procedure Dummy2; 140 | procedure Dummy3; 141 | function GetDescription(pszName: String; cchMaxName: Integer): HResult; 142 | function SetDescription(pszName: String): HResult; 143 | function GetWorkingDirectory(pszDir: String; cchMaxPath: Integer): HResult; 144 | function SetWorkingDirectory(pszDir: String): HResult; 145 | function GetArguments(pszArgs: String; cchMaxPath: Integer): HResult; 146 | function SetArguments(pszArgs: String): HResult; 147 | function GetHotkey(var pwHotkey: Word): HResult; 148 | function SetHotkey(wHotkey: Word): HResult; 149 | function GetShowCmd(out piShowCmd: Integer): HResult; 150 | function SetShowCmd(iShowCmd: Integer): HResult; 151 | function GetIconLocation(pszIconPath: String; cchIconPath: Integer; 152 | out piIcon: Integer): HResult; 153 | function SetIconLocation(pszIconPath: String; iIcon: Integer): HResult; 154 | function SetRelativePath(pszPathRel: String; dwReserved: DWORD): HResult; 155 | function Resolve(Wnd: HWND; fFlags: DWORD): HResult; 156 | function SetPath(pszFile: String): HResult; 157 | end; 158 | 159 | IPersist = interface(IUnknown) 160 | '{0000010C-0000-0000-C000-000000000046}' 161 | function GetClassID(var classID: TGUID): HResult; 162 | end; 163 | 164 | IPersistFile = interface(IPersist) 165 | '{0000010B-0000-0000-C000-000000000046}' 166 | function IsDirty: HResult; 167 | function Load(pszFileName: String; dwMode: Longint): HResult; 168 | function Save(pszFileName: String; fRemember: BOOL): HResult; 169 | function SaveCompleted(pszFileName: String): HResult; 170 | function GetCurFile(out pszFileName: String): HResult; 171 | end; 172 | 173 | IShellLinkDataList = interface(IUnknown) 174 | '{45E2B4AE-B1C3-11D0-B92F-00A0C90312E1}' 175 | procedure Dummy; 176 | procedure Dummy2; 177 | procedure Dummy3; 178 | function GetFlags(out dwFlags: DWORD): HResult; 179 | function SetFlags(dwFlags: DWORD): HResult; 180 | end; 181 | 182 | const 183 | MinPort = 1; 184 | MaxPort = 65535; 185 | WM_CLOSE = $0010; 186 | GENERIC_WRITE = $40000000; 187 | GENERIC_READ = $80000000; 188 | OPEN_EXISTING = 3; 189 | INVALID_HANDLE_VALUE = $FFFFFFFF; 190 | SLDF_RUNAS_USER = $00002000; 191 | CLSID_ShellLink = '{00021401-0000-0000-C000-000000000046}'; 192 | 193 | var 194 | // This lets AbortInstallation() terminate setup without prompting the user 195 | CancelWithoutPrompt: Boolean; 196 | ErrorMessage, LocalFilesDir: String; 197 | SeedDownloadPageId, DependencyDownloadPageId: Integer; 198 | PythonDep, GitDep: TDependency; 199 | InstallDepPage: TOutputProgressWizardPage; 200 | InstallOptions: TInstallOptions; 201 | // Uninstall variables 202 | UninstallRemoveData: Boolean; 203 | 204 | // Import some Win32 functions 205 | function CreateFile( 206 | lpFileName: String; 207 | dwDesiredAccess: LongWord; 208 | dwSharedMode: LongWord; 209 | lpSecurityAttributes: LongWord; 210 | dwCreationDisposition: LongWord; 211 | dwFlagsAndAttributes: LongWord; 212 | hTemplateFile: LongWord): LongWord; 213 | external 'CreateFileW@kernel32.dll stdcall'; 214 | 215 | function CloseHandle(hObject: LongWord): Boolean; 216 | external 'CloseHandle@kernel32.dll stdcall'; 217 | 218 | procedure AbortInstallation(ErrorMessage: String); 219 | begin 220 | MsgBox(ErrorMessage + #13#10#13#10 'Setup will now terminate.', mbError, 0) 221 | CancelWithoutPrompt := True 222 | PostMessage(WizardForm.Handle, WM_CLOSE, 0, 0); 223 | end; 224 | 225 | procedure CheckInstallerVersion(SeedFile: String); 226 | var 227 | InstallerVersion, CurrentVersion: Integer; 228 | DownloadUrl: String; 229 | begin 230 | InstallerVersion := StrToInt(ExpandConstant('{#InstallerVersion}')) 231 | 232 | CurrentVersion := GetIniInt('Installer', 'Version', 0, 0, MaxInt, SeedFile) 233 | 234 | if CurrentVersion = 0 then begin 235 | AbortInstallation('Unable to parse configuration.') 236 | end; 237 | 238 | if CurrentVersion > InstallerVersion then begin 239 | DownloadUrl := GetIniString('Installer', 'DownloadUrl', ExpandConstant('{#AppURL}'), SeedFile) 240 | AbortInstallation(ExpandConstant('This is an old version of the {#AppName} installer. Please get the latest version at:') + #13#10#13#10 + DownloadUrl) 241 | end; 242 | end; 243 | 244 | function GetDependencyVersion(Dependency: TDependency): String; 245 | var 246 | StartIndex: Integer; 247 | begin 248 | Result := Dependency.Filename; 249 | // Handle Git dependency 250 | if Pos('git', Lowercase(Dependency.Name)) <> 0 then begin 251 | // --> MinGit-2.22.0-32-bit.zip 252 | // --> MinGit-2.22.0-64-bit.zip 253 | StartIndex := Pos('-', Result) + 1; 254 | Result := Copy(Result, StartIndex, Length(Result)); 255 | // <-- 2.22.0-64-bit.zip 256 | StartIndex := Pos('-', Result); 257 | Delete(Result, StartIndex, Length(Result)); 258 | // <-- 2.22.0 259 | // Handle Python dependency 260 | end else if Pos('python', Lowercase(Dependency.Name)) <> 0 then begin 261 | // --> pythonx86.3.7.3.nupkg 262 | // --> python.3.7.3.nupkg 263 | StartIndex := Pos('.', Result) + 1; 264 | Result := Copy(Result, StartIndex, Length(Result)); 265 | // <-- 3.7.3.nupkg 266 | StartIndex := Pos('nupkg', Result) - 1; 267 | Delete(Result, StartIndex, Length(Result)); 268 | // <-- 3.7.3 269 | end else begin 270 | Result := ''; 271 | end; 272 | end; 273 | 274 | procedure UpdateComponentsPageDependencyVersions(); 275 | var 276 | Idx: Integer; 277 | Version: String; 278 | begin 279 | for Idx := 0 to WizardForm.ComponentsList.Items.Count - 1 do 280 | begin 281 | Version := ''; 282 | 283 | if Pos('python', Lowercase(WizardForm.ComponentsList.ItemCaption[Idx])) <> 0 then begin 284 | Version := ' (v' + PythonDep.Version + ')'; 285 | end; 286 | 287 | if Pos('git', Lowercase(WizardForm.ComponentsList.ItemCaption[Idx])) <> 0 then begin 288 | Version := ' (v' + GitDep.Version + ')'; 289 | end; 290 | 291 | WizardForm.ComponentsList.ItemCaption[Idx] := WizardForm.ComponentsList.ItemCaption[Idx] + Version; 292 | end; 293 | end; 294 | 295 | procedure ParseDependency(var Dependency: TDependency; Name, SeedFile: String); 296 | begin 297 | Dependency.Name := Name; 298 | Dependency.URL := GetIniString(Name, 'url', '', SeedFile) 299 | Dependency.Filename := GetIniString(Name, 'filename', '', SeedFile) 300 | Dependency.Size := GetIniInt(Name, 'size', 0, 0, MaxInt, SeedFile) 301 | Dependency.SHA1 := GetIniString(Name, 'sha1', '', SeedFile) 302 | Dependency.Version := ''; 303 | 304 | if (Dependency.URL = '') or (Dependency.Size = 0) or (Dependency.SHA1 = '') then begin 305 | AbortInstallation('Error parsing dependency information for ' + Name + '.') 306 | end; 307 | 308 | // If a filename was not supplied, use the last part of the URL as the filename 309 | if Dependency.Filename = '' then begin 310 | Dependency.Filename := Dependency.URL 311 | while Pos('/', Dependency.Filename) <> 0 do begin 312 | Delete(Dependency.Filename, 1, Pos('/', Dependency.Filename)) 313 | end; 314 | end; 315 | 316 | Dependency.Version := GetDependencyVersion(Dependency); 317 | end; 318 | 319 | procedure ParseSeedFile(); 320 | var 321 | SeedFile: String; 322 | Arch: String; 323 | begin 324 | SeedFile := ExpandConstant('{tmp}\installer.ini') 325 | 326 | // Make sure we're running the latest version of the installer 327 | CheckInstallerVersion(SeedFile) 328 | 329 | if Is64BitInstallMode then 330 | Arch := 'x64' 331 | else 332 | Arch := 'x86'; 333 | 334 | ParseDependency(PythonDep, 'Python.' + Arch, SeedFile) 335 | ParseDependency(GitDep, 'Git.' + Arch, SeedFile) 336 | end; 337 | 338 | procedure InitializeSeedDownload(); 339 | var 340 | DownloadPage: TWizardPage; 341 | Seed: String; 342 | IsRemote: Boolean; 343 | begin 344 | IsRemote := True 345 | 346 | Seed := ExpandConstant('{param:SEED}') 347 | if (Lowercase(Copy(Seed, 1, 7)) <> 'http://') and (Lowercase(Copy(Seed, 1, 8)) <> 'https://') then begin 348 | if Seed = '' then begin 349 | Seed := ExpandConstant('{#InstallerSeedUrl}') 350 | end else begin 351 | if FileExists(Seed) then begin 352 | IsRemote := False 353 | end else begin 354 | MsgBox('Invalid SEED specified: ' + Seed, mbError, 0) 355 | Seed := ExpandConstant('{#InstallerSeedUrl}') 356 | end; 357 | end; 358 | end; 359 | 360 | if not IsRemote then begin 361 | FileCopy(Seed, ExpandConstant('{tmp}\installer.ini'), False) 362 | ParseSeedFile() 363 | UpdateComponentsPageDependencyVersions() 364 | end else begin 365 | // Download the installer seed INI file 366 | idpAddFile(Seed, ExpandConstant('{tmp}\installer.ini')) 367 | 368 | SeedDownloadPageId := idpCreateDownloadForm(wpWelcome) 369 | DownloadPage := PageFromID(SeedDownloadPageId) 370 | DownloadPage.Caption := 'Downloading Installer Configuration' 371 | DownloadPage.Description := 'Setup is downloading its configuration file...' 372 | 373 | idpConnectControls() 374 | end; 375 | end; 376 | 377 | function CheckFileInUse(Filename: String): Boolean; 378 | var 379 | FileHandle: LongWord; 380 | begin 381 | if not FileExists(Filename) then begin 382 | Result := False 383 | exit 384 | end; 385 | 386 | FileHandle := CreateFile(Filename, GENERIC_READ or GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0) 387 | if (FileHandle <> 0) and (FileHandle <> INVALID_HANDLE_VALUE) then begin 388 | CloseHandle(FileHandle) 389 | Result := False 390 | end else begin 391 | Result := True 392 | end; 393 | end; 394 | 395 | function GetPythonExecutable(Param: String): String; 396 | begin 397 | if WizardIsComponentSelected('python') then begin 398 | Result := ExpandConstant('{app}\Python\python.exe') 399 | end else begin 400 | Result := InstallOptions.Python.Path.Caption 401 | end; 402 | end; 403 | 404 | function GetGitExecutable(Param: String): String; 405 | begin 406 | if WizardIsComponentSelected('git') then begin 407 | Result := ExpandConstant('{app}\Git\cmd\git.exe') 408 | end else begin 409 | Result := InstallOptions.Git.Path.Caption 410 | end; 411 | end; 412 | 413 | function GetWebPort(Param: String): String; 414 | begin 415 | Result := InstallOptions.WebPort.Text 416 | end; 417 | 418 | function GetBranch(Param: String): String; 419 | var 420 | Idx: Integer; 421 | begin 422 | for Idx := 0 to InstallOptions.Branch.Items.Count - 1 do 423 | begin 424 | if InstallOptions.Branch.Checked[Idx] then begin 425 | Result := InstallOptions.Branch.ItemCaption[Idx] 426 | break; 427 | end; 428 | end; 429 | end; 430 | 431 | function ConfigureCustomPython(Python: String): String; 432 | var 433 | VirtualEnvPath: String; 434 | ResultCode: Integer; 435 | begin 436 | VirtualEnvPath := ExpandConstant('{app}\Python') 437 | Result := VirtualEnvPath + '\Scripts\python.exe' 438 | 439 | Exec(Python, '-m venv "'+VirtualEnvPath+'"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 440 | if ResultCode = 0 then begin 441 | // Success using venv (Included in Python >= 3.3) 442 | exit; 443 | end; 444 | 445 | // Fall back to installing latest `virtualenv` using Pip and using that. 446 | // Some versions of Pip for Python 2.7 on Windows have an issue where having a progress bar can fail the install. 447 | Exec(Python, '-m pip install --progress-bar off --upgrade virtualenv', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 448 | if ResultCode = 0 then begin 449 | // Install/Upgrade successful 450 | Exec(Python, '-m virtualenv "'+VirtualEnvPath+'"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 451 | if ResultCode = 0 then begin 452 | // Success using virtualenv (external package) 453 | exit; 454 | end; 455 | end; 456 | 457 | // Fall back to non-virtualenv (not recommended). 458 | Result := Python; 459 | MsgBox( 460 | 'Failed to configure custom Python:' #13#10#13#10 'Unable to install/upgrade or configure virtualenv.' + \ 461 | #13#10 'Falling back to using the custom Python executable as-is (not recommended).', 462 | mbError, MB_OK 463 | ); 464 | end; 465 | 466 | procedure CreateService(); 467 | var 468 | PythonExecutable: String; 469 | GitPath: String; 470 | Nssm: String; 471 | ResultCode: Integer; 472 | OldProgressString: String; 473 | WindowsVersion: TWindowsVersion; 474 | begin 475 | PythonExecutable := GetPythonExecutable('') 476 | GitPath := ExtractFileDir(GetGitExecutable('')) 477 | 478 | OldProgressString := WizardForm.StatusLabel.Caption; 479 | 480 | // Python was not selected, let's isolate this install using v(irtual)env 481 | if not WizardIsComponentSelected('python') then begin 482 | WizardForm.StatusLabel.Caption := ExpandConstant('Configuring virtual environment for custom Python...'); 483 | // Returns the path to the new virtual env's Python executable. 484 | PythonExecutable := ConfigureCustomPython(PythonExecutable); 485 | end; 486 | 487 | Nssm := ExpandConstant('{app}\Installer\nssm.exe') 488 | GetWindowsVersionEx(WindowsVersion); 489 | 490 | WizardForm.StatusLabel.Caption := ExpandConstant('Installing {#AppName} service...') 491 | 492 | // Keep extra quotes; See "Quoting issues" on https://nssm.cc/usage 493 | Exec(Nssm, ExpandConstant('install "{#AppServiceName}" "'+PythonExecutable+'" """{app}\{#AppName}\start.py""" --nolaunch --datadir="""{app}\Data"""'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 494 | Exec(Nssm, ExpandConstant('set "{#AppServiceName}" AppDirectory "{app}\Data"'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 495 | Exec(Nssm, ExpandConstant('set "{#AppServiceName}" Description "{#AppServiceDescription}"'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 496 | Exec(Nssm, ExpandConstant('set "{#AppServiceName}" AppStopMethodSkip 6'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 497 | Exec(Nssm, ExpandConstant('set "{#AppServiceName}" AppStopMethodConsole 20000'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 498 | Exec(Nssm, ExpandConstant('set "{#AppServiceName}" AppEnvironmentExtra "PATH='+GitPath+';%PATH%"'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 499 | 500 | if WindowsVersion.NTPlatform and (WindowsVersion.Major = 10) and (WindowsVersion.Minor = 0) and (WindowsVersion.Build > 14393) then begin 501 | Exec(Nssm, ExpandConstant('set "{#AppServiceName}" AppNoConsole 1'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 502 | end; 503 | 504 | WizardForm.StatusLabel.Caption := OldProgressString; 505 | end; 506 | 507 | procedure StopService(); 508 | var 509 | Nssm: String; 510 | ResultCode: Integer; 511 | Retries: Integer; 512 | OldProgressString: String; 513 | begin 514 | Retries := 30 515 | 516 | OldProgressString := UninstallProgressForm.StatusLabel.Caption; 517 | UninstallProgressForm.StatusLabel.Caption := ExpandConstant('Stopping {#AppName} service...') 518 | 519 | Nssm := ExpandConstant('{app}\Installer\nssm.exe') 520 | Exec(Nssm, ExpandConstant('stop "{#AppServiceName}"'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 521 | 522 | while (Retries > 0) and (CheckFileInUse(Nssm)) do begin 523 | UninstallProgressForm.StatusLabel.Caption := ExpandConstant('Waiting for {#AppName} service to stop (') + IntToStr(Retries) + ')...' 524 | Sleep(1000) 525 | Retries := Retries - 1 526 | end; 527 | 528 | UninstallProgressForm.StatusLabel.Caption := OldProgressString; 529 | end; 530 | 531 | procedure CleanPython(); 532 | var 533 | PythonPath: String; 534 | begin 535 | PythonPath := ExpandConstant('{app}\Python') 536 | DelTree(PythonPath + '\Tools', True, True, True) 537 | end; 538 | 539 | procedure InstallPython(); 540 | var 541 | ResultCode: Integer; 542 | begin 543 | InstallDepPage.SetText('Installing Python...', '') 544 | Exec(ExpandConstant('{tmp}\7za.exe'), ExpandConstantEx('x "{tmp}\{filename}" -o"{app}" tools', 'filename', PythonDep.Filename), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 545 | RenameFile(ExpandConstant('{app}\tools'), ExpandConstant('{app}\Python')) 546 | CleanPython() 547 | InstallDepPage.SetProgress(InstallDepPage.ProgressBar.Position+1, InstallDepPage.ProgressBar.Max) 548 | end; 549 | 550 | procedure InstallGit(); 551 | var 552 | ResultCode: Integer; 553 | begin 554 | InstallDepPage.SetText('Installing Git...', '') 555 | Exec(ExpandConstant('{tmp}\7za.exe'), ExpandConstantEx('x "{tmp}\{filename}" -o"{app}\Git"', 'filename', GitDep.Filename), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) 556 | InstallDepPage.SetProgress(InstallDepPage.ProgressBar.Position+1, InstallDepPage.ProgressBar.Max) 557 | end; 558 | 559 | function VerifyDependency(Dependency: TDependency): Boolean; 560 | begin 561 | Result := True 562 | 563 | InstallDepPage.SetText('Verifying dependency files...', Dependency.Filename) 564 | if GetSHA1OfFile(ExpandConstant('{tmp}\') + Dependency.Filename) <> Dependency.SHA1 then begin 565 | MsgBox('SHA1 hash of ' + Dependency.Filename + ' does not match.', mbError, 0) 566 | Result := False 567 | end; 568 | InstallDepPage.SetProgress(InstallDepPage.ProgressBar.Position+1, InstallDepPage.ProgressBar.Max) 569 | end; 570 | 571 | function VerifyDependencies(): Boolean; 572 | begin 573 | Result := True 574 | 575 | if WizardIsComponentSelected('python') then begin 576 | Result := Result and VerifyDependency(PythonDep) 577 | end; 578 | if WizardIsComponentSelected('git') then begin 579 | Result := Result and VerifyDependency(GitDep) 580 | end; 581 | end; 582 | 583 | procedure OnPythonBrowseClick(Sender: TObject); 584 | var 585 | Filename: String; 586 | FilenameLength: Integer; 587 | begin 588 | // Browse for a custom Python executable 589 | if GetOpenFilename('Path to Python executable:', Filename, '', 'Python executable (python.exe)|python.exe', 'python.exe') then begin 590 | FilenameLength := Length(Filename); 591 | if Copy(Filename, FilenameLength - Length('\python.exe') + 1, FilenameLength) = '\python.exe' then begin 592 | InstallOptions.Python.Path.Caption := Filename; 593 | end; 594 | end; 595 | end; 596 | 597 | procedure OnGitBrowseClick(Sender: TObject); 598 | var 599 | Filename: String; 600 | FilenameLength: Integer; 601 | begin 602 | // Browse for a custom Git executable 603 | if GetOpenFilename('Path to Git executable:', Filename, '', 'Git executable (git.exe)|git.exe', 'git.exe') then begin 604 | FilenameLength := Length(Filename); 605 | if Copy(Filename, FilenameLength - Length('\git.exe') + 1, FilenameLength) = '\git.exe' then begin 606 | InstallOptions.Git.Path.Caption := Filename; 607 | end; 608 | end; 609 | end; 610 | 611 | procedure AppendDependency(Dependency: TDependency); 612 | var 613 | LocalFile: String; 614 | DownloadTarget: String; 615 | begin 616 | // Add the dependency to the download list (or use it from a local file) 617 | if LocalFilesDir <> '' then begin 618 | LocalFile := LocalFilesDir + '\' + Dependency.Filename 619 | end; 620 | 621 | DownloadTarget := ExpandConstant('{tmp}\') + Dependency.Filename 622 | if (LocalFile <> '') and (FileExists(LocalFile)) then begin 623 | FileCopy(LocalFile, DownloadTarget, True) 624 | end else begin 625 | idpAddFileSize(Dependency.URL, DownloadTarget, Dependency.Size) 626 | end; 627 | end; 628 | 629 | procedure PrepareDependencies(); 630 | var 631 | DownloadPage: TWizardPage; 632 | PythonSelected, GitSelected: Boolean; 633 | begin 634 | PythonSelected := WizardIsComponentSelected('python') 635 | GitSelected := WizardIsComponentSelected('git') 636 | 637 | if not (PythonSelected or GitSelected) then begin 638 | // No dependencies selected, skip creating a "Downloading Dependencies" page 639 | exit; 640 | end; 641 | 642 | if PythonSelected then AppendDependency(PythonDep); 643 | if GitSelected then AppendDependency(GitDep); 644 | 645 | DependencyDownloadPageId := idpCreateDownloadForm(wpPreparing) 646 | DownloadPage := PageFromID(DependencyDownloadPageId) 647 | DownloadPage.Caption := 'Downloading Dependencies' 648 | DownloadPage.Description := ExpandConstant('Setup is downloading {#AppName} dependencies...') 649 | 650 | idpSetOption('DetailedMode', '1') 651 | idpSetOption('DetailsButton', '0') 652 | 653 | idpConnectControls() 654 | end; 655 | 656 | function PrepareToInstall(var NeedsRestart: Boolean): String; 657 | begin 658 | if ErrorMessage <> '' then begin 659 | Result := ErrorMessage 660 | exit; 661 | end; 662 | 663 | PrepareDependencies(); 664 | end; 665 | 666 | procedure InstallDependencies(); 667 | var 668 | PythonSelected: Boolean; 669 | GitSelected: Boolean; 670 | MaxProgress: Integer; 671 | begin 672 | PythonSelected := WizardIsComponentSelected('python') 673 | GitSelected := WizardIsComponentSelected('git') 674 | 675 | if not (PythonSelected or GitSelected) then begin 676 | exit; 677 | end; 678 | 679 | try 680 | InstallDepPage.Show 681 | 682 | MaxProgress := 0; 683 | // Stages for each dependency - Verify + Install 684 | if PythonSelected then MaxProgress := MaxProgress + 2; 685 | if GitSelected then MaxProgress := MaxProgress + 2; 686 | 687 | InstallDepPage.SetProgress(0, MaxProgress) 688 | 689 | if VerifyDependencies() then begin 690 | ExtractTemporaryFile('7za.exe') 691 | if PythonSelected then InstallPython(); 692 | if GitSelected then InstallGit(); 693 | end else begin 694 | ErrorMessage := 'There was an error installing the required dependencies.' 695 | end; 696 | finally 697 | InstallDepPage.Hide 698 | end; 699 | end; 700 | 701 | procedure InitializeWizard(); 702 | var 703 | WebPortCaption: TNewStaticText; 704 | begin 705 | InitializeSeedDownload() 706 | 707 | idpInitMessages() 708 | 709 | InstallDepPage := CreateOutputProgressPage('Installing Dependencies', ExpandConstant('Setup is installing {#AppName} dependencies...')); 710 | 711 | // Custom Options Page 712 | InstallOptions.Page := CreateCustomPage(wpSelectProgramGroup, 'Additional Options', ExpandConstant('Additional {#AppName} configuration options')); 713 | 714 | WebPortCaption := TNewStaticText.Create(InstallOptions.Page); 715 | WebPortCaption.Anchors := [akLeft, akRight]; 716 | WebPortCaption.Caption := ExpandConstant('{#AppName} Web Server Port:'); 717 | WebPortCaption.AutoSize := True; 718 | WebPortCaption.Parent := InstallOptions.Page.Surface; 719 | 720 | InstallOptions.WebPort := TNewEdit.Create(InstallOptions.Page); 721 | InstallOptions.WebPort.Top := WebPortCaption.Top + WebPortCaption.Height + ScaleY(8); 722 | InstallOptions.WebPort.Text := ExpandConstant('{#DefaultPort}'); 723 | InstallOptions.WebPort.Parent := InstallOptions.Page.Surface; 724 | InstallOptions.WebPort.Width := ScaleX(50); 725 | 726 | InstallOptions.Branch := TNewCheckListBox.Create(InstallOptions.Page); 727 | InstallOptions.Branch.Top := InstallOptions.WebPort.Top + InstallOptions.WebPort.Height; 728 | InstallOptions.Branch.Width := ScaleX(50); 729 | InstallOptions.Branch.Height := ScaleY(70); 730 | InstallOptions.Branch.Anchors := [akLeft, akRight]; 731 | InstallOptions.Branch.BorderStyle := bsNone; 732 | InstallOptions.Branch.ParentColor := True; 733 | InstallOptions.Branch.MinItemHeight := WizardForm.TasksList.MinItemHeight; 734 | InstallOptions.Branch.ShowLines := False; 735 | InstallOptions.Branch.WantTabs := True; 736 | InstallOptions.Branch.Parent := InstallOptions.Page.Surface; 737 | InstallOptions.Branch.AddGroup('Branch:', '', 0, nil); 738 | InstallOptions.Branch.AddRadioButton('master', '(stable)', 0, True, True, nil); 739 | InstallOptions.Branch.AddRadioButton('develop', '(development)', 0, False, True, nil); 740 | 741 | InstallOptions.Python.Caption := TNewStaticText.Create(InstallOptions.Page); 742 | InstallOptions.Python.Caption.Anchors := [akLeft, akRight]; 743 | InstallOptions.Python.Caption.Top := InstallOptions.Branch.Top + InstallOptions.Branch.Height + ScaleY(8); 744 | InstallOptions.Python.Caption.Caption := 'Path to Python executable:'; 745 | InstallOptions.Python.Caption.AutoSize := True; 746 | InstallOptions.Python.Caption.Parent := InstallOptions.Page.Surface; 747 | InstallOptions.Python.Caption.Visible := False; 748 | 749 | InstallOptions.Python.Path := TNewStaticText.Create(InstallOptions.Page); 750 | InstallOptions.Python.Path.Anchors := [akLeft]; 751 | InstallOptions.Python.Path.Top := InstallOptions.Python.Caption.Top; 752 | InstallOptions.Python.Path.Left := InstallOptions.Python.Caption.Left + InstallOptions.Python.Caption.Width + ScaleX(2); 753 | InstallOptions.Python.Path.AutoSize := True; 754 | InstallOptions.Python.Path.Parent := InstallOptions.Page.Surface; 755 | InstallOptions.Python.Path.Caption := ''; 756 | InstallOptions.Python.Path.Visible := False; 757 | 758 | InstallOptions.Python.Browse := TNewButton.Create(InstallOptions.Page); 759 | InstallOptions.Python.Browse.Anchors := [akLeft]; 760 | InstallOptions.Python.Browse.Top := InstallOptions.Python.Path.Top + InstallOptions.Python.Path.Height; 761 | InstallOptions.Python.Browse.Parent := InstallOptions.Page.Surface; 762 | InstallOptions.Python.Browse.Width := ScaleX(75); 763 | InstallOptions.Python.Browse.Height := ScaleY(23); 764 | InstallOptions.Python.Browse.Caption := 'Browse...'; 765 | InstallOptions.Python.Browse.Visible := False; 766 | InstallOptions.Python.Browse.OnClick := @OnPythonBrowseClick; 767 | 768 | InstallOptions.Git.Caption := TNewStaticText.Create(InstallOptions.Page); 769 | InstallOptions.Git.Caption.Anchors := [akLeft, akRight]; 770 | InstallOptions.Git.Caption.Top := InstallOptions.Python.Browse.Top + InstallOptions.Python.Browse.Height + ScaleY(8); 771 | InstallOptions.Git.Caption.Caption := 'Path to Git executable:'; 772 | InstallOptions.Git.Caption.AutoSize := True; 773 | InstallOptions.Git.Caption.Parent := InstallOptions.Page.Surface; 774 | InstallOptions.Git.Caption.Visible := False; 775 | 776 | InstallOptions.Git.Path := TNewStaticText.Create(InstallOptions.Page); 777 | InstallOptions.Git.Path.Anchors := [akLeft]; 778 | InstallOptions.Git.Path.Top := InstallOptions.Git.Caption.Top; 779 | InstallOptions.Git.Path.Left := InstallOptions.Git.Caption.Left + InstallOptions.Git.Caption.Width + ScaleX(2); 780 | InstallOptions.Git.Path.AutoSize := True; 781 | InstallOptions.Git.Path.Parent := InstallOptions.Page.Surface; 782 | InstallOptions.Git.Path.Caption := ''; 783 | InstallOptions.Git.Path.Visible := False; 784 | 785 | InstallOptions.Git.Browse := TNewButton.Create(InstallOptions.Page); 786 | InstallOptions.Git.Browse.Anchors := [akLeft]; 787 | InstallOptions.Git.Browse.Top := InstallOptions.Git.Path.Top + InstallOptions.Git.Path.Height; 788 | InstallOptions.Git.Browse.Parent := InstallOptions.Page.Surface; 789 | InstallOptions.Git.Browse.Width := ScaleX(75); 790 | InstallOptions.Git.Browse.Height := ScaleY(23); 791 | InstallOptions.Git.Browse.Caption := 'Browse...'; 792 | InstallOptions.Git.Browse.Visible := False; 793 | InstallOptions.Git.Browse.OnClick := @OnGitBrowseClick; 794 | end; 795 | 796 | function ShellLinkRunAsAdmin(LinkFilename: String): Boolean; 797 | var 798 | Obj: IUnknown; 799 | SL: IShellLinkW; 800 | PF: IPersistFile; 801 | DL: IShellLinkDataList; 802 | Flags: DWORD; 803 | begin 804 | try 805 | Obj := CreateComObject(StringToGuid(CLSID_ShellLink)); 806 | 807 | SL := IShellLinkW(Obj); 808 | PF := IPersistFile(Obj); 809 | 810 | // Load the ShellLink 811 | OleCheck(PF.Load(LinkFilename, 0)); 812 | 813 | // Set the SLDF_RUNAS_USER flag 814 | DL := IShellLinkDataList(Obj); 815 | OleCheck(DL.GetFlags(Flags)) 816 | OleCheck(DL.SetFlags(Flags or SLDF_RUNAS_USER)) 817 | 818 | // Save the ShellLink 819 | OleCheck(PF.Save(LinkFilename, True)); 820 | 821 | Result := True 822 | except 823 | Result := False 824 | end; 825 | end; 826 | 827 | procedure ModifyServiceLinks(); 828 | begin 829 | ShellLinkRunAsAdmin(ExpandConstant('{#ServiceStartIcon}.lnk')) 830 | ShellLinkRunAsAdmin(ExpandConstant('{#ServiceStopIcon}.lnk')) 831 | ShellLinkRunAsAdmin(ExpandConstant('{#ServiceEditIcon}.lnk')) 832 | end; 833 | 834 | function InitializeSetup(): Boolean; 835 | begin 836 | CancelWithoutPrompt := False 837 | 838 | LocalFilesDir := ExpandConstant('{param:LOCALFILES}') 839 | if (LocalFilesDir <> '') and (not DirExists(LocalFilesDir)) then begin 840 | MsgBox('Invalid LOCALFILES specified: ' + LocalFilesDir, mbError, 0) 841 | LocalFilesDir := '' 842 | end; 843 | 844 | // Don't allow installations over top 845 | if RegKeyExists(HKEY_LOCAL_MACHINE, ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#AppId}_is1')) then begin 846 | MsgBox(ExpandConstant('{#AppName} is already installed. If you''re reinstalling or upgrading, please uninstall the current version first.'), mbError, 0) 847 | Result := False 848 | end else begin 849 | Result := True 850 | end; 851 | end; 852 | 853 | function NextButtonClick(CurPageID: Integer): Boolean; 854 | var 855 | Port: Integer; 856 | begin 857 | Result := True; 858 | 859 | if CurPageID = SeedDownloadPageId then begin 860 | ParseSeedFile() 861 | UpdateComponentsPageDependencyVersions() 862 | end else if CurPageId = wpSelectComponents then begin 863 | // Make options visible depending on component selection 864 | InstallOptions.Python.Caption.Visible := not WizardIsComponentSelected('python'); 865 | InstallOptions.Python.Path.Visible := not WizardIsComponentSelected('python'); 866 | InstallOptions.Python.Path.Caption := ''; 867 | InstallOptions.Python.Browse.Visible := not WizardIsComponentSelected('python'); 868 | 869 | InstallOptions.Git.Caption.Visible := not WizardIsComponentSelected('git'); 870 | InstallOptions.Git.Path.Visible := not WizardIsComponentSelected('git'); 871 | InstallOptions.Git.Path.Caption := ''; 872 | InstallOptions.Git.Browse.Visible := not WizardIsComponentSelected('git'); 873 | end else if CurPageId = InstallOptions.Page.ID then begin 874 | // Make sure valid port is specified 875 | Port := StrToIntDef(GetWebPort(''), 0) 876 | if (Port = 0) or (Port < MinPort) or (Port > MaxPort) then begin 877 | MsgBox(FmtMessage('Please specify a valid port between %1 and %2.', [IntToStr(MinPort), IntToStr(MaxPort)]), mbError, 0) 878 | Result := False; 879 | exit; 880 | end; 881 | 882 | // Make sure custom Python path is supplied 883 | if not WizardIsComponentSelected('python') then begin 884 | if InstallOptions.Python.Path.Caption = '' then begin 885 | MsgBox('Please specify the path to the Python executable.', mbError, 0) 886 | Result := False; 887 | exit; 888 | end; 889 | end; 890 | 891 | // Make sure custom Git path is supplied 892 | if not WizardIsComponentSelected('git') then begin 893 | if InstallOptions.Git.Path.Caption = '' then begin 894 | MsgBox('Please specify the path to the Git executable.', mbError, 0) 895 | Result := False; 896 | exit; 897 | end; 898 | end; 899 | end; 900 | end; 901 | 902 | function UninstallShouldRemoveData(): Boolean; 903 | begin 904 | Result := MsgBox(ExpandConstant('Do you want to remove your {#AppName} database and configuration?' #13#10#13#10 'Select No if you plan on reinstalling {#AppName}.'), mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES; 905 | end; 906 | 907 | procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean); 908 | begin 909 | Confirm := not CancelWithoutPrompt; 910 | end; 911 | 912 | procedure InitializeUninstallProgressForm(); 913 | begin 914 | UninstallRemoveData := UninstallShouldRemoveData() 915 | end; 916 | 917 | procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); 918 | begin 919 | if CurUninstallStep = usUninstall then begin 920 | // Stop service 921 | StopService() 922 | 923 | // Remove data if requested 924 | if UninstallRemoveData then begin 925 | DelTree(ExpandConstant('{app}\Data'), True, True, True) 926 | end; 927 | end; 928 | end; 929 | 930 | function Append(const Lines, S, NewLine: String): String; 931 | begin 932 | Result := S; 933 | if Lines <> '' then begin 934 | if Result <> '' then 935 | Result := Result + NewLine + NewLine; 936 | Result := Result + Lines; 937 | end; 938 | end; 939 | 940 | procedure InjectDependencyVersions(var MemoComponentsInfo: String); 941 | var 942 | SearchStr: String; 943 | Position: Integer; 944 | begin 945 | if MemoComponentsInfo = '' then begin 946 | exit; 947 | end; 948 | 949 | // Inject dependency versions into MemoComponentsInfo 950 | if WizardIsComponentSelected('git') and (GitDep.Version <> '') then begin 951 | SearchStr := ' Git' 952 | Position := Pos(SearchStr, MemoComponentsInfo) 953 | if Position <> 0 then begin 954 | Insert(' (v' + GitDep.Version + ')', MemoComponentsInfo, Position + Length(SearchStr)); 955 | end; 956 | end; 957 | 958 | if WizardIsComponentSelected('python') and (PythonDep.Version <> '') then begin 959 | SearchStr := ' Python' 960 | Position := Pos(SearchStr, MemoComponentsInfo) 961 | if Position <> 0 then begin 962 | Insert(' (v' + PythonDep.Version + ')', MemoComponentsInfo, Position + Length(SearchStr)); 963 | end; 964 | end; 965 | end; 966 | 967 | function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, 968 | MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String; 969 | var 970 | S: String; 971 | begin 972 | S := ''; 973 | 974 | S := Append(MemoDirInfo, S, NewLine); 975 | S := Append(MemoGroupInfo, S, NewLine); 976 | // S := Append(MemoTypeInfo, S, NewLine); 977 | 978 | InjectDependencyVersions(MemoComponentsInfo); 979 | 980 | S := Append(MemoComponentsInfo, S, NewLine); 981 | 982 | S := Append('Web server port:' + NewLine + Space + GetWebPort(''), S, NewLine); 983 | S := Append('Branch: ' + NewLine + Space + GetBranch(''), S, NewLine); 984 | 985 | S := Append(MemoTasksInfo, S, NewLine); 986 | 987 | Result := S; 988 | end; 989 | 990 | procedure CurStepChanged(CurStep: TSetupStep); 991 | begin 992 | if CurStep = ssInstall then begin 993 | InstallDependencies() 994 | end; 995 | end; 996 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MedusaInstaller 2 | A Windows Installer for Medusa 3 | 4 | ### **Please note:** 5 | By default, this installer provides the dependencies required to run Medusa. 6 | If you have Git or Python already installed on your system, and you would prefer to use those versions, 7 | you may deselect the dependencies you already have and provide a path to the already-installed versions. 8 | 9 | Features 10 | -------- 11 | Here are some of the features of MedusaInstaller: 12 | - Downloads Medusa dependencies (Git, Python) 13 | - Installs everything (Medusa and dependencies) in a self-contained directory 14 | - Installs Medusa as a Windows service (handled by NSSM) 15 | - Detects 32-bit and 64-bit architectures and installs appropriate dependencies 16 | - Creates Start Menu shortcuts 17 | - When uninstalling, asks user if they want to delete or keep their database and configuration 18 | - Allows configuring the web UI port during install 19 | - Allows configuring the branch during install 20 | 21 | The install script is written using the excellent [Inno Setup](http://www.jrsoftware.org/isinfo.php) by Jordan Russell. 22 | 23 | Download 24 | -------- 25 | Head on over to the [releases](https://github.com/pymedusa/MedusaInstaller/releases) tab. 26 | 27 | How It Works 28 | ------------ 29 | First, the installer will grab a 'seed' file, located [here](https://raw.githubusercontent.com/pymedusa/MedusaInstaller/master/seed.ini). This has a list of the dependencies, the URLs they can be downloaded from, their size, and an SHA1 hash. It also uses this file to make sure the user is running the latest version of the installer. 30 | 31 | Once the user steps through the pages of the wizard, the installer downloads the dependency files and verifies the SHA1 hash. It then installs them into the directory chosen by the user. Once the dependencies are installed, it uses Git to clone the Medusa repository. 32 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | version: '{build}' 3 | max_jobs: 1 4 | clone_depth: 5 5 | 6 | pull_requests: 7 | do_not_increment_build_number: true 8 | skip_tags: true 9 | only_commits: 10 | files: 11 | - Medusa.iss 12 | - seed.ini 13 | - idp/** 14 | - assets/** 15 | - utils/** 16 | - appveyor.yml 17 | 18 | install: 19 | - ps: choco install -y --no-progress InnoSetup 20 | 21 | before_build: 22 | - ps: | 23 | $env:InstallerVersion = Select-String -Path .\Medusa.iss ` 24 | -Pattern '^#define\sInstallerVersion\s([\d]+)' | ` 25 | % { $($_.matches.groups[1]).Value }; 26 | 27 | - ps: | 28 | $env:SeedVersion = Select-String -Path .\seed.ini ` 29 | -Pattern '^Version=([\d]+)' | ` 30 | % { $($_.matches.groups[1]).Value }; 31 | 32 | - ps: | 33 | If ($env:InstallerVersion -ne $env:SeedVersion -and $env:APPVEYOR_REPO_BRANCH -eq "master") { 34 | $message = "Seed version differs from version in source code!"; 35 | $details = ` 36 | "`tVersion in source code: $env:InstallerVersion`n" + 37 | "`tVersion in seed file: $env:SeedVersion`n" + 38 | "Make sure to update the value before pushing to master."; 39 | Add-AppveyorMessage ` 40 | -Message "$message" ` 41 | -Category Warning ` 42 | -Details "$details"; 43 | Write-Warning "$message`n$details"; 44 | } 45 | 46 | - ps: | 47 | $env:ReleaseVersion = Select-String -Path .\Medusa.iss ` 48 | -Pattern '^#define\sMedusaInstallerVersion\s"v([\d.]+)"' | ` 49 | % { $($_.matches.groups[1]).Value }; 50 | 51 | build_script: 52 | # On pull requests, set output file to: "Output\MedusaInstaller-[commit_hash[:7]].exe" 53 | # On other commits, set output file to: "Output\MedusaInstaller-[release_version].exe" 54 | - ps: | 55 | If ($env:APPVEYOR_PULL_REQUEST_NUMBER) { 56 | $FileVersion = $env:APPVEYOR_REPO_COMMIT.Substring(0, 7); 57 | } Else { 58 | $FileVersion = $env:ReleaseVersion; 59 | } 60 | $env:OutputFile = "MedusaInstaller-$FileVersion"; 61 | - ps: '& "${env:ProgramFiles(x86)}\Inno Setup 6\iscc.exe" /O"Output" /F"$env:OutputFile" /Q Medusa.iss;' 62 | - ps: $env:InstallerFile = "Output\${env:OutputFile}.exe"; 63 | 64 | test: off 65 | 66 | notifications: 67 | - provider: GitHubPullRequest 68 | template: > 69 | {{#passed}}:white_check_mark:{{/passed}}{{#failed}}:x:{{/failed}} [Build {{&projectName}} {{buildVersion}} {{status}}]({{buildUrl}}) (commit {{commitUrl}} by @{{&commitAuthorUsername}}) 70 | {{#jobs}}{{#artifacts}}
Artifact: [{{fileName}}]({{permalink}}) ({{size}} bytes){{/artifacts}}{{/jobs}} 71 |

Build messages:

72 | 81 | 82 | artifacts: 83 | - path: $(InstallerFile) 84 | name: MedusaInstaller 85 | 86 | environment: 87 | GH_TOKEN: 88 | secure: epQRjuQ/hfy8BkTBL3NYVnmkFnsVlYEDKX5XB7RcY2Ort3Fo2sXX8LZdW+sNyRa3 89 | 90 | before_deploy: 91 | - ps: | 92 | If ($env:InstallerVersion -ne $env:SeedVersion) { 93 | $message = "Seed version differs from version in source code!"; 94 | $details = ` 95 | "`tVersion in source code: $env:InstallerVersion`n" + 96 | "`tVersion in seed file: $env:SeedVersion`n" + 97 | "You must to update the value before releasing."; 98 | Add-AppveyorMessage ` 99 | -Message "$message" ` 100 | -Category Error ` 101 | -Details "$details"; 102 | throw "$message`n$details"; 103 | } 104 | 105 | - ps: | 106 | $headers = @{ 107 | "Authorization" = "Bearer $env:GH_TOKEN" 108 | "Content-type" = "application/json" 109 | }; 110 | $LatestGitHubRelease = Invoke-RestMethod ` 111 | -Uri "https://api.github.com/repos/$env:APPVEYOR_REPO_NAME/releases/latest" ` 112 | -Headers $headers ` 113 | -Method GET; 114 | If ($env:ReleaseVersion -eq $LatestGitHubRelease.tag_name -and -not $LatestGitHubRelease.draft) { 115 | Write-Warning "Not deploying because release $env:ReleaseVersion is already published."; 116 | Exit-AppveyorBuild; 117 | } 118 | 119 | deploy: 120 | - provider: GitHub 121 | tag: $(ReleaseVersion) 122 | release: Release $(ReleaseVersion) 123 | force_update: true # replaces current release 124 | draft: true # release as a draft 125 | auth_token: $(GH_TOKEN) 126 | artifact: MedusaInstaller # artifact name 127 | on: 128 | branch: master # release from master branch only 129 | -------------------------------------------------------------------------------- /assets/Wizard.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/assets/Wizard.bmp -------------------------------------------------------------------------------- /assets/WizardSmall.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/assets/WizardSmall.bmp -------------------------------------------------------------------------------- /assets/github.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/assets/github.ico -------------------------------------------------------------------------------- /assets/medusa.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/assets/medusa.ico -------------------------------------------------------------------------------- /idp/idp.iss: -------------------------------------------------------------------------------- 1 | ; Inno Download Plugin 2 | ; (c)2013-2014 Mitrich Software 3 | ; http://mitrichsoftware.wordpress.com/ 4 | ; https://code.google.com/p/inno-download-plugin/ 5 | 6 | #define IDPROOT ExtractFilePath(__PATHFILENAME__) 7 | 8 | #ifdef UNICODE 9 | #pragma include __INCLUDE__ + ";" + IDPROOT + "\unicode" 10 | #else 11 | #pragma include __INCLUDE__ + ";" + IDPROOT + "\ansi" 12 | #endif 13 | 14 | ; If IDP_DEBUG is defined before including idp.iss, script will use debug version of idp.dll (not included, you need to build it yourself). 15 | ; Debug dll messages can be viewed with SysInternals DebugView (http://technet.microsoft.com/en-us/sysinternals/bb896647.aspx) 16 | #ifdef IDP_DEBUG 17 | #define DBGSUFFIX " debug" 18 | #else 19 | #define DBGSUFFIX 20 | #endif 21 | 22 | #ifdef UNICODE 23 | #define IDPDLLDIR IDPROOT + "\unicode" + DBGSUFFIX 24 | #else 25 | #define IDPDLLDIR IDPROOT + "\ansi" + DBGSUFFIX 26 | #endif 27 | 28 | #define IDP_VER_MAJOR 29 | #define IDP_VER_MINOR 30 | #define IDP_VER_REV 31 | #define IDP_VER_BUILD 32 | 33 | #expr ParseVersion(IDPDLLDIR + "\idp.dll", IDP_VER_MAJOR, IDP_VER_MINOR, IDP_VER_REV, IDP_VER_BUILD) 34 | #define IDP_VER EncodeVer(IDP_VER_MAJOR, IDP_VER_MINOR, IDP_VER_REV, IDP_VER_BUILD) 35 | 36 | #define IDP_VER_STR GetFileVersion(IDPDLLDIR + "\idp.dll") 37 | 38 | [Files] 39 | Source: "{#IDPDLLDIR}\idp.dll"; Flags: dontcopy; 40 | 41 | [Code] 42 | procedure idpAddFile(url, filename: String); external 'idpAddFile@files:idp.dll cdecl'; 43 | procedure idpAddFileComp(url, filename, components: String); external 'idpAddFileComp@files:idp.dll cdecl'; 44 | procedure idpAddMirror(url, mirror: String); external 'idpAddMirror@files:idp.dll cdecl'; 45 | procedure idpAddFtpDir(url, mask, destdir: String; recursive: Boolean); external 'idpAddFtpDir@files:idp.dll cdecl'; 46 | procedure idpAddFtpDirComp(url, mask, destdir: String; recursive: Boolean; components: String); external 'idpAddFtpDirComp@files:idp.dll cdecl'; 47 | procedure idpClearFiles; external 'idpClearFiles@files:idp.dll cdecl'; 48 | function idpFilesCount: Integer; external 'idpFilesCount@files:idp.dll cdecl'; 49 | function idpFtpDirsCount: Integer; external 'idpFtpDirsCount@files:idp.dll cdecl'; 50 | function idpFileDownloaded(url: String): Boolean; external 'idpFileDownloaded@files:idp.dll cdecl'; 51 | function idpFilesDownloaded: Boolean; external 'idpFilesDownloaded@files:idp.dll cdecl'; 52 | function idpDownloadFile(url, filename: String): Boolean; external 'idpDownloadFile@files:idp.dll cdecl'; 53 | function idpDownloadFiles: Boolean; external 'idpDownloadFiles@files:idp.dll cdecl'; 54 | function idpDownloadFilesComp: Boolean; external 'idpDownloadFilesComp@files:idp.dll cdecl'; 55 | function idpDownloadFilesCompUi: Boolean; external 'idpDownloadFilesCompUi@files:idp.dll cdecl'; 56 | procedure idpStartDownload; external 'idpStartDownload@files:idp.dll cdecl'; 57 | procedure idpStopDownload; external 'idpStopDownload@files:idp.dll cdecl'; 58 | procedure idpSetLogin(login, password: String); external 'idpSetLogin@files:idp.dll cdecl'; 59 | procedure idpSetProxyMode(mode: String); external 'idpSetProxyMode@files:idp.dll cdecl'; 60 | procedure idpSetProxyName(name: String); external 'idpSetProxyName@files:idp.dll cdecl'; 61 | procedure idpSetProxyLogin(login, password: String); external 'idpSetProxyLogin@files:idp.dll cdecl'; 62 | procedure idpConnectControl(name: String; Handle: HWND); external 'idpConnectControl@files:idp.dll cdecl'; 63 | procedure idpAddMessage(name, message: String); external 'idpAddMessage@files:idp.dll cdecl'; 64 | procedure idpSetInternalOption(name, value: String); external 'idpSetInternalOption@files:idp.dll cdecl'; 65 | procedure idpSetDetailedMode(mode: Boolean); external 'idpSetDetailedMode@files:idp.dll cdecl'; 66 | procedure idpSetComponents(components: String); external 'idpSetComponents@files:idp.dll cdecl'; 67 | procedure idpReportError; external 'idpReportError@files:idp.dll cdecl'; 68 | procedure idpTrace(text: String); external 'idpTrace@files:idp.dll cdecl'; 69 | 70 | #if defined(UNICODE) && (Ver >= 0x05050300) 71 | procedure idpAddFileSize(url, filename: String; size: Int64); external 'idpAddFileSize@files:idp.dll cdecl'; 72 | procedure idpAddFileSizeComp(url, filename: String; size: Int64; components: String); external 'idpAddFileSize@files:idp.dll cdecl'; 73 | function idpGetFileSize(url: String; var size: Int64): Boolean; external 'idpGetFileSize@files:idp.dll cdecl'; 74 | function idpGetFilesSize(var size: Int64): Boolean; external 'idpGetFilesSize@files:idp.dll cdecl'; 75 | #else 76 | procedure idpAddFileSize(url, filename: String; size: Dword); external 'idpAddFileSize32@files:idp.dll cdecl'; 77 | procedure idpAddFileSizeComp(url, filename: String; size: Dword; components: String); external 'idpAddFileSize32@files:idp.dll cdecl'; 78 | function idpGetFileSize(url: String; var size: Dword): Boolean; external 'idpGetFileSize32@files:idp.dll cdecl'; 79 | function idpGetFilesSize(var size: Dword): Boolean; external 'idpGetFilesSize32@files:idp.dll cdecl'; 80 | #endif 81 | 82 | type TIdpForm = record 83 | Page : TWizardPage; 84 | TotalProgressBar : TNewProgressBar; 85 | FileProgressBar : TNewProgressBar; 86 | TotalProgressLabel: TNewStaticText; 87 | CurrentFileLabel : TNewStaticText; 88 | TotalDownloaded : TNewStaticText; 89 | FileDownloaded : TNewStaticText; 90 | FileNameLabel : TNewStaticText; 91 | SpeedLabel : TNewStaticText; 92 | StatusLabel : TNewStaticText; 93 | ElapsedTimeLabel : TNewStaticText; 94 | RemainingTimeLabel: TNewStaticText; 95 | FileName : TNewStaticText; 96 | Speed : TNewStaticText; 97 | Status : TNewStaticText; 98 | ElapsedTime : TNewStaticText; 99 | RemainingTime : TNewStaticText; 100 | DetailsButton : TNewButton; 101 | GIDetailsButton : HWND; //Graphical Installer 102 | DetailsVisible : Boolean; 103 | InvisibleButton : TNewButton; 104 | end; 105 | 106 | TIdpOptions = record 107 | DetailedMode : Boolean; 108 | NoDetailsButton: Boolean; 109 | NoRetryButton : Boolean; 110 | NoSkinnedButton: Boolean; //Graphical Installer 111 | end; 112 | 113 | var IDPForm : TIdpForm; 114 | IDPOptions: TIdpOptions; 115 | 116 | function StrToBool(value: String): Boolean; 117 | var s: String; 118 | begin 119 | s := LowerCase(value); 120 | 121 | if s = 'true' then result := true 122 | else if s = 't' then result := true 123 | else if s = 'yes' then result := true 124 | else if s = 'y' then result := true 125 | else if s = 'false' then result := false 126 | else if s = 'f' then result := false 127 | else if s = 'no' then result := false 128 | else if s = 'n' then result := false 129 | else result := StrToInt(value) > 0; 130 | end; 131 | 132 | function WizardVerySilent: Boolean; 133 | var i: Integer; 134 | begin 135 | for i := 1 to ParamCount do 136 | begin 137 | if UpperCase(ParamStr(i)) = '/VERYSILENT' then 138 | begin 139 | result := true; 140 | exit; 141 | end; 142 | end; 143 | 144 | result := false; 145 | end; 146 | 147 | function WizardSupressMsgBoxes: Boolean; 148 | var i: Integer; 149 | begin 150 | for i := 1 to ParamCount do 151 | begin 152 | if UpperCase(ParamStr(i)) = '/SUPPRESSMSGBOXES' then 153 | begin 154 | result := true; 155 | exit; 156 | end; 157 | end; 158 | 159 | result := false; 160 | end; 161 | 162 | procedure idpSetOption(name, value: String); 163 | var key: String; 164 | begin 165 | key := LowerCase(name); 166 | 167 | if key = 'detailedmode' then IDPOptions.DetailedMode := StrToBool(value) 168 | else if key = 'detailsvisible' then IDPOptions.DetailedMode := StrToBool(value) //alias 169 | else if key = 'detailsbutton' then IDPOptions.NoDetailsButton := not StrToBool(value) 170 | else if key = 'skinnedbutton' then IDPOptions.NoSkinnedButton := not StrToBool(value) 171 | else if key = 'retrybutton' then 172 | begin 173 | IDPOptions.NoRetryButton := StrToInt(value) = 0; 174 | idpSetInternalOption('RetryButton', value); 175 | end 176 | else 177 | idpSetInternalOption(name, value); 178 | end; 179 | 180 | procedure idpShowDetails(show: Boolean); 181 | begin 182 | IDPForm.FileProgressBar.Visible := show; 183 | IDPForm.CurrentFileLabel.Visible := show; 184 | IDPForm.FileDownloaded.Visible := show; 185 | IDPForm.FileNameLabel.Visible := show; 186 | IDPForm.SpeedLabel.Visible := show; 187 | IDPForm.StatusLabel.Visible := show; 188 | IDPForm.ElapsedTimeLabel.Visible := show; 189 | IDPForm.RemainingTimeLabel.Visible := show; 190 | IDPForm.FileName.Visible := show; 191 | IDPForm.Speed.Visible := show; 192 | IDPForm.Status.Visible := show; 193 | IDPForm.ElapsedTime.Visible := show; 194 | IDPForm.RemainingTime.Visible := show; 195 | 196 | IDPForm.DetailsVisible := show; 197 | 198 | if IDPForm.DetailsVisible then 199 | begin 200 | IDPForm.DetailsButton.Caption := ExpandConstant('{cm:IDP_HideButton}'); 201 | IDPForm.DetailsButton.Top := ScaleY(184); 202 | end 203 | else 204 | begin 205 | IDPForm.DetailsButton.Caption := ExpandConstant('{cm:IDP_DetailsButton}'); 206 | IDPForm.DetailsButton.Top := ScaleY(44); 207 | end; 208 | 209 | idpSetDetailedMode(show); 210 | end; 211 | 212 | procedure idpDetailsButtonClick(Sender: TObject); 213 | begin 214 | idpShowDetails(not IDPForm.DetailsVisible); 215 | end; 216 | 217 | #ifdef GRAPHICAL_INSTALLER_PROJECT 218 | procedure idpGIDetailsButtonClick(hButton: HWND); 219 | begin 220 | idpShowDetails(not IDPForm.DetailsVisible); 221 | 222 | if IDPForm.DetailsVisible then 223 | begin 224 | ButtonSetText(IDPForm.GIDetailsButton, PAnsiChar(ExpandConstant('{cm:IDP_HideButton}'))); 225 | ButtonSetPosition(IDPForm.GIDetailsButton, IDPForm.DetailsButton.Left-ScaleX(5), ScaleY(184), ButtonWidth, ButtonHeight); 226 | end 227 | else 228 | begin 229 | ButtonSetText(IDPForm.GIDetailsButton, PAnsiChar(ExpandConstant('{cm:IDP_DetailsButton}'))); 230 | ButtonSetPosition(IDPForm.GIDetailsButton, IDPForm.DetailsButton.Left-ScaleX(5), ScaleY(44), ButtonWidth, ButtonHeight); 231 | end; 232 | 233 | ButtonRefresh(hButton); 234 | end; 235 | 236 | procedure idpCreateGIDetailsButton; 237 | var swButtonNormalColor : TColor; 238 | swButtonFocusedColor : TColor; 239 | swButtonPressedColor : TColor; 240 | swButtonDisabledColor: TColor; 241 | begin 242 | swButtonNormalColor := SwitchColorFormat(ExpandConstant('{#ButtonNormalColor}')); 243 | swButtonFocusedColor := SwitchColorFormat(ExpandConstant('{#ButtonFocusedColor}')); 244 | swButtonPressedColor := SwitchColorFormat(ExpandConstant('{#ButtonPressedColor}')); 245 | swButtonDisabledColor := SwitchColorFormat(ExpandConstant('{#ButtonDisabledColor}')); 246 | 247 | with IDPForm.DetailsButton do 248 | begin 249 | IDPForm.GIDetailsButton := ButtonCreate(IDPForm.Page.Surface.Handle, Left-ScaleX(5), Top, ButtonWidth, ButtonHeight, 250 | ExpandConstant('{tmp}\{#ButtonPicture}'), coButtonShadow, False); 251 | 252 | ButtonSetEvent(IDPForm.GIDetailsButton, ButtonClickEventID, WrapButtonCallback(@idpGIDetailsButtonClick, 1)); 253 | ButtonSetFont(IDPForm.GIDetailsButton, ButtonFont.Handle); 254 | ButtonSetFontColor(IDPForm.GIDetailsButton, swButtonNormalColor, swButtonFocusedColor, swButtonPressedColor, swButtonDisabledColor); 255 | ButtonSetText(IDPForm.GIDetailsButton, PAnsiChar(Caption)); 256 | ButtonSetVisibility(IDPForm.GIDetailsButton, true); 257 | ButtonSetEnabled(IDPForm.GIDetailsButton, true); 258 | end; 259 | end; 260 | #endif 261 | 262 | procedure idpFormActivate(Page: TWizardPage); 263 | begin 264 | if WizardSilent then 265 | idpSetOption('RetryButton', '0'); 266 | 267 | if WizardSupressMsgBoxes then 268 | idpSetInternalOption('ErrorDialog', 'none'); 269 | 270 | if not IDPOptions.NoRetryButton then 271 | WizardForm.BackButton.Caption := ExpandConstant('{cm:IDP_RetryButton}'); 272 | 273 | idpShowDetails(IDPOptions.DetailedMode); 274 | IDPForm.DetailsButton.Visible := not IDPOptions.NoDetailsButton; 275 | 276 | #ifdef GRAPHICAL_INSTALLER_PROJECT 277 | idpSetInternalOption('RedrawBackground', '1'); 278 | idpConnectControl('GIBackButton', hBackButton); 279 | idpConnectControl('GINextButton', hNextButton); 280 | 281 | if not IDPOptions.NoSkinnedButton then 282 | begin 283 | IDPForm.DetailsButton.Visible := false; 284 | if IDPForm.GIDetailsButton = 0 then 285 | idpCreateGIDetailsButton; 286 | end; 287 | 288 | if IDPOptions.NoRetryButton then 289 | WizardForm.BackButton.Enabled := false 290 | else 291 | WizardForm.BackButton.Visible := false; 292 | 293 | WizardForm.NextButton.Enabled := false; 294 | #endif 295 | idpSetComponents(WizardSelectedComponents(false)); 296 | 297 | if WizardVerySilent then 298 | idpDownloadFilesComp 299 | else if WizardSilent then 300 | begin 301 | WizardForm.Show; 302 | WizardForm.Repaint; 303 | idpDownloadFilesCompUi; 304 | WizardForm.Hide; 305 | end 306 | else 307 | idpStartDownload; 308 | end; 309 | 310 | function idpShouldSkipPage(Page: TWizardPage): Boolean; 311 | begin 312 | idpSetComponents(WizardSelectedComponents(false)); 313 | Result := ((idpFilesCount = 0) and (idpFtpDirsCount = 0)) or idpFilesDownloaded; 314 | end; 315 | 316 | function idpBackButtonClick(Page: TWizardPage): Boolean; 317 | begin 318 | if not IDPOptions.NoRetryButton then // Retry button clicked 319 | begin 320 | idpStartDownload; 321 | Result := False; 322 | end 323 | else 324 | Result := true; 325 | end; 326 | 327 | function idpNextButtonClick(Page: TWizardPage): Boolean; 328 | begin 329 | Result := True; 330 | end; 331 | 332 | procedure idpCancelButtonClick(Page: TWizardPage; var Cancel, Confirm: Boolean); 333 | begin 334 | if ExitSetupMsgBox then 335 | begin 336 | IDPForm.Status.Caption := ExpandConstant('{cm:IDP_CancellingDownload}'); 337 | WizardForm.Repaint; 338 | idpStopDownload; 339 | Cancel := true; 340 | Confirm := false; 341 | end 342 | else 343 | Cancel := false; 344 | end; 345 | 346 | procedure idpReportErrorHelper(Sender: TObject); 347 | begin 348 | idpReportError; //calling idpReportError in main thread for compatibility with VCL Styles for IS 349 | end; 350 | 351 | function idpCreateDownloadForm(PreviousPageId: Integer): Integer; 352 | begin 353 | IDPForm.Page := CreateCustomPage(PreviousPageId, ExpandConstant('{cm:IDP_FormCaption}'), ExpandConstant('{cm:IDP_FormDescription}')); 354 | 355 | IDPForm.TotalProgressBar := TNewProgressBar.Create(IDPForm.Page); 356 | with IDPForm.TotalProgressBar do 357 | begin 358 | Parent := IDPForm.Page.Surface; 359 | Left := ScaleX(0); 360 | Top := ScaleY(16); 361 | Width := ScaleX(410); 362 | Height := ScaleY(20); 363 | Min := 0; 364 | Max := 100; 365 | end; 366 | 367 | IDPForm.TotalProgressLabel := TNewStaticText.Create(IDPForm.Page); 368 | with IDPForm.TotalProgressLabel do 369 | begin 370 | Parent := IDPForm.Page.Surface; 371 | Caption := ExpandConstant('{cm:IDP_TotalProgress}'); 372 | Left := ScaleX(0); 373 | Top := ScaleY(0); 374 | Width := ScaleX(200); 375 | Height := ScaleY(14); 376 | AutoSize := False; 377 | TabOrder := 1; 378 | end; 379 | 380 | IDPForm.CurrentFileLabel := TNewStaticText.Create(IDPForm.Page); 381 | with IDPForm.CurrentFileLabel do 382 | begin 383 | Parent := IDPForm.Page.Surface; 384 | Caption := ExpandConstant('{cm:IDP_CurrentFile}'); 385 | Left := ScaleX(0); 386 | Top := ScaleY(48); 387 | Width := ScaleX(200); 388 | Height := ScaleY(14); 389 | AutoSize := False; 390 | TabOrder := 2; 391 | end; 392 | 393 | IDPForm.FileProgressBar := TNewProgressBar.Create(IDPForm.Page); 394 | with IDPForm.FileProgressBar do 395 | begin 396 | Parent := IDPForm.Page.Surface; 397 | Left := ScaleX(0); 398 | Top := ScaleY(64); 399 | Width := ScaleX(410); 400 | Height := ScaleY(20); 401 | Min := 0; 402 | Max := 100; 403 | end; 404 | 405 | IDPForm.TotalDownloaded := TNewStaticText.Create(IDPForm.Page); 406 | with IDPForm.TotalDownloaded do 407 | begin 408 | Parent := IDPForm.Page.Surface; 409 | Caption := ''; 410 | Left := ScaleX(290); 411 | Top := ScaleY(0); 412 | Width := ScaleX(120); 413 | Height := ScaleY(14); 414 | AutoSize := False; 415 | TabOrder := 4; 416 | end; 417 | 418 | IDPForm.FileDownloaded := TNewStaticText.Create(IDPForm.Page); 419 | with IDPForm.FileDownloaded do 420 | begin 421 | Parent := IDPForm.Page.Surface; 422 | Caption := ''; 423 | Left := ScaleX(290); 424 | Top := ScaleY(48); 425 | Width := ScaleX(120); 426 | Height := ScaleY(14); 427 | AutoSize := False; 428 | TabOrder := 5; 429 | end; 430 | 431 | IDPForm.FileNameLabel := TNewStaticText.Create(IDPForm.Page); 432 | with IDPForm.FileNameLabel do 433 | begin 434 | Parent := IDPForm.Page.Surface; 435 | Caption := ExpandConstant('{cm:IDP_File}'); 436 | Left := ScaleX(0); 437 | Top := ScaleY(100); 438 | Width := ScaleX(116); 439 | Height := ScaleY(14); 440 | AutoSize := False; 441 | TabOrder := 6; 442 | end; 443 | 444 | IDPForm.SpeedLabel := TNewStaticText.Create(IDPForm.Page); 445 | with IDPForm.SpeedLabel do 446 | begin 447 | Parent := IDPForm.Page.Surface; 448 | Caption := ExpandConstant('{cm:IDP_Speed}'); 449 | Left := ScaleX(0); 450 | Top := ScaleY(116); 451 | Width := ScaleX(116); 452 | Height := ScaleY(14); 453 | AutoSize := False; 454 | TabOrder := 7; 455 | end; 456 | 457 | IDPForm.StatusLabel := TNewStaticText.Create(IDPForm.Page); 458 | with IDPForm.StatusLabel do 459 | begin 460 | Parent := IDPForm.Page.Surface; 461 | Caption := ExpandConstant('{cm:IDP_Status}'); 462 | Left := ScaleX(0); 463 | Top := ScaleY(132); 464 | Width := ScaleX(116); 465 | Height := ScaleY(14); 466 | AutoSize := False; 467 | TabOrder := 8; 468 | end; 469 | 470 | IDPForm.ElapsedTimeLabel := TNewStaticText.Create(IDPForm.Page); 471 | with IDPForm.ElapsedTimeLabel do 472 | begin 473 | Parent := IDPForm.Page.Surface; 474 | Caption := ExpandConstant('{cm:IDP_ElapsedTime}'); 475 | Left := ScaleX(0); 476 | Top := ScaleY(148); 477 | Width := ScaleX(116); 478 | Height := ScaleY(14); 479 | AutoSize := False; 480 | TabOrder := 9; 481 | end; 482 | 483 | IDPForm.RemainingTimeLabel := TNewStaticText.Create(IDPForm.Page); 484 | with IDPForm.RemainingTimeLabel do 485 | begin 486 | Parent := IDPForm.Page.Surface; 487 | Caption := ExpandConstant('{cm:IDP_RemainingTime}'); 488 | Left := ScaleX(0); 489 | Top := ScaleY(164); 490 | Width := ScaleX(116); 491 | Height := ScaleY(14); 492 | AutoSize := False; 493 | TabOrder := 10; 494 | end; 495 | 496 | IDPForm.FileName := TNewStaticText.Create(IDPForm.Page); 497 | with IDPForm.FileName do 498 | begin 499 | Parent := IDPForm.Page.Surface; 500 | Caption := ''; 501 | Left := ScaleX(120); 502 | Top := ScaleY(100); 503 | Width := ScaleX(280); 504 | Height := ScaleY(14); 505 | AutoSize := False; 506 | TabOrder := 11; 507 | end; 508 | 509 | IDPForm.Speed := TNewStaticText.Create(IDPForm.Page); 510 | with IDPForm.Speed do 511 | begin 512 | Parent := IDPForm.Page.Surface; 513 | Caption := ''; 514 | Left := ScaleX(120); 515 | Top := ScaleY(116); 516 | Width := ScaleX(280); 517 | Height := ScaleY(14); 518 | AutoSize := False; 519 | TabOrder := 12; 520 | end; 521 | 522 | IDPForm.Status := TNewStaticText.Create(IDPForm.Page); 523 | with IDPForm.Status do 524 | begin 525 | Parent := IDPForm.Page.Surface; 526 | Caption := ''; 527 | Left := ScaleX(120); 528 | Top := ScaleY(132); 529 | Width := ScaleX(280); 530 | Height := ScaleY(14); 531 | AutoSize := False; 532 | TabOrder := 13; 533 | end; 534 | 535 | IDPForm.ElapsedTime := TNewStaticText.Create(IDPForm.Page); 536 | with IDPForm.ElapsedTime do 537 | begin 538 | Parent := IDPForm.Page.Surface; 539 | Caption := ''; 540 | Left := ScaleX(120); 541 | Top := ScaleY(148); 542 | Width := ScaleX(280); 543 | Height := ScaleY(14); 544 | AutoSize := False; 545 | TabOrder := 14; 546 | end; 547 | 548 | IDPForm.RemainingTime := TNewStaticText.Create(IDPForm.Page); 549 | with IDPForm.RemainingTime do 550 | begin 551 | Parent := IDPForm.Page.Surface; 552 | Caption := ''; 553 | Left := ScaleX(120); 554 | Top := ScaleY(164); 555 | Width := ScaleX(280); 556 | Height := ScaleY(14); 557 | AutoSize := False; 558 | TabOrder := 15; 559 | end; 560 | 561 | IDPForm.DetailsButton := TNewButton.Create(IDPForm.Page); 562 | with IDPForm.DetailsButton do 563 | begin 564 | Parent := IDPForm.Page.Surface; 565 | Caption := ExpandConstant('{cm:IDP_DetailsButton}'); 566 | Left := ScaleX(336); 567 | Top := ScaleY(184); 568 | Width := ScaleX(75); 569 | Height := ScaleY(23); 570 | TabOrder := 16; 571 | OnClick := @idpDetailsButtonClick; 572 | end; 573 | 574 | IDPForm.InvisibleButton := TNewButton.Create(IDPForm.Page); 575 | with IDPForm.InvisibleButton do 576 | begin 577 | Parent := IDPForm.Page.Surface; 578 | Caption := ExpandConstant('You must not see this button'); 579 | Left := ScaleX(0); 580 | Top := ScaleY(0); 581 | Width := ScaleX(10); 582 | Height := ScaleY(10); 583 | TabOrder := 17; 584 | Visible := False; 585 | OnClick := @idpReportErrorHelper; 586 | end; 587 | 588 | with IDPForm.Page do 589 | begin 590 | OnActivate := @idpFormActivate; 591 | OnShouldSkipPage := @idpShouldSkipPage; 592 | OnBackButtonClick := @idpBackButtonClick; 593 | OnNextButtonClick := @idpNextButtonClick; 594 | OnCancelButtonClick := @idpCancelButtonClick; 595 | end; 596 | 597 | Result := IDPForm.Page.ID; 598 | end; 599 | 600 | procedure idpConnectControls; 601 | begin 602 | idpConnectControl('TotalProgressLabel', IDPForm.TotalProgressLabel.Handle); 603 | idpConnectControl('TotalProgressBar', IDPForm.TotalProgressBar.Handle); 604 | idpConnectControl('FileProgressBar', IDPForm.FileProgressBar.Handle); 605 | idpConnectControl('TotalDownloaded', IDPForm.TotalDownloaded.Handle); 606 | idpConnectControl('FileDownloaded', IDPForm.FileDownloaded.Handle); 607 | idpConnectControl('FileName', IDPForm.FileName.Handle); 608 | idpConnectControl('Speed', IDPForm.Speed.Handle); 609 | idpConnectControl('Status', IDPForm.Status.Handle); 610 | idpConnectControl('ElapsedTime', IDPForm.ElapsedTime.Handle); 611 | idpConnectControl('RemainingTime', IDPForm.RemainingTime.Handle); 612 | idpConnectControl('InvisibleButton', IDPForm.InvisibleButton.Handle); 613 | idpConnectControl('WizardPage', IDPForm.Page.Surface.Handle); 614 | idpConnectControl('WizardForm', WizardForm.Handle); 615 | idpConnectControl('BackButton', WizardForm.BackButton.Handle); 616 | idpConnectControl('NextButton', WizardForm.NextButton.Handle); 617 | idpConnectControl('LabelFont', IDPForm.TotalDownloaded.Font.Handle); 618 | end; 619 | 620 | procedure idpInitMessages; 621 | begin 622 | idpAddMessage('Total progress', ExpandConstant('{cm:IDP_TotalProgress}')); 623 | idpAddMessage('KB/s', ExpandConstant('{cm:IDP_KBs}')); 624 | idpAddMessage('MB/s', ExpandConstant('{cm:IDP_MBs}')); 625 | idpAddMessage('%.2f of %.2f', ExpandConstant('{cm:IDP_X_of_X}')); 626 | idpAddMessage('KB', ExpandConstant('{cm:IDP_KB}')); 627 | idpAddMessage('MB', ExpandConstant('{cm:IDP_MB}')); 628 | idpAddMessage('GB', ExpandConstant('{cm:IDP_GB}')); 629 | idpAddMessage('Initializing...', ExpandConstant('{cm:IDP_Initializing}')); 630 | idpAddMessage('Getting file information...', ExpandConstant('{cm:IDP_GettingFileInformation}')); 631 | idpAddMessage('Starting download...', ExpandConstant('{cm:IDP_StartingDownload}')); 632 | idpAddMessage('Connecting...', ExpandConstant('{cm:IDP_Connecting}')); 633 | idpAddMessage('Downloading...', ExpandConstant('{cm:IDP_Downloading}')); 634 | idpAddMessage('Download complete', ExpandConstant('{cm:IDP_DownloadComplete}')); 635 | idpAddMessage('Download failed', ExpandConstant('{cm:IDP_DownloadFailed}')); 636 | idpAddMessage('Cannot connect', ExpandConstant('{cm:IDP_CannotConnect}')); 637 | idpAddMessage('Unknown', ExpandConstant('{cm:IDP_Unknown}')); 638 | idpAddMessage('Download cancelled', ExpandConstant('{cm:IDP_DownloadCancelled}')); 639 | idpAddMessage('HTTP error %d', ExpandConstant('{cm:IDP_HTTPError_X}')); 640 | idpAddMessage('400', ExpandConstant('{cm:IDP_400}')); 641 | idpAddMessage('401', ExpandConstant('{cm:IDP_401}')); 642 | idpAddMessage('404', ExpandConstant('{cm:IDP_404}')); 643 | idpAddMessage('407', ExpandConstant('{cm:IDP_407}')); 644 | idpAddMessage('500', ExpandConstant('{cm:IDP_500}')); 645 | idpAddMessage('502', ExpandConstant('{cm:IDP_502}')); 646 | idpAddMessage('503', ExpandConstant('{cm:IDP_503}')); 647 | idpAddMessage('Retry', ExpandConstant('{cm:IDP_RetryButton}')); 648 | idpAddMessage('Ignore', ExpandConstant('{cm:IDP_IgnoreButton}')); 649 | idpAddMessage('Cancel', SetupMessage(msgButtonCancel)); 650 | idpAddMessage('The following files were not downloaded:', ExpandConstant('{cm:IDP_FilesNotDownloaded}')); 651 | idpAddMessage('Check your connection and click ''Retry'' to try downloading the files again, or click ''Next'' to continue installing anyway.', ExpandConstant('{cm:IDP_RetryNext}')); 652 | idpAddMessage('Check your connection and click ''Retry'' to try downloading the files again, or click ''Cancel'' to terminate setup.', ExpandConstant('{cm:IDP_RetryCancel}')); 653 | end; 654 | 655 | procedure idpDownloadAfter(PageAfterId: Integer); 656 | begin 657 | idpCreateDownloadForm(PageAfterId); 658 | idpConnectControls; 659 | idpInitMessages; 660 | end; 661 | 662 | #include 663 | -------------------------------------------------------------------------------- /idp/unicode/idp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/idp/unicode/idp.dll -------------------------------------------------------------------------------- /idp/unicode/idplang/ChineseSimplified.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | zh.IDP_FormCaption =下载文件 3 | zh.IDP_FormDescription =请等待安装程序下载文件... 4 | zh.IDP_TotalProgress =总进度 5 | zh.IDP_CurrentFile =当前文件 6 | zh.IDP_File =文件: 7 | zh.IDP_Speed =速度: 8 | zh.IDP_Status =状态: 9 | zh.IDP_ElapsedTime =已用时间: 10 | zh.IDP_RemainingTime =剩余时间: 11 | zh.IDP_DetailsButton =详情 12 | zh.IDP_HideButton =隐藏 13 | zh.IDP_RetryButton =重试 14 | zh.IDP_IgnoreButton =忽略 15 | zh.IDP_KBs =KB/s 16 | zh.IDP_MBs =MB/s 17 | zh.IDP_X_of_X =%.2f of %.2f 18 | zh.IDP_KB =KB 19 | zh.IDP_MB =MB 20 | zh.IDP_GB =GB 21 | zh.IDP_Initializing =正在初始化... 22 | zh.IDP_GettingFileInformation=正在获取文件信息... 23 | zh.IDP_StartingDownload =开始下载... 24 | zh.IDP_Connecting =正在连接... 25 | zh.IDP_Downloading =正在下载... 26 | zh.IDP_DownloadComplete =下载完成 27 | zh.IDP_DownloadFailed =下载失败 28 | zh.IDP_CannotConnect =不能连接 29 | zh.IDP_CancellingDownload =正在取消下载... 30 | zh.IDP_Unknown =未知 31 | zh.IDP_DownloadCancelled =下载被取消 32 | zh.IDP_RetryNext =检查你的网络连接,点击“重试”重新下载,或点击“下一步”继续安装。 33 | zh.IDP_RetryCancel =检查你的网络连接,点击“重试”重新下载,或点击“取消”中止安装。 34 | zh.IDP_FilesNotDownloaded =下面文件不能下载: 35 | zh.IDP_HTTPError_X =HTTP 错误 %d 36 | zh.IDP_400 =错误的请求 (400) 37 | zh.IDP_401 =访问被拒绝 (401) 38 | zh.IDP_404 =未找到文件 (404) 39 | zh.IDP_407 =需要验证代理 (407) 40 | zh.IDP_500 =服务器内部错误 (500) 41 | zh.IDP_502 =错误的网关 (502) 42 | zh.IDP_503 =服务暂时不可用 (503) -------------------------------------------------------------------------------- /idp/unicode/idplang/belarusian.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | be.IDP_FormCaption =Спампоўванне дадатковых файлаў 3 | be.IDP_FormDescription =Калі ласка, пачакайце, пакуль усталёўнік пампуе дадатковыя файлы... 4 | be.IDP_TotalProgress =Агульны прагрэс 5 | be.IDP_CurrentFile =Бягучы файл 6 | be.IDP_File =Файл: 7 | be.IDP_Speed =Хуткасць: 8 | be.IDP_Status =Стан: 9 | be.IDP_ElapsedTime =Мінула часу: 10 | be.IDP_RemainingTime =Засталося часу: 11 | be.IDP_DetailsButton =Падрабязней 12 | be.IDP_HideButton =Схаваць 13 | be.IDP_RetryButton =Паўтарыць 14 | be.IDP_IgnoreButton = 15 | be.IDP_KBs =КБ/с 16 | be.IDP_MBs =МБ/с 17 | be.IDP_X_of_X =%.2f з %.2f 18 | be.IDP_KB =КБ 19 | be.IDP_MB =МБ 20 | be.IDP_GB =ГБ 21 | be.IDP_Initializing =Ініцыялізацыя... 22 | be.IDP_GettingFileInformation=Атрыманне звестак пра файл... 23 | be.IDP_StartingDownload =Спампоўванне пачынаецца... 24 | be.IDP_Connecting =Злучэнне... 25 | be.IDP_Downloading =Спампоўванне... 26 | be.IDP_DownloadComplete =Спампоўванне скончылася 27 | be.IDP_DownloadFailed =Не ўдалося спампаваць 28 | be.IDP_CannotConnect =Нельга злучыцца 29 | be.IDP_CancellingDownload =Скасаванне спампоўвання... 30 | be.IDP_Unknown =Невядома 31 | be.IDP_DownloadCancelled =Загрузка отменена 32 | be.IDP_RetryNext =Праверце сваё злучэнне з Сецівам і націсніце 'Паўтарыць' каб пачаць спампоўванне нанова, або націсніце 'Далей' каб працягнуць усталяванне. 33 | be.IDP_RetryCancel =Праверце сваё злучэнне з Сецівам і націсніце 'Паўтарыць' каб пачаць спампоўванне нанова, або націсніце 'Скасаваць' каб скасаваць усталяванне. 34 | be.IDP_FilesNotDownloaded = 35 | be.IDP_HTTPError_X =Памылка HTTP %d 36 | be.IDP_400 =Памылковы запыт (400) 37 | be.IDP_401 =Доступ забаронены (401) 38 | be.IDP_404 =Файл не знойдзены (404) 39 | be.IDP_407 =Неабходна аўтарызацыя прокси (407) 40 | be.IDP_500 =Унутраная памылка сервера (500) 41 | be.IDP_502 =Памылковы шлюз (502) 42 | be.IDP_503 =Сервер часовы недаступны (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/brazilianPortuguese.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | pt_br.IDP_FormCaption =Baixando arquivos 3 | pt_br.IDP_FormDescription =Por favor aguarde, enquanto recebe arquivos adicionais... 4 | pt_br.IDP_TotalProgress =Progresso total 5 | pt_br.IDP_CurrentFile =Arquivo atual 6 | pt_br.IDP_File =Arquivo: 7 | pt_br.IDP_Speed =Velocidade: 8 | pt_br.IDP_Status =Estado: 9 | pt_br.IDP_ElapsedTime =Tempo decorrido: 10 | pt_br.IDP_RemainingTime =Tempo remanescente: 11 | pt_br.IDP_DetailsButton =Detalhes 12 | pt_br.IDP_HideButton =Ocultar 13 | pt_br.IDP_RetryButton =Repetir 14 | pt_br.IDP_IgnoreButton = 15 | pt_br.IDP_KBs =KB/s 16 | pt_br.IDP_MBs =MB/s 17 | pt_br.IDP_X_of_X =%.2f de %.2f 18 | pt_br.IDP_KB =KB 19 | pt_br.IDP_MB =MB 20 | pt_br.IDP_GB =GB 21 | pt_br.IDP_Initializing =Inicializando... 22 | pt_br.IDP_GettingFileInformation=Recebendo informações do arquivo... 23 | pt_br.IDP_StartingDownload =Iniciando o download... 24 | pt_br.IDP_Connecting =Conectando... 25 | pt_br.IDP_Downloading =Baixando... 26 | pt_br.IDP_DownloadComplete =Download finalizado 27 | pt_br.IDP_DownloadFailed =Falha no download 28 | pt_br.IDP_CannotConnect =Não pode conectar 29 | pt_br.IDP_CancellingDownload =Cancelando o download... 30 | pt_br.IDP_Unknown =Desconhecido 31 | pt_br.IDP_DownloadCancelled =Download cancelado 32 | pt_br.IDP_RetryNext =Verifique sua conexão e clique em 'Repetir' para tentar novamente o download dos arquivos, ou clique em 'Próximo' para continuar a instalação mesmo assim. 33 | pt_br.IDP_RetryCancel =Verifique sua conexão e clique em 'Repetir' para tentar novamente o download dos arquivos, ou clique em 'Cancel' para finalizar a Instalação. 34 | pt_br.IDP_FilesNotDownloaded = 35 | pt_br.IDP_HTTPError_X =erro HTTP %d 36 | pt_br.IDP_400 =Requisição inválida (400) 37 | pt_br.IDP_401 =Acesso negado (401) 38 | pt_br.IDP_404 =Arquivo não encontrado (404) 39 | pt_br.IDP_407 =Autenticação de proxy necessária (407) 40 | pt_br.IDP_500 =Erro interno do servidor (500) 41 | pt_br.IDP_502 =Bad Gateway (502) 42 | pt_br.IDP_503 =Serviço temporariamente indisponível (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/bulgarian.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | bg.IDP_FormCaption =Изтегляне на допълнителни файлове 3 | bg.IDP_FormDescription =Моля, изчакайте, докато съветникът изтегля допълнителните файлове... 4 | bg.IDP_TotalProgress =Общ ход 5 | bg.IDP_CurrentFile =Текущ файл 6 | bg.IDP_File =Файл: 7 | bg.IDP_Speed =Скорост: 8 | bg.IDP_Status =Състояние: 9 | bg.IDP_ElapsedTime =Изминало време: 10 | bg.IDP_RemainingTime =Оставащо време: 11 | bg.IDP_DetailsButton =Повече информация 12 | bg.IDP_HideButton =Скрий 13 | bg.IDP_RetryButton =Опитай отново 14 | bg.IDP_IgnoreButton =Пренебрегни 15 | bg.IDP_KBs =КБ/с 16 | bg.IDP_MBs =МБ/с 17 | bg.IDP_X_of_X =%.2f от %.2f 18 | bg.IDP_KB =КБ 19 | bg.IDP_MB =МБ 20 | bg.IDP_GB =ГБ 21 | bg.IDP_Initializing =Стартиране... 22 | bg.IDP_GettingFileInformation=Получаване на информация за файла... 23 | bg.IDP_StartingDownload =Започване на изтеглянето... 24 | bg.IDP_Connecting =Свързване... 25 | bg.IDP_Downloading =Изтегляне... 26 | bg.IDP_DownloadComplete =Изтеглянето е завършено 27 | bg.IDP_DownloadFailed =Изтеглянето беше неуспешно 28 | bg.IDP_CannotConnect =Връзката е невъзможна 29 | bg.IDP_CancellingDownload =Отмяна на изтеглянето... 30 | bg.IDP_Unknown =Неизвестно 31 | bg.IDP_DownloadCancelled =Изтеглянето е отменено 32 | bg.IDP_RetryNext =Проверете Вашата Интернет връзка и кликнете „Опитай отново“, за да направите повторен опит за изтегляне на файловете, или кликнете „Напред“, за да продължите с инсталацията въпреки това. 33 | bg.IDP_RetryCancel =Проверете Вашата Интернет връзка и кликнете „Опитай отново“, за да направите повторен опит за изтегляне на файловете, или кликнете „Отмени“, за да прекратите инсталацията. 34 | bg.IDP_FilesNotDownloaded =Следните файлове не бяха изтеглени: 35 | bg.IDP_HTTPError_X =HTTP грешка %d 36 | bg.IDP_400 =Грешна заявка (400) 37 | bg.IDP_401 =Достъпът е отказан (401) 38 | bg.IDP_404 =Файлът не е открит (404) 39 | bg.IDP_407 =Изисква се идентификация на прокси (407) 40 | bg.IDP_500 =Вътрешна сървърна грешка (500) 41 | bg.IDP_502 =Грешен шлюз (502) 42 | bg.IDP_503 =Услугата е временно недостъпна (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/czech.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | cz.IDP_FormCaption =Stahování souborů 3 | cz.IDP_FormDescription =Prosím čekejte, než instalátor stáhne dodatečné soubory... 4 | cz.IDP_TotalProgress =Celkový průběh stahování 5 | cz.IDP_CurrentFile =Aktuální soubor 6 | cz.IDP_File =Soubor: 7 | cz.IDP_Speed =Rychlost: 8 | cz.IDP_Status =Stav: 9 | cz.IDP_ElapsedTime =Uplynulý čas: 10 | cz.IDP_RemainingTime =Zbývá: 11 | cz.IDP_DetailsButton =Detaily 12 | cz.IDP_HideButton =Skrýt 13 | cz.IDP_RetryButton =Znovu 14 | cz.IDP_IgnoreButton =Ignorovat 15 | cz.IDP_KBs =KB/s 16 | cz.IDP_MBs =MB/s 17 | cz.IDP_X_of_X =%.2f of %.2f 18 | cz.IDP_KB =KB 19 | cz.IDP_MB =MB 20 | cz.IDP_GB =GB 21 | cz.IDP_Initializing =Inicializace... 22 | cz.IDP_GettingFileInformation=Stahování informací o souboru... 23 | cz.IDP_StartingDownload =Začínám stahovat... 24 | cz.IDP_Connecting =Navazování spojení... 25 | cz.IDP_Downloading =Stahování... 26 | cz.IDP_DownloadComplete =Stahování ukončeno 27 | cz.IDP_DownloadFailed =Stahování selhalo 28 | cz.IDP_CannotConnect =Není možné navázat spojení 29 | cz.IDP_CancellingDownload =Ukončení stahování... 30 | cz.IDP_Unknown =Neznámé 31 | cz.IDP_DownloadCancelled =Stahování zrušeno 32 | cz.IDP_RetryNext =Zkontrolujte připojení k internetu a klikněte na "Znovu" pro opakované stahování souborů, nebo klikměte na "Další" a pokračujte v instalaci. 33 | cz.IDP_RetryCancel =Zkontrolujte připojení k internetu a klikněte na "Znovu" pro opakované stahování souborů, nebo klikměte na "Zrušit" a zrušte instalaci. 34 | cz.IDP_FilesNotDownloaded =Následující soubory nebyly staženy: 35 | cz.IDP_HTTPError_X =Chyba HTTP %d 36 | cz.IDP_400 =Nesprávný požadavek (400) 37 | cz.IDP_401 =Přístup zakázán (401) 38 | cz.IDP_404 =Soubor nenalezen (404) 39 | cz.IDP_407 =Vyžadováno ověření proxy (407) 40 | cz.IDP_500 =Chyba serveru (500) 41 | cz.IDP_502 =Chyba brány (502) 42 | cz.IDP_503 =Služba je dočasně nedostupná (503) -------------------------------------------------------------------------------- /idp/unicode/idplang/default.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | IDP_FormCaption =Downloading additional files 3 | IDP_FormDescription =Please wait while Setup is downloading additional files... 4 | IDP_TotalProgress =Total progress 5 | IDP_CurrentFile =Current file 6 | IDP_File =File: 7 | IDP_Speed =Speed: 8 | IDP_Status =Status: 9 | IDP_ElapsedTime =Elapsed time: 10 | IDP_RemainingTime =Remaining time: 11 | IDP_DetailsButton =Details 12 | IDP_HideButton =Hide 13 | IDP_RetryButton =Retry 14 | IDP_IgnoreButton =Ignore 15 | IDP_KBs =KB/s 16 | IDP_MBs =MB/s 17 | IDP_X_of_X =%.2f of %.2f 18 | IDP_KB =KB 19 | IDP_MB =MB 20 | IDP_GB =GB 21 | IDP_Initializing =Initializing... 22 | IDP_GettingFileInformation=Getting file information... 23 | IDP_StartingDownload =Starting download... 24 | IDP_Connecting =Connecting... 25 | IDP_Downloading =Downloading... 26 | IDP_DownloadComplete =Download complete 27 | IDP_DownloadFailed =Download failed 28 | IDP_CannotConnect =Cannot connect 29 | IDP_CancellingDownload =Cancelling download... 30 | IDP_Unknown =Unknown 31 | IDP_DownloadCancelled =Download cancelled 32 | IDP_RetryNext =Check your connection and click 'Retry' to try downloading the files again, or click 'Next' to continue installing anyway. 33 | IDP_RetryCancel =Check your connection and click 'Retry' to try downloading the files again, or click 'Cancel' to terminate setup. 34 | IDP_FilesNotDownloaded =The following files were not downloaded: 35 | IDP_HTTPError_X =HTTP error %d 36 | IDP_400 =Bad request (400) 37 | IDP_401 =Access denied (401) 38 | IDP_404 =File not found (404) 39 | IDP_407 =Proxy authentication required (407) 40 | IDP_500 =Server internal error (500) 41 | IDP_502 =Bad gateway (502) 42 | IDP_503 =Service temporaily unavailable (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/finnish.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | fi.IDP_FormCaption =Tiedostojen lataus 3 | fi.IDP_FormDescription =Odota, asennusohjelma lataa nyt tiedostoja koneellesi... 4 | fi.IDP_TotalProgress =Latauksien edistyminen 5 | fi.IDP_CurrentFile =Nyt ladattava tiedosto 6 | fi.IDP_File =Tiedosto: 7 | fi.IDP_Speed =Nopeus: 8 | fi.IDP_Status =Tila: 9 | fi.IDP_ElapsedTime =Aikaa käytetty: 10 | fi.IDP_RemainingTime =Aikaa jäljellä: 11 | fi.IDP_DetailsButton =Tiedot 12 | fi.IDP_HideButton =Piilota 13 | fi.IDP_RetryButton =Yritä uudelleen 14 | fi.IDP_IgnoreButton =Hylkää 15 | fi.IDP_KBs =KT/s 16 | fi.IDP_MBs =MT/s 17 | fi.IDP_X_of_X =%.2f of %.2f 18 | fi.IDP_KB =KT 19 | fi.IDP_MB =MT 20 | fi.IDP_GB =GT 21 | fi.IDP_Initializing =Alustetaan... 22 | fi.IDP_GettingFileInformation=Haetaan tiedostojen tietoja... 23 | fi.IDP_StartingDownload =Aloitetaan latausta... 24 | fi.IDP_Connecting =Yhdistetään... 25 | fi.IDP_Downloading =Ladataan... 26 | fi.IDP_DownloadComplete =Lataus valmis 27 | fi.IDP_DownloadFailed =Lataus epäonnistui 28 | fi.IDP_CannotConnect =Virhe yhdistettäessä 29 | fi.IDP_CancellingDownload =Peruutetaan latausta... 30 | fi.IDP_Unknown =Tuntematon 31 | fi.IDP_DownloadCancelled =Lataus peruttiin 32 | fi.IDP_RetryNext =Tarkista nettiyhteytesi tila ja klikkaa 'Yritä uudelleen' jatkaaksesi tiedostojen lataamista, tai klikkaa 'Seuraava' jatkaaksesi asennusta ilman ladattuja tiedostoja. 33 | fi.IDP_RetryCancel =Tarkista nettiyhteytesi tila ja klikkaa 'Yritä uudelleen' jatkaaksesi tiedostojen lataamista, tai klikkaa 'Peruuta' keskeyttääksesi asennus. 34 | fi.IDP_FilesNotDownloaded =Seuraavia tiedostoja ei pystytty lataamaan: 35 | fi.IDP_HTTPError_X =HTTP virhe %d 36 | fi.IDP_400 =Virheellinen pyyntö (400) 37 | fi.IDP_401 =Käyttö estetty (401) 38 | fi.IDP_404 =Tiedostoa ei löydy (404) 39 | ;fi.IDP_407 =??? 40 | fi.IDP_500 =Palvelimen sisäinen virhe (500) 41 | fi.IDP_502 =Virheellinen gateway (502) 42 | fi.IDP_503 =Palvelu väliaikaisesti ei saatavilla (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/french.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | fr.IDP_FormCaption =Téléchargement des fichiers additionnels 3 | fr.IDP_FormDescription =Veuillez patienter durant le téléchargement des fichiers additionnels... 4 | fr.IDP_TotalProgress =Progression générale: 5 | fr.IDP_CurrentFile =Fichier en cours: 6 | fr.IDP_File =Fichier: 7 | fr.IDP_Speed =Vitesse: 8 | fr.IDP_Status =Status: 9 | fr.IDP_ElapsedTime =Temps écoulé: 10 | fr.IDP_RemainingTime =Temps restant: 11 | fr.IDP_DetailsButton =Détails 12 | fr.IDP_HideButton =Cacher 13 | fr.IDP_RetryButton =Réessayer 14 | fr.IDP_IgnoreButton =Ignorer 15 | fr.IDP_KBs =KO/s 16 | fr.IDP_MBs =MO/s 17 | fr.IDP_X_of_X =%.2f de %.2f 18 | fr.IDP_KB =KO 19 | fr.IDP_MB =MO 20 | fr.IDP_GB =GB 21 | fr.IDP_Initializing =Initializing... 22 | fr.IDP_GettingFileInformation=Récupération du fichier d'information... 23 | fr.IDP_StartingDownload =Début du téléchargement... 24 | fr.IDP_Connecting =Connexion... 25 | fr.IDP_Downloading =Téléchargement... 26 | fr.IDP_DownloadComplete =Téléchargement terminé 27 | fr.IDP_DownloadFailed =Téléchargement interrompu 28 | fr.IDP_CannotConnect =Impossible de se connecter 29 | fr.IDP_CancellingDownload =Annulation de téléchargement... 30 | fr.IDP_Unknown =Inconnu 31 | fr.IDP_DownloadCancelled =Télécharger annulé 32 | fr.IDP_RetryNext =Désolé, le fichier n'a pu être téléchargé. Cliquez réessayer pour essayer à nouveau de le télécharger, ou cliquez suivant pour continuer l'installation. 33 | fr.IDP_RetryCancel =Désolé, le fichier n'a pu être téléchargé. Cliquez réessayer pour essayer à nouveau de le télécharger, ou cliquez annuler pour terminer l'installation. 34 | fr.IDP_FilesNotDownloaded =Les fichiers suivants n'ont pas put téléchargés: 35 | fr.IDP_HTTPError_X =Erreur HTTP %d 36 | fr.IDP_400 =Requête incorrecte (400) 37 | fr.IDP_401 =Accès Refusé (401) 38 | fr.IDP_404 =Fichier introuvable (404) 39 | fr.IDP_407 =Authentification proxy requise (407) 40 | fr.IDP_500 =Erreur interne du serveur (500) 41 | fr.IDP_502 =Mauvaise Passerelle (502) 42 | fr.IDP_503 =Service Temporairement Indisponible (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/german.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | de.IDP_FormCaption =Download zusätzlicher Dateien 3 | de.IDP_FormDescription =Bitte warten, das Setup lädt nun zusätzliche Dateien... 4 | de.IDP_TotalProgress =Gesamter Fortschritt: 5 | de.IDP_CurrentFile =Aktuelle Datei: 6 | de.IDP_File =Datei: 7 | de.IDP_Speed =Geschwindigkeit: 8 | de.IDP_Status =Status: 9 | de.IDP_ElapsedTime =Vergangene Zeit: 10 | de.IDP_RemainingTime =Verbleibende Zeit: 11 | de.IDP_DetailsButton =Details 12 | de.IDP_HideButton =Verstecken 13 | de.IDP_RetryButton =Wiederholen 14 | de.IDP_IgnoreButton = 15 | de.IDP_KBs =kB/s 16 | de.IDP_MBs =MB/s 17 | de.IDP_X_of_X =%.2f von %.2f 18 | de.IDP_KB =KB 19 | de.IDP_MB =MB 20 | de.IDP_GB =GB 21 | de.IDP_Initializing =Initialisieren... 22 | de.IDP_GettingFileInformation=Empfange Dateiinformationen... 23 | de.IDP_StartingDownload =Starte Download... 24 | de.IDP_Connecting =Verbinde... 25 | de.IDP_Downloading =Downloade... 26 | de.IDP_DownloadComplete =Download abgeschlossen 27 | de.IDP_DownloadFailed =Download fehlgeschlagen 28 | de.IDP_CannotConnect =Die Verbindung konnte nicht hergestellt werden 29 | de.IDP_CancellingDownload =Download wird abgebrochen... 30 | de.IDP_Unknown =Unbekannt 31 | de.IDP_DownloadCancelled =Download abgebrochen 32 | de.IDP_RetryNext =Prüfen Sie Ihre Verbindung und klicken Sie auf 'Wiederholen' für einen erneuten Versuch oder klicken Sie auf 'Weiter' um dennoch fortzusetzen. 33 | de.IDP_RetryCancel =Prüfen Sie Ihre Verbindung und klicken Sie auf 'Wiederholen' für einen erneuten Versuch oder klicken Sie auf 'Abbrechen' um das Setup zu verlassen. 34 | de.IDP_FilesNotDownloaded = 35 | de.IDP_HTTPError_X =HTTP Fehler %d 36 | de.IDP_400 =Ungültige Anforderung (400) 37 | de.IDP_401 =Nicht autorisiert (401) 38 | de.IDP_404 =Datei nicht gefunden (404) 39 | ;de.IDP_407 =??? 40 | de.IDP_500 =Interner Serverfehler (500) 41 | de.IDP_502 =Falsches Gateway (502) 42 | de.IDP_503 =Service nicht verfügbar (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/hungarian.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | hu.IDP_FormCaption =További fájlok letöltése 3 | hu.IDP_FormDescription =Kérjük várjon, amíg a telepítő további fájlokat tölt le az internetről... 4 | hu.IDP_TotalProgress =Teljes folyamat: 5 | hu.IDP_CurrentFile =Jelenlegi fájl: 6 | hu.IDP_File =Fájl: 7 | hu.IDP_Speed =Sebesség: 8 | hu.IDP_Status =Állapot: 9 | hu.IDP_ElapsedTime =Eltelt idő: 10 | hu.IDP_RemainingTime =Fennmaradó idő: 11 | hu.IDP_DetailsButton =Részletek 12 | hu.IDP_HideButton =Elrejtés 13 | hu.IDP_RetryButton =Ismét 14 | hu.IDP_IgnoreButton =Kihagy 15 | hu.IDP_KBs =kB/s 16 | hu.IDP_MBs =MB/s 17 | hu.IDP_X_of_X =%.2f / %.2f 18 | hu.IDP_KB =KB 19 | hu.IDP_MB =MB 20 | hu.IDP_GB =GB 21 | hu.IDP_Initializing =Felkészülés... 22 | hu.IDP_GettingFileInformation=A fájlinformációk lekérése... 23 | hu.IDP_StartingDownload =A letöltés megkezdése... 24 | hu.IDP_Connecting =Csatlakozás... 25 | hu.IDP_Downloading =Letöltés... 26 | hu.IDP_DownloadComplete =Letöltés befejezve 27 | hu.IDP_DownloadFailed =Letöltés megszakítva 28 | hu.IDP_CannotConnect =Nem sikert csatlakozni a kiszolgálóhoz 29 | hu.IDP_CancellingDownload =A letöltés megszaktása... 30 | hu.IDP_Unknown =Ismeretlen 31 | hu.IDP_DownloadCancelled =Felhasználó által megszakítva 32 | hu.IDP_RetryNext =Ellenőrizze az internetkapcsolatot, majd kattintson az "Ismét" gombra az újrapróbálkozáshoz, vagy a "Tovább" gombra a fájlok kihagyásához. 33 | hu.IDP_RetryCancel =Ellenőrizze az internetkapcsolatot, majd kattintson az "Ismét" gombra az újrapróbálkozáshoz, vagy a "Mégse" gombra a telepítés megszakításához. 34 | hu.IDP_FilesNotDownloaded =Az alábbi fájlok nem lettek letöltve: 35 | hu.IDP_HTTPError_X =HTTP hiba %d 36 | hu.IDP_400 =Nem megfelelő kérés (400) 37 | hu.IDP_401 =Hozzáférés megtagadva (401) 38 | hu.IDP_404 =A fájl nem található (404) 39 | hu.IDP_407 =Proxy hitelesítés szükséges (407) 40 | hu.IDP_500 =A kiszolgáló belső hibája (500) 41 | hu.IDP_502 =Nem megfelelő átjáró (502) 42 | hu.IDP_503 =A szolgáltatás átmenetileg nem elérhető (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/italian.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | it.IDP_FormCaption =Download file aggiuntivi 3 | it.IDP_FormDescription =Attendere prego, download dei file aggiuntivi in corso... 4 | it.IDP_TotalProgress =Progresso totale 5 | it.IDP_CurrentFile =File corrente 6 | it.IDP_File =File: 7 | it.IDP_Speed =Velocita': 8 | it.IDP_Status =Status: 9 | it.IDP_ElapsedTime =Tempo trascorso: 10 | it.IDP_RemainingTime =Tempo rimanente: 11 | it.IDP_DetailsButton =Dettagli 12 | it.IDP_HideButton =Nascondi 13 | it.IDP_RetryButton =Riprova 14 | it.IDP_IgnoreButton =Ignora 15 | it.IDP_KBs =KB/s 16 | it.IDP_MBs =MB/s 17 | it.IDP_X_of_X =%.2f of %.2f 18 | it.IDP_KB =KB 19 | it.IDP_MB =MB 20 | it.IDP_GB =GB 21 | it.IDP_Initializing =Inizializzazione... 22 | it.IDP_GettingFileInformation=Acquisizione informazioni sui file... 23 | it.IDP_StartingDownload =Avvio download... 24 | it.IDP_Connecting =Connessione in corso... 25 | it.IDP_Downloading =Download in corso... 26 | it.IDP_DownloadComplete =Download completato 27 | it.IDP_DownloadFailed =Download fallito 28 | it.IDP_CannotConnect =Impossibile connettersi 29 | it.IDP_CancellingDownload =Annullamento del download... 30 | it.IDP_Unknown =Sconosciuto 31 | it.IDP_DownloadCancelled =Download annullato 32 | it.IDP_RetryNext =Controlla la tua connessione e clicca 'Riprova' per provare nuovamente a scaricare i file, o clicca 'Avanti' per proseguire con l'installazione comunque. 33 | it.IDP_RetryCancel =Controlla la tua connessione e clicca 'Riprova' per provare nuovamente a scaricare i file, o clicca 'Annulla' per uscire dall'installazione. 34 | it.IDP_FilesNotDownloaded =I seguenti file non sono stati scaricati: 35 | it.IDP_HTTPError_X =Errore HTTP %d 36 | it.IDP_400 =Bad request (400) 37 | it.IDP_401 =Access denied (401) 38 | it.IDP_404 =File not found (404) 39 | ;it.IDP_407 =??? 40 | it.IDP_500 =Server internal error (500) 41 | it.IDP_502 =Bad gateway (502) 42 | it.IDP_503 =Service temporaily unavailable (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/polish.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | pl.IDP_FormCaption =Pobieranie dodatkowych plików 3 | pl.IDP_FormDescription =Proszę czekać, instalator pobiera dodatkowe pliki... 4 | pl.IDP_TotalProgress =Postęp całkowity 5 | pl.IDP_CurrentFile =Bieżący plik 6 | pl.IDP_File =Plik: 7 | pl.IDP_Speed =Transfer: 8 | pl.IDP_Status =Status: 9 | pl.IDP_ElapsedTime =Czas pobierania: 10 | pl.IDP_RemainingTime =Czas pozostały: 11 | pl.IDP_DetailsButton =Szczegóły 12 | pl.IDP_HideButton =Ukryj 13 | pl.IDP_RetryButton =Powtórz 14 | pl.IDP_IgnoreButton = 15 | pl.IDP_KBs =KB/s 16 | pl.IDP_MBs =MB/s 17 | pl.IDP_X_of_X =%.2f z %.2f 18 | pl.IDP_MB =KB 19 | pl.IDP_MB =MB 20 | pl.IDP_MB =GB 21 | pl.IDP_Initializing =Trwa inicjalizacja... 22 | pl.IDP_GettingFileInformation=Pobieranie informacji o pliku... 23 | pl.IDP_StartingDownload =Rozpoczęcie pobierania... 24 | pl.IDP_Connecting =Nawiązywanie połączenia... 25 | pl.IDP_Downloading =Pobieranie... 26 | pl.IDP_DownloadComplete =Pobieranie zakończone 27 | pl.IDP_DownloadFailed =Pobieranie nieudane 28 | pl.IDP_CannotConnect =Nie można nazwiązać połączenia 29 | pl.IDP_CancellingDownload =Anulowanie pobierania... 30 | pl.IDP_Unknown =Nieznany 31 | pl.IDP_DownloadCancelled =Pobieranie anulowane 32 | pl.IDP_RetryNext =Sprawdź połączenie sieciowe i kliknij 'Powtórz' aby pobrać pliki ponownie lub kliknij 'Dalej' aby kontynuować instalację. 33 | pl.IDP_RetryCancel =Sprawdź połączenie sieciowe i kliknij 'Powtórz' aby pobrać pliki ponownie lub kliknij 'Anuluj' aby przerwać instalację. 34 | pl.IDP_FilesNotDownloaded = 35 | pl.IDP_HTTPError_X =Błąd HTTP %d 36 | pl.IDP_400 =Nieprawidłowe żądanie (400) 37 | pl.IDP_401 =Dostęp zabroniony (401) 38 | pl.IDP_404 =Plik nie został znaleziony (404) 39 | ;pl.IDP_407 =??? 40 | pl.IDP_500 =Błąd wewnętrzny serwera (500) 41 | pl.IDP_502 =Błąd bramy sieciowej (502) 42 | pl.IDP_503 =Usługa czasowo niedostępna (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/russian.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | ru.IDP_FormCaption =Скачивание дополнительных файлов 3 | ru.IDP_FormDescription =Пожалуйста подождите, пока инсталлятор скачает дополнительные файлы... 4 | ru.IDP_TotalProgress =Общий прогресс 5 | ru.IDP_CurrentFile =Текущий файл 6 | ru.IDP_File =Файл: 7 | ru.IDP_Speed =Скорость: 8 | ru.IDP_Status =Состояние: 9 | ru.IDP_ElapsedTime =Прошло времени: 10 | ru.IDP_RemainingTime =Осталось времени: 11 | ru.IDP_DetailsButton =Подробно 12 | ru.IDP_HideButton =Скрыть 13 | ru.IDP_RetryButton =Повтор 14 | ru.IDP_IgnoreButton =Пропустить 15 | ru.IDP_KBs =КБ/с 16 | ru.IDP_MBs =МБ/с 17 | ru.IDP_X_of_X =%.2f из %.2f 18 | ru.IDP_KB =КБ 19 | ru.IDP_MB =МБ 20 | ru.IDP_GB =ГБ 21 | ru.IDP_Initializing =Инициализация... 22 | ru.IDP_GettingFileInformation=Получение информации о файле... 23 | ru.IDP_StartingDownload =Начало загрузки... 24 | ru.IDP_Connecting =Соединение... 25 | ru.IDP_Downloading =Загрузка... 26 | ru.IDP_DownloadComplete =Загрузка завершена 27 | ru.IDP_DownloadFailed =Загрузка не удалась 28 | ru.IDP_CannotConnect =Невозможно соединиться 29 | ru.IDP_CancellingDownload =Отмена загрузки... 30 | ru.IDP_Unknown =Неизвестно 31 | ru.IDP_DownloadCancelled =Загрузка отменена 32 | ru.IDP_RetryNext =Проверьте ваше подключение к сети Интернет и нажмите 'Повторить' чтобы начать скачивание заново, или нажмите 'Далее' для продолжения установки. 33 | ru.IDP_RetryCancel =Проверьте ваше подключение к сети Интернет и нажмите 'Повторить' чтобы начать скачивание заново, или нажмите 'Отмена' чтобы прервать установку. 34 | ru.IDP_FilesNotDownloaded =Не удалось загрузить следующие файлы: 35 | ru.IDP_HTTPError_X =Ошибка HTTP %d 36 | ru.IDP_400 =Неверный запрос (400) 37 | ru.IDP_401 =Доступ запрещен (401) 38 | ru.IDP_404 =Файл не найден (404) 39 | ru.IDP_407 =Необходима авторизация прокси (407) 40 | ru.IDP_500 =Внутренняя ошибка сервера (500) 41 | ru.IDP_502 =Неправильный шлюз (502) 42 | ru.IDP_503 =Сервер временно недоступен (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/slovak.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | sk.IDP_FormCaption =Preberanie súborov 3 | sk.IDP_FormDescription =Prosím čakajte, kým inštalátor prevezme dodatočné súbory... 4 | sk.IDP_TotalProgress =Celkový progres 5 | sk.IDP_CurrentFile =Aktuálny súbor 6 | sk.IDP_File =Súbor: 7 | sk.IDP_Speed =Rýchlosť: 8 | sk.IDP_Status =Stav: 9 | sk.IDP_ElapsedTime =Ubehnutý čas: 10 | sk.IDP_RemainingTime =Zostáva: 11 | sk.IDP_DetailsButton =Detaily 12 | sk.IDP_HideButton =Skryť 13 | sk.IDP_RetryButton =Znova 14 | sk.IDP_IgnoreButton =Ignorovať 15 | sk.IDP_KBs =KB/s 16 | sk.IDP_MBs =MB/s 17 | sk.IDP_X_of_X =%.2f of %.2f 18 | sk.IDP_KB =KB 19 | sk.IDP_MB =MB 20 | sk.IDP_GB =GB 21 | sk.IDP_Initializing =Inicializácia... 22 | sk.IDP_GettingFileInformation=Preberanie informácií o súbore... 23 | sk.IDP_StartingDownload =Začínam prebereť... 24 | sk.IDP_Connecting =Nadväzovanie spojenia... 25 | sk.IDP_Downloading =Preberanie... 26 | sk.IDP_DownloadComplete =Preberanie ukončené 27 | sk.IDP_DownloadFailed =Preberanie zlyhalo 28 | sk.IDP_CannotConnect =Nie je možné nadviazať spojenie 29 | sk.IDP_CancellingDownload =Ukončenie preberania... 30 | sk.IDP_Unknown =Neznáme 31 | sk.IDP_DownloadCancelled =Preberanie zrušené 32 | sk.IDP_RetryNext =Skontrolujte nastavenie siete a kliknite na 'Znova' na opätovné prebratie súborov, alebo kliknite na 'Ďalej' a pokračujte v inštalácii. 33 | sk.IDP_RetryCancel =Skontrolujte nastavenie siete a kliknite na 'Znova' na opätovné prebratie súborov, alebo kliknite na 'Zrušiť' a zrušte inštaláciu. 34 | sk.IDP_FilesNotDownloaded =Nasledujúce súbory neboli stiahnuté: 35 | sk.IDP_HTTPError_X =Chyba HTTP %d 36 | sk.IDP_400 =Nesprávna požiadavka (400) 37 | sk.IDP_401 =Prístup zakázaný (401) 38 | sk.IDP_404 =Súbor nenájdený (404) 39 | sk.IDP_407 =Proxy authentication required (407) 40 | sk.IDP_500 =Chyba servera (500) 41 | sk.IDP_502 =Chyba gateway (502) 42 | sk.IDP_503 =Služba je dočasne nedostupná (503) 43 | -------------------------------------------------------------------------------- /idp/unicode/idplang/spanish.iss: -------------------------------------------------------------------------------- 1 | [CustomMessages] 2 | es.IDP_FormCaption =Descargando Archivos adicionales 3 | es.IDP_FormDescription =Por favor, espere mientras se descargan archivos adicionales... 4 | es.IDP_TotalProgress =Progreso Total 5 | es.IDP_CurrentFile =Archivo actual 6 | es.IDP_File =Archivo: 7 | es.IDP_Speed =Velocidad: 8 | es.IDP_Status =Estado: 9 | es.IDP_ElapsedTime =Tiempo Transcurrido: 10 | es.IDP_RemainingTime =Tiempo Restante: 11 | es.IDP_DetailsButton =Detalles 12 | es.IDP_HideButton =Ocultar 13 | es.IDP_RetryButton =Reintentar 14 | es.IDP_IgnoreButton =Ignorar 15 | es.IDP_KBs =KB/s 16 | es.IDP_MBs =MB/s 17 | es.IDP_X_of_X =%.2f de %.2f 18 | es.IDP_KB =KB 19 | es.IDP_MB =MB 20 | es.IDP_GB =GB 21 | es.IDP_Initializing =Iniciando... 22 | es.IDP_GettingFileInformation=Obteniendo información del Archivo... 23 | es.IDP_StartingDownload =Iniciando descarga... 24 | es.IDP_Connecting =Conectando... 25 | es.IDP_Downloading =Descargando... 26 | es.IDP_DownloadComplete =Descarga completada 27 | es.IDP_DownloadFailed =Descarga fallida 28 | es.IDP_CannotConnect =No se puede conectar 29 | es.IDP_CancellingDownload =Cancelando descarga... 30 | es.IDP_Unknown =Desconocido 31 | es.IDP_DownloadCancelled =Descarga cancelada 32 | es.IDP_RetryNext =Compruebe su conexión y pulse "Reintentar' para tratar de descargar de nuevo, o pulse 'Siguiente' para continuar instalando de todos modos. 33 | es.IDP_RetryCancel =Compruebe su conexión y pulse "Reintentar' para tratar de descargar de nuevo, o pulse 'Cancelar' para descartar la descarga. 34 | es.IDP_FilesNotDownloaded =Los siguientesArchivos no han sido descargados: 35 | es.IDP_HTTPError_X =Error HTTP %d 36 | es.IDP_400 =Error 'Bad request' (400) 37 | es.IDP_401 =Acceso denegado (401) 38 | es.IDP_404 =Archivo no encontrado (404) 39 | es.IDP_407 =Autentificación de Proxy requerida (407) 40 | es.IDP_500 =Error interno del servidor (500) 41 | es.IDP_502 =Error 'Bad gateway' (502) 42 | es.IDP_503 =Servicio no disponible temporalmente (503) 43 | -------------------------------------------------------------------------------- /run_installer.cmd: -------------------------------------------------------------------------------- 1 | :: Run the installer with the local seed and external files 2 | 3 | @ECHO OFF 4 | SETLOCAL EnableDelayedExpansion 5 | 6 | CALL :NORMALIZEPATH "." 7 | SET ROOT=%RETVAL% 8 | SET INSTALLER="%ROOT%\Output\MedusaInstaller.exe" 9 | 10 | IF NOT EXIST %INSTALLER% ( 11 | ECHO Installer not found, have you compiled the .iss file? 12 | EXIT /B 1 13 | ) 14 | 15 | IF "%~1" NEQ "" ( 16 | SET REPO=%~dpfn1 17 | IF NOT EXIST !REPO!\ ( 18 | ECHO Provided local repo location does not exist. 19 | EXIT /B 1 20 | ) 21 | SET LOCALREPO=/LOCALREPO="!REPO!" 22 | ) ELSE ( 23 | SET LOCALREPO= 24 | ) 25 | 26 | :: Runs the installer with the local seed and local install files 27 | %INSTALLER% ^ 28 | /SEED="%ROOT%\seed.ini" ^ 29 | /LOCALFILES="%ROOT%\files" ^ 30 | /LOG="%ROOT%\Output\installer.log" ^ 31 | %LOCALREPO% 32 | 33 | :: ========== FUNCTIONS ========== 34 | EXIT /B %ERRORLEVEL% 35 | 36 | :NORMALIZEPATH 37 | SET RETVAL=%~dpfn1 38 | EXIT /B 39 | -------------------------------------------------------------------------------- /seed.ini: -------------------------------------------------------------------------------- 1 | [Installer] 2 | Version=10006 3 | DownloadUrl=https://github.com/pymedusa/MedusaInstaller 4 | 5 | [Python.x86] 6 | url=https://www.nuget.org/api/v2/package/pythonx86/3.7.3 7 | filename=pythonx86.3.7.3.nupkg 8 | size=10974884 9 | sha1=46330a0e3b40d1b9d16593e9d4993aac28b6c163 10 | 11 | [Python.x64] 12 | url=https://www.nuget.org/api/v2/package/python/3.7.3 13 | filename=python.3.7.3.nupkg 14 | size=11466309 15 | sha1=1aeffcacdfd5b85855120ee321595b2af43720f3 16 | 17 | [Git.x86] 18 | url=https://github.com/git-for-windows/git/releases/download/v2.22.0.windows.1/MinGit-2.22.0-32-bit.zip 19 | size=22627458 20 | sha1=7bca1deafe61ce9252704281e2232de6f9b5e2a6 21 | 22 | [Git.x64] 23 | url=https://github.com/git-for-windows/git/releases/download/v2.22.0.windows.1/MinGit-2.22.0-64-bit.zip 24 | size=22608951 25 | sha1=8c23e5a093db42aab389e9ff14e9b5039bffb8e2 26 | -------------------------------------------------------------------------------- /seed_data.cmd: -------------------------------------------------------------------------------- 1 | :: Generate seed data for seed.ini 2 | 3 | @ECHO OFF 4 | 5 | IF NOT EXIST %1 ( 6 | ECHO %1 is not a valid file 7 | EXIT /B 1 8 | ) 9 | 10 | SET FILENAME=%~nx1 11 | SET SIZE=%~z1 12 | FOR /F "tokens=*" %%i IN ('CertUtil -hashfile "%~1" SHA1 ^| find /i /v "SHA1" ^| find /i /v "certutil"') do ( 13 | SET SHA1=%%i 14 | ) 15 | 16 | echo filename=%FILENAME% 17 | echo size=%SIZE% 18 | echo sha1=%SHA1% 19 | -------------------------------------------------------------------------------- /utils/7za-License.txt: -------------------------------------------------------------------------------- 1 | 7-Zip Extra 2 | ~~~~~~~~~~~ 3 | License for use and distribution 4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 5 | 6 | Copyright (C) 1999-2019 Igor Pavlov. 7 | 8 | 7-Zip Extra files are under the GNU LGPL license. 9 | 10 | 11 | Notes: 12 | You can use 7-Zip Extra on any computer, including a computer in a commercial 13 | organization. You don't need to register or pay for 7-Zip. 14 | 15 | 16 | GNU LGPL information 17 | -------------------- 18 | 19 | This library is free software; you can redistribute it and/or 20 | modify it under the terms of the GNU Lesser General Public 21 | License as published by the Free Software Foundation; either 22 | version 2.1 of the License, or (at your option) any later version. 23 | 24 | This library is distributed in the hope that it will be useful, 25 | but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 | Lesser General Public License for more details. 28 | 29 | You can receive a copy of the GNU Lesser General Public License from 30 | http://www.gnu.org/ 31 | -------------------------------------------------------------------------------- /utils/7za.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/utils/7za.exe -------------------------------------------------------------------------------- /utils/nssm-License.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016, Alexander 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /utils/nssm32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/utils/nssm32.exe -------------------------------------------------------------------------------- /utils/nssm64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pymedusa/MedusaInstaller/aeb8cd07e142c3e0aa5ddf14f1b99f903bac881d/utils/nssm64.exe --------------------------------------------------------------------------------