зеркало из https://github.com/mozilla/pjs.git
Support for GRE (recently known as MRE)
(Bug #157211, r=syd, sr=dveditz)
This commit is contained in:
Родитель
f8e75f99a3
Коммит
ef1b7ba16c
|
@ -2805,7 +2805,9 @@ void CommitInstall(void)
|
|||
HRESULT hrErr;
|
||||
char szDestPath[MAX_BUF];
|
||||
char szInstallLogFile[MAX_BUF];
|
||||
long RetrieveResults;
|
||||
|
||||
LogISShared();
|
||||
LogISDestinationPath();
|
||||
LogISSetupType();
|
||||
LogISComponentsSelected();
|
||||
|
@ -2849,87 +2851,104 @@ void CommitInstall(void)
|
|||
DeleteFile(szInstallLogFile);
|
||||
|
||||
/* PRE_DOWNLOAD process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_PRE_DOWNLOAD);
|
||||
|
||||
if(RetrieveArchives() == WIZ_OK)
|
||||
RetrieveResults = WIZ_OK;
|
||||
if(sgProduct.bInstallFiles)
|
||||
{
|
||||
/* Check to see if Turbo is required. If so, set the
|
||||
* appropriate Windows registry keys */
|
||||
SetTurboArgs();
|
||||
ProcessFileOpsForAll(T_PRE_DOWNLOAD);
|
||||
RetrieveResults = RetrieveArchives();
|
||||
}
|
||||
|
||||
if(gbDownloadTriggered || gbPreviousUnfinishedDownload)
|
||||
SetSetupState(SETUP_STATE_UNPACK_XPCOM);
|
||||
|
||||
/* POST_DOWNLOAD process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_POST_DOWNLOAD);
|
||||
/* PRE_XPCOM process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_PRE_XPCOM);
|
||||
|
||||
if(ProcessXpcomFile() != FO_SUCCESS)
|
||||
if(RetrieveResults == WIZ_OK)
|
||||
{
|
||||
if(sgProduct.bInstallFiles)
|
||||
{
|
||||
bSDUserCanceled = TRUE;
|
||||
CleanupXpcomFile();
|
||||
PostQuitMessage(0);
|
||||
/* Check to see if Turbo is required. If so, set the
|
||||
* appropriate Windows registry keys */
|
||||
SetTurboArgs();
|
||||
|
||||
return;
|
||||
if(gbDownloadTriggered || gbPreviousUnfinishedDownload)
|
||||
SetSetupState(SETUP_STATE_UNPACK_XPCOM);
|
||||
|
||||
/* POST_DOWNLOAD process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_POST_DOWNLOAD);
|
||||
/* PRE_XPCOM process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_PRE_XPCOM);
|
||||
|
||||
if(ProcessXpcomFile() != FO_SUCCESS)
|
||||
{
|
||||
bSDUserCanceled = TRUE;
|
||||
CleanupXpcomFile();
|
||||
PostQuitMessage(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(gbDownloadTriggered || gbPreviousUnfinishedDownload)
|
||||
SetSetupState(SETUP_STATE_INSTALL_XPI); // clears and sets new setup state
|
||||
|
||||
/* POST_XPCOM process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_POST_XPCOM);
|
||||
/* PRE_SMARTUPDATE process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_PRE_SMARTUPDATE);
|
||||
|
||||
/* save the installer files in the local machine */
|
||||
if(diAdditionalOptions.bSaveInstaller)
|
||||
SaveInstallerFiles();
|
||||
|
||||
if(CheckInstances())
|
||||
{
|
||||
bSDUserCanceled = TRUE;
|
||||
CleanupXpcomFile();
|
||||
PostQuitMessage(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lstrcat(szDestPath, "uninstall\\");
|
||||
CreateDirectoriesAll(szDestPath, TRUE);
|
||||
|
||||
/* save the installer files in the local machine */
|
||||
if(diAdditionalOptions.bSaveInstaller)
|
||||
SaveInstallerFiles();
|
||||
|
||||
hrErr = SmartUpdateJars();
|
||||
}
|
||||
else
|
||||
hrErr = WIZ_OK;
|
||||
|
||||
if(gbDownloadTriggered || gbPreviousUnfinishedDownload)
|
||||
SetSetupState(SETUP_STATE_INSTALL_XPI); // clears and sets new setup state
|
||||
|
||||
/* POST_XPCOM process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_POST_XPCOM);
|
||||
/* PRE_SMARTUPDATE process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_PRE_SMARTUPDATE);
|
||||
|
||||
/* save the installer files in the local machine */
|
||||
if(diAdditionalOptions.bSaveInstaller)
|
||||
SaveInstallerFiles();
|
||||
|
||||
if(CheckInstances())
|
||||
{
|
||||
bSDUserCanceled = TRUE;
|
||||
CleanupXpcomFile();
|
||||
PostQuitMessage(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
lstrcat(szDestPath, "uninstall\\");
|
||||
CreateDirectoriesAll(szDestPath, TRUE);
|
||||
|
||||
/* save the installer files in the local machine */
|
||||
if(diAdditionalOptions.bSaveInstaller)
|
||||
SaveInstallerFiles();
|
||||
|
||||
hrErr = SmartUpdateJars();
|
||||
if((hrErr == WIZ_OK) || (hrErr == 999))
|
||||
{
|
||||
UpdateJSProxyInfo();
|
||||
if(sgProduct.bInstallFiles)
|
||||
UpdateJSProxyInfo();
|
||||
|
||||
/* POST_SMARTUPDATE process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_POST_SMARTUPDATE);
|
||||
/* PRE_LAUNCHAPP process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_PRE_LAUNCHAPP);
|
||||
|
||||
LaunchApps();
|
||||
if(sgProduct.bInstallFiles)
|
||||
{
|
||||
/* PRE_LAUNCHAPP process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_PRE_LAUNCHAPP);
|
||||
|
||||
/* POST_LAUNCHAPP process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_POST_LAUNCHAPP);
|
||||
/* DEPEND_REBOOT process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_DEPEND_REBOOT);
|
||||
LaunchApps();
|
||||
|
||||
// Refresh system icons if necessary
|
||||
if(gSystemInfo.bRefreshIcons)
|
||||
RefreshIcons();
|
||||
/* POST_LAUNCHAPP process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_POST_LAUNCHAPP);
|
||||
/* DEPEND_REBOOT process file manipulation functions */
|
||||
ProcessFileOpsForAll(T_DEPEND_REBOOT);
|
||||
|
||||
UnsetSetupState(); // clear setup state
|
||||
ClearWinRegUninstallFileDeletion();
|
||||
if(!gbIgnoreProgramFolderX)
|
||||
ProcessProgramFolderShowCmd();
|
||||
// Refresh system icons if necessary
|
||||
if(gSystemInfo.bRefreshIcons)
|
||||
RefreshIcons();
|
||||
|
||||
UnsetSetupState(); // clear setup state
|
||||
ClearWinRegUninstallFileDeletion();
|
||||
if(!gbIgnoreProgramFolderX)
|
||||
ProcessProgramFolderShowCmd();
|
||||
|
||||
CleanupArgsRegistry();
|
||||
CleanupPreviousVersionRegKeys();
|
||||
}
|
||||
|
||||
CleanupArgsRegistry();
|
||||
CleanupPreviousVersionRegKeys();
|
||||
if(NeedReboot())
|
||||
{
|
||||
CleanupXpcomFile();
|
||||
|
|
|
@ -275,6 +275,17 @@ BOOL VerifyRestrictedAccess(void)
|
|||
return(bRv);
|
||||
}
|
||||
|
||||
void SetInstallFilesVar(LPSTR szProdDir)
|
||||
{
|
||||
// MCP - Eventually we should make this check for a representative file
|
||||
// instead of settling for the existence of the folder. That is why
|
||||
// I left this as a function even though it is essentially a one-liner
|
||||
// at the moment.
|
||||
|
||||
if(FileExists(szProdDir))
|
||||
sgProduct.bInstallFiles = FALSE;
|
||||
}
|
||||
|
||||
void UnsetSetupState(void)
|
||||
{
|
||||
char szKey[MAX_BUF_TINY];
|
||||
|
@ -2501,6 +2512,8 @@ HRESULT InitSetupGeneral()
|
|||
|
||||
gSystemInfo.bRefreshIcons = FALSE;
|
||||
sgProduct.dwMode = NORMAL;
|
||||
sgProduct.bSharedInst = FALSE;
|
||||
sgProduct.bInstallFiles = TRUE;
|
||||
sgProduct.dwCustomType = ST_RADIO0;
|
||||
sgProduct.dwNumberOfComponents = 0;
|
||||
sgProduct.bLockPath = FALSE;
|
||||
|
@ -2536,10 +2549,16 @@ HRESULT InitSetupGeneral()
|
|||
|
||||
if((szSiteSelectorDescription = NS_GlobalAlloc(MAX_BUF)) == NULL)
|
||||
return(1);
|
||||
|
||||
if(GetPrivateProfileString("Messages", "CB_DEFAULT", "", szBuf, sizeof(szBuf), szFileIniInstall))
|
||||
lstrcpy(szSiteSelectorDescription, szBuf);
|
||||
|
||||
if((sgProduct.szAppID = NS_GlobalAlloc(MAX_BUF)) == NULL)
|
||||
return(1);
|
||||
if((sgProduct.szAppPath = NS_GlobalAlloc(MAX_BUF)) == NULL)
|
||||
return(1);
|
||||
if((sgProduct.szRegPath = NS_GlobalAlloc(MAX_BUF)) == NULL)
|
||||
return(1);
|
||||
|
||||
return(0);
|
||||
}
|
||||
|
||||
|
@ -2558,6 +2577,9 @@ void DeInitSetupGeneral()
|
|||
FreeMemory(&(sgProduct.szProgramFolderPath));
|
||||
FreeMemory(&(sgProduct.szAlternateArchiveSearchPath));
|
||||
FreeMemory(&(sgProduct.szParentProcessFilename));
|
||||
FreeMemory(&(sgProduct.szAppID));
|
||||
FreeMemory(&(sgProduct.szAppPath));
|
||||
FreeMemory(&(sgProduct.szRegPath));
|
||||
FreeMemory(&(szTempSetupPath));
|
||||
FreeMemory(&(szSiteSelectorDescription));
|
||||
}
|
||||
|
@ -5013,6 +5035,34 @@ DWORD ParseCommandLine(LPSTR lpszCmdLine)
|
|||
else if(!lstrcmpi(szArgVBuf, "-ispf") || !lstrcmpi(szArgVBuf, "/ispf"))
|
||||
/* ignore [Program FolderX] sections */
|
||||
gbIgnoreProgramFolderX = TRUE;
|
||||
else if(!lstrcmpi(szArgVBuf, "-dd") || !lstrcmpi(szArgVBuf, "/dd"))
|
||||
{
|
||||
++i;
|
||||
GetArgV(lpszCmdLine, i, szArgVBuf, sizeof(szArgVBuf));
|
||||
lstrcpy(sgProduct.szPath, szArgVBuf);
|
||||
}
|
||||
// identifies the app which is installing for shared installs
|
||||
else if(!lstrcmpi(szArgVBuf, "-app") || !lstrcmpi(szArgVBuf, "/app"))
|
||||
{
|
||||
++i;
|
||||
GetArgV(lpszCmdLine, i, szArgVBuf, sizeof(szArgVBuf));
|
||||
lstrcpy(sgProduct.szAppID, szArgVBuf);
|
||||
}
|
||||
// points to a file belonging to the app which can be searched to determine
|
||||
// if the app is installed
|
||||
else if(!lstrcmpi(szArgVBuf, "-app_path") || !lstrcmpi(szArgVBuf, "/app_path"))
|
||||
{
|
||||
++i;
|
||||
GetArgV(lpszCmdLine, i, szArgVBuf, sizeof(szArgVBuf));
|
||||
lstrcpy(sgProduct.szAppPath, szArgVBuf);
|
||||
}
|
||||
// alternative path in Windows registry, for private sharable installations
|
||||
else if(!lstrcmpi(szArgVBuf, "-reg_path") || !lstrcmpi(szArgVBuf, "/reg_path"))
|
||||
{
|
||||
++i;
|
||||
GetArgV(lpszCmdLine, i, szArgVBuf, sizeof(szArgVBuf));
|
||||
lstrcpy(sgProduct.szRegPath, szArgVBuf);
|
||||
}
|
||||
|
||||
#ifdef XXX_DEBUG
|
||||
itoa(i, szBuf, 10);
|
||||
|
@ -5743,6 +5793,17 @@ HRESULT ParseConfigIni(LPSTR lpszCmdLine)
|
|||
if(ParseCommandLine(lpszCmdLine))
|
||||
return(1);
|
||||
|
||||
/* find out if we are doing a shared install */
|
||||
GetPrivateProfileString("General", "Shared Install", "", szBuf, sizeof(szBuf), szFileIniConfig);
|
||||
if(lstrcmpi(szBuf, "TRUE") == 0)
|
||||
{
|
||||
sgProduct.bSharedInst = TRUE;
|
||||
}
|
||||
|
||||
/* this is a default value so don't change it if it has already been set */
|
||||
if(*sgProduct.szAppID == '\0')
|
||||
GetPrivateProfileString("General", "Default AppID", "", sgProduct.szAppID, MAX_BUF, szFileIniConfig);
|
||||
|
||||
if(GetPrivateProfileString("Messages", "MSG_INIT_SETUP", "", szMsgInitSetup, sizeof(szMsgInitSetup), szFileIniInstall))
|
||||
ShowMessage(szMsgInitSetup, TRUE);
|
||||
|
||||
|
@ -5752,6 +5813,11 @@ HRESULT ParseConfigIni(LPSTR lpszCmdLine)
|
|||
GetPrivateProfileString("General", "Product Name Internal", "", sgProduct.szProductNameInternal, MAX_BUF, szFileIniConfig);
|
||||
if (sgProduct.szProductNameInternal[0] == 0)
|
||||
lstrcpy(sgProduct.szProductNameInternal, sgProduct.szProductName);
|
||||
|
||||
/* this is a default value so don't change it if it has already been set */
|
||||
if (sgProduct.szRegPath[0] == 0)
|
||||
wsprintf(sgProduct.szRegPath, "Software\\%s\\%s", sgProduct.szCompanyName, sgProduct.szProductNameInternal);
|
||||
|
||||
GetPrivateProfileString("General", "Product Name Previous", "", sgProduct.szProductNamePrevious, MAX_BUF, szFileIniConfig);
|
||||
GetPrivateProfileString("General", "Uninstall Filename", "", sgProduct.szUninstallFilename, MAX_BUF, szFileIniConfig);
|
||||
GetPrivateProfileString("General", "User Agent", "", sgProduct.szUserAgent, MAX_BUF, szFileIniConfig);
|
||||
|
@ -5807,13 +5873,17 @@ HRESULT ParseConfigIni(LPSTR lpszCmdLine)
|
|||
/* get main install path */
|
||||
if(LocatePreviousPath("Locate Previous Product Path", szPreviousPath, sizeof(szPreviousPath)) == FALSE)
|
||||
{
|
||||
GetPrivateProfileString("General", "Path", "", szBuf, sizeof(szBuf), szFileIniConfig);
|
||||
DecryptString(sgProduct.szPath, szBuf);
|
||||
// If the path was set on the command-line than we don't want to use the default here.
|
||||
if(*sgProduct.szPath == '\0')
|
||||
{
|
||||
GetPrivateProfileString("General", "Path", "", szBuf, sizeof(szBuf), szFileIniConfig);
|
||||
DecryptString(sgProduct.szPath, szBuf);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If the previous path is located in the regsitry, then we need to check to see if the path from
|
||||
* the regsitry plus the Sub Path contains the Program Name file. If it does, then we have the
|
||||
/* If the previous path is located in the registry, then we need to check to see if the path from
|
||||
* the registry plus the Sub Path contains the Program Name file. If it does, then we have the
|
||||
* correct path, so just use it.
|
||||
*
|
||||
* If it does not contain the Program Name file, then check the parent path (from the registry) +
|
||||
|
@ -5869,6 +5939,11 @@ HRESULT ParseConfigIni(LPSTR lpszCmdLine)
|
|||
{
|
||||
lstrcpy(sgProduct.szPath, szPreviousPath);
|
||||
}
|
||||
|
||||
// Since we found the path in the registry, lets check to see if files need to be
|
||||
// installed for share installations while we're here
|
||||
if(sgProduct.bSharedInst == TRUE)
|
||||
SetInstallFilesVar(szPreviousPath);
|
||||
}
|
||||
RemoveBackSlash(sgProduct.szPath);
|
||||
|
||||
|
@ -6959,6 +7034,18 @@ HRESULT DecryptVariable(LPSTR szVariable, DWORD dwVariableSize)
|
|||
|
||||
wsprintf(szVariable, "Software\\%s\\%s\\%s", sgProduct.szCompanyName, sgProduct.szProductNamePrevious, szBuf);
|
||||
}
|
||||
else if(lstrcmpi(szVariable, "APP_ID") == 0)
|
||||
{
|
||||
lstrcpy(szVariable, sgProduct.szAppID);
|
||||
}
|
||||
else if(lstrcmpi(szVariable, "PATH_TO_APP") == 0)
|
||||
{
|
||||
lstrcpy(szVariable, sgProduct.szAppPath);
|
||||
}
|
||||
else if(lstrcmpi(szVariable, "REGPATH") == 0)
|
||||
{
|
||||
lstrcpy(szVariable, sgProduct.szRegPath);
|
||||
}
|
||||
else if(szVariable[0] == '$')
|
||||
{
|
||||
// the $ indicates that there's another section that defines a lookup for this string.
|
||||
|
|
|
@ -214,6 +214,7 @@ DWORD ParseOSType(char *szOSType);
|
|||
BOOL ShowAdditionalOptionsDialog(void);
|
||||
DWORD GetPreviousUnfinishedState(void);
|
||||
void RefreshIcons();
|
||||
void NeedToInstallFiles(LPSTR szProdDir);
|
||||
|
||||
#endif /* _EXTRA_H_ */
|
||||
|
||||
|
|
|
@ -287,19 +287,28 @@ void CleanupPreviousVersionRegKeys(void)
|
|||
|
||||
void ProcessFileOps(DWORD dwTiming, char *szSectionPrefix)
|
||||
{
|
||||
ProcessUncompressFile(dwTiming, szSectionPrefix);
|
||||
ProcessCreateDirectory(dwTiming, szSectionPrefix);
|
||||
ProcessMoveFile(dwTiming, szSectionPrefix);
|
||||
ProcessCopyFile(dwTiming, szSectionPrefix);
|
||||
ProcessCopyFileSequential(dwTiming, szSectionPrefix);
|
||||
ProcessSelfRegisterFile(dwTiming, szSectionPrefix);
|
||||
ProcessDeleteFile(dwTiming, szSectionPrefix);
|
||||
ProcessRemoveDirectory(dwTiming, szSectionPrefix);
|
||||
if(!gbIgnoreRunAppX)
|
||||
ProcessRunApp(dwTiming, szSectionPrefix);
|
||||
if(sgProduct.bInstallFiles)
|
||||
{
|
||||
ProcessUncompressFile(dwTiming, szSectionPrefix);
|
||||
ProcessCreateDirectory(dwTiming, szSectionPrefix);
|
||||
ProcessMoveFile(dwTiming, szSectionPrefix);
|
||||
ProcessCopyFile(dwTiming, szSectionPrefix);
|
||||
ProcessCopyFileSequential(dwTiming, szSectionPrefix);
|
||||
ProcessSelfRegisterFile(dwTiming, szSectionPrefix);
|
||||
ProcessDeleteFile(dwTiming, szSectionPrefix);
|
||||
ProcessRemoveDirectory(dwTiming, szSectionPrefix);
|
||||
if(!gbIgnoreRunAppX)
|
||||
ProcessRunApp(dwTiming, szSectionPrefix);
|
||||
}
|
||||
|
||||
// This is the only operation we do if we are not installing files
|
||||
ProcessWinReg(dwTiming, szSectionPrefix);
|
||||
ProcessProgramFolder(dwTiming, szSectionPrefix);
|
||||
ProcessSetVersionRegistry(dwTiming, szSectionPrefix);
|
||||
|
||||
if(sgProduct.bInstallFiles)
|
||||
{
|
||||
ProcessProgramFolder(dwTiming, szSectionPrefix);
|
||||
ProcessSetVersionRegistry(dwTiming, szSectionPrefix);
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessFileOpsForSelectedComponents(DWORD dwTiming)
|
||||
|
@ -323,8 +332,11 @@ void ProcessFileOpsForSelectedComponents(DWORD dwTiming)
|
|||
void ProcessFileOpsForAll(DWORD dwTiming)
|
||||
{
|
||||
ProcessFileOps(dwTiming, NULL);
|
||||
ProcessFileOpsForSelectedComponents(dwTiming);
|
||||
ProcessCreateCustomFiles(dwTiming);
|
||||
if(sgProduct.bInstallFiles)
|
||||
{
|
||||
ProcessFileOpsForSelectedComponents(dwTiming);
|
||||
ProcessCreateCustomFiles(dwTiming);
|
||||
}
|
||||
}
|
||||
|
||||
int VerifyArchive(LPSTR szArchive)
|
||||
|
|
|
@ -125,6 +125,15 @@ void LogISDestinationPath(void)
|
|||
UpdateInstallStatusLog(szBuf);
|
||||
}
|
||||
|
||||
void LogISShared(void)
|
||||
{
|
||||
char szBuf[MAX_BUF];
|
||||
|
||||
if(sgProduct.bSharedInst == TRUE)
|
||||
wsprintf(szBuf,"\n Shared Installation: TRUE\n");
|
||||
UpdateInstallStatusLog(szBuf);
|
||||
}
|
||||
|
||||
void LogISSetupType(void)
|
||||
{
|
||||
char szBuf[MAX_BUF_TINY];
|
||||
|
|
|
@ -41,6 +41,7 @@ void LogISLaunchAppsComponent(char *szComponentName);
|
|||
void LogISLaunchAppsComponentUncompress(char *szComponentName,
|
||||
DWORD dwErr);
|
||||
void LogISProcessXpcomFile(int iStatus, int iResult);
|
||||
void LogISShared(void);
|
||||
void LogISDiskSpace(dsN *dsnComponentDSRequirement);
|
||||
void LogISTurboMode(BOOL bTurboMode);
|
||||
void LogMSProductInfo(void);
|
||||
|
|
|
@ -402,6 +402,11 @@ typedef struct setupStruct
|
|||
LPSTR szAlternateArchiveSearchPath;
|
||||
LPSTR szParentProcessFilename;
|
||||
BOOL bLockPath;
|
||||
BOOL bSharedInst;
|
||||
BOOL bInstallFiles;
|
||||
LPSTR szAppID;
|
||||
LPSTR szAppPath;
|
||||
LPSTR szRegPath;
|
||||
} setupGen;
|
||||
|
||||
typedef struct sinfoSmartDownload
|
||||
|
|
|
@ -294,7 +294,7 @@ HRESULT SmartUpdateJars()
|
|||
SetDlgItemText(dlgInfo.hWndDlg, IDC_STATUS0, szBuf);
|
||||
LogISXPInstallComponent(siCObject->szDescriptionShort);
|
||||
|
||||
hrResult = pfnXpiInstall(szArchive, "", 0xFFFF);
|
||||
hrResult = pfnXpiInstall(szArchive, sgProduct.szRegPath, 0xFFFF);
|
||||
if(hrResult == E_REBOOT)
|
||||
bReboot = TRUE;
|
||||
else if((hrResult != WIZ_OK) &&
|
||||
|
|
Загрузка…
Ссылка в новой задаче